summaryrefslogtreecommitdiff
path: root/cli/src/trading_cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli/src/trading_cli')
-rw-r--r--cli/src/trading_cli/commands/backtest.py45
-rw-r--r--cli/src/trading_cli/commands/data.py73
-rw-r--r--cli/src/trading_cli/commands/portfolio.py18
-rw-r--r--cli/src/trading_cli/commands/service.py1
-rw-r--r--cli/src/trading_cli/commands/trade.py2
-rw-r--r--cli/src/trading_cli/main.py9
6 files changed, 71 insertions, 77 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}...")
diff --git a/cli/src/trading_cli/main.py b/cli/src/trading_cli/main.py
index db3c282..0ed2307 100644
--- a/cli/src/trading_cli/main.py
+++ b/cli/src/trading_cli/main.py
@@ -1,16 +1,17 @@
import click
-from trading_cli.commands.data import data
-from trading_cli.commands.trade import trade
+
from trading_cli.commands.backtest import backtest
+from trading_cli.commands.data import data
from trading_cli.commands.portfolio import portfolio
-from trading_cli.commands.strategy import strategy
from trading_cli.commands.service import service
+from trading_cli.commands.strategy import strategy
+from trading_cli.commands.trade import trade
@click.group()
@click.version_option(version="0.1.0")
def cli():
- """Trading Platform CLI — Binance spot crypto trading"""
+ """Trading Platform CLI — US stock trading"""
pass