diff options
Diffstat (limited to 'cli/src/trading_cli/commands')
| -rw-r--r-- | cli/src/trading_cli/commands/backtest.py | 45 | ||||
| -rw-r--r-- | cli/src/trading_cli/commands/data.py | 73 | ||||
| -rw-r--r-- | cli/src/trading_cli/commands/portfolio.py | 18 | ||||
| -rw-r--r-- | cli/src/trading_cli/commands/service.py | 1 | ||||
| -rw-r--r-- | cli/src/trading_cli/commands/trade.py | 2 |
5 files changed, 66 insertions, 73 deletions
diff --git a/cli/src/trading_cli/commands/backtest.py b/cli/src/trading_cli/commands/backtest.py index 01fe092..c17c61f 100644 --- a/cli/src/trading_cli/commands/backtest.py +++ b/cli/src/trading_cli/commands/backtest.py @@ -20,9 +20,9 @@ def backtest(): @backtest.command() @click.option("--strategy", required=True, help="Strategy name to backtest") -@click.option("--symbol", required=True, help="Trading symbol (e.g. BTCUSDT)") +@click.option("--symbol", required=True, help="Trading symbol (e.g. AAPL)") @click.option("--timeframe", default="1h", show_default=True, help="Candle timeframe") -@click.option("--balance", default=10000.0, show_default=True, help="Initial balance in USDT") +@click.option("--balance", default=10000.0, show_default=True, help="Initial balance in USD") @click.option( "--output", "output_format", @@ -35,11 +35,12 @@ def backtest(): def run(strategy, symbol, timeframe, balance, output_format, file_path): """Run a backtest for a strategy.""" try: - from strategy_engine.plugin_loader import load_strategies from backtester.engine import BacktestEngine - from backtester.reporter import format_report, export_csv, export_json - from shared.db import Database + from backtester.reporter import export_csv, export_json, format_report + from strategy_engine.plugin_loader import load_strategies + from shared.config import Settings + from shared.db import Database from shared.models import Candle except ImportError as e: click.echo(f"Error: Could not import required modules: {e}", err=True) @@ -58,7 +59,7 @@ def run(strategy, symbol, timeframe, balance, output_format, file_path): async def _run(): settings = Settings() - db = Database(settings.database_url) + db = Database(settings.database_url.get_secret_value()) await db.connect() try: candle_rows = await db.get_candles(symbol, timeframe, limit=500) @@ -66,20 +67,19 @@ def run(strategy, symbol, timeframe, balance, output_format, file_path): click.echo(f"Error: No candles found for {symbol} {timeframe}", err=True) sys.exit(1) - candles = [] - for row in reversed(candle_rows): # get_candles returns DESC, we need ASC - candles.append( - Candle( - symbol=row["symbol"], - timeframe=row["timeframe"], - open_time=row["open_time"], - open=row["open"], - high=row["high"], - low=row["low"], - close=row["close"], - volume=row["volume"], - ) + candles = [ + Candle( + symbol=row["symbol"], + timeframe=row["timeframe"], + open_time=row["open_time"], + open=row["open"], + high=row["high"], + low=row["low"], + close=row["close"], + volume=row["volume"], ) + for row in reversed(candle_rows) # get_candles returns DESC, we need ASC + ] engine = BacktestEngine(strat, Decimal(str(balance))) result = engine.run(candles) @@ -111,10 +111,11 @@ def run(strategy, symbol, timeframe, balance, output_format, file_path): def walk_forward(strategy, symbol, timeframe, balance, windows): """Run walk-forward analysis to detect overfitting.""" try: - from strategy_engine.plugin_loader import load_strategies from backtester.walk_forward import WalkForwardEngine - from shared.db import Database + from strategy_engine.plugin_loader import load_strategies + from shared.config import Settings + from shared.db import Database from shared.models import Candle except ImportError as e: click.echo(f"Error: Could not import required modules: {e}", err=True) @@ -131,7 +132,7 @@ def walk_forward(strategy, symbol, timeframe, balance, windows): async def _run(): settings = Settings() - db = Database(settings.database_url) + db = Database(settings.database_url.get_secret_value()) await db.connect() try: rows = await db.get_candles(symbol, timeframe, limit=2000) diff --git a/cli/src/trading_cli/commands/data.py b/cli/src/trading_cli/commands/data.py index 2810a07..64639cf 100644 --- a/cli/src/trading_cli/commands/data.py +++ b/cli/src/trading_cli/commands/data.py @@ -1,5 +1,6 @@ import asyncio import sys +from datetime import UTC from pathlib import Path import click @@ -16,10 +17,10 @@ def data(): @data.command() -@click.option("--symbol", required=True, help="Trading symbol (e.g. BTCUSDT)") +@click.option("--symbol", required=True, help="Trading symbol (e.g. AAPL)") @click.option("--timeframe", default="1m", show_default=True, help="Candle timeframe") def collect(symbol, timeframe): - """Start collecting live market data for a symbol.""" + """Start collecting live stock market data for a symbol.""" click.echo(f"To collect live data for {symbol} at {timeframe}, run the data-collector service:") click.echo() click.echo(" docker compose up -d data-collector") @@ -31,76 +32,63 @@ def collect(symbol, timeframe): @data.command() -@click.option("--symbol", required=True, help="Trading symbol (e.g. BTCUSDT)") -@click.option("--timeframe", default="1m", show_default=True, help="Candle timeframe") +@click.option("--symbol", required=True, help="Trading symbol (e.g. AAPL)") +@click.option("--timeframe", default="1Day", show_default=True, help="Bar timeframe") @click.option("--from", "since", default=None, help="Start date (ISO format)") -@click.option("--limit", default=1000, show_default=True, help="Number of candles to fetch") +@click.option("--limit", default=1000, show_default=True, help="Number of bars to fetch") def history(symbol, timeframe, since, limit): - """Download historical market data for a symbol.""" - sys.path.insert(0, str(_ROOT / "services" / "data-collector" / "src")) - + """Download historical stock market data for a symbol.""" try: - from data_collector.binance_rest import fetch_historical_candles - from shared.db import Database + from shared.alpaca_client import AlpacaClient from shared.config import Settings + from shared.db import Database except ImportError as e: click.echo(f"Error: Could not import required modules: {e}", err=True) sys.exit(1) async def _fetch(): - import ccxt.async_support as ccxt - from datetime import datetime, timezone + from datetime import datetime settings = Settings() - db = Database(settings.database_url) + db = Database(settings.database_url.get_secret_value()) await db.connect() - # Parse the since date to a timestamp in ms + start = None if since: try: - dt = datetime.fromisoformat(since).replace(tzinfo=timezone.utc) - since_ms = int(dt.timestamp() * 1000) + start = datetime.fromisoformat(since).replace(tzinfo=UTC) except ValueError: click.echo( f"Error: Invalid date format '{since}'. Use ISO format (e.g. 2024-01-01).", err=True, ) sys.exit(1) - else: - # Default: fetch from 1000 candles ago (approximate) - since_ms = None - - # Normalize symbol for ccxt (BTCUSDT -> BTC/USDT) - ccxt_symbol = symbol - if "/" not in symbol and "USDT" in symbol: - base = symbol.replace("USDT", "") - ccxt_symbol = f"{base}/USDT" - - exchange = ccxt.binance( - { - "apiKey": settings.binance_api_key, - "secret": settings.binance_api_secret, - } + + client = AlpacaClient( + api_key=settings.alpaca_api_key.get_secret_value(), + api_secret=settings.alpaca_api_secret.get_secret_value(), + base_url=getattr(settings, "alpaca_base_url", "https://paper-api.alpaca.markets"), ) try: - kwargs = {"limit": limit} - if since_ms is not None: - kwargs["since"] = since_ms - - candles = await fetch_historical_candles(exchange, ccxt_symbol, timeframe, **kwargs) + candles = await client.get_historical_bars( + symbol=symbol, + timeframe=timeframe, + start=start, + limit=limit, + ) count = 0 for candle in candles: await db.insert_candle(candle) count += 1 - click.echo(f"Saved {count} candles for {symbol} ({timeframe}) to database.") + click.echo(f"Saved {count} bars for {symbol} ({timeframe}) to database.") except Exception as e: - click.echo(f"Error fetching candles: {e}", err=True) + click.echo(f"Error fetching bars: {e}", err=True) sys.exit(1) finally: - await exchange.close() + await client.close() await db.close() asyncio.run(_fetch()) @@ -110,17 +98,18 @@ def history(symbol, timeframe, since, limit): def list_(): """List available data streams and symbols.""" try: - from shared.db import Database + from sqlalchemy import func, select + from shared.config import Settings + from shared.db import Database from shared.sa_models import CandleRow - from sqlalchemy import select, func except ImportError as e: click.echo(f"Error: Could not import required modules: {e}", err=True) sys.exit(1) async def _list(): settings = Settings() - db = Database(settings.database_url) + db = Database(settings.database_url.get_secret_value()) await db.connect() try: stmt = ( diff --git a/cli/src/trading_cli/commands/portfolio.py b/cli/src/trading_cli/commands/portfolio.py index ad9a6b4..fd3ebd6 100644 --- a/cli/src/trading_cli/commands/portfolio.py +++ b/cli/src/trading_cli/commands/portfolio.py @@ -1,6 +1,6 @@ import asyncio import sys -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta import click from rich.console import Console @@ -17,17 +17,18 @@ def portfolio(): def show(): """Show the current portfolio holdings and balances.""" try: - from shared.db import Database + from sqlalchemy import select + from shared.config import Settings + from shared.db import Database from shared.sa_models import PositionRow - from sqlalchemy import select except ImportError as e: click.echo(f"Error: Could not import required modules: {e}", err=True) sys.exit(1) async def _show(): settings = Settings() - db = Database(settings.database_url) + db = Database(settings.database_url.get_secret_value()) await db.connect() try: async with db.get_session() as session: @@ -71,20 +72,21 @@ def show(): def history(days): """Show PnL history for the portfolio.""" try: - from shared.db import Database + from sqlalchemy import select + from shared.config import Settings + from shared.db import Database from shared.sa_models import PortfolioSnapshotRow - from sqlalchemy import select except ImportError as e: click.echo(f"Error: Could not import required modules: {e}", err=True) sys.exit(1) async def _history(): settings = Settings() - db = Database(settings.database_url) + db = Database(settings.database_url.get_secret_value()) await db.connect() try: - since = datetime.now(timezone.utc) - timedelta(days=days) + since = datetime.now(UTC) - timedelta(days=days) stmt = ( select(PortfolioSnapshotRow) .where(PortfolioSnapshotRow.snapshot_at >= since) diff --git a/cli/src/trading_cli/commands/service.py b/cli/src/trading_cli/commands/service.py index d01eaae..6d02f14 100644 --- a/cli/src/trading_cli/commands/service.py +++ b/cli/src/trading_cli/commands/service.py @@ -1,4 +1,5 @@ import subprocess + import click diff --git a/cli/src/trading_cli/commands/trade.py b/cli/src/trading_cli/commands/trade.py index f90e0ed..6bbd5a6 100644 --- a/cli/src/trading_cli/commands/trade.py +++ b/cli/src/trading_cli/commands/trade.py @@ -9,7 +9,7 @@ def trade(): @trade.command() @click.option("--strategy", required=True, help="Strategy name to run") -@click.option("--symbol", required=True, help="Trading symbol (e.g. BTCUSDT)") +@click.option("--symbol", required=True, help="Trading symbol (e.g. AAPL)") def start(strategy, symbol): """Start a trading bot for a strategy and symbol.""" click.echo(f"Starting trading bot: strategy={strategy}, symbol={symbol}...") |
