Repository navigation
Expand file tree
/
Copy pathsimple_cli.py
More file actions
executable file
·360 lines (299 loc) · 15.1 KB
/
Copy pathsimple_cli.py
File metadata and controls
executable file
·360 lines (299 loc) · 15.1 KB
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
#!/usr/bin/env python3
"""
简化版 CLI 实盘交易脚本
跳过所有非必要组件,只保留核心交易功能
"""
import sys
import os
import asyncio
from dotenv import load_dotenv
# 加载 .env 文件,但不覆盖已存在的系统环境变量
# 系统环境变量优先于 .env 文件配置
load_dotenv(override=False)
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '.')))
# 版本号: v+日期+迭代次数
VERSION = "v20260111_3"
from src.api.binance_client import BinanceClient
from src.execution.engine import ExecutionEngine
from src.risk.manager import RiskManager
from src.agents import DataSyncAgent, QuantAnalystAgent, RiskAuditAgent
from src.data.processor import MarketDataProcessor
from src.strategy.llm_engine import StrategyEngine
from src.utils.logger import log
import time
from datetime import datetime
class SimpleTradingBot:
"""简化版交易机器人 - 只包含核心功能"""
def __init__(self, symbols=None, test_mode=True):
print("="*60)
print(f"🤖 Simple Trading Bot - Minimal CLI Mode ({VERSION})")
print("="*60)
# 从 .env 读取默认币种配置
if symbols is None:
env_symbols = os.environ.get('TRADING_SYMBOLS', 'BTCUSDT').strip()
symbols = [s.strip() for s in env_symbols.split(',') if s.strip()]
# 检查是否使用 AUTO3 模式
self.use_auto3 = 'AUTO3' in symbols
if self.use_auto3:
symbols.remove('AUTO3')
print("\n🔝 AUTO3 mode detected - Will select best symbols via backtest...")
# 如果没有符号或只有 AUTO3,使用默认值
if not symbols:
symbols = ['BTCUSDT']
self.symbols = symbols
self.current_symbol = symbols[0]
self.test_mode = test_mode
# 核心组件
print("\n📦 Initializing core components...")
self.client = BinanceClient()
self.risk_manager = RiskManager()
self.execution_engine = ExecutionEngine(self.client, self.risk_manager)
# Agents
print("🤖 Initializing agents...")
self.data_sync_agent = DataSyncAgent(self.client)
self.quant_analyst = QuantAnalystAgent()
self.risk_audit = RiskAuditAgent(
max_leverage=10.0,
max_position_pct=0.3,
min_stop_loss_pct=0.005,
max_stop_loss_pct=0.05
)
self.processor = MarketDataProcessor()
# 策略引擎
print("🧠 Initializing strategy engine...")
self.strategy_engine = StrategyEngine()
print("\n✅ Initialization complete!")
print(f"📊 Trading: {', '.join(self.symbols)}")
print(f"🧪 Test Mode: {test_mode}")
print("="*60)
# AUTO3 初始化:选择最佳币种
if self.use_auto3:
self._init_auto3()
def _init_auto3(self):
"""初始化 AUTO3 - 选择最佳交易币种"""
print("\n" + "="*60)
print("🔝 AUTO3 STARTUP - Selecting best trading symbols...")
print("="*60)
try:
from src.agents.symbol_selector_agent import get_selector
selector = get_selector()
# 运行异步选择
loop = asyncio.get_event_loop()
top_symbols = loop.run_until_complete(selector.select_top3(force_refresh=False))
if top_symbols:
self.symbols = top_symbols
self.current_symbol = top_symbols[0]
print(f"\n✅ AUTO3 selected: {', '.join(top_symbols)}")
else:
print("\n⚠️ AUTO3 failed to select symbols, using defaults")
self.symbols = ['BTCUSDT', 'ETHUSDT']
self.current_symbol = 'BTCUSDT'
print("="*60)
except Exception as e:
log.error(f"AUTO3 initialization failed: {e}")
print(f"\n⚠️ AUTO3 error: {e}")
print("Using default symbols: BTCUSDT, ETHUSDT")
self.symbols = ['BTCUSDT', 'ETHUSDT']
self.current_symbol = 'BTCUSDT'
async def run_once(self):
"""执行一次交易循环"""
print(f"\n{'='*60}")
print(f"🔄 Trading Cycle | {datetime.now().strftime('%H:%M:%S')} | {self.current_symbol}")
print(f"{'='*60}")
try:
# 1. 获取市场数据
print("\n[1/4] 📊 Fetching market data...")
market_snapshot = await self.data_sync_agent.fetch_all_timeframes(
self.current_symbol,
limit=300
)
current_price = market_snapshot.live_5m.get('close')
print(f" ✅ Current price: ${current_price:,.2f}")
# 2. 处理数据
print("\n[2/4] 🔬 Processing indicators...")
processed_dfs = {}
for tf in ['5m', '15m', '1h']:
raw_klines = getattr(market_snapshot, f'raw_{tf}')
df = self.processor.process_klines(raw_klines, self.current_symbol, tf)
processed_dfs[tf] = df
# 更新快照
market_snapshot.stable_5m = processed_dfs['5m']
market_snapshot.stable_15m = processed_dfs['15m']
market_snapshot.stable_1h = processed_dfs['1h']
# 3. 生成信号
print("\n[3/4] 🎯 Generating trading signals...")
signals = self.quant_analyst.analyze(market_snapshot)
print(f" 📈 Trend Signal: {signals.get('trend_signal', 'N/A')}")
print(f" 📊 Oscillator Signal: {signals.get('oscillator_signal', 'N/A')}")
# 4. 策略决策
print("\n[4/4] 🧠 Making decision...")
action = 'hold'
if self.strategy_engine.is_ready:
# 构建市场上下文数据
df_5m = processed_dfs['5m']
df_15m = processed_dfs['15m']
df_1h = processed_dfs['1h']
# 获取最新指标
latest_5m = df_5m.iloc[-1] if not df_5m.empty else {}
latest_15m = df_15m.iloc[-1] if not df_15m.empty else {}
latest_1h = df_1h.iloc[-1] if not df_1h.empty else {}
market_context_data = {
'symbol': self.current_symbol,
'timestamp': datetime.now().isoformat(),
'current_price': current_price,
'indicators': {
'5m': {
'rsi': float(latest_5m.get('rsi', 50)) if hasattr(latest_5m, 'get') else 50,
'ema_12': float(latest_5m.get('ema_12', current_price)) if hasattr(latest_5m, 'get') else current_price,
'ema_26': float(latest_5m.get('ema_26', current_price)) if hasattr(latest_5m, 'get') else current_price,
'macd': float(latest_5m.get('macd', 0)) if hasattr(latest_5m, 'get') else 0,
'macd_signal': float(latest_5m.get('macd_signal', 0)) if hasattr(latest_5m, 'get') else 0,
},
'15m': {
'rsi': float(latest_15m.get('rsi', 50)) if hasattr(latest_15m, 'get') else 50,
'ema_12': float(latest_15m.get('ema_12', current_price)) if hasattr(latest_15m, 'get') else current_price,
'ema_26': float(latest_15m.get('ema_26', current_price)) if hasattr(latest_15m, 'get') else current_price,
'macd': float(latest_15m.get('macd', 0)) if hasattr(latest_15m, 'get') else 0,
},
'1h': {
'rsi': float(latest_1h.get('rsi', 50)) if hasattr(latest_1h, 'get') else 50,
'ema_12': float(latest_1h.get('ema_12', current_price)) if hasattr(latest_1h, 'get') else current_price,
'ema_26': float(latest_1h.get('ema_26', current_price)) if hasattr(latest_1h, 'get') else current_price,
'macd': float(latest_1h.get('macd', 0)) if hasattr(latest_1h, 'get') else 0,
}
},
'signals': signals
}
# 判断各时间框架趋势
def get_trend(ema12, ema26, rsi):
if ema12 > ema26 and rsi > 50:
return "📈 BULLISH"
elif ema12 < ema26 and rsi < 50:
return "📉 BEARISH"
else:
return "➡️ NEUTRAL"
trend_5m = get_trend(
market_context_data['indicators']['5m']['ema_12'],
market_context_data['indicators']['5m']['ema_26'],
market_context_data['indicators']['5m']['rsi']
)
trend_15m = get_trend(
market_context_data['indicators']['15m']['ema_12'],
market_context_data['indicators']['15m']['ema_26'],
market_context_data['indicators']['15m']['rsi']
)
trend_1h = get_trend(
market_context_data['indicators']['1h']['ema_12'],
market_context_data['indicators']['1h']['ema_26'],
market_context_data['indicators']['1h']['rsi']
)
# 构建完整的市场上下文文本
market_context_text = f"""
=== Market Analysis for {self.current_symbol} ===
Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
Current Price: ${current_price:,.2f}
📊 1H TIMEFRAME (PRIMARY TREND):
- Trend: {trend_1h}
- RSI: {market_context_data['indicators']['1h']['rsi']:.1f}
- EMA12: ${market_context_data['indicators']['1h']['ema_12']:,.2f}
- EMA26: ${market_context_data['indicators']['1h']['ema_26']:,.2f}
- MACD: {market_context_data['indicators']['1h']['macd']:.4f}
📊 15M TIMEFRAME (CONFLUENCE):
- Trend: {trend_15m}
- RSI: {market_context_data['indicators']['15m']['rsi']:.1f}
- EMA12: ${market_context_data['indicators']['15m']['ema_12']:,.2f}
- EMA26: ${market_context_data['indicators']['15m']['ema_26']:,.2f}
- MACD: {market_context_data['indicators']['15m']['macd']:.4f}
📊 5M TIMEFRAME (ENTRY):
- Trend: {trend_5m}
- RSI: {market_context_data['indicators']['5m']['rsi']:.1f}
- EMA12: ${market_context_data['indicators']['5m']['ema_12']:,.2f}
- EMA26: ${market_context_data['indicators']['5m']['ema_26']:,.2f}
- MACD: {market_context_data['indicators']['5m']['macd']:.4f}
- MACD Signal: {market_context_data['indicators']['5m']['macd_signal']:.4f}
📈 Signal Summary:
- Trend Signal: {signals.get('trend_signal', 'N/A')}
- Oscillator Signal: {signals.get('oscillator_signal', 'N/A')}
- Composite Signal: {signals.get('composite_signal', 'N/A')}
📊 Multi-Timeframe Alignment:
- 1H + 15M + 5M: {trend_1h} | {trend_15m} | {trend_5m}
"""
try:
decision = self.strategy_engine.make_decision(
market_context_text,
market_context_data
)
action = decision.get('action', 'hold')
confidence = decision.get('confidence', 0)
reasoning = decision.get('reasoning', 'N/A')
print(f"\n{'='*60}")
print(f"📋 DECISION SUMMARY")
print(f"{'='*60}")
print(f" Action: {action.upper()}")
print(f" Confidence: {confidence}%")
print(f" Reasoning: {reasoning[:100] if reasoning else 'N/A'}...")
print(f"{'='*60}")
# 风控审计
if action != 'hold':
# 注意:风控审计需要异步调用,这里简化处理
print(f"\n✅ Risk check: Action={action}, Confidence={confidence}%")
# 执行交易(测试模式下只打印)
if action != 'hold':
if self.test_mode:
print(f"\n🧪 TEST MODE - Would execute: {action} {self.current_symbol}")
else:
print(f"\n💰 Executing: {action} {self.current_symbol}")
# 实际执行逻辑在这里
else:
print(f"\n⏸️ HOLD - No action taken")
except Exception as e:
log.error(f"LLM decision failed: {e}")
print(f"\n⚠️ Decision failed: {e}")
action = 'hold'
else:
print(" ⚠️ Strategy engine not ready (missing API key)")
return {'status': 'success', 'action': action}
except Exception as e:
log.error(f"Trading cycle failed: {e}")
import traceback
traceback.print_exc()
return {'status': 'error', 'error': str(e)}
def run_continuous(self, interval_minutes=3):
"""持续运行"""
print(f"\n🔄 Starting continuous mode (interval: {interval_minutes} min)")
print("Press Ctrl+C to stop\n")
loop = asyncio.get_event_loop()
try:
while True:
loop.run_until_complete(self.run_once())
print(f"\n⏳ Waiting {interval_minutes} minutes until next cycle...")
time.sleep(interval_minutes * 60)
except KeyboardInterrupt:
print("\n\n👋 Shutting down gracefully...")
print("✅ Stopped")
def main():
import argparse
parser = argparse.ArgumentParser(description='Simple Trading Bot CLI')
parser.add_argument('--symbols', type=str, default=None,
help='Trading symbols (comma-separated), default: read from .env')
parser.add_argument('--mode', choices=['once', 'continuous'], default='once',
help='Run mode')
parser.add_argument('--interval', type=float, default=3.0,
help='Interval in minutes (continuous mode)')
parser.add_argument('--live', action='store_true',
help='Live mode (default is test mode)')
args = parser.parse_args()
# 处理符号:命令行参数优先,否则使用 None 让 Bot 从 .env 读取
symbols = None
if args.symbols:
symbols = [s.strip() for s in args.symbols.split(',')]
test_mode = not args.live
bot = SimpleTradingBot(symbols=symbols, test_mode=test_mode)
if args.mode == 'once':
loop = asyncio.get_event_loop()
result = loop.run_until_complete(bot.run_once())
print(f"\n✅ Cycle complete: {result}")
else:
bot.run_continuous(interval_minutes=args.interval)
if __name__ == '__main__':
main()