#!/usr/bin/env python3 """ Generate Trading Signal - Combine quantitative analysis and LLM decision making """ import sys import json import logging import os import argparse from pathlib import Path # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) from config.settings import settings from analysis.engine import MarketAnalysisEngine from signals.quantitative import QuantitativeSignalGenerator from signals.llm_decision import LLMDecisionMaker from signals.llm_gate import LLMGate from signals.aggregator import SignalAggregator from notifiers.dingtalk import DingTalkNotifier # Setup logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) def print_section(title: str, width: int = 80): """Print section header""" print(f"\n{'=' * width}") print(f"{title:^{width}}") print(f"{'=' * width}") def print_signal(signal: dict, title: str): """Pretty print a signal""" print(f"\n{title}") print("-" * 60) print(f"Signal: {signal['signal_type']}") print(f"Confidence: {signal.get('confidence', 0):.2%}") # Display trade type if available (from LLM) if 'trade_type' in signal: trade_type = signal['trade_type'] trade_type_display = { 'INTRADAY': 'šŸ“Š ę—„å†…äŗ¤ę˜“', 'SWING': 'šŸ“ˆ äø­é•æēŗæäŗ¤ę˜“', 'NONE': 'āøļø 观ꜛ' }.get(trade_type, trade_type) print(f"Trade Type: {trade_type_display}") if 'composite_score' in signal: print(f"Composite Score: {signal['composite_score']:.1f}") if 'scores' in signal: print("\nComponent Scores:") for component, score in signal['scores'].items(): print(f" {component:12}: {score:>6.1f}") if 'levels' in signal: levels = signal['levels'] print(f"\nPrice Levels:") print(f" Current: ${levels.get('current_price', 0):>10,.2f}") print(f" Entry: ${levels.get('entry', 0):>10,.2f}") print(f" Stop: ${levels.get('stop_loss', 0):>10,.2f}") print(f" Target 1: ${levels.get('take_profit_1', 0):>10,.2f}") print(f" Target 2: ${levels.get('take_profit_2', 0):>10,.2f}") print(f" Target 3: ${levels.get('take_profit_3', 0):>10,.2f}") if 'risk_reward_ratio' in signal: rr = signal['risk_reward_ratio'] if rr > 0: print(f"\nRisk/Reward: 1:{rr:.2f}") # Display opportunities breakdown (from LLM) if 'opportunities' in signal: opps = signal['opportunities'] # Intraday opportunity if opps.get('intraday', {}).get('exists'): intra = opps['intraday'] print(f"\nšŸ“Š ę—„å†…äŗ¤ę˜“ęœŗä¼š:") print(f" 方向: {intra.get('direction', 'N/A')}") if intra.get('entry_price'): print(f" å…„åœŗ: ${intra['entry_price']:,.2f}") if intra.get('stop_loss'): print(f" ę­¢ęŸ: ${intra['stop_loss']:,.2f}") if intra.get('take_profit'): print(f" ę­¢ē›ˆ: ${intra['take_profit']:,.2f}") if intra.get('reasoning'): print(f" čÆ“ę˜Ž: {intra['reasoning']}") # Swing opportunity if opps.get('swing', {}).get('exists'): swing = opps['swing'] print(f"\nšŸ“ˆ äø­é•æēŗæäŗ¤ę˜“ęœŗä¼š:") print(f" 方向: {swing.get('direction', 'N/A')}") if swing.get('entry_price'): print(f" å…„åœŗ: ${swing['entry_price']:,.2f}") if swing.get('stop_loss'): print(f" ę­¢ęŸ: ${swing['stop_loss']:,.2f}") if swing.get('take_profit'): print(f" ę­¢ē›ˆ: ${swing['take_profit']:,.2f}") if swing.get('reasoning'): print(f" čÆ“ę˜Ž: {swing['reasoning']}") # Ambush opportunity if opps.get('ambush', {}).get('exists'): ambush = opps['ambush'] print(f"\nšŸ“Œ åŸ‹ä¼ē‚¹ä½:") if ambush.get('price_level'): print(f" 埋伏价位: ${ambush['price_level']:,.2f}") if ambush.get('reasoning'): print(f" čÆ“ę˜Ž: {ambush['reasoning']}") if 'reasoning' in signal: print(f"\nReasoning: {signal['reasoning']}") def print_aggregated_signal(aggregated: dict): """Print aggregated signal""" print_section("šŸ“Š AGGREGATED TRADING SIGNAL") print(f"\nšŸŽÆ Final Signal: {aggregated['final_signal']}") print(f"šŸ“ˆ Confidence: {aggregated['final_confidence']:.2%}") print(f"šŸ¤ Consensus: {aggregated['consensus']}") print(f"āœ… Agreement Score: {aggregated['agreement_score']:.2%}") # Quantitative signal print("\n" + "─" * 80) quant = aggregated['quantitative_signal'] print(f"šŸ”¢ QUANTITATIVE SIGNAL: {quant.get('signal_type', quant.get('signal', 'HOLD'))} (confidence: {quant.get('confidence', 0):.2%})") print(f" Composite Score: {quant.get('composite_score', 0):.1f}") if 'scores' in quant: scores = quant['scores'] print(f" Trend: {scores.get('trend', 0):>6.1f} | " f"Momentum: {scores.get('momentum', 0):>6.1f} | " f"OrderFlow: {scores.get('orderflow', 0):>6.1f} | " f"Breakout: {scores.get('breakout', 0):>6.1f}") # LLM signal print("\n" + "─" * 80) llm = aggregated.get('llm_signal') if llm and isinstance(llm, dict): trade_type_icon = { 'INTRADAY': 'šŸ“Š', 'SWING': 'šŸ“ˆ', 'AMBUSH': 'šŸ“Œ', 'NONE': 'āøļø' }.get(llm.get('trade_type', 'NONE'), 'ā“') trade_type_text = { 'INTRADAY': 'ę—„å†…äŗ¤ę˜“', 'SWING': '中长线', 'AMBUSH': '埋伏', 'NONE': '观ꜛ' }.get(llm.get('trade_type', 'NONE'), llm.get('trade_type', 'N/A')) print(f"šŸ¤– LLM SIGNAL: {llm.get('signal_type', llm.get('signal', 'HOLD'))} (confidence: {llm.get('confidence', 0):.2%})") print(f" Trade Type: {trade_type_icon} {trade_type_text}") # Display opportunities if available if 'opportunities' in llm: opps = llm['opportunities'] if opps.get('intraday', {}).get('exists'): intra = opps['intraday'] print(f" šŸ“Š 旄内: {intra.get('direction')} @ ${intra.get('entry_price', 0):,.0f}") if opps.get('swing', {}).get('exists'): swing = opps['swing'] print(f" šŸ“ˆ 中长线: {swing.get('direction')} @ ${swing.get('entry_price', 0):,.0f}") if opps.get('ambush', {}).get('exists'): ambush = opps['ambush'] print(f" šŸ“Œ 埋伏: ${ambush.get('price_level', 0):,.0f}") print(f" Reasoning: {llm.get('reasoning', 'N/A')[:200]}") if llm.get('key_factors'): print(f" Key Factors: {', '.join(llm['key_factors'][:3])}") else: print("šŸ¤– LLM SIGNAL: Not available (no API key configured)") # Final levels print("\n" + "─" * 80) levels = aggregated['levels'] print("šŸ’° RECOMMENDED LEVELS:") print(f" Current Price: ${levels['current_price']:>10,.2f}") print(f" Entry: ${levels['entry']:>10,.2f}") print(f" Stop Loss: ${levels['stop_loss']:>10,.2f}") print(f" Take Profit 1: ${levels['take_profit_1']:>10,.2f}") print(f" Take Profit 2: ${levels['take_profit_2']:>10,.2f}") print(f" Take Profit 3: ${levels['take_profit_3']:>10,.2f}") rr = aggregated.get('risk_reward_ratio', 0) if rr > 0: print(f"\n Risk/Reward Ratio: 1:{rr:.2f}") # Recommendation print("\n" + "─" * 80) print(f"šŸ’” RECOMMENDATION:") print(f" {aggregated['recommendation']}") # Warnings if aggregated.get('warnings'): print("\n" + "─" * 80) print("āš ļø WARNINGS:") for warning in aggregated['warnings']: print(f" {warning}") print("\n" + "=" * 80) def main(): # Parse command line arguments parser = argparse.ArgumentParser(description='Generate trading signals') parser.add_argument('--send-dingtalk', action='store_true', help='Send notification to DingTalk') args = parser.parse_args() print_section("šŸš€ TRADING SIGNAL GENERATOR", 80) # Initialize components logger.info("Initializing analysis engine...") engine = MarketAnalysisEngine() logger.info("Initializing signal generators...") quant_generator = QuantitativeSignalGenerator() # Initialize DingTalk notifier if requested dingtalk = None if args.send_dingtalk: dingtalk_webhook = os.getenv('DINGTALK_WEBHOOK') dingtalk_secret = os.getenv('DINGTALK_SECRET') dingtalk = DingTalkNotifier( webhook_url=dingtalk_webhook, secret=dingtalk_secret, enabled=bool(dingtalk_webhook) ) # Initialize LLM gate (ęžē®€é—ØęŽ§ - é¢‘ēŽ‡äøŗäø»ļ¼Œé‡åŒ–åˆē­›) llm_gate = None if settings.LLM_GATE_ENABLED: logger.info("Initializing simplified LLM gate...") llm_gate = LLMGate( min_candles=settings.LLM_MIN_CANDLES, min_composite_score=settings.LLM_MIN_COMPOSITE_SCORE, max_calls_per_day=settings.LLM_MAX_CALLS_PER_DAY, min_call_interval_minutes=settings.LLM_MIN_INTERVAL_MINUTES, ) # Try to initialize LLM (will be disabled if no API key) # Use 'openai' provider - supports OpenAI, Deepseek, and other OpenAI-compatible APIs llm_maker = LLMDecisionMaker(provider='openai') # or 'claude' # Step 1: Perform market analysis print_section("1ļøāƒ£ MARKET ANALYSIS") analysis = engine.analyze_current_market(timeframe='5m') if 'error' in analysis: print(f"āŒ Error: {analysis['error']}") print("\nšŸ’” Tip: Wait for more data to accumulate (need at least 200 candles)") return print(f"āœ… Analysis complete") print(f" Price: ${analysis['current_price']:,.2f}") print(f" Trend: {analysis['trend_analysis'].get('direction', 'unknown')}") print(f" RSI: {analysis['momentum'].get('rsi', 0):.1f}") print(f" MACD: {analysis['momentum'].get('macd_signal', 'unknown')}") # Step 2: Generate quantitative signal print_section("2ļøāƒ£ QUANTITATIVE SIGNAL") quant_signal = quant_generator.generate_signal(analysis) print_signal(quant_signal, "šŸ“Š Quantitative Analysis") # Step 3: Check LLM gate and generate LLM decision print_section("3ļøāƒ£ LLM DECISION") llm_signal = None should_call_llm = True gate_reason = "LLM gate disabled" # Check LLM gate prerequisites if llm_gate: should_call_llm, gate_reason = llm_gate.should_call_llm(quant_signal, analysis) if should_call_llm: print(f"\nāœ… LLM Gate: PASSED") print(f" Reason: {gate_reason}") else: print(f"\nāŒ LLM Gate: BLOCKED") print(f" Reason: {gate_reason}") print(f"\nšŸ’” LLM will NOT be called. Using quantitative signal only.") print(f" Quantitative score: {quant_signal.get('composite_score', 0):.1f}") print(f" Quantitative confidence: {quant_signal.get('confidence', 0):.2%}") # Call LLM only if gate passed if should_call_llm: llm_context = engine.get_llm_context(format='full') llm_signal = llm_maker.generate_decision(llm_context, analysis) if llm_signal.get('enabled', True): print_signal(llm_signal, "šŸ¤– LLM Analysis") else: print("\nšŸ¤– LLM Analysis: Disabled (no API key)") print(" Set ANTHROPIC_API_KEY or OPENAI_API_KEY to enable") else: # LLM blocked by gate, use None (aggregator will use quant-only) print("\nšŸ¤– LLM Analysis: Skipped (gate blocked)") # Step 4: Aggregate signals print_section("4ļøāƒ£ SIGNAL AGGREGATION") aggregated = SignalAggregator.aggregate_signals(quant_signal, llm_signal) print_aggregated_signal(aggregated) # Step 5: Export to JSON output_file = Path(__file__).parent.parent / 'output' / 'latest_signal.json' output_file.parent.mkdir(exist_ok=True) output_data = { 'aggregated_signal': aggregated, 'market_analysis': { 'price': analysis['current_price'], 'trend': analysis['trend_analysis'], 'momentum': analysis['momentum'], }, 'quantitative_signal': quant_signal, 'llm_signal': llm_signal if llm_signal and llm_signal.get('enabled', True) else None, } with open(output_file, 'w') as f: json.dump(output_data, f, indent=2, ensure_ascii=False) print(f"\nšŸ’¾ Signal saved to: {output_file}") # Send DingTalk notification if enabled if dingtalk: print(f"\nšŸ“± Sending DingTalk notification...") success = dingtalk.send_signal(aggregated) if success: print(f"āœ… DingTalk notification sent successfully") else: print(f"āŒ Failed to send DingTalk notification") print_section("āœ… SIGNAL GENERATION COMPLETE", 80) if __name__ == "__main__": main()