File size: 8,359 Bytes
ed6a20b
706e07a
ed6a20b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706e07a
 
ed6a20b
 
 
 
 
 
 
 
 
 
 
 
 
706e07a
 
 
 
 
 
 
 
 
 
 
ed6a20b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706e07a
 
 
 
 
 
ed6a20b
 
 
 
 
 
 
 
 
706e07a
 
 
 
 
 
 
 
ed6a20b
 
 
 
 
 
 
 
 
 
 
706e07a
 
 
 
 
 
 
 
 
ed6a20b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706e07a
ed6a20b
 
 
 
706e07a
 
 
ed6a20b
 
 
 
 
706e07a
 
ed6a20b
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# learning_hub/reflector.py
# (محدث بالكامل - V2 - تمرير الأخبار للتعلم السريع)
import json
import traceback
from typing import Dict, Any, TYPE_CHECKING
from .schemas import TraceLog, ReflectorOutput
from .memory_store import MemoryStore

# (استخدام TYPE_CHECKING لتجنب الاستيراد الدائري الفعلي)
if TYPE_CHECKING:
    from LLM import LLMService

class Reflector:
    def __init__(self, llm_service: 'LLMService', memory_store: MemoryStore):
        self.llm_service = llm_service
        self.memory_store = memory_store
        print("✅ Learning Hub Module: Reflector (Fast-Learner) loaded")

    async def analyze_trade_outcome(self, trade_object: Dict[str, Any], close_reason: str):
        """
        Analyzes the trade outcome using LLM to generate a learning 'Delta' (rule).
        (Implements Point 2 & 4 of the 16-point plan)
        """
        try:
            # 1. Create the TraceLog
            # (Note: We assume TradeManager now saves 'market_context_at_decision',
            # 'indicators_at_decision', 'news_text', and 'news_score'
            # inside 'decision_data' when opening the trade)
            
            decision_data = trade_object.get('decision_data', {})
            
            trace_log = TraceLog(
                decision_context=decision_data,
                market_context_at_decision=decision_data.get('market_context_at_decision', {}),
                indicators_at_decision=decision_data.get('indicators_at_decision', {}),
                closed_trade_object=trade_object,
                actual_outcome_reason=close_reason
            )
            
            # 2. Create the Reflector Prompt (Now in English)
            # 🔴 --- START OF CHANGE (V2 - News Learning) --- 🔴
            # (تمرير بيانات الأخبار (التي يفترض أنها في decision_data) إلى الـ prompt)
            news_text_at_decision = decision_data.get('news_text', 'No news data available at decision time.')
            news_score_at_decision = decision_data.get('news_score', 0.0) # (VADER raw score)
            
            prompt = self._create_reflector_prompt(
                trace_log, 
                news_text_at_decision, 
                news_score_at_decision
            )
            # 🔴 --- END OF CHANGE --- 🔴
            
            # 3. Call the LLM
            response_text = await self.llm_service._call_llm(prompt)
            
            if not response_text:
                raise ValueError("Reflector LLM call returned no response.")

            # 4. Parse the response
            # (We use the enhanced parser from LLM.py which handles JSON)
            reflector_json = self.llm_service._parse_llm_response_enhanced(
                response_text, 
                fallback_strategy="reflection", 
                symbol=trade_object.get('symbol', 'N/A')
            )
            
            if not reflector_json:
                raise ValueError(f"Failed to parse Reflector LLM response: {response_text}")

            # (Validate against the strict schema from schemas.py)
            reflector_output = ReflectorOutput(**reflector_json)
            
            # 5. Determine the 'Domain' for the Delta
            strategy = trade_object.get('strategy', 'general')
            domain = self._determine_domain(strategy, reflector_output.error_mode)
            
            # 6. Save the suggested 'Delta' to the Memory Store
            # (MemoryStore will use PolicyEngine to decide on auto-approval)
            await self.memory_store.save_new_delta(
                reflector_output=reflector_output,
                trade_object=trade_object,
                domain=domain
            )
            
            print(f"✅ [Reflector] Successfully analyzed {trade_object.get('symbol')}. New Delta created.")
            
        except Exception as e:
            print(f"❌ [Reflector] Failed to analyze trade outcome for {trade_object.get('symbol')}: {e}")
            traceback.print_exc()

    def _determine_domain(self, strategy: str, error_mode: str) -> str:
        """Determines the domain the suggested Delta belongs to."""
        error_mode = error_mode.lower()
        if "pattern" in error_mode or "triangle" in error_mode or "flag" in error_mode:
            return "pattern"
        if "indicator" in error_mode or "rsi" in error_mode or "macd" in error_mode:
            return "indicator"
        if "monte_carlo" in error_mode or "garch" in error_mode or "simulation" in error_mode:
            return "monte_carlo"
        
        # 🔴 --- START OF CHANGE (V2 - News Learning) --- 🔴
        if "news" in error_mode or "sentiment" in error_mode or "sec" in error_mode:
            return "general" # (أو يمكننا إنشاء مجال "news" جديد)
        # 🔴 --- END OF CHANGE --- 🔴
        
        if "strategy" in error_mode or "exit" in error_mode or "entry" in error_mode:
            return "strategy"
        
        # Default to the strategy's domain
        if strategy in ["trend_following", "mean_reversion", "breakout_momentum"]:
             return "strategy"
             
        return "general"

    # 🔴 --- START OF CHANGE (V2 - News Learning) --- 🔴
    def _create_reflector_prompt(
        self, 
        trace_log: TraceLog, 
        news_text: str, 
        news_score: float
    ) -> str:
    # 🔴 --- END OF CHANGE --- 🔴
        """
        Creates the (English-only) prompt for the LLM to act as a Reflector.
        (Implements Point 4 - Reflector prompt)
        """
        
        trade = trace_log.closed_trade_object
        pnl_percent = trade.get('pnl_percent', 0)
        
        # Determine initial success
        is_success = pnl_percent > 0.1 # (Consider any small profit a success)
        
        # 🔴 --- START OF CHANGE (V2 - News Learning) --- 🔴
        # (إضافة قسم الأخبار إلى الـ prompt)
        news_context_section = f"""
4.  **News Context (at entry):**
    * VADER Score (Raw): {news_score:.4f}
    * News Text: {news_text}
"""
        # 🔴 --- END OF CHANGE --- 🔴
        
        prompt = f"""
SYSTEM: You are an expert trading analyst Reflector. Your task is to analyze a completed trade "Trace" and determine the cause of success or failure. You must suggest a concise "Rule" (Delta) (max 25 words) to improve future performance.

--- TRACE LOG START ---

1.  **Original Decision Context (What we decided):**
    * Strategy Used: {trade.get('strategy', 'N/A')}
    * Exit Profile: {trade.get('decision_data', {}).get('exit_profile', 'N/A')}
    * Reasoning (at entry): {trade.get('decision_data', {}).get('reasoning', 'N/A')[:200]}...
    * Entry Price: {trade.get('entry_price')}
    * Initial Stop Loss: {trade.get('stop_loss')}
    * Initial Take Profit: {trade.get('take_profit')}

2.  **Environment Context (When we decided):**
    * Market Context: {json.dumps(trace_log.market_context_at_decision)}
    * Key Indicators: {json.dumps(trace_log.indicators_at_decision)}

3.  **Actual Outcome (What happened):**
    * Close Price: {trade.get('close_price')}
    * Final PnL: {pnl_percent:+.2f}%
    * Close Reason: {trace_log.actual_outcome_reason}
    * Trade Duration: {trade.get('hold_duration_minutes', 'N/A')} minutes

{news_context_section}
--- TRACE LOG END ---

TASK: Analyze the Trace above.
1.  Compare the "Actual Outcome" with the "Original Decision Context".
2.  **Crucially, review the "News Context".** Did the market react as the VADER score predicted? Did the news text contain critical information that was missed?
3.  Identify the primary "Error Mode" (e.g., 'ignored_negative_news', 'premature_exit') or "Success Factor" (e.g., 'correct_pattern_identification').
4.  Suggest ONE concise "Rule" (Delta) (max 25 words) to improve performance. If the news was the cause, the rule MUST mention news.

OUTPUT FORMAT (JSON Only - Adhere strictly to this schema):
{{
  "success": {str(is_success).lower()},
  "score": 0.0,
  "error_mode": "Short description of the error mode (e.g., 'ignored_negative_news_SEC_investigation').",
  "suggested_rule": "The concise 25-word rule (e.g., 'If news contains 'SEC' or 'investigation', do not BUY regardless of technicals.').",
  "confidence": 0.0 
}}
"""
        return prompt