first comit
This commit is contained in:
207
src/main.rs
Normal file
207
src/main.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
//! 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 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"
|
||||
)]
|
||||
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,
|
||||
|
||||
/// 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<()> {
|
||||
// Combine years and months (default to 1 year if neither specified)
|
||||
let total_years = args.years + (args.months / 12.0);
|
||||
let total_years = if total_years <= 0.0 { 1.0 } else { total_years };
|
||||
|
||||
let client = AlpacaClient::new(api_key, api_secret)?;
|
||||
let mut backtester = Backtester::new(args.capital, args.timeframe);
|
||||
|
||||
let result = 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
|
||||
}
|
||||
Reference in New Issue
Block a user