Fix async/sync conflict in paper backtest — nested event loop error
run_backtest changed from async to sync function. Pipeline refresh (async) runs via asyncio.run() before the sync BacktestRunner, avoiding nested event loop when SnapshotStore.load() calls asyncio.run(). CLI updated to call run_backtest() directly (no asyncio.run wrapper). Tested: `fithia2 paper backtest --config v6new.24 --start 2025-03-23 --end 2026-03-23` works. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>main
parent
33acf12baa
commit
0134476632
@ -0,0 +1,588 @@
|
|||||||
|
"""CLI for paper trading with Alpaca API.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
fithia2 paper <command> [options]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
auto 자동 데몬 — 매일 파이프라인 + 매매를 스케줄에 맞게 자동 실행
|
||||||
|
start Create a new paper trading session
|
||||||
|
run Run daily processing for a session
|
||||||
|
run-close 장 마감 직전: same-day 이벤트 → MOC 매수
|
||||||
|
run-open 장 시작 직후: 전날 exit + after-close 이벤트 → 시장가 매수
|
||||||
|
run-all Run daily processing for all active sessions
|
||||||
|
monitor 장중 실시간 stop/target 모니터링
|
||||||
|
status Show session status
|
||||||
|
positions Show current positions (live from Alpaca)
|
||||||
|
trades Show trade history
|
||||||
|
equity Show equity curve
|
||||||
|
sessions List all sessions
|
||||||
|
pause Pause a session
|
||||||
|
resume Resume a paused session
|
||||||
|
close Close session (liquidates all positions)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
_console = Console(width=140)
|
||||||
|
|
||||||
|
# Default path for the paper trading SQLite DB
|
||||||
|
_DEFAULT_DB = os.environ.get("PAPER_TRADER_DB", "paper_trading.db")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_state_manager(db_path: str):
|
||||||
|
from apps.paper_trader.state import StateManager
|
||||||
|
return StateManager(db_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_broker():
|
||||||
|
from apps.paper_trader.alpaca_broker import AlpacaBroker
|
||||||
|
try:
|
||||||
|
return AlpacaBroker.from_env()
|
||||||
|
except (ValueError, ImportError) as exc:
|
||||||
|
_console.print(f"[red]ERROR: {exc}[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_session(state, name_or_id: str):
|
||||||
|
session = state.get_session(name_or_id)
|
||||||
|
if session is None:
|
||||||
|
_console.print(f"[red]ERROR: Session not found: '{name_or_id}'[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Commands
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
def cmd_start(args: argparse.Namespace) -> None:
|
||||||
|
"""Create a new paper trading session."""
|
||||||
|
config_path = args.config
|
||||||
|
if not Path(config_path).exists():
|
||||||
|
_console.print(f"[red]ERROR: Config not found: {config_path}[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
|
||||||
|
# Check for duplicate name
|
||||||
|
existing = state.get_session(args.name)
|
||||||
|
if existing is not None:
|
||||||
|
_console.print(f"[red]ERROR: Session '{args.name}' already exists (id={existing.session_id}).[/]")
|
||||||
|
_console.print("Use a different name or close the existing session first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
session_id = state.create_session(
|
||||||
|
session_name=args.name,
|
||||||
|
config_path=config_path,
|
||||||
|
initial_equity=args.capital,
|
||||||
|
)
|
||||||
|
_console.print(f"[green]Session created:[/] {args.name} id={session_id} capital=${args.capital:,.0f}")
|
||||||
|
_console.print(f"[dim]Config: {config_path}[/]")
|
||||||
|
_console.print(f"\nRun daily processing with:\n fithia2 paper run --session {args.name}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_run(args: argparse.Namespace) -> None:
|
||||||
|
"""Run daily processing for a specific session."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
|
||||||
|
if session.status == "paused":
|
||||||
|
_console.print(f"[yellow]Session '{session.session_name}' is paused. Use 'resume' first.[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
if session.status == "closed":
|
||||||
|
_console.print(f"[red]Session '{session.session_name}' is closed.[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
broker = _get_broker()
|
||||||
|
|
||||||
|
from apps.paper_trader.event_detector import EventDetector
|
||||||
|
from apps.paper_trader.engine import PaperTradingEngine
|
||||||
|
from apps.paper_trader.reporter import print_run_summary
|
||||||
|
|
||||||
|
oracle_url = os.environ.get("ORACLE_URL") or os.environ.get("STOCK_ORACLE_URL", "http://localhost:8000")
|
||||||
|
db_dsn = os.environ.get("DB_DSN") or os.environ.get("POSTGRES_DSN", "")
|
||||||
|
|
||||||
|
detector = EventDetector(db_dsn=db_dsn, oracle_url=oracle_url)
|
||||||
|
engine = PaperTradingEngine(session=session, broker=broker, state=state, event_detector=detector)
|
||||||
|
|
||||||
|
target_date = None
|
||||||
|
if args.date:
|
||||||
|
import datetime as dt
|
||||||
|
try:
|
||||||
|
target_date = dt.date.fromisoformat(args.date)
|
||||||
|
except ValueError:
|
||||||
|
_console.print(f"[red]ERROR: Invalid date format: {args.date}. Use YYYY-MM-DD.[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
summary = asyncio.run(engine.run_daily(target_date=target_date, force=getattr(args, "force", False)))
|
||||||
|
print_run_summary(summary)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_engine(session, args):
|
||||||
|
broker = _get_broker()
|
||||||
|
from apps.paper_trader.event_detector import EventDetector
|
||||||
|
from apps.paper_trader.engine import PaperTradingEngine
|
||||||
|
oracle_url = os.environ.get("ORACLE_URL") or os.environ.get("STOCK_ORACLE_URL", "http://localhost:8000")
|
||||||
|
db_dsn = os.environ.get("DB_DSN") or os.environ.get("POSTGRES_DSN", "")
|
||||||
|
detector = EventDetector(db_dsn=db_dsn, oracle_url=oracle_url)
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
return PaperTradingEngine(session=session, broker=broker, state=state, event_detector=detector)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_run_close(args: argparse.Namespace) -> None:
|
||||||
|
"""장 마감 직전: same-day 이벤트 → MOC 매수."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
|
||||||
|
if session.status != "active":
|
||||||
|
_console.print(f"[red]Session '{session.session_name}' is not active.[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
import datetime as dt
|
||||||
|
target_date = None
|
||||||
|
if args.date:
|
||||||
|
try:
|
||||||
|
target_date = dt.date.fromisoformat(args.date)
|
||||||
|
except ValueError:
|
||||||
|
_console.print(f"[red]ERROR: Invalid date: {args.date}[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
engine = _make_engine(session, args)
|
||||||
|
from apps.paper_trader.reporter import print_run_summary
|
||||||
|
summary = asyncio.run(engine.run_reaction_close(target_date=target_date, force=getattr(args, "force", False)))
|
||||||
|
print_run_summary(summary)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_run_open(args: argparse.Namespace) -> None:
|
||||||
|
"""장 시작 직후: 전날 exit 판단 + after-close 이벤트 → 시장가 매수."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
|
||||||
|
if session.status != "active":
|
||||||
|
_console.print(f"[red]Session '{session.session_name}' is not active.[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
import datetime as dt
|
||||||
|
target_date = None
|
||||||
|
if args.date:
|
||||||
|
try:
|
||||||
|
target_date = dt.date.fromisoformat(args.date)
|
||||||
|
except ValueError:
|
||||||
|
_console.print(f"[red]ERROR: Invalid date: {args.date}[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
engine = _make_engine(session, args)
|
||||||
|
from apps.paper_trader.reporter import print_run_summary
|
||||||
|
summary = asyncio.run(engine.run_next_open(target_date=target_date, force=getattr(args, "force", False)))
|
||||||
|
print_run_summary(summary)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_monitor(args: argparse.Namespace) -> None:
|
||||||
|
"""장중 실시간 stop/target 모니터링 (Ctrl+C로 종료)."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
|
||||||
|
if session.status != "active":
|
||||||
|
_console.print(f"[red]Session '{session.session_name}' is not active.[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
_console.print(f"[cyan]Monitoring session '{session.session_name}' every {args.interval}s (Ctrl+C to stop)...[/]")
|
||||||
|
engine = _make_engine(session, args)
|
||||||
|
try:
|
||||||
|
asyncio.run(engine.run_monitor(interval_sec=args.interval))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
_console.print("\n[yellow]Monitor stopped.[/]")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_run_all(args: argparse.Namespace) -> None:
|
||||||
|
"""Run daily processing for all active sessions."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
sessions = [s for s in state.list_sessions() if s.status == "active"]
|
||||||
|
|
||||||
|
if not sessions:
|
||||||
|
_console.print("[dim]No active sessions found.[/]")
|
||||||
|
return
|
||||||
|
|
||||||
|
broker = _get_broker()
|
||||||
|
|
||||||
|
from apps.paper_trader.event_detector import EventDetector
|
||||||
|
from apps.paper_trader.engine import PaperTradingEngine
|
||||||
|
from apps.paper_trader.reporter import print_run_summary
|
||||||
|
|
||||||
|
oracle_url = os.environ.get("ORACLE_URL") or os.environ.get("STOCK_ORACLE_URL", "http://localhost:8000")
|
||||||
|
db_dsn = os.environ.get("DB_DSN") or os.environ.get("POSTGRES_DSN", "")
|
||||||
|
|
||||||
|
for session in sessions:
|
||||||
|
_console.print(f"\n[bold]Running session: {session.session_name}[/]")
|
||||||
|
detector = EventDetector(db_dsn=db_dsn, oracle_url=oracle_url)
|
||||||
|
engine = PaperTradingEngine(session=session, broker=broker, state=state, event_detector=detector)
|
||||||
|
summary = asyncio.run(engine.run_daily())
|
||||||
|
print_run_summary(summary)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_status(args: argparse.Namespace) -> None:
|
||||||
|
"""Show session status."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
broker = _get_broker()
|
||||||
|
|
||||||
|
from apps.paper_trader.reporter import print_status
|
||||||
|
print_status(session, broker, state)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_positions(args: argparse.Namespace) -> None:
|
||||||
|
"""Show current positions (live from Alpaca)."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
broker = _get_broker()
|
||||||
|
|
||||||
|
from apps.paper_trader.reporter import print_positions
|
||||||
|
print_positions(session, broker, state)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_trades(args: argparse.Namespace) -> None:
|
||||||
|
"""Show trade history."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
|
||||||
|
from apps.paper_trader.reporter import print_trades
|
||||||
|
print_trades(session, state, last=args.last)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_equity(args: argparse.Namespace) -> None:
|
||||||
|
"""Show equity curve."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
|
||||||
|
from apps.paper_trader.reporter import print_equity
|
||||||
|
print_equity(session, state)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_sessions(args: argparse.Namespace) -> None:
|
||||||
|
"""List all sessions."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
sessions = state.list_sessions()
|
||||||
|
|
||||||
|
from apps.paper_trader.reporter import print_sessions
|
||||||
|
print_sessions(sessions)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_pause(args: argparse.Namespace) -> None:
|
||||||
|
"""Pause a session (stops daily processing)."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
if session.status != "active":
|
||||||
|
_console.print(f"[yellow]Session '{session.session_name}' is already {session.status}.[/]")
|
||||||
|
return
|
||||||
|
state.set_session_status(session.session_id, "paused")
|
||||||
|
_console.print(f"[yellow]Session '{session.session_name}' paused.[/]")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_resume(args: argparse.Namespace) -> None:
|
||||||
|
"""Resume a paused session."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
if session.status != "paused":
|
||||||
|
_console.print(f"[yellow]Session '{session.session_name}' is not paused (status={session.status}).[/]")
|
||||||
|
return
|
||||||
|
state.set_session_status(session.session_id, "active")
|
||||||
|
_console.print(f"[green]Session '{session.session_name}' resumed.[/]")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_backtest(args: argparse.Namespace) -> None:
|
||||||
|
"""Run historical backtest simulation using paper trading engine."""
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
for cfg in args.configs:
|
||||||
|
if not Path(cfg).exists():
|
||||||
|
_console.print(f"[red]ERROR: Config not found: {cfg}[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_date = dt.date.fromisoformat(args.start)
|
||||||
|
end_date = dt.date.fromisoformat(args.end)
|
||||||
|
except ValueError as exc:
|
||||||
|
_console.print(f"[red]ERROR: Invalid date: {exc}[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
oracle_url = os.environ.get("ORACLE_URL") or os.environ.get("STOCK_ORACLE_URL", "http://localhost:8000")
|
||||||
|
db_dsn = os.environ.get("DB_DSN") or os.environ.get("POSTGRES_DSN", "")
|
||||||
|
|
||||||
|
if not db_dsn:
|
||||||
|
_console.print("[red]ERROR: POSTGRES_DSN (or DB_DSN) env var not set.[/]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
_console.print(f"[bold cyan]Backtest:[/] {start_date} → {end_date} capital=${args.capital:,.0f}")
|
||||||
|
_console.print(f"Strategies: {', '.join(args.configs)}")
|
||||||
|
|
||||||
|
from apps.paper_trader.backtest_sim import run_backtest
|
||||||
|
from apps.paper_trader.reporter import print_backtest_results
|
||||||
|
|
||||||
|
results = run_backtest(
|
||||||
|
configs=args.configs,
|
||||||
|
capital=args.capital,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
|
db_dsn=db_dsn,
|
||||||
|
oracle_url=oracle_url,
|
||||||
|
console=_console,
|
||||||
|
)
|
||||||
|
print_backtest_results(results, output_dir=args.output)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_auto(args: argparse.Namespace) -> None:
|
||||||
|
"""자동 데몬 — Phoenix(MST) 기준 ET 장 스케줄에 맞게 파이프라인 + 매매 자동 실행."""
|
||||||
|
from apps.paper_trader.auto import run_auto
|
||||||
|
run_auto(
|
||||||
|
sessions=args.session or [],
|
||||||
|
db=args.db,
|
||||||
|
dry_run=args.dry_run,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_close(args: argparse.Namespace) -> None:
|
||||||
|
"""Close a session and liquidate all positions."""
|
||||||
|
state = _get_state_manager(args.db)
|
||||||
|
session = _resolve_session(state, args.session)
|
||||||
|
|
||||||
|
if session.status == "closed":
|
||||||
|
_console.print(f"[dim]Session '{session.session_name}' is already closed.[/]")
|
||||||
|
return
|
||||||
|
|
||||||
|
broker = _get_broker()
|
||||||
|
|
||||||
|
# Confirm
|
||||||
|
if not args.yes:
|
||||||
|
positions = broker.list_positions()
|
||||||
|
if positions:
|
||||||
|
_console.print(f"[yellow]This will liquidate {len(positions)} open position(s):[/]")
|
||||||
|
for p in positions:
|
||||||
|
_console.print(f" {p.symbol}: {p.qty} shares")
|
||||||
|
confirm = input(f"Close session '{session.session_name}'? [y/N] ")
|
||||||
|
if confirm.lower() != "y":
|
||||||
|
_console.print("Cancelled.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Liquidate
|
||||||
|
try:
|
||||||
|
orders = broker.close_all_positions()
|
||||||
|
_console.print(f"[yellow]Liquidated {len(orders)} position(s).[/]")
|
||||||
|
except Exception as exc:
|
||||||
|
_console.print(f"[red]WARNING: Failed to close positions: {exc}[/]")
|
||||||
|
|
||||||
|
# Mark all open strategy states as closed
|
||||||
|
for ss in state.get_open_strategy_states(session.session_id):
|
||||||
|
state.close_strategy_state(session.session_id, ss.symbol)
|
||||||
|
|
||||||
|
state.set_session_status(session.session_id, "closed")
|
||||||
|
_console.print(f"[red]Session '{session.session_name}' closed.[/]")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Main
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
def _print_help() -> None:
|
||||||
|
from rich import box as rbox
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
_console.print()
|
||||||
|
_console.print(Panel(
|
||||||
|
"[bold cyan]fithia2 paper-trader[/] — Alpaca Paper Trading CLI\n"
|
||||||
|
"[dim]전략 백테스트 로직으로 판단, Alpaca Paper Trading API로 주문 실행[/]",
|
||||||
|
border_style="cyan",
|
||||||
|
padding=(0, 2),
|
||||||
|
))
|
||||||
|
|
||||||
|
tbl = Table(box=rbox.SIMPLE, show_header=True, header_style="bold yellow", padding=(0, 2))
|
||||||
|
tbl.add_column("Command", style="bold green", no_wrap=True)
|
||||||
|
tbl.add_column("Description")
|
||||||
|
tbl.add_column("Key Options", style="dim")
|
||||||
|
|
||||||
|
tbl.add_row("[bold cyan]backtest[/]", "과거 기간 시뮬레이션 (복수 전략 비교)", "--config PATH [--config PATH] --start DATE --end DATE [--capital N] [--output DIR]")
|
||||||
|
tbl.add_row("[bold cyan]auto[/]", "자동 데몬 — 스케줄에 맞게 파이프라인+매매 자동 실행", "[--session NAME] [--dry-run]")
|
||||||
|
tbl.add_row("", "", "")
|
||||||
|
tbl.add_row("start", "새 세션 생성", "--config PATH --capital FLOAT --name STR")
|
||||||
|
tbl.add_row("run", "일일 처리 실행 (특정 세션)", "--session NAME [--date YYYY-MM-DD]")
|
||||||
|
tbl.add_row("run-close", "장 마감 직전: same-day 이벤트 → MOC 매수", "--session NAME [--date]")
|
||||||
|
tbl.add_row("run-open", "장 시작 직후: exit + after-close 신규진입", "--session NAME [--date]")
|
||||||
|
tbl.add_row("run-all", "전체 활성 세션 일일 처리", "")
|
||||||
|
tbl.add_row("monitor", "장중 실시간 stop/target 모니터링", "--session NAME [--interval N]")
|
||||||
|
tbl.add_row("", "", "")
|
||||||
|
tbl.add_row("status", "세션 상태 조회", "--session NAME")
|
||||||
|
tbl.add_row("positions", "보유 포지션 조회 (Alpaca 실시간)", "--session NAME")
|
||||||
|
tbl.add_row("trades", "거래 내역", "--session NAME [--last N]")
|
||||||
|
tbl.add_row("equity", "Equity curve", "--session NAME")
|
||||||
|
tbl.add_row("sessions", "세션 목록", "")
|
||||||
|
tbl.add_row("pause", "세션 일시정지", "--session NAME")
|
||||||
|
tbl.add_row("resume", "세션 재개", "--session NAME")
|
||||||
|
tbl.add_row("close", "세션 종료 (전 포지션 청산)", "--session NAME [--yes]")
|
||||||
|
|
||||||
|
_console.print(tbl)
|
||||||
|
_console.print(
|
||||||
|
" [dim]공통 옵션:[/] [bold]--db[/] [dim](기본: paper_trading.db)[/]\n"
|
||||||
|
" [dim]환경변수:[/] ALPACA_API_KEY ALPACA_SECRET_KEY ALPACA_PAPER=true "
|
||||||
|
"ORACLE_URL DB_DSN\n"
|
||||||
|
)
|
||||||
|
_console.print(" [bold]예시[/]")
|
||||||
|
_console.print(" [green]fithia2 paper auto --session my_session[/] [dim]← 추천: 데몬으로 모든 것을 자동 처리[/]")
|
||||||
|
_console.print(" [green]fithia2 paper start --config configs/experiments/return_max_long_v504.json --name my_session[/]")
|
||||||
|
_console.print(" [green]fithia2 paper positions --session my_session[/]")
|
||||||
|
_console.print(" [green]fithia2 paper trades --session my_session --last 20[/]")
|
||||||
|
_console.print()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
# Auto-load .env file (python-dotenv)
|
||||||
|
try:
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
_print_help()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="fithia2 Paper Trader — Alpaca Paper Trading CLI",
|
||||||
|
add_help=True,
|
||||||
|
)
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
db_kwargs = {"default": _DEFAULT_DB, "help": f"SQLite DB path (default: {_DEFAULT_DB})"}
|
||||||
|
|
||||||
|
# backtest
|
||||||
|
p = sub.add_parser("backtest", help="Run historical backtest simulation using paper trading engine")
|
||||||
|
p.add_argument("--config", "-c", action="append", required=True,
|
||||||
|
dest="configs", metavar="PATH",
|
||||||
|
help="Config path (repeat for multiple strategies)")
|
||||||
|
p.add_argument("--capital", "-k", type=float, default=10000.0,
|
||||||
|
help="Per-session capital (default: 10000)")
|
||||||
|
p.add_argument("--start", required=True, metavar="YYYY-MM-DD",
|
||||||
|
help="Backtest start date")
|
||||||
|
p.add_argument("--end", required=True, metavar="YYYY-MM-DD",
|
||||||
|
help="Backtest end date")
|
||||||
|
p.add_argument("--output", "-o", default=None,
|
||||||
|
help="Directory to save results CSV (optional)")
|
||||||
|
|
||||||
|
# auto
|
||||||
|
p = sub.add_parser("auto", help="자동 데몬 — ET 장 스케줄에 맞게 파이프라인+매매 자동 실행")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", nargs="*", default=[], metavar="NAME",
|
||||||
|
help="Session name(s). Default: all active sessions.")
|
||||||
|
p.add_argument("--dry-run", action="store_true",
|
||||||
|
help="Print commands without executing.")
|
||||||
|
|
||||||
|
# start
|
||||||
|
p = sub.add_parser("start", help="Create a new paper trading session")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--config", "-c", required=True, help="Path to experiment manifest or config JSON")
|
||||||
|
p.add_argument("--capital", "-k", type=float, default=10000.0, help="Initial capital (default: 10000)")
|
||||||
|
p.add_argument("--name", "-n", required=True, help="Session name (unique)")
|
||||||
|
|
||||||
|
# run
|
||||||
|
p = sub.add_parser("run", help="Run daily processing for a session")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
p.add_argument("--date", "-d", help="Target date YYYY-MM-DD (default: today)")
|
||||||
|
p.add_argument("--force", "-f", action="store_true", help="Re-run even if date already processed")
|
||||||
|
|
||||||
|
# run-close
|
||||||
|
p = sub.add_parser("run-close", help="장 마감 직전: same-day 이벤트 → MOC 매수")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
p.add_argument("--date", "-d", help="Target date YYYY-MM-DD (default: today)")
|
||||||
|
p.add_argument("--force", "-f", action="store_true", help="Re-run even if already processed")
|
||||||
|
|
||||||
|
# run-open
|
||||||
|
p = sub.add_parser("run-open", help="장 시작 직후: 전날 exit + after-close 이벤트 → 시장가 매수")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
p.add_argument("--date", "-d", help="Target date YYYY-MM-DD (default: today)")
|
||||||
|
p.add_argument("--force", "-f", action="store_true", help="Re-run even if already processed")
|
||||||
|
|
||||||
|
# monitor
|
||||||
|
p = sub.add_parser("monitor", help="장중 실시간 stop/target 모니터링")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
p.add_argument("--interval", "-i", type=int, default=60, help="Check interval in seconds (default: 60)")
|
||||||
|
|
||||||
|
# run-all
|
||||||
|
p = sub.add_parser("run-all", help="Run daily processing for all active sessions")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
|
||||||
|
# status
|
||||||
|
p = sub.add_parser("status", help="Show session status")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
|
||||||
|
# positions
|
||||||
|
p = sub.add_parser("positions", help="Show current positions")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
|
||||||
|
# trades
|
||||||
|
p = sub.add_parser("trades", help="Show trade history")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
p.add_argument("--last", "-n", type=int, default=None, help="Show last N trades")
|
||||||
|
|
||||||
|
# equity
|
||||||
|
p = sub.add_parser("equity", help="Show equity curve")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
|
||||||
|
# sessions
|
||||||
|
p = sub.add_parser("sessions", help="List all sessions")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
|
||||||
|
# pause
|
||||||
|
p = sub.add_parser("pause", help="Pause a session")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
|
||||||
|
# resume
|
||||||
|
p = sub.add_parser("resume", help="Resume a paused session")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
|
||||||
|
# close
|
||||||
|
p = sub.add_parser("close", help="Close session (liquidates all positions)")
|
||||||
|
p.add_argument("--db", **db_kwargs)
|
||||||
|
p.add_argument("--session", "-s", required=True, help="Session name or ID")
|
||||||
|
p.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
dispatch = {
|
||||||
|
"backtest": cmd_backtest,
|
||||||
|
"auto": cmd_auto,
|
||||||
|
"start": cmd_start,
|
||||||
|
"run": cmd_run,
|
||||||
|
"run-close": cmd_run_close,
|
||||||
|
"run-open": cmd_run_open,
|
||||||
|
"monitor": cmd_monitor,
|
||||||
|
"run-all": cmd_run_all,
|
||||||
|
"status": cmd_status,
|
||||||
|
"positions": cmd_positions,
|
||||||
|
"trades": cmd_trades,
|
||||||
|
"equity": cmd_equity,
|
||||||
|
"sessions": cmd_sessions,
|
||||||
|
"pause": cmd_pause,
|
||||||
|
"resume": cmd_resume,
|
||||||
|
"close": cmd_close,
|
||||||
|
}
|
||||||
|
dispatch[args.command](args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue