//! Tech Giants Trading Bot - Algorithmic trading system using Alpaca API. //! //! A momentum + mean-reversion hybrid strategy for 50 stocks across multiple sectors. //! Uses RSI, MACD, EMA, ADX, Bollinger Bands, and momentum indicators for trade signals. //! //! # Usage //! //! ```bash //! # Live paper trading //! invest-bot //! //! # Backtest with historical data //! invest-bot --backtest --years 3 //! invest-bot --backtest --years 5 --capital 50000 //! ``` mod alpaca; mod backtester; mod bot; mod config; mod dashboard; mod indicators; mod paths; mod strategy; mod types; use anyhow::{Context, Result}; use clap::Parser; use std::env; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; use crate::alpaca::AlpacaClient; use crate::backtester::{save_backtest_results, Backtester}; use crate::bot::TradingBot; use crate::config::{Timeframe, DEFAULT_INITIAL_CAPITAL}; /// Vibe Invest - Trade with Alpaca #[derive(Parser, Debug)] #[command(name = "vibe-invest")] #[command(author = "Vibe Invest Team")] #[command(version = "0.1.0")] #[command(about = "Tech Giants Trading Bot - Algorithmic trading system using Alpaca API")] #[command( long_about = "A momentum + mean-reversion hybrid strategy for 50 stocks across multiple sectors.\n\ Uses RSI, MACD, EMA, ADX, Bollinger Bands, and momentum indicators for trade signals.\n\n\ Examples:\n \ Live trading: invest-bot\n \ Live daily bars: invest-bot --timeframe daily\n \ Backtest 3 years: invest-bot --backtest --years 3\n \ Backtest 6 months: invest-bot --backtest --months 6\n \ Backtest 1y 6m: invest-bot --backtest --years 1 --months 6\n \ Custom capital: invest-bot --backtest --years 5 --capital 50000\n \ Hourly backtest: invest-bot --backtest --years 1 --timeframe hourly\n \ Custom date range: invest-bot --backtest --start-date 2007-01-01 --end-date 2008-12-31" )] struct Args { /// Run in backtest mode instead of live trading #[arg(short, long)] backtest: bool, /// Number of years to backtest (default: 1 if --months not specified) #[arg(short, long, default_value_t = 0.0)] years: f64, /// Number of months to backtest (can combine with --years) #[arg(short, long, default_value_t = 0.0)] months: f64, /// Start date for backtest (YYYY-MM-DD). Overrides --years/--months if provided. #[arg(long, value_name = "YYYY-MM-DD")] start_date: Option, /// End date for backtest (YYYY-MM-DD). Defaults to now if not provided. #[arg(long, value_name = "YYYY-MM-DD")] end_date: Option, /// Initial capital for backtesting #[arg(short, long, default_value_t = DEFAULT_INITIAL_CAPITAL)] capital: f64, /// Timeframe for bars #[arg(short, long, value_enum, default_value_t = Timeframe::Daily)] timeframe: Timeframe, } fn setup_logging(backtest_mode: bool) { let filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info")); if backtest_mode { // Console only for backtest tracing_subscriber::registry() .with(filter) .with(fmt::layer()) .init(); } else { // Console + file for live trading let log_path = &paths::LOG_FILE; let log_dir = log_path.parent().unwrap(); let log_file_name = log_path.file_name().unwrap(); let file_appender = tracing_appender::rolling::never(log_dir, log_file_name); let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); tracing_subscriber::registry() .with(filter) .with(fmt::layer()) .with(fmt::layer().with_writer(non_blocking).with_ansi(false)) .init(); } } fn print_banner(backtest: bool) { if backtest { println!( r#" +---------------------------------------------------------+ | TECH GIANTS TRADING BOT - BACKTEST MODE | +---------------------------------------------------------+ | Symbols: AAPL, MSFT, GOOGL, AMZN, META, NVDA, TSLA | | Strategy: Momentum + RSI Mean Reversion + MACD | +---------------------------------------------------------+ "# ); } else { let dashboard_port = env::var("DASHBOARD_PORT").unwrap_or_else(|_| "5000".to_string()); println!( r#" +---------------------------------------------------------+ | TECH GIANTS TRADING BOT - PAPER TRADING | +---------------------------------------------------------+ | Symbols: AAPL, MSFT, GOOGL, AMZN, META, NVDA, TSLA | | Strategy: Momentum + RSI Mean Reversion + MACD | | Mode: Alpaca Paper Trading | | Dashboard: http://localhost:{:<29}| +---------------------------------------------------------+ "#, dashboard_port ); } } fn get_credentials() -> Result<(String, String)> { let api_key = env::var("ALPACA_API_KEY").context( "Missing ALPACA_API_KEY. Please set it in your .env file or environment.", )?; let api_secret = env::var("ALPACA_SECRET_KEY").context( "Missing ALPACA_SECRET_KEY. Please set it in your .env file or environment.", )?; Ok((api_key, api_secret)) } #[tokio::main] async fn main() -> Result<()> { // Load .env file if present dotenvy::dotenv().ok(); let args = Args::parse(); setup_logging(args.backtest); print_banner(args.backtest); let (api_key, api_secret) = match get_credentials() { Ok(creds) => creds, Err(e) => { eprintln!("\nConfiguration Error: {}", e); eprintln!("\nPlease create a .env file with your Alpaca credentials:"); eprintln!("ALPACA_API_KEY=your_api_key"); eprintln!("ALPACA_SECRET_KEY=your_secret_key"); eprintln!("\nGet your free paper trading API keys at: https://alpaca.markets/"); std::process::exit(1); } }; if args.backtest { run_backtest(api_key, api_secret, args).await } else { run_live_trading(api_key, api_secret, args).await } } async fn run_backtest(api_key: String, api_secret: String, args: Args) -> Result<()> { use chrono::NaiveDate; let client = AlpacaClient::new(api_key, api_secret)?; let mut backtester = Backtester::new(args.capital, args.timeframe); let result = if args.start_date.is_some() || args.end_date.is_some() { // Custom date range mode let start_date = if let Some(ref s) = args.start_date { NaiveDate::parse_from_str(s, "%Y-%m-%d") .context("Invalid start date format. Use YYYY-MM-DD (e.g., 2007-01-01)")? } else { // If no start date provided, default to 1 year before end date let end = if let Some(ref e) = args.end_date { NaiveDate::parse_from_str(e, "%Y-%m-%d")? } else { chrono::Utc::now().date_naive() }; end - chrono::Duration::days(365) }; let end_date = if let Some(ref e) = args.end_date { NaiveDate::parse_from_str(e, "%Y-%m-%d") .context("Invalid end date format. Use YYYY-MM-DD (e.g., 2008-12-31)")? } else { chrono::Utc::now().date_naive() }; // Validate date range if start_date >= end_date { anyhow::bail!("Start date must be before end date"); } backtester.run_with_dates(&client, start_date, end_date).await? } else { // Years/months mode (existing behavior) let total_years = args.years + (args.months / 12.0); let total_years = if total_years <= 0.0 { 1.0 } else { total_years }; backtester.run(&client, total_years).await? }; // Save results to CSV save_backtest_results(&result)?; Ok(()) } async fn run_live_trading(api_key: String, api_secret: String, args: Args) -> Result<()> { let dashboard_port: u16 = env::var("DASHBOARD_PORT") .unwrap_or_else(|_| "5000".to_string()) .parse() .unwrap_or(5000); // Create a separate client for the dashboard let dashboard_client = AlpacaClient::new(api_key.clone(), api_secret.clone())?; // Spawn dashboard in background tokio::spawn(async move { if let Err(e) = dashboard::start_dashboard(dashboard_client, dashboard_port).await { tracing::error!("Dashboard error: {}", e); } }); // Run the trading bot let mut bot = TradingBot::new(api_key, api_secret, args.timeframe).await?; bot.run().await }