feat: add orderbook shadow training pipeline

This commit is contained in:
Курнат Андрей
2026-07-15 09:44:29 +03:00
parent f7a625586e
commit 5d8ad1437e
19 changed files with 1486 additions and 23 deletions
+51 -1
View File
@@ -20,6 +20,7 @@ from crypto_spot_bot.learning import TradeLearner
from crypto_spot_bot.market_data import MarketData
from crypto_spot_bot.patterns import PatternAnalyzer
from crypto_spot_bot.reconciliation import reconciliation_snapshot
from crypto_spot_bot.shadow import shadow_gate_snapshot
from crypto_spot_bot.storage import Storage
from crypto_spot_bot.strategy import SpotStrategy
from crypto_spot_bot.time_series import TimeSeriesForecaster
@@ -47,7 +48,23 @@ def create_app(settings: Settings | None = None) -> FastAPI:
pattern_analyzer = PatternAnalyzer()
learner = TradeLearner(settings, storage)
forecaster = TimeSeriesForecaster(settings)
bot = CryptoSpotBot(settings, storage, market, broker, strategy, pattern_analyzer, learner, forecaster)
runtime_dir = settings.time_series_lstm_model_path.parent
shadow_forecaster = TimeSeriesForecaster(
settings,
model_path=runtime_dir / "lstm_forecaster.shadow.json",
calibration_path=runtime_dir / "torch_shadow_calibration.json",
)
bot = CryptoSpotBot(
settings,
storage,
market,
broker,
strategy,
pattern_analyzer,
learner,
forecaster,
shadow_forecaster,
)
training = TrainingCoordinator(settings.time_series_lstm_model_path.parent)
authorizer = ApiAuthorizer(settings)
@@ -143,12 +160,31 @@ def create_app(settings: Settings | None = None) -> FastAPI:
async def retrain(_: None = Depends(authorizer.require)) -> dict[str, Any]:
data = _runtime_json(settings, "torch_retrain_guard.json")
data["coordination"] = training.status()
data["shadow"] = shadow_gate_snapshot(storage, shadow_forecaster.artifact_sha256())
return data
@app.get("/api/training/status")
async def training_status(_: None = Depends(authorizer.require)) -> dict[str, Any]:
return training.status()
@app.get("/api/training/shadow")
async def training_shadow_status(
_: None = Depends(authorizer.require),
) -> dict[str, Any]:
return shadow_gate_snapshot(storage, shadow_forecaster.artifact_sha256())
@app.post("/api/training/shadow/promote")
async def training_shadow_promote(
_: None = Depends(authorizer.require),
) -> dict[str, Any]:
gate = shadow_gate_snapshot(storage, shadow_forecaster.artifact_sha256())
if not gate.get("passed"):
raise HTTPException(status_code=409, detail={"message": "shadow forward gate has not passed", "gate": gate})
try:
return training.promote_shadow(gate)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@app.get("/api/training/market-observations")
async def training_market_observations(
symbol: str,
@@ -170,6 +206,16 @@ def create_app(settings: Settings | None = None) -> FastAPI:
"next_after_id": int(items[-1]["id"]) if items else max(0, after_id),
}
@app.get("/api/training/market-observations/manifest")
async def training_market_observation_manifest(
_: None = Depends(authorizer.require_training),
) -> dict[str, Any]:
items = storage.market_observation_manifest()
return {
"items": items,
"total_samples": sum(int(item.get("samples", 0) or 0) for item in items),
}
@app.post("/api/training/retrain")
async def training_retrain(
payload: dict[str, Any] | None = None,
@@ -233,6 +279,10 @@ def create_app(settings: Settings | None = None) -> FastAPI:
row_limit = 220
retrain_data = _runtime_json(settings, "torch_retrain_guard.json")
retrain_data["coordination"] = training.status()
retrain_data["shadow"] = shadow_gate_snapshot(
storage,
shadow_forecaster.artifact_sha256(),
)
return {
"health": {
"ok": True,