Crypto trading bot: engine, brokers, backtester, dashboard, deploy
CI/CD / backend (push) Successful in 5m18s
CI/CD / frontend (push) Successful in 5m2s
CI/CD / build (push) Successful in 34s
CI/CD / cleanup (push) Successful in 6s
CI/CD / deploy (push) Successful in 22s

FastAPI backend with a single-strategy async engine, paper and Binance
brokers, pure-python indicators, a backtester that shares the engine's
portfolio math, and a Vue dashboard with a live WebSocket feed. Adds the
Gitea Actions CI/CD workflow that deploys to Traefik via docker_default.
This commit is contained in:
matitos committed 2026-09-18 17:02:33 +00:00
1 parent 98028248c2
commit 9c3bee87ac
73 files changed
+9759 -1

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
from __future__ import annotations
from collections.abc import Sequence
from app.schemas import BacktestMetricsOut, BacktestPointOut, BacktestTradeOut
# Bars per year for annualising the Sharpe ratio. Unknown timeframes -> None.
BARS_PER_YEAR: dict[str, float] = {
"1m": 525_600,
"3m": 175_200,
"5m": 105_120,
"15m": 35_040,
"30m": 17_520,
"1h": 8_760,
"2h": 4_380,
"4h": 2_190,
"6h": 1_460,
"12h": 730,
"1d": 365,
"3d": 121,
"1w": 52,
}
def max_drawdown_pct(equities: Sequence[float]) -> float:
peak = equities[0] if equities else 0.0
worst = 0.0
for value in equities:
peak = max(peak, value)
if peak > 0:
worst = max(worst, (peak - value) / peak)
return worst * 100
def sharpe_ratio(equities: Sequence[float], timeframe: str) -> float | None:
if len(equities) < 3:
return None
returns = [
equities[i] / equities[i - 1] - 1 for i in range(1, len(equities)) if equities[i - 1] > 0
]
if len(returns) < 2:
return None
mean = sum(returns) / len(returns)
variance = sum((r - mean) ** 2 for r in returns) / (len(returns) - 1)
std = variance**0.5
periods = BARS_PER_YEAR.get(timeframe)
if std == 0 or not periods:
return None
return mean / std * (periods**0.5)
def compute_metrics(
curve: Sequence[BacktestPointOut],
trades: Sequence[BacktestTradeOut],
starting_balance: float,
timeframe: str,
) -> BacktestMetricsOut:
equities = [point.equity for point in curve] or [starting_balance]
final_equity = equities[-1]
total_return = (final_equity / starting_balance - 1) * 100 if starting_balance else 0.0
buy_hold = (
(curve[-1].buy_hold / starting_balance - 1) * 100 if curve and starting_balance else 0.0
)
returns = [trade.return_pct for trade in trades]
gross_profit = sum(trade.pnl for trade in trades if trade.pnl > 0)
gross_loss = -sum(trade.pnl for trade in trades if trade.pnl < 0)
wins = sum(1 for trade in trades if trade.pnl > 0)
return BacktestMetricsOut(
initial_equity=starting_balance,
final_equity=final_equity,
total_return_pct=total_return,
buy_hold_return_pct=buy_hold,
max_drawdown_pct=max_drawdown_pct(equities),
num_trades=len(trades),
win_rate_pct=(wins / len(trades) * 100) if trades else 0.0,
profit_factor=(gross_profit / gross_loss) if gross_loss > 0 else None,
avg_trade_pct=(sum(returns) / len(returns)) if returns else 0.0,
best_trade_pct=max(returns) if returns else 0.0,
worst_trade_pct=min(returns) if returns else 0.0,
sharpe=sharpe_ratio(equities, timeframe),
)