stock-calc 1.0.1

股票收益计算器 - 命令行工具
mod error;
mod models;
mod config;
mod api;
mod calculator;
mod storage;
mod notification;
mod cli;

use clap::Parser;
use crate::error::Result;
use crate::cli::{Cli, Commands, InteractiveMode};
use crate::config::AppConfig;
use crate::models::StockData;
use chrono::Utc;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<()> {
    // 初始化日志
    env_logger::init();

    // 解析命令行参数
    let cli = Cli::parse();

    // 加载配置
    let config = AppConfig::load().unwrap_or_else(|_| {
        log::warn!("无法加载配置文件,使用默认配置");
        AppConfig::default()
    });

    match cli.command {
        Commands::Calculate { code, quantity, avg_price, target_profit, max_loss, save } => {
            handle_calculate(&config, &code, quantity, avg_price, target_profit, max_loss, save).await?;
        }
        Commands::Monitor { code, interval, retry } => {
            handle_monitor(&config, &code, interval, retry).await?;
        }
        Commands::List { detailed } => {
            handle_list(&config, detailed).await?;
        }
        Commands::Remove { code } => {
            handle_remove(&config, &code).await?;
        }
        Commands::Interactive => {
            InteractiveMode::run().await?;
        }
        Commands::Config { subcommand } => {
            handle_config(&config, subcommand).await?;
        }
        Commands::Test { code } => {
            handle_test(&config, &code).await?;
        }
    }

    Ok(())
}

async fn handle_calculate(
    config: &AppConfig,
    code: &str,
    quantity: f64,
    avg_price: f64,
    target_profit: f64,
    max_loss: f64,
    save: bool,
) -> Result<()> {
    let stock_codes = crate::cli::parse_stock_codes(code);
    
    if stock_codes.is_empty() {
        return Err(crate::error::StockCalcError::ParseError("未提供有效的股票代码".to_string()));
    }

    let api = crate::api::StockApi::new(config.clone())?;
    let storage = crate::storage::Storage::new(config.clone());
    let notifier = crate::notification::Notifier::new(config.clone());

    for (i, stock_code) in stock_codes.iter().enumerate() {
        if i > 0 {
            println!("{}", "".repeat(50));
        }

        // 验证输入
        crate::calculator::StockCalculator::validate_input(
            stock_code, quantity, avg_price, target_profit, max_loss,
        )?;

        // 创建股票数据
        let stock_data = StockData {
            code: stock_code.clone(),
            quantity,
            avg_price,
            target_profit,
            max_loss,
            last_updated: Utc::now(),
        };

        // 获取实时价格
        let current_price = api.fetch_stock_price(stock_code).await?;

        // 计算分析
        let analysis = crate::calculator::StockCalculator::calculate_analysis(&stock_data, current_price);

        // 显示结果
        notifier.print_analysis(&analysis);

        // 检查提醒
        notifier.check_alerts(&analysis).await?;

        // 保存到数据库
        if save {
            storage.add_stock(stock_data)?;
            println!("✅ 数据已保存到数据库");
        }
    }

    Ok(())
}

async fn handle_monitor(
    config: &AppConfig,
    code: &str,
    interval: u64,
    retry: u32,
) -> Result<()> {
    let stock_codes = crate::cli::parse_stock_codes(code);
    
    if stock_codes.is_empty() {
        return Err(crate::error::StockCalcError::ParseError("未提供有效的股票代码".to_string()));
    }

    let api = crate::api::StockApi::new(config.clone())?;
    let storage = crate::storage::Storage::new(config.clone());
    let notifier = crate::notification::Notifier::new(config.clone());

    // 获取所有股票数据
    let mut stock_data_map = std::collections::HashMap::new();
    for stock_code in &stock_codes {
        match storage.get_stock(stock_code)? {
            Some(data) => {
                stock_data_map.insert(stock_code.clone(), data);
            }
            None => {
                println!("❌ 未找到股票 {} 的数据,请先使用 calculate 命令添加", stock_code);
                return Ok(());
            }
        }
    }

    println!("🔄 实时监控: {} (每{}秒更新)", stock_codes.join(", "), interval);
    println!("{}", "".repeat(50));

    let mut interval_timer = tokio::time::interval(Duration::from_secs(interval));

    loop {
        interval_timer.tick().await;

        let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
        println!("{}", timestamp);

        for (i, stock_code) in stock_codes.iter().enumerate() {
            if i > 0 {
                println!("{}", "".repeat(30));
            }

            match api.fetch_stock_price(stock_code).await {
                Ok(current_price) => {
                    let stock_data = &stock_data_map[stock_code];
                    let analysis = crate::calculator::StockCalculator::calculate_analysis(stock_data, current_price);
                    
                    // 显示实时状态
                    println!("📈 {}: {} ({})", 
                        stock_code,
                        crate::calculator::StockCalculator::format_currency(current_price),
                        if analysis.profit_ratio > 0.0 {
                            format!("+{:.2}%", analysis.profit_ratio)
                        } else {
                            format!("{:.2}%", analysis.profit_ratio)
                        }
                    );
                    println!("📊 距离目标: {:.2}% | 距离止损: {:.2}%", 
                        analysis.distance_to_target, analysis.distance_to_stop_loss);

                    // 检查提醒
                    notifier.check_alerts(&analysis).await?;
                }
                Err(e) => {
                    println!("{} 获取价格失败: {}", stock_code, e);
                }
            }
        }
        println!();
    }
}

async fn handle_list(config: &AppConfig, detailed: bool) -> Result<()> {
    let storage = crate::storage::Storage::new(config.clone());
    let stocks = storage.list_stocks()?;

    if stocks.is_empty() {
        println!("📭 数据库中没有股票数据");
        println!("💡 使用 'stock-calc calculate' 或 'stock-calc interactive' 添加数据");
        return Ok(());
    }

    println!("📋 股票列表 (共 {} 只)", stocks.len());
    println!("{}", "".repeat(50));

    for stock in stocks {
        println!("📈 {} - {} 股 @ ¥{:.3}", 
            stock.code, 
            stock.quantity,
            stock.avg_price
        );
        
        if detailed {
            println!("   预期收益: ¥{:.3}", stock.target_profit);
            println!("   最大亏损: ¥{:.3}", stock.max_loss);
            println!("   最后更新: {}", stock.last_updated.format("%Y-%m-%d %H:%M:%S"));
            println!();
        }
    }

    if !detailed {
        println!("\n💡 使用 --detailed 查看详细信息");
    }

    Ok(())
}

async fn handle_remove(config: &AppConfig, code: &str) -> Result<()> {
    let stock_codes = crate::cli::parse_stock_codes(code);
    
    if stock_codes.is_empty() {
        return Err(crate::error::StockCalcError::ParseError("未提供有效的股票代码".to_string()));
    }

    let storage = crate::storage::Storage::new(config.clone());
    
    for stock_code in stock_codes {
        match storage.remove_stock(&stock_code)? {
            Some(_) => println!("✅ 已删除股票 {} 的数据", stock_code),
            None => println!("❌ 未找到股票 {} 的数据", stock_code),
        }
    }

    Ok(())
}

async fn handle_config(config: &AppConfig, subcommand: crate::cli::ConfigSubcommand) -> Result<()> {
    match subcommand {
        crate::cli::ConfigSubcommand::Show => {
            println!("📋 当前配置:");
            println!("{}", "".repeat(30));
            println!("默认间隔: {}", config.general.default_interval);
            println!("启用通知: {}", config.general.enable_notifications);
            println!("启用声音: {}", config.general.enable_sound);
            println!("日志级别: {}", config.general.log_level);
            println!("API超时: {}", config.api.timeout);
            println!("重试次数: {}", config.api.retry_count);
            println!("彩色输出: {}", config.display.color_output);
            println!("显示进度: {}", config.display.show_progress);
        }
        crate::cli::ConfigSubcommand::Reset => {
            let default_config = AppConfig::default();
            default_config.save()?;
            println!("✅ 配置已重置为默认值");
        }
        crate::cli::ConfigSubcommand::Edit => {
            println!("💡 请手动编辑配置文件:");
            match AppConfig::get_config_path() {
                Ok(path) => println!("   {}", path.display()),
                Err(_) => println!("   无法获取配置文件路径"),
            }
        }
    }

    Ok(())
}

async fn handle_test(config: &AppConfig, code: &str) -> Result<()> {
    let stock_codes = crate::cli::parse_stock_codes(code);
    
    if stock_codes.is_empty() {
        return Err(crate::error::StockCalcError::ParseError("未提供有效的股票代码".to_string()));
    }

    let api = crate::api::StockApi::new(config.clone())?;
    
    for (i, stock_code) in stock_codes.iter().enumerate() {
        if i > 0 {
            println!("{}", "".repeat(50));
        }

        // 尝试获取股票信息
        match api.fetch_stock_info(stock_code).await {
            Ok(stock_info) => {
                println!("✅ 股票代码 {} 可查", stock_code);
                println!("{}", "".repeat(50));
                println!("📈 股票名称: {}", stock_info.name);
                println!("🔢 股票代码: {}", stock_info.code);
                println!("💰 当前价格: ¥{:.3}", stock_info.current_price);
                println!("📊 涨跌金额: ¥{:.3}", stock_info.change_amount);
                println!("📈 涨跌幅度: {:.2}%", stock_info.change_percent);
                println!("📅 昨收价格: ¥{:.3}", stock_info.yesterday_close);
                println!("🌅 开盘价格: ¥{:.3}", stock_info.open_price);
                println!("📈 最高价格: ¥{:.3}", stock_info.high_price);
                println!("📉 最低价格: ¥{:.3}", stock_info.low_price);
                println!("📊 成交量: {}", stock_info.volume / 100);
                println!("💵 成交额: ¥{:.2}", stock_info.turnover / 10000.0);
            }
            Err(_) => {
                println!("❌ 请重新填写正确的股票代码: {}", stock_code);
            }
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_calculate_command() {
        let config = AppConfig::default();
        let result = handle_calculate(
            &config,
            "000001",
            1000.0,
            15.5,
            5000.0,
            2000.0,
            false,
        ).await;
        
        // 这个测试可能会因为网络问题失败,所以我们只检查函数能正常执行
        // 实际测试中应该使用模拟的API
        assert!(result.is_ok() || result.is_err());
    }
}