179 lines
6.2 KiB
Python
179 lines
6.2 KiB
Python
"""
|
|
web/app.py — ChanMacro dashboard (Flask, port 8124).
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import json
|
|
from datetime import date as Date
|
|
from flask import Flask, render_template, jsonify, request
|
|
|
|
from database import get_connection
|
|
from config import config
|
|
from scoring.price_structure import PriceStructureScorer
|
|
from scoring.breadth_scorer import BreadthScorer
|
|
from scoring.oi_matrix import OIMatrixScorer
|
|
from scoring.volatility_regime import VolatilityRegimeScorer
|
|
from regime_detector import RegimeDetector
|
|
from models import MarketStateVector
|
|
from expectancy.engine import BayesianExpectancyEngine
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
def _build_state(target: Date):
|
|
"""Build MarketStateVector and persist regime to DB."""
|
|
ps = PriceStructureScorer().compute(target)
|
|
br = BreadthScorer().compute(target)
|
|
oi = OIMatrixScorer().compute(target)
|
|
vol = VolatilityRegimeScorer().compute(target)
|
|
|
|
detector = RegimeDetector()
|
|
detector.load_state(config.db_path)
|
|
r = detector.detect(ps.score, br.breadth_top50, vol.vol_regime.value, target)
|
|
|
|
state = MarketStateVector(
|
|
date=target, regime=r.regime, regime_confidence=r.confidence,
|
|
regime_version=r.regime_version, regime_maturity_score=r.maturity_score,
|
|
breadth_top20=br.breadth_top20, breadth_top30=br.breadth_top30,
|
|
breadth_top50=br.breadth_top50, breadth_bucket=br.breadth_bucket,
|
|
breadth_divergence=br.breadth_divergence,
|
|
oi_state=oi.oi_state, volatility_regime=vol.vol_regime,
|
|
price_structure_score=ps, breadth_score=br,
|
|
oi_matrix_score=oi, volatility_regime_score=vol,
|
|
)
|
|
state.market_state_hash = state.compute_hash()
|
|
|
|
# Persist regime to DB so load_state() works across requests
|
|
conn = get_connection()
|
|
conn.execute("""
|
|
INSERT OR REPLACE INTO regime_history
|
|
(date, regime, confidence, regime_version, maturity_score, all_scores_json,
|
|
prior_regime, confirmation_days)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
""", (
|
|
str(target), r.regime.value, r.confidence, r.regime_version,
|
|
r.maturity_score, json.dumps(r.all_scores),
|
|
r.prior_regime.value if r.prior_regime else None,
|
|
r.confirmation_days,
|
|
))
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return state
|
|
|
|
|
|
@app.route("/")
|
|
def dashboard():
|
|
return render_template("index.html")
|
|
|
|
|
|
@app.route("/api/state")
|
|
def api_state():
|
|
"""Current market state with all factor scores."""
|
|
try:
|
|
target = Date.today()
|
|
state = _build_state(target)
|
|
return jsonify({
|
|
"date": str(state.date),
|
|
"regime": state.regime.value,
|
|
"regime_confidence": state.regime_confidence,
|
|
"regime_maturity": state.regime_maturity_score,
|
|
"breadth": {
|
|
"score": state.breadth_score.score,
|
|
"bucket": state.breadth_bucket.value,
|
|
"top20": state.breadth_top20,
|
|
"top30": state.breadth_top30,
|
|
"top50": state.breadth_top50,
|
|
"divergence": state.breadth_divergence,
|
|
"narrative": state.breadth_score.narrative,
|
|
},
|
|
"oi_state": state.oi_state.value,
|
|
"oi_score": state.oi_matrix_score.score,
|
|
"oi_narrative": state.oi_matrix_score.narrative,
|
|
"volatility": state.volatility_regime.value,
|
|
"price_structure": {
|
|
"score": state.price_structure_score.score,
|
|
"trend": state.price_structure_score.trend_strength,
|
|
"vol_comp": state.price_structure_score.volatility_compression,
|
|
"momentum": state.price_structure_score.momentum,
|
|
"label": state.price_structure_score.label,
|
|
"narrative": state.price_structure_score.narrative,
|
|
},
|
|
})
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
|
|
@app.route("/api/history")
|
|
def api_history():
|
|
"""Regime and factor score history."""
|
|
days = request.args.get("days", 60, type=int)
|
|
conn = get_connection()
|
|
|
|
# Regime history
|
|
regimes = conn.execute(
|
|
"SELECT date, regime, confidence, maturity_score FROM regime_history ORDER BY date DESC LIMIT ?",
|
|
(days,)
|
|
).fetchall()
|
|
|
|
# Breadth history
|
|
breadth = conn.execute(
|
|
"SELECT date, advance_top50, decline_top50, above_ema20_top50 FROM breadth_daily ORDER BY date DESC LIMIT ?",
|
|
(days,)
|
|
).fetchall()
|
|
|
|
conn.close()
|
|
|
|
return jsonify({
|
|
"regimes": [{"date": r["date"], "regime": r["regime"],
|
|
"confidence": r["confidence"], "maturity": r["maturity_score"]}
|
|
for r in reversed(regimes)],
|
|
"breadth": [{"date": b["date"], "advance": b["advance_top50"],
|
|
"decline": b["decline_top50"], "above_ema20": b["above_ema20_top50"]}
|
|
for b in reversed(breadth)],
|
|
})
|
|
|
|
|
|
@app.route("/api/expectancy")
|
|
def api_expectancy():
|
|
"""Query signal expectancy."""
|
|
signal = request.args.get("signal", "B3")
|
|
try:
|
|
target = Date.today()
|
|
state = _build_state(target)
|
|
engine = BayesianExpectancyEngine(level_min_samples=5)
|
|
report = engine.estimate(state, signal_type=signal, target_date=target)
|
|
|
|
layers = []
|
|
for l in report.layers:
|
|
layers.append({
|
|
"name": l.name,
|
|
"samples": l.samples,
|
|
"effective_samples": l.effective_samples,
|
|
"raw_winrate": l.raw_winrate,
|
|
"posterior_winrate": l.posterior_winrate,
|
|
"avg_return": l.avg_return,
|
|
})
|
|
|
|
return jsonify({
|
|
"signal": signal,
|
|
"final_estimate": report.final_estimate,
|
|
"sufficiency": report.sufficiency.value,
|
|
"source": report.source,
|
|
"avg_return_7d": report.avg_return_7d,
|
|
"profit_factor": report.profit_factor,
|
|
"max_adverse": report.max_adverse_excursion,
|
|
"layers": layers,
|
|
})
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from scheduler import get_scheduler
|
|
get_scheduler().start()
|
|
app.run(host="0.0.0.0", port=8124, debug=True)
|