feat: Implement pairing synchronization from Kraken and enhance market data feed
- Added `sync_pairings_from_kraken` function to fetch and upsert asset pairs into the config_pairings table. - Introduced `run_pairing_sync_loop` for periodic synchronization of pairings. - Enhanced `KrakenWsClient` to manage subscribed symbols for market data feeds. - Created `build_detector_from_enabled_pairings` to initialize cycle detection based on enabled pairings. - Updated FastAPI app to start market data feed and pairing synchronization tasks. - Added new API routes for managing pairings, including listing, toggling, and syncing from Kraken. - Improved dashboard templates to display pairing options and allow user interaction for backtesting. - Refactored database queries to streamline fetching and updating of pairing data.
This commit is contained in:
+16
-20
@@ -19,12 +19,10 @@ def _resolve_fee_rate(fee_rate: float | None, db_path: str | None = None) -> flo
|
||||
if db_path is not None:
|
||||
try:
|
||||
conn = duckdb.connect(db_path)
|
||||
row = conn.execute(
|
||||
"""
|
||||
row = conn.execute("""
|
||||
SELECT maker_fee FROM kraken_account_snapshots
|
||||
ORDER BY snapshot_at DESC LIMIT 1
|
||||
"""
|
||||
).fetchone()
|
||||
""").fetchone()
|
||||
conn.close()
|
||||
if row is not None and row[0] is not None:
|
||||
return float(row[0])
|
||||
@@ -53,14 +51,13 @@ def _parse_balances(raw: str) -> Mapping[str, float]:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Run a deterministic replay backtest.")
|
||||
parser = argparse.ArgumentParser(description="Run backtest.")
|
||||
parser.add_argument("--events", type=Path, required=True)
|
||||
parser.add_argument("--starting-balances", type=str, default="USD=1000.0")
|
||||
parser.add_argument("--trade-capital", type=float, default=100.0)
|
||||
parser.add_argument("--fee-rate", type=float, default=None)
|
||||
parser.add_argument("--slippage-bps", type=float, default=4.0)
|
||||
parser.add_argument("--execution-latency-ms", type=float, default=20.0)
|
||||
parser.add_argument("--db-path", type=str, default=None, help="DuckDB path for fee lookup")
|
||||
args = parser.parse_args()
|
||||
|
||||
cycles_by_pair, available_pairs = _build_graph()
|
||||
@@ -79,24 +76,23 @@ def main() -> int:
|
||||
config=config,
|
||||
started_at=events[0].occurred_at if events else datetime.now(UTC),
|
||||
)
|
||||
report = asyncio.run(
|
||||
engine.run(events, starting_balances=_parse_balances(args.starting_balances))
|
||||
)
|
||||
starting_balances = _parse_balances(args.starting_balances)
|
||||
r = asyncio.run(engine.run(events, starting_balances=starting_balances))
|
||||
|
||||
print("Backtest report:")
|
||||
print(f"- processed_events: {report.processed_events}")
|
||||
print(f"- opportunities_seen: {report.opportunities_seen}")
|
||||
print(f"- trades_executed: {report.trades_executed}")
|
||||
print(f"- win_rate: {report.win_rate if report.win_rate is not None else 'n/a'}")
|
||||
print(f"- fill_rate: {report.fill_rate if report.fill_rate is not None else 'n/a'}")
|
||||
print(f"- realized_pnl_usd: {report.realized_pnl_usd:.4f}")
|
||||
print(f"- max_drawdown_usd: {report.max_drawdown_usd:.4f}")
|
||||
print(f"- miss_reasons: {dict(report.miss_reasons)}")
|
||||
print(f"- processed_events: {r.processed_events}")
|
||||
print(f"- opportunities_seen: {r.opportunities_seen}")
|
||||
print(f"- trades_executed: {r.trades_executed}")
|
||||
print(f"- win_rate: {r.win_rate if r.win_rate is not None else 'n/a'}")
|
||||
print(f"- fill_rate: {r.fill_rate if r.fill_rate is not None else 'n/a'}")
|
||||
print(f"- realized_pnl_usd: {r.realized_pnl_usd:.4f}")
|
||||
print(f"- max_drawdown_usd: {r.max_drawdown_usd:.4f}")
|
||||
print(f"- miss_reasons: {dict(r.miss_reasons)}")
|
||||
print(
|
||||
"- execution_latency_ms: "
|
||||
f"p50={report.execution_latency_p50_ms or 0.0:.4f}, "
|
||||
f"p95={report.execution_latency_p95_ms or 0.0:.4f}, "
|
||||
f"p99={report.execution_latency_p99_ms or 0.0:.4f}"
|
||||
f"p50={r.execution_latency_p50_ms or 0.0:.4f}, "
|
||||
f"p95={r.execution_latency_p95_ms or 0.0:.4f}, "
|
||||
f"p99={r.execution_latency_p99_ms or 0.0:.4f}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -13,14 +13,13 @@ from arbitrade.storage.db import DuckDBStore
|
||||
|
||||
def _python_scan_compute(store: DuckDBStore) -> tuple[float, float | None, float | None]:
|
||||
with store.connect() as conn:
|
||||
trade_rows = conn.execute(
|
||||
"""
|
||||
trade_rows = conn.execute("""
|
||||
SELECT started_at, finished_at, realized_pnl
|
||||
FROM trades
|
||||
WHERE finished_at IS NOT NULL
|
||||
"""
|
||||
).fetchall()
|
||||
opportunity_rows = conn.execute("SELECT detected_at FROM opportunities").fetchall()
|
||||
""").fetchall()
|
||||
sql_d = "SELECT detected_at FROM opportunities"
|
||||
orows = conn.execute(sql_d).fetchall()
|
||||
|
||||
realized = sum(float(row[2]) for row in trade_rows if row[2] is not None)
|
||||
durations = [
|
||||
@@ -30,10 +29,10 @@ def _python_scan_compute(store: DuckDBStore) -> tuple[float, float | None, float
|
||||
]
|
||||
avg_duration = fmean(durations) if durations else None
|
||||
|
||||
times = [row[0] for row in opportunity_rows if isinstance(row[0], datetime)]
|
||||
times = [row[0] for row in orows if isinstance(row[0], datetime)]
|
||||
if len(times) >= 2:
|
||||
span_seconds = (max(times) - min(times)).total_seconds()
|
||||
opm = len(times) / (span_seconds / 60.0) if span_seconds > 0.0 else float(len(times))
|
||||
ss = (max(times) - min(times)).total_seconds()
|
||||
opm = len(times) / (ss / 60.0) if ss > 0.0 else float(len(times))
|
||||
elif len(times) == 1:
|
||||
opm = 60.0
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user