MidasBot: bot trading crypto IA + stratégies Ichimoku validées
- Infra: Freqtrade (futures dry-run) + Redis + dashboard + Docker Compose - Couche IA: ai_analyzer (Claude via abonnement, MCP TradingView, backfill biais) - Stratégies: SampleStrategy, AiBiasStrategy, IchimokuLS (long/short, validée train/test + données vierges + walk-forward), MTFIchimoku, variantes hyperopt - Arbitrage CEX (dry-run), backtesting, walk-forward, volatility targeting - IchimokuLS en dry-run live (config_live.json) Claude-Session: https://claude.ai/code/session_01VHETcFacdnDhQzthLpdYFR
This commit is contained in:
89
ai_analyzer/signal_store.py
Normal file
89
ai_analyzer/signal_store.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""Stockage des biais de marché.
|
||||
|
||||
- Redis : état COURANT (lu par la stratégie en live/dry-run). TTL court.
|
||||
- Historique CSV horodaté par paire : trace durable pour backtester l'IA
|
||||
(la stratégie en mode backtest lit le biais valide à chaque bougie).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import redis
|
||||
|
||||
from models import MarketBias
|
||||
|
||||
_KEY_PREFIX = "bias:"
|
||||
_DEFAULT_TTL = 3 * 3600 # 3 h : un biais expire s'il n'est pas rafraîchi
|
||||
_HISTORY_HEADER = ["timestamp", "direction", "confidence", "key_support", "key_resistance"]
|
||||
|
||||
|
||||
def _client() -> redis.Redis:
|
||||
url = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
|
||||
return redis.Redis.from_url(url, decode_responses=True)
|
||||
|
||||
|
||||
def _key(pair: str) -> str:
|
||||
return f"{_KEY_PREFIX}{pair}"
|
||||
|
||||
|
||||
def write_bias(bias: MarketBias, ttl: int = _DEFAULT_TTL, r: Optional[redis.Redis] = None) -> None:
|
||||
r = r or _client()
|
||||
r.set(_key(bias.pair), bias.model_dump_json(), ex=ttl)
|
||||
|
||||
|
||||
def read_bias(pair: str, r: Optional[redis.Redis] = None) -> Optional[MarketBias]:
|
||||
r = r or _client()
|
||||
raw = r.get(_key(pair))
|
||||
if not raw:
|
||||
return None
|
||||
try:
|
||||
return MarketBias.model_validate_json(raw)
|
||||
except Exception: # noqa: BLE001 — donnée corrompue : on l'ignore
|
||||
return None
|
||||
|
||||
|
||||
def _history_path(history_dir: str, pair: str) -> Path:
|
||||
return Path(history_dir) / f"{pair.replace('/', '_')}.csv"
|
||||
|
||||
|
||||
def append_history(
|
||||
bias: MarketBias, history_dir: str, ts: Optional[datetime] = None
|
||||
) -> None:
|
||||
"""Ajoute une ligne horodatée à l'historique CSV de la paire (créé si absent)."""
|
||||
ts = ts or datetime.now(timezone.utc)
|
||||
path = _history_path(history_dir, bias.pair)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
write_header = not path.exists()
|
||||
with path.open("a", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
if write_header:
|
||||
writer.writerow(_HISTORY_HEADER)
|
||||
writer.writerow(
|
||||
[
|
||||
ts.isoformat(),
|
||||
bias.direction,
|
||||
bias.confidence,
|
||||
bias.key_support if bias.key_support is not None else "",
|
||||
bias.key_resistance if bias.key_resistance is not None else "",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def read_all(r: Optional[redis.Redis] = None) -> dict[str, MarketBias]:
|
||||
r = r or _client()
|
||||
out: dict[str, MarketBias] = {}
|
||||
for key in r.scan_iter(f"{_KEY_PREFIX}*"):
|
||||
raw = r.get(key)
|
||||
if not raw:
|
||||
continue
|
||||
try:
|
||||
bias = MarketBias.model_validate_json(raw)
|
||||
out[bias.pair] = bias
|
||||
except Exception: # noqa: BLE001
|
||||
continue
|
||||
return out
|
||||
Reference in New Issue
Block a user