CoolFace
Apppublic

HARSHARAVURI/stoker-mft

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
feedback_loop.py206 linesDownload Raw Back to root
1"""2Phase 6 — Weekly Evaluation & Feedback Loop3Analyses closed losing trades and generates prompt improvement suggestions.4 5Usage:6    python feedback_loop.py              # analyse last 7 days7    python feedback_loop.py --days 14    # analyse last 14 days8    python feedback_loop.py --save       # save suggestions to prompts/refinements.md9"""10import sys11import math12import argparse13import json14from datetime import datetime, timedelta15from zoneinfo import ZoneInfo16from dotenv import load_dotenv17 18_IST = ZoneInfo("Asia/Kolkata")19load_dotenv()20 21if sys.platform == "win32":22    sys.stdout.reconfigure(encoding="utf-8")23 24from database.ledger import init_db, get_all_trades, get_portfolio_value25from tools.llm_factory import get_llm_instance26from langchain_core.messages import SystemMessage, HumanMessage27 28DIVIDER = "─" * 6029 30ANALYSIS_PROMPT = """You are a quantitative trading desk review committee.31You are reviewing a set of paper trades that hit their stop-loss this week.32Each entry includes the agent's reasoning at the time of the trade.33 34Your job is to:351. Identify recurring logical flaws or blind spots in the reasoning362. Identify which data signals were ignored or misweighted373. Suggest SPECIFIC changes to the agent system prompts to prevent these errors384. Rate the severity of each flaw: HIGH / MEDIUM / LOW39 40Be direct and specific. Reference the actual rationale text."""41 42 43def compute_stats(trades: list[dict], days: int) -> dict:44    cutoff = (datetime.now(_IST) - timedelta(days=days)).isoformat()45    recent = [t for t in trades if t["timestamp"] >= cutoff]46 47    total = len(recent)48    wins = [t for t in recent if t["status"] == "CLOSED_TP"]49    losses = [t for t in recent if t["status"] == "CLOSED_SL"]50    open_pos = [t for t in recent if t["status"] == "OPEN"]51 52    closed = wins + losses53    win_rate = len(wins) / len(closed) * 100 if closed else 054    pnl_series = [float(t["realized_pnl"] or 0) for t in closed]55    total_pnl = sum(pnl_series)56    avg_win = sum(t["realized_pnl"] or 0 for t in wins) / len(wins) if wins else 057    avg_loss = sum(t["realized_pnl"] or 0 for t in losses) / len(losses) if losses else 058    r_multiple = abs(avg_win / avg_loss) if avg_loss != 0 else 059 60    # Sharpe ratio (annualised, assuming each trade = 1 day unit)61    sharpe = 0.062    if len(pnl_series) >= 2:63        mean_pnl = total_pnl / len(pnl_series)64        variance = sum((p - mean_pnl) ** 2 for p in pnl_series) / len(pnl_series)65        std_pnl = math.sqrt(variance)66        sharpe = round((mean_pnl / std_pnl) * math.sqrt(252), 2) if std_pnl > 0 else 0.067 68    # Confidence calibration: avg predicted confidence vs actual win rate per confidence bucket69    conf_buckets: dict[str, dict] = {}70    for t in closed:71        conf = float(t.get("agent_confidence") or 0)72        bucket = f"{int(conf * 10) * 10}-{int(conf * 10) * 10 + 10}%"73        if bucket not in conf_buckets:74            conf_buckets[bucket] = {"trades": 0, "wins": 0, "avg_conf": []}75        conf_buckets[bucket]["trades"] += 176        conf_buckets[bucket]["avg_conf"].append(conf)77        if t["status"] == "CLOSED_TP":78            conf_buckets[bucket]["wins"] += 179    calibration = {80        k: {81            "trades": v["trades"],82            "predicted_conf": round(sum(v["avg_conf"]) / len(v["avg_conf"]) * 100, 1),83            "actual_win_rate": round(v["wins"] / v["trades"] * 100, 1),84        }85        for k, v in conf_buckets.items()86    }87 88    return {89        "period_days": days,90        "total_trades": total,91        "wins": len(wins),92        "losses": len(losses),93        "open": len(open_pos),94        "win_rate": round(win_rate, 1),95        "total_pnl": round(total_pnl, 2),96        "avg_win": round(avg_win, 2),97        "avg_loss": round(avg_loss, 2),98        "r_multiple": round(r_multiple, 2),99        "sharpe": sharpe,100        "confidence_calibration": calibration,101        "losing_trades": losses,102    }103 104 105def analyse_losing_trades(losses: list[dict]) -> str:106    if not losses:107        return "No losing trades to analyse."108 109    trade_summaries = []110    for t in losses:111        trade_summaries.append({112            "asset": t["asset"],113            "direction": t["direction"],114            "entry": t["simulated_entry"],115            "stop_loss": t["stop_loss"],116            "close_price": t["close_price"],117            "pnl": t["realized_pnl"],118            "market_theme": t["market_theme"],119            "rationale": (t["rationale_log"] or "")[:600],120        })121 122    llm = get_llm_instance(temperature=0.2)123    messages = [124        SystemMessage(content=ANALYSIS_PROMPT),125        HumanMessage(content=f"Losing trades to review:\n\n{json.dumps(trade_summaries, indent=2)}"),126    ]127    response = llm.invoke(messages)128    return response.content.strip()129 130 131def print_stats(stats: dict):132    print(f"\n{'=' * 60}")133    print(f"  PERFORMANCE REVIEW — Last {stats['period_days']} days")134    print(f"{'=' * 60}")135    print(f"  Total Trades : {stats['total_trades']}  ({stats['open']} still open)")136    print(f"  Wins / Losses: {stats['wins']} / {stats['losses']}")137    print(f"  Win Rate     : {stats['win_rate']}%")138    print(f"  Total P&L    : ₹{stats['total_pnl']:,.2f}")139    print(f"  Avg Win      : ₹{stats['avg_win']:,.2f}")140    print(f"  Avg Loss     : ₹{stats['avg_loss']:,.2f}")141    print(f"  R-Multiple   : {stats['r_multiple']:.2f}x")142    print(f"  Sharpe Ratio : {stats['sharpe']:.2f}")143 144    portfolio = get_portfolio_value()145    print(f"  Portfolio    : ₹{portfolio:,.2f}")146 147    # Confidence calibration table148    if stats["confidence_calibration"]:149        print(f"\n  Confidence Calibration:")150        print(f"  {'Bucket':<12} {'Trades':>6} {'Predicted':>10} {'Actual WR':>10}")151        print(f"  {'-'*42}")152        for bucket, data in sorted(stats["confidence_calibration"].items()):153            print(f"  {bucket:<12} {data['trades']:>6} {data['predicted_conf']:>9.1f}% {data['actual_win_rate']:>9.1f}%")154 155    # Live trading gate156    total_closed = stats["wins"] + stats["losses"]157    print(f"\n  Live Trading Gate:")158    print(f"  {'✅' if total_closed >= 100 else '⬜'} 100 closed trades ({total_closed}/100)")159    print(f"  {'✅' if stats['win_rate'] >= 52 else '⬜'} Win rate ≥ 52% ({stats['win_rate']}%)")160    print(f"  {'✅' if stats['r_multiple'] >= 1.0 else '⬜'} R-Multiple ≥ 1.0 ({stats['r_multiple']}x)")161    print(f"  {'✅' if stats['sharpe'] >= 1.0 else '⬜'} Sharpe ≥ 1.0 ({stats['sharpe']:.2f})")162 163 164def run(days: int = 7, save: bool = False):165    init_db()166    trades = get_all_trades()167 168    if not trades:169        print("No trades in ledger yet. Run some cycles first.")170        return171 172    stats = compute_stats(trades, days)173    print_stats(stats)174 175    losses = stats["losing_trades"]176    if not losses:177        print(f"\n  No stop-loss hits in the last {days} days. Nothing to refine.")178        return179 180    print(f"\n{DIVIDER}")181    print(f"  LLM ANALYSIS OF {len(losses)} LOSING TRADE(S)")182    print(DIVIDER)183    analysis = analyse_losing_trades(losses)184    print(f"\n{analysis}")185 186    if save:187        from pathlib import Path188        out_path = Path("prompts/refinements.md")189        out_path.parent.mkdir(exist_ok=True)190        with open(out_path, "a", encoding="utf-8") as f:191            f.write(f"\n\n## Review — {datetime.now().date()} (last {days} days)\n\n")192            f.write(f"**Stats:** {stats['wins']}W / {stats['losses']}L | "193                    f"Win rate: {stats['win_rate']}% | P&L: ₹{stats['total_pnl']:,.2f}\n\n")194            f.write(analysis)195        print(f"\n  Saved to {out_path}")196 197    print()198 199 200if __name__ == "__main__":201    parser = argparse.ArgumentParser(description="Weekly feedback loop analysis")202    parser.add_argument("--days", type=int, default=7, help="Look-back window in days")203    parser.add_argument("--save", action="store_true", help="Save suggestions to prompts/refinements.md")204    args = parser.parse_args()205    run(args.days, args.save)206