feat: trading bot MVP — ICT Order Block + Liquidity Sweep strategy
Full-stack trading bot with: - FastAPI backend with ICT strategy (Order Block + Liquidity Sweep detection) - Backtester engine with rolling window, spread simulation, and performance metrics - Hybrid market data service (yfinance + TwelveData with rate limiting + SQLite cache) - Simulated exchange for paper trading - React/TypeScript frontend with TradingView lightweight-charts v5 - Live dashboard with candlestick chart, OHLC legend, trade markers - Backtest page with configurable parameters, equity curve, and trade table - WebSocket support for real-time updates - Bot runner with asyncio loop for automated trading Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
0
backend/app/models/__init__.py
Normal file
0
backend/app/models/__init__.py
Normal file
32
backend/app/models/backtest_result.py
Normal file
32
backend/app/models/backtest_result.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import Float, Integer, String, DateTime, JSON
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class BacktestResult(Base):
|
||||
__tablename__ = "backtest_results"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
instrument: Mapped[str] = mapped_column(String(20))
|
||||
granularity: Mapped[str] = mapped_column(String(10))
|
||||
start_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
end_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
initial_balance: Mapped[float] = mapped_column(Float, default=10000.0)
|
||||
final_balance: Mapped[float] = mapped_column(Float)
|
||||
total_pnl: Mapped[float] = mapped_column(Float)
|
||||
total_trades: Mapped[int] = mapped_column(Integer)
|
||||
winning_trades: Mapped[int] = mapped_column(Integer)
|
||||
losing_trades: Mapped[int] = mapped_column(Integer)
|
||||
win_rate: Mapped[float] = mapped_column(Float)
|
||||
max_drawdown: Mapped[float] = mapped_column(Float)
|
||||
sharpe_ratio: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
expectancy: Mapped[float] = mapped_column(Float)
|
||||
# Courbe d'équité [{time, balance}] stockée en JSON
|
||||
equity_curve: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
# Paramètres de la stratégie utilisés
|
||||
strategy_params: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
25
backend/app/models/candle.py
Normal file
25
backend/app/models/candle.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Float, Integer, String, DateTime, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Candle(Base):
|
||||
__tablename__ = "candles"
|
||||
__table_args__ = (
|
||||
# Garantit INSERT OR IGNORE sur (instrument, granularity, time)
|
||||
UniqueConstraint("instrument", "granularity", "time", name="uq_candle"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
instrument: Mapped[str] = mapped_column(String(20), index=True)
|
||||
granularity: Mapped[str] = mapped_column(String(10))
|
||||
time: Mapped[datetime] = mapped_column(DateTime, index=True)
|
||||
open: Mapped[float] = mapped_column(Float)
|
||||
high: Mapped[float] = mapped_column(Float)
|
||||
low: Mapped[float] = mapped_column(Float)
|
||||
close: Mapped[float] = mapped_column(Float)
|
||||
volume: Mapped[int] = mapped_column(Integer, default=0)
|
||||
complete: Mapped[bool] = mapped_column(default=True)
|
||||
34
backend/app/models/trade.py
Normal file
34
backend/app/models/trade.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import Float, Integer, String, DateTime
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Trade(Base):
|
||||
__tablename__ = "trades"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
# "live" | "backtest"
|
||||
source: Mapped[str] = mapped_column(String(10), default="live")
|
||||
# identifiant OANDA du trade live (si applicable)
|
||||
oanda_trade_id: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
instrument: Mapped[str] = mapped_column(String(20), index=True)
|
||||
# "buy" | "sell"
|
||||
direction: Mapped[str] = mapped_column(String(4))
|
||||
units: Mapped[float] = mapped_column(Float)
|
||||
entry_price: Mapped[float] = mapped_column(Float)
|
||||
stop_loss: Mapped[float] = mapped_column(Float)
|
||||
take_profit: Mapped[float] = mapped_column(Float)
|
||||
exit_price: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
pnl: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
# "open" | "closed" | "cancelled"
|
||||
status: Mapped[str] = mapped_column(String(10), default="open")
|
||||
# Signal ayant déclenché le trade
|
||||
signal_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
opened_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
closed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||
# ID du backtest parent (si applicable)
|
||||
backtest_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, index=True)
|
||||
Reference in New Issue
Block a user