|
|
|
|
|
import os, re, json, hashlib |
|
|
from datetime import datetime |
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
|
|
|
def safe_float_conversion(value, default=0.0): |
|
|
try: |
|
|
if value is None: return default |
|
|
if isinstance(value, (int, float)): return float(value) |
|
|
if isinstance(value, str): |
|
|
cleaned = ''.join(c for c in value if c.isdigit() or c in '.-') |
|
|
return float(cleaned) if cleaned else default |
|
|
return default |
|
|
except (ValueError, TypeError): return default |
|
|
|
|
|
def _apply_patience_logic(decision, hold_minutes, trade_data, processed_data): |
|
|
action = decision.get('action') |
|
|
if action == "CLOSE_TRADE" and hold_minutes < 20: |
|
|
current_price = processed_data.get('current_price', 0) |
|
|
entry_price = trade_data.get('entry_price', 0) |
|
|
try: profit_loss_percent = ((current_price - entry_price) / entry_price) * 100 |
|
|
except (TypeError, ZeroDivisionError): profit_loss_percent = 0 |
|
|
if profit_loss_percent < 2: |
|
|
decision.update({ |
|
|
'action': "HOLD", |
|
|
'reasoning': f"Patience Filter: Blocked premature sell. Held for {hold_minutes:.1f}m. Giving trade more time." |
|
|
}) |
|
|
return decision |
|
|
|
|
|
def parse_json_from_response(response_text: str): |
|
|
try: |
|
|
json_match = re.search(r'```json\n(.*?)\n```', response_text, re.DOTALL) |
|
|
if json_match: return json_match.group(1).strip() |
|
|
json_match = re.search(r'\{.*\}', response_text, re.DOTALL) |
|
|
if json_match: return json_match.group() |
|
|
return None |
|
|
except Exception: return None |
|
|
|
|
|
def validate_required_fields(data_dict: dict, required_fields: list) -> bool: |
|
|
return all(field in data_dict for field in required_fields) |
|
|
|
|
|
def format_technical_indicators(advanced_indicators): |
|
|
"""تنسيق جميع المؤشرات الفنية بشكل شامل للنموذج الضخم""" |
|
|
if not advanced_indicators: return "No data for advanced indicators." |
|
|
|
|
|
summary = [] |
|
|
for timeframe, indicators in advanced_indicators.items(): |
|
|
if indicators: |
|
|
parts = [] |
|
|
|
|
|
|
|
|
if 'ema_9' in indicators: parts.append(f"EMA9: {indicators['ema_9']:.6f}") |
|
|
if 'ema_21' in indicators: parts.append(f"EMA21: {indicators['ema_21']:.6f}") |
|
|
if 'ema_50' in indicators: parts.append(f"EMA50: {indicators['ema_50']:.6f}") |
|
|
if 'ema_200' in indicators: parts.append(f"EMA200: {indicators['ema_200']:.6f}") |
|
|
if 'adx' in indicators: parts.append(f"ADX: {indicators['adx']:.2f}") |
|
|
if 'ichimoku_conversion' in indicators: parts.append(f"Ichimoku Conv: {indicators['ichimoku_conversion']:.6f}") |
|
|
if 'ichimoku_base' in indicators: parts.append(f"Ichimoku Base: {indicators['ichimoku_base']:.6f}") |
|
|
|
|
|
|
|
|
if 'rsi' in indicators: parts.append(f"RSI: {indicators['rsi']:.2f}") |
|
|
if 'macd_hist' in indicators: parts.append(f"MACD Hist: {indicators['macd_hist']:.6f}") |
|
|
if 'macd_line' in indicators: parts.append(f"MACD Line: {indicators['macd_line']:.6f}") |
|
|
if 'stoch_rsi_k' in indicators: parts.append(f"Stoch RSI: {indicators['stoch_rsi_k']:.2f}") |
|
|
if 'williams_r' in indicators: parts.append(f"Williams %R: {indicators['williams_r']:.2f}") |
|
|
|
|
|
|
|
|
if 'bb_upper' in indicators: parts.append(f"BB Upper: {indicators['bb_upper']:.6f}") |
|
|
if 'bb_lower' in indicators: parts.append(f"BB Lower: {indicators['bb_lower']:.6f}") |
|
|
if 'bb_middle' in indicators: parts.append(f"BB Middle: {indicators['bb_middle']:.6f}") |
|
|
if 'atr' in indicators: parts.append(f"ATR: {indicators['atr']:.6f}") |
|
|
if 'atr_percent' in indicators: parts.append(f"ATR %: {indicators['atr_percent']:.2f}%") |
|
|
|
|
|
|
|
|
if 'volume_ratio' in indicators: parts.append(f"Volume Ratio: {indicators['volume_ratio']:.2f}x") |
|
|
if 'vwap' in indicators: parts.append(f"VWAP: {indicators['vwap']:.6f}") |
|
|
if 'obv' in indicators: parts.append(f"OBV: {indicators['obv']:.0f}") |
|
|
if 'mfi' in indicators: parts.append(f"MFI: {indicators['mfi']:.2f}") |
|
|
|
|
|
|
|
|
if 'hull_ma' in indicators: parts.append(f"Hull MA: {indicators['hull_ma']:.6f}") |
|
|
if 'supertrend' in indicators: parts.append(f"Supertrend: {indicators['supertrend']:.6f}") |
|
|
|
|
|
if parts: |
|
|
summary.append(f"{timeframe.upper()}: {', '.join(parts)}") |
|
|
|
|
|
return "\n".join(summary) if summary else "Insufficient indicator data." |
|
|
|
|
|
def format_strategy_scores(strategy_scores, recommended_strategy): |
|
|
if not strategy_scores: return "No strategy data available." |
|
|
summary = [f"Recommended Strategy: {recommended_strategy}"] |
|
|
sorted_scores = sorted(strategy_scores.items(), key=lambda item: item[1], reverse=True) |
|
|
for strategy, score in sorted_scores: |
|
|
score_display = f"{score:.3f}" if isinstance(score, (int, float)) else str(score) |
|
|
summary.append(f" • {strategy}: {score_display}") |
|
|
return "\n".join(summary) |
|
|
|
|
|
def format_whale_analysis_for_llm(whale_analysis): |
|
|
"""تنسيق تحليل الحيتان للنموذج الضخم بشكل مفيد وواضح""" |
|
|
if not whale_analysis or not whale_analysis.get('data_available', False): |
|
|
return "📊 تحليل الحيتان: لا توجد بيانات عن تحركات الحيتان الحديثة" |
|
|
|
|
|
summary = whale_analysis.get('llm_friendly_summary', {}) |
|
|
|
|
|
if not summary: |
|
|
return "📊 تحليل الحيتان: بيانات الحيتان غير متوفرة" |
|
|
|
|
|
formatted = f"📊 تحليل الحيتان:\n" |
|
|
formatted += f" • النشاط: {summary.get('whale_activity_summary', 'لا توجد معلومات')}\n" |
|
|
formatted += f" • التوصية: {summary.get('recommended_action', 'HOLD')}\n" |
|
|
formatted += f" • مستوى الثقة: {summary.get('confidence', 0.5):.1%}\n" |
|
|
|
|
|
metrics = summary.get('key_metrics', {}) |
|
|
if metrics: |
|
|
flow_direction = metrics.get('net_flow_direction', 'غير معروف') |
|
|
impact_level = metrics.get('whale_movement_impact', 'غير معروف') |
|
|
exchange_involvement = metrics.get('exchange_involvement', 'غير معروف') |
|
|
|
|
|
formatted += f" • اتجاه التدفق: {flow_direction}\n" |
|
|
formatted += f" • مستوى التأثير: {impact_level}\n" |
|
|
formatted += f" • مشاركة المنصات: {exchange_involvement}" |
|
|
|
|
|
|
|
|
if whale_analysis.get('trading_signal', {}).get('critical_alert', False): |
|
|
formatted += "\n ⚠️ تحذير: نشاط حيتان حرج يتطلب الحذر" |
|
|
|
|
|
return formatted |
|
|
|
|
|
def validate_candidate_data_enhanced(candidate): |
|
|
try: |
|
|
required_fields = ['symbol', 'current_price', 'final_score', 'enhanced_final_score'] |
|
|
for field in required_fields: |
|
|
if field not in candidate: candidate[field] = 0.0 if field.endswith('_score') or field == 'current_price' else 'UNKNOWN' |
|
|
candidate['current_price'] = safe_float_conversion(candidate.get('current_price'), 0.0) |
|
|
candidate['final_score'] = safe_float_conversion(candidate.get('final_score'), 0.0) |
|
|
candidate['enhanced_final_score'] = safe_float_conversion(candidate.get('enhanced_final_score'), candidate['final_score']) |
|
|
if 'reasons_for_candidacy' not in candidate: candidate['reasons_for_candidacy'] = ['unknown_reason'] |
|
|
if 'sentiment_data' not in candidate: candidate['sentiment_data'] = {'btc_sentiment': 'NEUTRAL','fear_and_greed_index': 50,'general_whale_activity': {'sentiment': 'NEUTRAL', 'critical_alert': False}} |
|
|
if 'advanced_indicators' not in candidate: candidate['advanced_indicators'] = {} |
|
|
if 'strategy_scores' not in candidate: candidate['strategy_scores'] = {} |
|
|
if 'target_strategy' not in candidate: candidate['target_strategy'] = 'GENERIC' |
|
|
return True |
|
|
except Exception as error: |
|
|
print(f"Failed to validate candidate data for {candidate.get('symbol')}: {error}") |
|
|
return False |
|
|
|
|
|
def normalize_weights(weights_dict): |
|
|
total = sum(weights_dict.values()) |
|
|
if total > 0: |
|
|
for strategy in weights_dict: |
|
|
weights_dict[strategy] /= total |
|
|
return weights_dict |
|
|
|
|
|
def calculate_market_volatility(market_context): |
|
|
try: |
|
|
btc_price = market_context.get('bitcoin_price_usd', 0) |
|
|
fear_greed = market_context.get('fear_and_greed_index', 50) |
|
|
whale_sentiment = market_context.get('general_whale_activity', {}).get('sentiment', 'NEUTRAL') |
|
|
|
|
|
volatility_score = 0 |
|
|
|
|
|
if btc_price > 0: |
|
|
if abs(fear_greed - 50) > 20: |
|
|
volatility_score += 1 |
|
|
|
|
|
if whale_sentiment in ['BULLISH', 'BEARISH']: |
|
|
volatility_score += 1 |
|
|
elif whale_sentiment == 'SLIGHTLY_BULLISH': |
|
|
volatility_score += 0.5 |
|
|
|
|
|
if volatility_score >= 1.5: |
|
|
return "high" |
|
|
elif volatility_score >= 0.5: |
|
|
return "medium" |
|
|
else: |
|
|
return "low" |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Volatility calculation error: {e}") |
|
|
return "medium" |
|
|
|
|
|
def generate_trade_id(): |
|
|
return str(int(time.time())) |
|
|
|
|
|
def should_update_weights(performance_history_count): |
|
|
if performance_history_count <= 10: |
|
|
return True |
|
|
return performance_history_count % 3 == 0 |
|
|
|
|
|
def format_enhanced_analysis_for_llm(candidate_data, whale_analysis=None, market_context=None): |
|
|
"""تنسيق تحليل متقدم شامل للنموذج الضخم""" |
|
|
formatted = "📈 التحليل الشامل للعملة:\n" |
|
|
|
|
|
|
|
|
formatted += f"💰 العملة: {candidate_data.get('symbol', 'N/A')}\n" |
|
|
formatted += f"💰 السعر الحالي: ${safe_float_conversion(candidate_data.get('current_price', 0)):.6f}\n" |
|
|
formatted += f"🎯 النتيجة المحسنة: {safe_float_conversion(candidate_data.get('enhanced_final_score', 0)):.3f}\n" |
|
|
|
|
|
|
|
|
advanced_indicators = candidate_data.get('advanced_indicators', {}) |
|
|
if advanced_indicators: |
|
|
formatted += "\n🔧 المؤشرات الفنية:\n" |
|
|
for timeframe, indicators in advanced_indicators.items(): |
|
|
if indicators: |
|
|
tech_parts = [] |
|
|
if 'rsi' in indicators: tech_parts.append(f"RSI: {indicators['rsi']:.1f}") |
|
|
if 'macd_hist' in indicators: tech_parts.append(f"MACD: {indicators['macd_hist']:.6f}") |
|
|
if 'volume_ratio' in indicators: tech_parts.append(f"Volume: {indicators['volume_ratio']:.1f}x") |
|
|
if 'ema_9' in indicators and 'ema_21' in indicators: |
|
|
ema_signal = "↑" if indicators['ema_9'] > indicators['ema_21'] else "↓" |
|
|
tech_parts.append(f"EMA: {ema_signal}") |
|
|
if 'adx' in indicators: tech_parts.append(f"ADX: {indicators['adx']:.1f}") |
|
|
if 'bb_upper' in indicators and 'bb_lower' in indicators: |
|
|
bb_position = (candidate_data.get('current_price', 0) - indicators['bb_lower']) / (indicators['bb_upper'] - indicators['bb_lower']) |
|
|
bb_signal = "HIGH" if bb_position > 0.8 else "LOW" if bb_position < 0.2 else "MID" |
|
|
tech_parts.append(f"BB: {bb_signal}") |
|
|
if tech_parts: |
|
|
formatted += f" • {timeframe}: {', '.join(tech_parts)}\n" |
|
|
|
|
|
|
|
|
strategy_scores = candidate_data.get('strategy_scores', {}) |
|
|
if strategy_scores: |
|
|
formatted += "\n🎯 استراتيجيات التداول:\n" |
|
|
sorted_strategies = sorted(strategy_scores.items(), key=lambda x: x[1], reverse=True)[:3] |
|
|
for strategy, score in sorted_strategies: |
|
|
formatted += f" • {strategy}: {score:.3f}\n" |
|
|
|
|
|
|
|
|
if whale_analysis: |
|
|
formatted += f"\n{format_whale_analysis_for_llm(whale_analysis)}\n" |
|
|
|
|
|
|
|
|
if market_context: |
|
|
formatted += "\n🌍 سياق السوق العام:\n" |
|
|
btc_sentiment = market_context.get('btc_sentiment', 'NEUTRAL') |
|
|
fear_greed = market_context.get('fear_and_greed_index', 50) |
|
|
formatted += f" • اتجاه البيتكوين: {btc_sentiment}\n" |
|
|
formatted += f" • مؤشر الخوف والجشع: {fear_greed}\n" |
|
|
|
|
|
|
|
|
reasons = candidate_data.get('reasons_for_candidacy', []) |
|
|
if reasons and len(reasons) > 0: |
|
|
formatted += "\n📋 أسباب الترشيح:\n" |
|
|
for i, reason in enumerate(reasons[:5], 1): |
|
|
formatted += f" {i}. {reason}\n" |
|
|
|
|
|
return formatted |
|
|
|
|
|
def create_whale_aware_trading_decision(base_decision, whale_analysis): |
|
|
"""إنشاء قرار تداول مدرك لبيانات الحيتان""" |
|
|
if not whale_analysis or not whale_analysis.get('data_available', False): |
|
|
return base_decision |
|
|
|
|
|
whale_signal = whale_analysis.get('trading_signal', {}) |
|
|
whale_action = whale_signal.get('action', 'HOLD') |
|
|
whale_confidence = whale_signal.get('confidence', 0.5) |
|
|
|
|
|
base_action = base_decision.get('action', 'HOLD') |
|
|
base_confidence = base_decision.get('confidence_level', 0.5) |
|
|
|
|
|
|
|
|
if whale_signal.get('critical_alert', False): |
|
|
if whale_action in ['STRONG_SELL', 'SELL'] and base_action == 'BUY': |
|
|
return { |
|
|
**base_decision, |
|
|
'action': 'HOLD', |
|
|
'confidence_level': base_confidence * 0.6, |
|
|
'reasoning': f"{base_decision.get('reasoning', '')} | تم التصحيح بسبب نشاط الحيتان الحرج: {whale_signal.get('reason', '')}" |
|
|
} |
|
|
elif whale_action in ['STRONG_BUY', 'BUY'] and base_action == 'HOLD': |
|
|
return { |
|
|
**base_decision, |
|
|
'action': 'BUY', |
|
|
'confidence_level': (base_confidence + whale_confidence) / 2, |
|
|
'reasoning': f"{base_decision.get('reasoning', '')} | تم التعزيز بسبب نشاط الحيتان الإيجابي: {whale_signal.get('reason', '')}" |
|
|
} |
|
|
|
|
|
|
|
|
combined_confidence = (base_confidence * 0.4) + (whale_confidence * 0.6) |
|
|
|
|
|
|
|
|
if whale_confidence > 0.8: |
|
|
if (whale_action in ['STRONG_SELL', 'SELL'] and base_action == 'BUY') or \ |
|
|
(whale_action in ['STRONG_BUY', 'BUY'] and base_action == 'SELL'): |
|
|
return { |
|
|
**base_decision, |
|
|
'action': 'HOLD', |
|
|
'confidence_level': combined_confidence * 0.8, |
|
|
'reasoning': f"{base_decision.get('reasoning', '')} | تعارض مع تحركات الحيتان: {whale_signal.get('reason', '')}" |
|
|
} |
|
|
|
|
|
|
|
|
if (whale_action in ['STRONG_BUY', 'BUY'] and base_action == 'BUY') or \ |
|
|
(whale_action in ['STRONG_SELL', 'SELL'] and base_action == 'SELL'): |
|
|
enhanced_confidence = min(combined_confidence * 1.2, 0.95) |
|
|
return { |
|
|
**base_decision, |
|
|
'confidence_level': enhanced_confidence, |
|
|
'reasoning': f"{base_decision.get('reasoning', '')} | متوافق مع تحركات الحيتان" |
|
|
} |
|
|
|
|
|
|
|
|
return { |
|
|
**base_decision, |
|
|
'confidence_level': combined_confidence, |
|
|
'reasoning': f"{base_decision.get('reasoning', '')} | أخذ بعين الاعتبار نشاط الحيتان" |
|
|
} |
|
|
|
|
|
def validate_whale_analysis_data(whale_data): |
|
|
"""التحقق من صحة بيانات تحليل الحيتان""" |
|
|
if not whale_data: |
|
|
return False, "بيانات الحيتان فارغة" |
|
|
|
|
|
required_fields = ['symbol', 'data_available', 'trading_signal'] |
|
|
for field in required_fields: |
|
|
if field not in whale_data: |
|
|
return False, f"حقل {field} مفقود في بيانات الحيتان" |
|
|
|
|
|
if not whale_data['data_available']: |
|
|
return True, "لا توجد بيانات حيتان متاحة" |
|
|
|
|
|
signal_fields = ['action', 'confidence', 'reason'] |
|
|
trading_signal = whale_data.get('trading_signal', {}) |
|
|
for field in signal_fields: |
|
|
if field not in trading_signal: |
|
|
return False, f"حقل {field} مفقود في إشارة التداول" |
|
|
|
|
|
valid_actions = ['STRONG_BUY', 'BUY', 'HOLD', 'SELL', 'STRONG_SELL'] |
|
|
if trading_signal.get('action') not in valid_actions: |
|
|
return False, f"إجراء تداول غير صالح: {trading_signal.get('action')}" |
|
|
|
|
|
confidence = trading_signal.get('confidence', 0) |
|
|
if not (0 <= confidence <= 1): |
|
|
return False, f"مستوى الثقة خارج النطاق: {confidence}" |
|
|
|
|
|
return True, "بيانات الحيتان صالحة" |
|
|
|
|
|
def calculate_whale_impact_score(whale_analysis): |
|
|
"""حساب درجة تأثير الحيتان من 0 إلى 100""" |
|
|
if not whale_analysis or not whale_analysis.get('data_available', False): |
|
|
return 0 |
|
|
|
|
|
trading_signal = whale_analysis.get('trading_signal', {}) |
|
|
action = trading_signal.get('action', 'HOLD') |
|
|
confidence = trading_signal.get('confidence', 0.5) |
|
|
|
|
|
|
|
|
action_weights = { |
|
|
'STRONG_BUY': 100, |
|
|
'BUY': 75, |
|
|
'HOLD': 50, |
|
|
'SELL': 25, |
|
|
'STRONG_SELL': 0 |
|
|
} |
|
|
|
|
|
base_score = action_weights.get(action, 50) |
|
|
|
|
|
|
|
|
if confidence > 0.8: |
|
|
adjusted_score = base_score * 1.2 |
|
|
elif confidence > 0.6: |
|
|
adjusted_score = base_score * 1.0 |
|
|
else: |
|
|
adjusted_score = base_score * 0.8 |
|
|
|
|
|
|
|
|
if trading_signal.get('critical_alert', False): |
|
|
if action in ['STRONG_SELL', 'SELL']: |
|
|
adjusted_score = max(0, adjusted_score - 20) |
|
|
elif action in ['STRONG_BUY', 'BUY']: |
|
|
adjusted_score = min(100, adjusted_score + 20) |
|
|
|
|
|
return min(100, max(0, adjusted_score)) |
|
|
|
|
|
def format_whale_impact_for_display(whale_analysis): |
|
|
"""تنسيق تأثير الحيتان للعرض في الواجهة""" |
|
|
impact_score = calculate_whale_impact_score(whale_analysis) |
|
|
|
|
|
if impact_score >= 80: |
|
|
return "🟢 تأثير إيجابي قوي" |
|
|
elif impact_score >= 60: |
|
|
return "🟡 تأثير إيجابي متوسط" |
|
|
elif impact_score >= 40: |
|
|
return "⚪ تأثير محايد" |
|
|
elif impact_score >= 20: |
|
|
return "🟠 تأثير سلبي متوسط" |
|
|
else: |
|
|
return "🔴 تأثير سلبي قوي" |
|
|
|
|
|
def should_override_trade_decision(base_decision, whale_analysis): |
|
|
"""تحديد إذا كان يجب تغيير قرار التداول بناء على تحركات الحيتان""" |
|
|
if not whale_analysis or not whale_analysis.get('data_available', False): |
|
|
return False |
|
|
|
|
|
whale_signal = whale_analysis.get('trading_signal', {}) |
|
|
whale_action = whale_signal.get('action', 'HOLD') |
|
|
whale_confidence = whale_signal.get('confidence', 0.5) |
|
|
|
|
|
base_action = base_decision.get('action', 'HOLD') |
|
|
|
|
|
|
|
|
mandatory_override_conditions = [ |
|
|
whale_signal.get('critical_alert', False) and whale_confidence > 0.8, |
|
|
whale_confidence > 0.9 and whale_action in ['STRONG_SELL', 'STRONG_BUY'], |
|
|
base_action == 'BUY' and whale_action == 'STRONG_SELL' and whale_confidence > 0.7, |
|
|
base_action == 'SELL' and whale_action == 'STRONG_BUY' and whale_confidence > 0.7 |
|
|
] |
|
|
|
|
|
return any(mandatory_override_conditions) |
|
|
|
|
|
def format_candle_data_for_pattern_analysis(ohlcv_data, timeframe='1h'): |
|
|
"""تنسيق بيانات الشموع لتحليل الأنماط البيانية""" |
|
|
if not ohlcv_data or timeframe not in ohlcv_data: |
|
|
return "لا توجد بيانات شموع كافية لتحليل الأنماط" |
|
|
|
|
|
candles = ohlcv_data[timeframe] |
|
|
if len(candles) < 20: |
|
|
return f"بيانات غير كافية ({len(candles)} شمعة فقط)" |
|
|
|
|
|
|
|
|
recent_candles = candles[-50:] if len(candles) > 50 else candles |
|
|
|
|
|
formatted = f"📊 بيانات الشموع للإطار {timeframe.upper()} (آخر {len(recent_candles)} شمعة):\n" |
|
|
|
|
|
|
|
|
first_close = recent_candles[0][4] |
|
|
last_close = recent_candles[-1][4] |
|
|
price_change = ((last_close - first_close) / first_close) * 100 |
|
|
trend = "🟢 صاعد" if price_change > 2 else "🔴 هابط" if price_change < -2 else "⚪ جانبي" |
|
|
|
|
|
|
|
|
highs = [c[2] for c in recent_candles] |
|
|
lows = [c[3] for c in recent_candles] |
|
|
high_max = max(highs) |
|
|
low_min = min(lows) |
|
|
volatility = ((high_max - low_min) / low_min) * 100 |
|
|
|
|
|
|
|
|
volumes = [c[5] for c in recent_candles] |
|
|
avg_volume = sum(volumes) / len(volumes) |
|
|
current_volume = recent_candles[-1][5] |
|
|
volume_ratio = current_volume / avg_volume if avg_volume > 0 else 1 |
|
|
|
|
|
formatted += f"📈 الاتجاه: {trend} ({price_change:+.2f}%)\n" |
|
|
formatted += f"🌊 التقلب: {volatility:.2f}% (النطاق: {low_min:.6f} - {high_max:.6f})\n" |
|
|
formatted += f"📦 الحجم: {volume_ratio:.2f}x المتوسط\n\n" |
|
|
|
|
|
|
|
|
formatted += "🕯️ آخر 10 شموع (من الأحدث إلى الأقدم):\n" |
|
|
for i in range(min(10, len(recent_candles))): |
|
|
idx = len(recent_candles) - 1 - i |
|
|
candle = recent_candles[idx] |
|
|
timestamp = datetime.fromtimestamp(candle[0] / 1000).strftime('%H:%M') |
|
|
open_price, high, low, close, volume = candle[1], candle[2], candle[3], candle[4], candle[5] |
|
|
|
|
|
candle_type = "🟢" if close > open_price else "🔴" if close < open_price else "⚪" |
|
|
body_size = abs(close - open_price) / open_price * 100 |
|
|
wick_upper = (high - max(open_price, close)) / high * 100 |
|
|
wick_lower = (min(open_price, close) - low) / low * 100 |
|
|
|
|
|
formatted += f" {timestamp} {candle_type} O:{open_price:.6f} H:{high:.6f} L:{low:.6f} C:{close:.6f} V:{volume:.0f}\n" |
|
|
formatted += f" الجسم: {body_size:.2f}% | الظلال: علوية {wick_upper:.2f}% / سفلية {wick_lower:.2f}%\n" |
|
|
|
|
|
return formatted |