Spaces:
Paused
Paused
Update helpers.py
Browse files- helpers.py +89 -43
helpers.py
CHANGED
|
@@ -1,63 +1,44 @@
|
|
| 1 |
-
import os, re
|
| 2 |
from datetime import datetime
|
| 3 |
|
| 4 |
def safe_float_conversion(value, default=0.0):
|
| 5 |
try:
|
| 6 |
-
if value is None:
|
| 7 |
-
|
| 8 |
-
if isinstance(value, (int, float)):
|
| 9 |
-
return float(value)
|
| 10 |
if isinstance(value, str):
|
| 11 |
-
cleaned = ''.join(
|
| 12 |
return float(cleaned) if cleaned else default
|
| 13 |
return default
|
| 14 |
-
except (ValueError, TypeError):
|
| 15 |
-
return default
|
| 16 |
|
| 17 |
def _apply_patience_logic(decision, hold_minutes, trade_data, processed_data):
|
| 18 |
action = decision.get('action')
|
| 19 |
-
|
| 20 |
if action == "CLOSE_TRADE" and hold_minutes < 20:
|
| 21 |
current_price = processed_data.get('current_price', 0)
|
| 22 |
entry_price = trade_data.get('entry_price', 0)
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
profit_loss_percent = ((current_price - entry_price) / entry_price) * 100
|
| 26 |
-
except (TypeError, ZeroDivisionError):
|
| 27 |
-
profit_loss_percent = 0
|
| 28 |
-
|
| 29 |
if profit_loss_percent < 2:
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
return decision
|
| 36 |
|
| 37 |
def parse_json_from_response(response_text: str):
|
| 38 |
-
"""استخراج JSON من رد النموذج"""
|
| 39 |
try:
|
| 40 |
json_match = re.search(r'```json\n(.*?)\n```', response_text, re.DOTALL)
|
| 41 |
-
if json_match:
|
| 42 |
-
return json_match.group(1).strip()
|
| 43 |
-
|
| 44 |
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 45 |
-
if json_match:
|
| 46 |
-
return json_match.group()
|
| 47 |
-
|
| 48 |
-
return None
|
| 49 |
-
except Exception:
|
| 50 |
return None
|
|
|
|
| 51 |
|
| 52 |
def validate_required_fields(data_dict: dict, required_fields: list) -> bool:
|
| 53 |
-
"""التحقق من وجود الحقول المطلوبة"""
|
| 54 |
return all(field in data_dict for field in required_fields)
|
| 55 |
|
| 56 |
def format_technical_indicators(advanced_indicators):
|
| 57 |
-
"
|
| 58 |
-
if not advanced_indicators:
|
| 59 |
-
return "No data for advanced indicators."
|
| 60 |
-
|
| 61 |
summary = []
|
| 62 |
for timeframe, indicators in advanced_indicators.items():
|
| 63 |
if indicators:
|
|
@@ -65,20 +46,85 @@ def format_technical_indicators(advanced_indicators):
|
|
| 65 |
if 'rsi' in indicators: parts.append(f"RSI: {indicators['rsi']:.2f}")
|
| 66 |
if 'macd_hist' in indicators: parts.append(f"MACD Hist: {indicators['macd_hist']:.4f}")
|
| 67 |
if 'volume_ratio' in indicators: parts.append(f"Volume: {indicators['volume_ratio']:.2f}x")
|
| 68 |
-
if parts:
|
| 69 |
-
summary.append(f"{timeframe}: {', '.join(parts)}")
|
| 70 |
-
|
| 71 |
return "\n".join(summary) if summary else "Insufficient indicator data."
|
| 72 |
|
| 73 |
def format_strategy_scores(strategy_scores, recommended_strategy):
|
| 74 |
-
"
|
| 75 |
-
if not strategy_scores:
|
| 76 |
-
return "No strategy data available."
|
| 77 |
-
|
| 78 |
summary = [f"Recommended Strategy: {recommended_strategy}"]
|
| 79 |
sorted_scores = sorted(strategy_scores.items(), key=lambda item: item[1], reverse=True)
|
| 80 |
for strategy, score in sorted_scores:
|
| 81 |
score_display = f"{score:.3f}" if isinstance(score, (int, float)) else str(score)
|
| 82 |
summary.append(f" • {strategy}: {score_display}")
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, re, json
|
| 2 |
from datetime import datetime
|
| 3 |
|
| 4 |
def safe_float_conversion(value, default=0.0):
|
| 5 |
try:
|
| 6 |
+
if value is None: return default
|
| 7 |
+
if isinstance(value, (int, float)): return float(value)
|
|
|
|
|
|
|
| 8 |
if isinstance(value, str):
|
| 9 |
+
cleaned = ''.join(c for c in value if c.isdigit() or c in '.-')
|
| 10 |
return float(cleaned) if cleaned else default
|
| 11 |
return default
|
| 12 |
+
except (ValueError, TypeError): return default
|
|
|
|
| 13 |
|
| 14 |
def _apply_patience_logic(decision, hold_minutes, trade_data, processed_data):
|
| 15 |
action = decision.get('action')
|
|
|
|
| 16 |
if action == "CLOSE_TRADE" and hold_minutes < 20:
|
| 17 |
current_price = processed_data.get('current_price', 0)
|
| 18 |
entry_price = trade_data.get('entry_price', 0)
|
| 19 |
+
try: profit_loss_percent = ((current_price - entry_price) / entry_price) * 100
|
| 20 |
+
except (TypeError, ZeroDivisionError): profit_loss_percent = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
if profit_loss_percent < 2:
|
| 22 |
+
decision.update({
|
| 23 |
+
'action': "HOLD",
|
| 24 |
+
'reasoning': f"Patience Filter: Blocked premature sell. Held for {hold_minutes:.1f}m. Giving trade more time."
|
| 25 |
+
})
|
|
|
|
| 26 |
return decision
|
| 27 |
|
| 28 |
def parse_json_from_response(response_text: str):
|
|
|
|
| 29 |
try:
|
| 30 |
json_match = re.search(r'```json\n(.*?)\n```', response_text, re.DOTALL)
|
| 31 |
+
if json_match: return json_match.group(1).strip()
|
|
|
|
|
|
|
| 32 |
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 33 |
+
if json_match: return json_match.group()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
return None
|
| 35 |
+
except Exception: return None
|
| 36 |
|
| 37 |
def validate_required_fields(data_dict: dict, required_fields: list) -> bool:
|
|
|
|
| 38 |
return all(field in data_dict for field in required_fields)
|
| 39 |
|
| 40 |
def format_technical_indicators(advanced_indicators):
|
| 41 |
+
if not advanced_indicators: return "No data for advanced indicators."
|
|
|
|
|
|
|
|
|
|
| 42 |
summary = []
|
| 43 |
for timeframe, indicators in advanced_indicators.items():
|
| 44 |
if indicators:
|
|
|
|
| 46 |
if 'rsi' in indicators: parts.append(f"RSI: {indicators['rsi']:.2f}")
|
| 47 |
if 'macd_hist' in indicators: parts.append(f"MACD Hist: {indicators['macd_hist']:.4f}")
|
| 48 |
if 'volume_ratio' in indicators: parts.append(f"Volume: {indicators['volume_ratio']:.2f}x")
|
| 49 |
+
if parts: summary.append(f"{timeframe}: {', '.join(parts)}")
|
|
|
|
|
|
|
| 50 |
return "\n".join(summary) if summary else "Insufficient indicator data."
|
| 51 |
|
| 52 |
def format_strategy_scores(strategy_scores, recommended_strategy):
|
| 53 |
+
if not strategy_scores: return "No strategy data available."
|
|
|
|
|
|
|
|
|
|
| 54 |
summary = [f"Recommended Strategy: {recommended_strategy}"]
|
| 55 |
sorted_scores = sorted(strategy_scores.items(), key=lambda item: item[1], reverse=True)
|
| 56 |
for strategy, score in sorted_scores:
|
| 57 |
score_display = f"{score:.3f}" if isinstance(score, (int, float)) else str(score)
|
| 58 |
summary.append(f" • {strategy}: {score_display}")
|
| 59 |
+
return "\n".join(summary)
|
| 60 |
+
|
| 61 |
+
def local_analyze_opportunity(candidate_data):
|
| 62 |
+
score = candidate_data.get('enhanced_final_score', candidate_data.get('final_score', 0))
|
| 63 |
+
quality_warnings = candidate_data.get('quality_warnings', [])
|
| 64 |
+
rsi_critical = any('🚨 RSI CRITICAL' in warning for warning in quality_warnings)
|
| 65 |
+
rsi_warning = any('⚠️ RSI WARNING' in warning for warning in quality_warnings)
|
| 66 |
+
if rsi_critical:
|
| 67 |
+
return {
|
| 68 |
+
"action": "HOLD", "reasoning": "Local analysis: CRITICAL RSI levels - extreme overbought condition.",
|
| 69 |
+
"trade_type": "NONE", "stop_loss": None, "take_profit": None, "expected_target_minutes": 15,
|
| 70 |
+
"confidence_level": 0.1, "model_source": "local_safety_filter", "strategy": "GENERIC"
|
| 71 |
+
}
|
| 72 |
+
advanced_indicators = candidate_data.get('advanced_indicators', {})
|
| 73 |
+
if not advanced_indicators:
|
| 74 |
+
return {
|
| 75 |
+
"action": "HOLD", "reasoning": "Local analysis: Insufficient advanced indicator data.",
|
| 76 |
+
"trade_type": "NONE", "stop_loss": None, "take_profit": None, "expected_target_minutes": 15,
|
| 77 |
+
"confidence_level": 0.3, "model_source": "local", "strategy": "GENERIC"
|
| 78 |
+
}
|
| 79 |
+
action, reasoning, trade_type = "HOLD", "Local analysis: No strong buy signal based on enhanced rules.", "NONE"
|
| 80 |
+
stop_loss, take_profit, expected_minutes, confidence = None, None, 15, 0.3
|
| 81 |
+
five_minute_indicators = advanced_indicators.get('5m', {})
|
| 82 |
+
one_hour_indicators = advanced_indicators.get('1h', {})
|
| 83 |
+
buy_conditions = total_conditions = 0
|
| 84 |
+
if isinstance(score, (int, float)) and score > 0.70: buy_conditions += 1
|
| 85 |
+
total_conditions += 1
|
| 86 |
+
rsi_five_minute = five_minute_indicators.get('rsi', 50)
|
| 87 |
+
if 30 <= rsi_five_minute <= 65: buy_conditions += 1
|
| 88 |
+
total_conditions += 1
|
| 89 |
+
if five_minute_indicators.get('macd_hist', 0) > 0: buy_conditions += 1
|
| 90 |
+
total_conditions += 1
|
| 91 |
+
if (five_minute_indicators.get('ema_9', 0) > five_minute_indicators.get('ema_21', 0) and
|
| 92 |
+
one_hour_indicators.get('ema_9', 0) > one_hour_indicators.get('ema_21', 0)): buy_conditions += 1
|
| 93 |
+
total_conditions += 1
|
| 94 |
+
if five_minute_indicators.get('volume_ratio', 0) > 1.5: buy_conditions += 1
|
| 95 |
+
total_conditions += 1
|
| 96 |
+
confidence = buy_conditions / total_conditions if total_conditions > 0 else 0.3
|
| 97 |
+
if rsi_warning:
|
| 98 |
+
confidence *= 0.7
|
| 99 |
+
reasoning += " RSI warning applied."
|
| 100 |
+
if confidence >= 0.6:
|
| 101 |
+
action = "BUY"
|
| 102 |
+
current_price = candidate_data['current_price']
|
| 103 |
+
trade_type = "LONG"
|
| 104 |
+
stop_loss = current_price * 0.93 if rsi_warning else current_price * 0.95
|
| 105 |
+
take_profit = five_minute_indicators.get('bb_upper', current_price * 1.05) * 1.02
|
| 106 |
+
expected_minutes = 10 if confidence >= 0.8 else 18 if confidence >= 0.6 else 25
|
| 107 |
+
reasoning = f"Local enhanced analysis: Strong buy signal with {buy_conditions}/{total_conditions} conditions met. Confidence: {confidence:.2f}"
|
| 108 |
+
if rsi_warning: reasoning += " (RSI warning - trading with caution)"
|
| 109 |
+
return {
|
| 110 |
+
"action": action, "reasoning": reasoning, "trade_type": trade_type, "stop_loss": stop_loss,
|
| 111 |
+
"take_profit": take_profit, "expected_target_minutes": expected_minutes, "confidence_level": confidence,
|
| 112 |
+
"model_source": "local", "strategy": "GENERIC"
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
def local_re_analyze_trade(trade_data, processed_data):
|
| 116 |
+
current_price = processed_data['current_price']
|
| 117 |
+
stop_loss = trade_data['stop_loss']
|
| 118 |
+
take_profit = trade_data['take_profit']
|
| 119 |
+
action = "HOLD"
|
| 120 |
+
reasoning = "Local re-analysis: No significant change to trigger an update or close."
|
| 121 |
+
if stop_loss and current_price <= stop_loss:
|
| 122 |
+
action, reasoning = "CLOSE_TRADE", "Local re-analysis: Stop loss has been hit."
|
| 123 |
+
elif take_profit and current_price >= take_profit:
|
| 124 |
+
action, reasoning = "CLOSE_TRADE", "Local re-analysis: Take profit has been hit."
|
| 125 |
+
strategy = trade_data.get('strategy', 'GENERIC')
|
| 126 |
+
if strategy == 'unknown': strategy = trade_data.get('decision_data', {}).get('strategy', 'GENERIC')
|
| 127 |
+
return {
|
| 128 |
+
"action": action, "reasoning": reasoning, "new_stop_loss": None, "new_take_profit": None,
|
| 129 |
+
"new_expected_minutes": None, "model_source": "local", "strategy": strategy
|
| 130 |
+
}
|