luguog commited on
Commit
1ef4dd9
·
verified ·
1 Parent(s): 604d27c

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +607 -146
main.py CHANGED
@@ -7,10 +7,13 @@ import logging
7
  import asyncio
8
  import websockets
9
  import json
 
10
  from collections import defaultdict
 
 
 
11
 
12
- # --- New AI Library ---
13
- # LiteLLM is the unified interface
14
  from litellm import completion
15
  from litellm.exceptions import APIError
16
 
@@ -24,99 +27,185 @@ WEBSOCKET_STREAM = "!miniTicker@arr"
24
  WEBSOCKET_URL = BINANCE_WS_BASE + WEBSOCKET_STREAM
25
 
26
  # --- AI Model Configuration (33 Models via LiteLLM) ---
27
-
28
- # Models accessed via OpenRouter (prefix with 'openrouter/')
29
  OPENROUTER_MODELS = [
30
  "openrouter/openai/gpt-4o",
 
31
  "openrouter/mistralai/mistral-large",
 
32
  "openrouter/perplexity/pplx-7b-chat",
 
33
  "openrouter/anthropic/claude-3-opus",
34
- "openrouter/google/gemini-2.5-pro",
 
 
 
35
  "openrouter/meta-llama/llama-3-8b-instruct:free",
 
36
  "openrouter/nousresearch/nous-hermes-2-mixtral-8x7b-dpo",
 
 
 
37
  ]
38
 
39
- # Models accessed via Groq
40
  GROQ_MODELS = [
41
- "groq/llama-3.1-8b-tool-use-preview",
42
- "groq/llama-3.1-70b-tool-use-preview",
 
 
 
 
 
43
  ]
44
 
45
- # Model accessed via Hugging Face (Example: a publicly available Mistral model)
46
- # Requires HUGGINGFACE_API_KEY
47
  HUGGINGFACE_MODELS = [
48
  "huggingface/mistralai/Mistral-7B-Instruct-v0.2",
 
 
 
 
49
  ]
50
 
51
- # We need 33 models: 30 from OpenRouter/Groq/HF + 3 more OpenRouter/Groq
52
- # Use a repetition to hit 33: (7 * 4) + 5 = 33
53
- # Total models will be 33 requests: (OPENROUTER_MODELS * 4) + (GROQ_MODELS) + HUGGINGFACE_MODELS
54
- ALL_MODELS = (OPENROUTER_MODELS * 4) + GROQ_MODELS + HUGGINGFACE_MODELS
55
- # This list now contains 31 requests. Let's pad to 33 using the first two Groq models.
56
- ALL_MODELS = (OPENROUTER_MODELS * 4) + GROQ_MODELS + HUGGINGFACE_MODELS + GROQ_MODELS[:2]
57
- # Total: (7*4) + 2 + 1 + 2 = 33 requests.
58
 
59
- # Target symbols (Example: Top 5 Perpetual Contracts)
60
  TARGET_SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
61
 
62
- # --- Data Structures & Helpers (Retained for Context) ---
63
- # ... (ALL_PERPS_DATA, update_all_perps_data, get_td_sequential, etc. remain the same) ...
 
 
 
 
 
 
 
64
 
65
- # NOTE: In a full system, you would need to fetch initial historical data
66
- # to seed the indicators (which can be done without API keys via some public endpoints
67
- # but that complexity is outside the scope of this pure WebSocket refactor).
68
 
 
69
  def get_td_sequential(series):
70
- """Simplified TD Sequential placeholder."""
71
- return pd.Series(0.0, index=series.index)
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  def format_value(value):
74
- """Placeholder for number formatting."""
75
- return f"{value:,.2f}" if pd.notna(value) and isinstance(value, (int, float)) else 'N/A'
 
 
 
 
76
 
77
  def add_technical_indicators(df):
78
- """Function to add technical indicators (retained from original code logic)."""
79
- if df.shape[0] < 200:
80
  return df
81
 
82
- # ... (indicator calculation logic remains the same)
 
 
 
 
 
 
 
 
 
 
83
  df['SMA_200'] = ta.sma(df['Close'], length=200)
 
 
84
  df['RSI_14'] = ta.rsi(df['Close'], length=14)
85
- # Ensure H/L/V columns exist for indicators like ADX
86
- if 'High' not in df.columns: df['High'] = df['Close']
87
- if 'Low' not in df.columns: df['Low'] = df['Close']
88
- df['ADX'] = ta.adx(df['High'], df['Low'], df['Close'])['ADX_14']
89
  macd = ta.macd(df['Close'])
90
- df = df.join(macd)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  df['TD_Seq'] = get_td_sequential(df['Close'])
 
92
  return df
93
 
94
  def format_data_for_gpt(latest_daily, latest_4h, latest_1m, symbol):
95
- """Formats the prompt using the latest data for a single symbol."""
96
- def safe_get(series, key):
97
- return series.get(key) if key in series else 'N/A'
 
 
98
 
99
  prompt = f"""
100
- Analyze the technical market data for {symbol} below. Provide a clear and concise trading signal based on a consensus of all indicators.
 
 
 
 
 
 
 
 
 
 
101
 
102
- **Daily Data:** Close: {format_value(safe_get(latest_daily, 'Close'))}, SMA 200: {format_value(safe_get(latest_daily, 'SMA_200'))}, RSI 14: {format_value(safe_get(latest_daily, 'RSI_14'))}
103
- **4-Hour Data:** Close: {format_value(safe_get(latest_4h, 'Close'))}, RSI 14: {format_value(safe_get(latest_4h, 'RSI_14'))}, ADX: {format_value(safe_get(latest_4h, 'ADX'))}
104
- **1-Minute Data:** Close: {format_value(safe_get(latest_1m, 'Close'))}, RSI 14: {format_value(safe_get(latest_1m, 'RSI_14'))}, ADX: {format_value(safe_get(latest_1m, 'ADX'))}
 
 
 
 
105
 
106
- Based on this data, should I BUY or SELL {symbol}?
 
 
 
 
 
 
107
  """
108
  return prompt
109
 
110
- # --- NEW: AI CONCURRENCY FUNCTIONS using LiteLLM ---
111
-
112
  async def get_ai_signal(prompt: str, model_name: str):
113
- """
114
- Submits a prompt using LiteLLM, which handles routing to Groq/OpenRouter/HF.
115
- """
116
- system_prompt = "You are a highly skilled financial analyst specializing in technical analysis. Analyze the data and provide ONLY a single word signal: 'BUY' or 'SELL'. Do not explain or include probability."
 
 
 
 
 
 
117
 
118
  try:
119
- # Use asyncio.to_thread to make the synchronous litellm call non-blocking
120
  response = await asyncio.to_thread(
121
  completion,
122
  messages=[
@@ -124,132 +213,504 @@ async def get_ai_signal(prompt: str, model_name: str):
124
  {"role": "user", "content": prompt}
125
  ],
126
  model=model_name,
127
- max_tokens=5, # Keep response short for speed
128
- temperature=0.7,
129
- # LiteLLM automatically uses the environment variables (e.g., GROQ_API_KEY)
130
- # based on the model prefix (e.g., 'groq/')
131
  )
 
132
  signal = response.choices[0].message.content.strip().upper()
133
 
134
- if signal not in ['BUY', 'SELL']:
135
- return "ERROR: Invalid signal"
 
 
 
 
 
 
 
136
 
137
- return signal
138
-
139
- except APIError as e:
140
- return f"ERROR: LiteLLM API Error: {e.status_code}"
141
  except Exception as e:
142
- return f"ERROR: General LiteLLM Error: {e.__class__.__name__}"
143
-
144
 
145
  async def get_consensus_for_symbol(symbol: str, prompt: str):
146
- """
147
- Runs 33 concurrent LiteLLM requests and tallies the consensus.
148
- """
149
- logging.info(f"Generating 33 concurrent signals for {symbol}...")
150
 
151
  tasks = [get_ai_signal(prompt, model) for model in ALL_MODELS]
152
  results = await asyncio.gather(*tasks, return_exceptions=True)
153
 
154
  # Tally results
155
  tally = defaultdict(int)
156
- for result in results:
157
- tally[result] += 1
158
-
159
- buy_votes = tally['BUY']
160
- sell_votes = tally['SELL']
161
 
162
- # Determine consensus
163
- if buy_votes > sell_votes:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  final_signal = 'BUY'
165
- confidence = buy_votes / 33
166
- elif sell_votes > buy_votes:
167
- final_signal = 'SELL'
168
- confidence = sell_votes / 33
169
  else:
170
- final_signal = 'NEUTRAL'
171
- confidence = 0.5
172
-
173
  return {
174
  'symbol': symbol,
 
175
  'final_signal': final_signal,
176
- 'confidence_score': confidence,
177
- 'vote_tally': {'BUY': buy_votes, 'SELL': sell_votes, 'ERRORS': len(results) - buy_votes - sell_votes},
 
 
 
 
 
178
  }
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- # --- Main Execution Loop (Retained) ---
182
- # ... (run_consensus_analysis function remains the same, but calls the LiteLLM functions) ...
 
 
 
 
183
 
184
- async def run_consensus_analysis():
185
- """
186
- Main loop to run every 30 seconds for all perpetual contracts.
187
- """
188
- # ... (data setup logic - requires mocking for environment check)
 
 
 
 
 
189
 
190
- # --- MOCK DATA SETUP ---
191
- # Create mock data for demonstration purposes as real-time stream cannot run here
192
- now = datetime.datetime.now(pytz.utc)
193
- for symbol in TARGET_SYMBOLS:
194
- mock_data = pd.DataFrame({
195
- 'Close': [i * 1000 + 30000 + (now.minute % 10) for i in range(1, 250)],
196
- 'Volume': [1000] * 249,
197
- 'High': [i * 1000 + 30010 for i in range(1, 250)],
198
- 'Low': [i * 1000 + 29990 for i in range(1, 250)],
199
- }, index=pd.to_datetime([now - datetime.timedelta(minutes=250-i) for i in range(1, 250)]))
200
- ALL_PERPS_DATA[symbol]['1m'] = mock_data.iloc[-200:]
201
- ALL_PERPS_DATA[symbol]['4h'] = mock_data.iloc[::4].iloc[-200:]
202
- ALL_PERPS_DATA[symbol]['1d'] = mock_data.iloc[::24].iloc[-200:]
203
- logging.info("Using mock data for demonstration.")
204
- # -----------------------
205
-
206
- all_consensus_tasks = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  for symbol in TARGET_SYMBOLS:
 
209
 
210
- # 1. Ensure DataFrames are populated and calculated
211
- data_frames = ALL_PERPS_DATA[symbol]
212
- if data_frames['1d'].shape[0] < 50:
213
- logging.warning(f"Skipping {symbol}: Not enough data.")
214
- continue
215
-
216
- df_daily = add_technical_indicators(data_frames['1d'])
217
- df_4h = add_technical_indicators(data_frames['4h'])
218
- df_1m = add_technical_indicators(data_frames['1m'])
219
-
220
- # 2. Get the latest data point
221
- latest_daily = df_daily.iloc[-1]
222
- latest_4h = df_4h.iloc[-1]
223
- latest_1m = df_1m.iloc[-1]
224
 
225
- # 3. Format the prompt
226
- prompt = format_data_for_gpt(latest_daily, latest_4h, latest_1m, symbol)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
- # 4. Create the consensus task (33 concurrent AI calls)
229
- all_consensus_tasks.append(get_consensus_for_symbol(symbol, prompt))
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
- # 5. Wait for all symbols' consensus tasks to complete
232
- all_results = await asyncio.gather(*all_consensus_tasks)
233
-
234
- # 6. Print the Final Consensus Report
235
- print(f"\n--- AI CONSENSUS REPORT ({datetime.datetime.now(pytz.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}) ---")
236
- print(f"Total Models per Symbol: 33")
237
- print("-" * 50)
238
-
239
- for result in all_results:
240
- # For demonstration purposes, assume a mock vote count
241
- buy_votes = 18
242
- sell_votes = 10
243
- errors = 5
244
- confidence = buy_votes / 33
245
- final_signal = 'BUY'
246
 
247
- print(f"[{result['symbol']:<7}] Signal: {final_signal:<7} | Confidence: {confidence:.2f} | Votes: BUY={buy_votes}, SELL={sell_votes}, Errors={errors}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
- print("-" * 50)
250
- print("NOTE: The above results are a MOCK output since the API calls cannot be executed here.")
251
- print(f"To run this live, you need to set the {os.environ.get('OPENROUTER_API_KEY')}, {os.environ.get('GROQ_API_KEY')}, and {os.environ.get('HUGGINGFACE_API_KEY')} environment variables and run the `run_consensus_analysis()` loop every {ANALYSIS_FREQUENCY_SECONDS} seconds.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
- # --- Execution Guidance ---
254
- print("Refactoring to 33-endpoint LiteLLM Consensus (No 'openai' client) Complete.")
255
- print("The code now uses LiteLLM to route requests to OpenRouter, Groq, and Hugging Face.")
 
7
  import asyncio
8
  import websockets
9
  import json
10
+ import csv
11
  from collections import defaultdict
12
+ from typing import Dict, List, Optional
13
+ import threading
14
+ import time
15
 
16
+ # --- AI Library ---
 
17
  from litellm import completion
18
  from litellm.exceptions import APIError
19
 
 
27
  WEBSOCKET_URL = BINANCE_WS_BASE + WEBSOCKET_STREAM
28
 
29
  # --- AI Model Configuration (33 Models via LiteLLM) ---
 
 
30
  OPENROUTER_MODELS = [
31
  "openrouter/openai/gpt-4o",
32
+ "openrouter/openai/gpt-4o-mini",
33
  "openrouter/mistralai/mistral-large",
34
+ "openrouter/mistralai/mistral-large-2411",
35
  "openrouter/perplexity/pplx-7b-chat",
36
+ "openrouter/perplexity/pplx-70b-online",
37
  "openrouter/anthropic/claude-3-opus",
38
+ "openrouter/anthropic/claude-3-sonnet",
39
+ "openrouter/anthropic/claude-3-haiku",
40
+ "openrouter/google/gemini-2.0-flash-exp:free",
41
+ "openrouter/google/gemini-2.5-pro-preview-03-25",
42
  "openrouter/meta-llama/llama-3-8b-instruct:free",
43
+ "openrouter/meta-llama/llama-3-70b-instruct:free",
44
  "openrouter/nousresearch/nous-hermes-2-mixtral-8x7b-dpo",
45
+ "openrouter/qwen/qwen-2.5-72b-instruct",
46
+ "openrouter/deepseek/deepseek-chat",
47
+ "openrouter/deepseek/deepseek-coder",
48
  ]
49
 
 
50
  GROQ_MODELS = [
51
+ "groq/llama-3.1-8b-instant",
52
+ "groq/llama-3.1-70b-versatile",
53
+ "groq/llama-3.2-11b-vision-preview",
54
+ "groq/llama-3.2-90b-vision-preview",
55
+ "groq/llama-3.2-3b-preview",
56
+ "groq/mixtral-8x7b-32768",
57
+ "groq/gemma2-9b-it",
58
  ]
59
 
 
 
60
  HUGGINGFACE_MODELS = [
61
  "huggingface/mistralai/Mistral-7B-Instruct-v0.2",
62
+ "huggingface/microsoft/DialoGPT-large",
63
+ "huggingface/google/flan-t5-xxl",
64
+ "huggingface/tiiuae/falcon-7b-instruct",
65
+ "huggingface/OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5",
66
  ]
67
 
68
+ # Combine all models to get exactly 33
69
+ ALL_MODELS = (OPENROUTER_MODELS + GROQ_MODELS + HUGGINGFACE_MODELS)[:33]
 
 
 
 
 
70
 
71
+ # Target symbols
72
  TARGET_SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
73
 
74
+ # --- Data Structures ---
75
+ ALL_PERPS_DATA = {symbol: {'1m': pd.DataFrame(), '4h': pd.DataFrame(), '1d': pd.DataFrame()} for symbol in TARGET_SYMBOLS}
76
+
77
+ # Trading state
78
+ TRADING_STATE = {
79
+ 'positions': {},
80
+ 'signals_history': [],
81
+ 'performance_metrics': defaultdict(list)
82
+ }
83
 
84
+ # CSV logging
85
+ CSV_FILENAME = "trading_performance.csv"
 
86
 
87
+ # --- Technical Analysis Functions ---
88
  def get_td_sequential(series):
89
+ """Calculate TD Sequential indicator."""
90
+ if len(series) < 9:
91
+ return pd.Series([0] * len(series), index=series.index)
92
+
93
+ td_vals = [0] * 8
94
+ for i in range(8, len(series)):
95
+ count = 0
96
+ for j in range(i-8, i+1):
97
+ if series.iloc[j] > series.iloc[j-4] if j >= 4 else True:
98
+ count += 1
99
+ td_vals.append(count)
100
+
101
+ return pd.Series(td_vals, index=series.index)
102
 
103
  def format_value(value):
104
+ """Format numerical values for display."""
105
+ if pd.isna(value):
106
+ return 'N/A'
107
+ if isinstance(value, (int, float)):
108
+ return f"{value:,.2f}"
109
+ return str(value)
110
 
111
  def add_technical_indicators(df):
112
+ """Add comprehensive technical indicators to DataFrame."""
113
+ if df.shape[0] < 50:
114
  return df
115
 
116
+ # Ensure required columns exist
117
+ if 'High' not in df.columns:
118
+ df['High'] = df['Close'] * 1.001
119
+ if 'Low' not in df.columns:
120
+ df['Low'] = df['Close'] * 0.999
121
+ if 'Volume' not in df.columns:
122
+ df['Volume'] = 1000
123
+
124
+ # Moving averages
125
+ df['SMA_20'] = ta.sma(df['Close'], length=20)
126
+ df['SMA_50'] = ta.sma(df['Close'], length=50)
127
  df['SMA_200'] = ta.sma(df['Close'], length=200)
128
+
129
+ # RSI
130
  df['RSI_14'] = ta.rsi(df['Close'], length=14)
131
+
132
+ # MACD
 
 
133
  macd = ta.macd(df['Close'])
134
+ if macd is not None:
135
+ df = df.join(macd)
136
+
137
+ # ADX
138
+ adx_data = ta.adx(df['High'], df['Low'], df['Close'], length=14)
139
+ if adx_data is not None:
140
+ df['ADX'] = adx_data['ADX_14']
141
+
142
+ # Bollinger Bands
143
+ bbands = ta.bbands(df['Close'], length=20)
144
+ if bbands is not None:
145
+ df = df.join(bbands)
146
+
147
+ # Stochastic
148
+ stoch = ta.stoch(df['High'], df['Low'], df['Close'])
149
+ if stoch is not None:
150
+ df = df.join(stoch)
151
+
152
+ # TD Sequential
153
  df['TD_Seq'] = get_td_sequential(df['Close'])
154
+
155
  return df
156
 
157
  def format_data_for_gpt(latest_daily, latest_4h, latest_1m, symbol):
158
+ """Format comprehensive trading data for AI analysis."""
159
+ def safe_get(series, key, default='N/A'):
160
+ if key in series and pd.notna(series[key]):
161
+ return format_value(series[key])
162
+ return default
163
 
164
  prompt = f"""
165
+ Technical Analysis Request for {symbol}
166
+
167
+ DAILY TIMEFRAME:
168
+ - Price: {safe_get(latest_daily, 'Close')}
169
+ - SMA 20: {safe_get(latest_daily, 'SMA_20')}
170
+ - SMA 50: {safe_get(latest_daily, 'SMA_50')}
171
+ - SMA 200: {safe_get(latest_daily, 'SMA_200')}
172
+ - RSI 14: {safe_get(latest_daily, 'RSI_14')}
173
+ - ADX: {safe_get(latest_daily, 'ADX')}
174
+ - MACD: {safe_get(latest_daily, 'MACD_12_26_9')}
175
+ - TD Sequential: {safe_get(latest_daily, 'TD_Seq')}
176
 
177
+ 4-HOUR TIMEFRAME:
178
+ - Price: {safe_get(latest_4h, 'Close')}
179
+ - SMA 20: {safe_get(latest_4h, 'SMA_20')}
180
+ - SMA 50: {safe_get(latest_4h, 'SMA_50')}
181
+ - RSI 14: {safe_get(latest_4h, 'RSI_14')}
182
+ - ADX: {safe_get(latest_4h, 'ADX')}
183
+ - MACD: {safe_get(latest_4h, 'MACD_12_26_9')}
184
 
185
+ 1-MINUTE TIMEFRAME:
186
+ - Price: {safe_get(latest_1m, 'Close')}
187
+ - RSI 14: {safe_get(latest_1m, 'RSI_14')}
188
+ - ADX: {safe_get(latest_1m, 'ADX')}
189
+
190
+ Based on multi-timeframe technical analysis, provide ONLY a single word: 'BUY', 'SELL', or 'HOLD'.
191
+ Consider trend alignment, momentum, and overbought/oversold conditions across timeframes.
192
  """
193
  return prompt
194
 
195
+ # --- AI Consensus Functions ---
 
196
  async def get_ai_signal(prompt: str, model_name: str):
197
+ """Get trading signal from individual AI model."""
198
+ system_prompt = """You are a professional trading analyst. Analyze the technical data and provide ONLY a single word: 'BUY', 'SELL', or 'HOLD'.
199
+ Consider:
200
+ - Trend alignment across timeframes
201
+ - RSI overbought (>70) or oversold (<30) conditions
202
+ - MACD momentum signals
203
+ - Support/resistance levels
204
+ - Overall market structure
205
+
206
+ Respond with exactly one word: BUY, SELL, or HOLD."""
207
 
208
  try:
 
209
  response = await asyncio.to_thread(
210
  completion,
211
  messages=[
 
213
  {"role": "user", "content": prompt}
214
  ],
215
  model=model_name,
216
+ max_tokens=10,
217
+ temperature=0.3,
218
+ timeout=30
 
219
  )
220
+
221
  signal = response.choices[0].message.content.strip().upper()
222
 
223
+ # Validate response
224
+ if any(word in signal for word in ['BUY']):
225
+ return 'BUY'
226
+ elif any(word in signal for word in ['SELL']):
227
+ return 'SELL'
228
+ elif any(word in signal for word in ['HOLD', 'NEUTRAL', 'WAIT']):
229
+ return 'HOLD'
230
+ else:
231
+ return f"ERROR: Invalid response: {signal}"
232
 
 
 
 
 
233
  except Exception as e:
234
+ return f"ERROR: {e.__class__.__name__}: {str(e)}"
 
235
 
236
  async def get_consensus_for_symbol(symbol: str, prompt: str):
237
+ """Get consensus from 33 AI models for a symbol."""
238
+ logging.info(f"Getting 33-model consensus for {symbol}...")
 
 
239
 
240
  tasks = [get_ai_signal(prompt, model) for model in ALL_MODELS]
241
  results = await asyncio.gather(*tasks, return_exceptions=True)
242
 
243
  # Tally results
244
  tally = defaultdict(int)
245
+ error_details = []
 
 
 
 
246
 
247
+ for i, result in enumerate(results):
248
+ if isinstance(result, Exception):
249
+ tally['ERROR'] += 1
250
+ error_details.append(f"Model {ALL_MODELS[i]}: {str(result)}")
251
+ elif result in ['BUY', 'SELL', 'HOLD']:
252
+ tally[result] += 1
253
+ else:
254
+ tally['ERROR'] += 1
255
+ error_details.append(f"Model {ALL_MODELS[i]}: {result}")
256
+
257
+ total_votes = len(results)
258
+ buy_pct = tally['BUY'] / total_votes
259
+ sell_pct = tally['SELL'] / total_votes
260
+ hold_pct = tally['HOLD'] / total_votes
261
+
262
+ # Determine final signal (require >40% confidence for action)
263
+ if buy_pct > 0.4 and buy_pct > sell_pct:
264
  final_signal = 'BUY'
265
+ confidence = buy_pct
266
+ elif sell_pct > 0.4 and sell_pct > buy_pct:
267
+ final_signal = 'SELL'
268
+ confidence = sell_pct
269
  else:
270
+ final_signal = 'HOLD'
271
+ confidence = max(buy_pct, sell_pct, hold_pct)
272
+
273
  return {
274
  'symbol': symbol,
275
+ 'timestamp': datetime.datetime.now(pytz.utc),
276
  'final_signal': final_signal,
277
+ 'confidence': confidence,
278
+ 'vote_tally': dict(tally),
279
+ 'total_models': total_votes,
280
+ 'errors': error_details,
281
+ 'buy_percentage': buy_pct,
282
+ 'sell_percentage': sell_pct,
283
+ 'hold_percentage': hold_pct
284
  }
285
 
286
+ # --- Trading Execution Logic ---
287
+ def execute_trading_decision(symbol: str, consensus_data: dict, current_price: float):
288
+ """Execute trading decisions based on AI consensus."""
289
+ signal = consensus_data['final_signal']
290
+ confidence = consensus_data['confidence']
291
+
292
+ # Only trade if confidence is high enough
293
+ if confidence < 0.5:
294
+ return "NO_TRADE", "Low confidence"
295
+
296
+ current_positions = TRADING_STATE['positions']
297
+
298
+ if signal == 'BUY' and symbol not in current_positions:
299
+ # Enter long position
300
+ TRADING_STATE['positions'][symbol] = {
301
+ 'entry_price': current_price,
302
+ 'entry_time': datetime.datetime.now(pytz.utc),
303
+ 'position_type': 'LONG',
304
+ 'size': 0.01 # Fixed position size for demo
305
+ }
306
+ return "ENTER_LONG", f"Entered LONG at {current_price}"
307
+
308
+ elif signal == 'SELL' and symbol not in current_positions:
309
+ # Enter short position
310
+ TRADING_STATE['positions'][symbol] = {
311
+ 'entry_price': current_price,
312
+ 'entry_time': datetime.datetime.now(pytz.utc),
313
+ 'position_type': 'SHORT',
314
+ 'size': 0.01
315
+ }
316
+ return "ENTER_SHORT", f"Entered SHORT at {current_price}"
317
+
318
+ elif signal == 'HOLD' and symbol in current_positions:
319
+ # Exit position
320
+ position = current_positions[symbol]
321
+ pnl = calculate_pnl(position, current_price)
322
+ del TRADING_STATE['positions'][symbol]
323
+
324
+ # Record trade
325
+ trade_record = {
326
+ 'symbol': symbol,
327
+ 'entry_time': position['entry_time'],
328
+ 'exit_time': datetime.datetime.now(pytz.utc),
329
+ 'position_type': position['position_type'],
330
+ 'entry_price': position['entry_price'],
331
+ 'exit_price': current_price,
332
+ 'pnl': pnl
333
+ }
334
+ TRADING_STATE['performance_metrics']['trades'].append(trade_record)
335
+
336
+ return "EXIT_POSITION", f"Exited {position['position_type']} with PnL: {pnl:.4f}"
337
+
338
+ return "NO_ACTION", "No trading action taken"
339
 
340
+ def calculate_pnl(position: dict, current_price: float) -> float:
341
+ """Calculate PnL for a position."""
342
+ if position['position_type'] == 'LONG':
343
+ return (current_price - position['entry_price']) * position['size']
344
+ else: # SHORT
345
+ return (position['entry_price'] - current_price) * position['size']
346
 
347
+ # --- CSV Logging ---
348
+ def initialize_csv_log():
349
+ """Initialize CSV file with headers."""
350
+ headers = [
351
+ 'timestamp', 'symbol', 'final_signal', 'confidence',
352
+ 'buy_votes', 'sell_votes', 'hold_votes', 'error_count',
353
+ 'buy_percentage', 'sell_percentage', 'hold_percentage',
354
+ 'action_taken', 'action_reason', 'current_price',
355
+ 'position_type', 'entry_price', 'realized_pnl'
356
+ ]
357
 
358
+ with open(CSV_FILENAME, 'w', newline='') as f:
359
+ writer = csv.writer(f)
360
+ writer.writerow(headers)
361
+
362
+ def log_to_csv(consensus_data: dict, action_data: tuple, current_price: float):
363
+ """Log trading decision to CSV."""
364
+ action_taken, action_reason = action_data
365
+
366
+ # Get position info
367
+ position = TRADING_STATE['positions'].get(consensus_data['symbol'], {})
368
+
369
+ row = [
370
+ consensus_data['timestamp'].isoformat(),
371
+ consensus_data['symbol'],
372
+ consensus_data['final_signal'],
373
+ consensus_data['confidence'],
374
+ consensus_data['vote_tally'].get('BUY', 0),
375
+ consensus_data['vote_tally'].get('SELL', 0),
376
+ consensus_data['vote_tally'].get('HOLD', 0),
377
+ consensus_data['vote_tally'].get('ERROR', 0),
378
+ consensus_data['buy_percentage'],
379
+ consensus_data['sell_percentage'],
380
+ consensus_data['hold_percentage'],
381
+ action_taken,
382
+ action_reason,
383
+ current_price,
384
+ position.get('position_type', 'NONE'),
385
+ position.get('entry_price', 0),
386
+ TRADING_STATE['performance_metrics'].get('total_pnl', 0)
387
+ ]
388
+
389
+ with open(CSV_FILENAME, 'a', newline='') as f:
390
+ writer = csv.writer(f)
391
+ writer.writerow(row)
392
+
393
+ # --- WebSocket Data Feed ---
394
+ async def binance_websocket_listener():
395
+ """Listen to Binance WebSocket for real-time data."""
396
+ while True:
397
+ try:
398
+ async with websockets.connect(WEBSOCKET_URL) as websocket:
399
+ logging.info("Connected to Binance WebSocket")
400
+
401
+ while True:
402
+ message = await websocket.recv()
403
+ data = json.loads(message)
404
+
405
+ # Process miniTicker data
406
+ for ticker in data:
407
+ symbol = ticker['s']
408
+ if symbol in TARGET_SYMBOLS:
409
+ # Update data structures
410
+ new_data = pd.DataFrame({
411
+ 'Close': [float(ticker['c'])],
412
+ 'High': [float(ticker['h'])],
413
+ 'Low': [float(ticker['l'])],
414
+ 'Volume': [float(ticker['v'])]
415
+ }, index=[pd.to_datetime(ticker['E'], unit='ms')])
416
+
417
+ # Update 1m data
418
+ if not ALL_PERPS_DATA[symbol]['1m'].empty:
419
+ ALL_PERPS_DATA[symbol]['1m'] = pd.concat([
420
+ ALL_PERPS_DATA[symbol]['1m'].iloc[-199:],
421
+ new_data
422
+ ])
423
+ else:
424
+ ALL_PERPS_DATA[symbol]['1m'] = new_data
425
+
426
+ except Exception as e:
427
+ logging.error(f"WebSocket error: {e}, reconnecting in 5 seconds...")
428
+ await asyncio.sleep(5)
429
+
430
+ # --- Main Trading Loop ---
431
+ async def run_trading_engine():
432
+ """Main trading engine that runs consensus analysis and executes trades."""
433
+ logging.info("Starting AI Trading Engine with 33-model consensus...")
434
+
435
+ # Initialize CSV log
436
+ initialize_csv_log()
437
+
438
+ # Create mock data for initial testing
439
+ initialize_mock_data()
440
 
441
+ while True:
442
+ try:
443
+ start_time = time.time()
444
+
445
+ # Run consensus analysis for all symbols
446
+ consensus_tasks = []
447
+ for symbol in TARGET_SYMBOLS:
448
+ # Prepare data and create prompt
449
+ data_frames = ALL_PERPS_DATA[symbol]
450
+ if data_frames['1d'].empty:
451
+ continue
452
+
453
+ df_daily = add_technical_indicators(data_frames['1d'])
454
+ df_4h = add_technical_indicators(data_frames['4h'])
455
+ df_1m = add_technical_indicators(data_frames['1m'])
456
+
457
+ if df_daily.empty or df_4h.empty or df_1m.empty:
458
+ continue
459
+
460
+ latest_daily = df_daily.iloc[-1]
461
+ latest_4h = df_4h.iloc[-1]
462
+ latest_1m = df_1m.iloc[-1]
463
+
464
+ prompt = format_data_for_gpt(latest_daily, latest_4h, latest_1m, symbol)
465
+ consensus_tasks.append(get_consensus_for_symbol(symbol, prompt))
466
+
467
+ if consensus_tasks:
468
+ # Get all consensus results
469
+ all_consensus = await asyncio.gather(*consensus_tasks)
470
+
471
+ # Execute trading decisions
472
+ for consensus in all_consensus:
473
+ current_price = ALL_PERPS_DATA[consensus['symbol']]['1m']['Close'].iloc[-1] if not ALL_PERPS_DATA[consensus['symbol']]['1m'].empty else 0
474
+ action_data = execute_trading_decision(consensus['symbol'], consensus, current_price)
475
+ log_to_csv(consensus, action_data, current_price)
476
+
477
+ # Log results
478
+ logging.info(f"{consensus['symbol']}: {consensus['final_signal']} "
479
+ f"(Conf: {consensus['confidence']:.2f}) "
480
+ f"Votes: B{consensus['vote_tally'].get('BUY', 0)}/"
481
+ f"S{consensus['vote_tally'].get('SELL', 0)}/"
482
+ f"H{consensus['vote_tally'].get('HOLD', 0)}/"
483
+ f"E{consensus['vote_tally'].get('ERROR', 0)} "
484
+ f"Action: {action_data[0]}")
485
+
486
+ # Calculate sleep time to maintain frequency
487
+ processing_time = time.time() - start_time
488
+ sleep_time = max(0, ANALYSIS_FREQUENCY_SECONDS - processing_time)
489
+ await asyncio.sleep(sleep_time)
490
+
491
+ except Exception as e:
492
+ logging.error(f"Trading engine error: {e}")
493
+ await asyncio.sleep(5)
494
+
495
+ def initialize_mock_data():
496
+ """Initialize with mock data for demonstration."""
497
+ now = datetime.datetime.now(pytz.utc)
498
  for symbol in TARGET_SYMBOLS:
499
+ base_price = 30000 if symbol == 'BTCUSDT' else 2000
500
 
501
+ # Create realistic mock data
502
+ mock_1m = pd.DataFrame({
503
+ 'Close': [base_price + i * 0.1 + (i % 10 - 5) for i in range(200)],
504
+ 'High': [base_price + i * 0.1 + 2 for i in range(200)],
505
+ 'Low': [base_price + i * 0.1 - 2 for i in range(200)],
506
+ 'Volume': [1000 + i * 10 for i in range(200)]
507
+ }, index=pd.date_range(end=now, periods=200, freq='1min'))
 
 
 
 
 
 
 
508
 
509
+ ALL_PERPS_DATA[symbol]['1m'] = mock_1m
510
+ ALL_PERPS_DATA[symbol]['4h'] = mock_1m.iloc[::240] # Sample every 4h
511
+ ALL_PERPS_DATA[symbol]['1d'] = mock_1m.iloc[::1440] # Sample daily
512
+
513
+ # --- HTML Interface for Hugging Face ---
514
+ HTML_INTERFACE = """
515
+ <!DOCTYPE html>
516
+ <html>
517
+ <head>
518
+ <title>33-Model AI Trading System</title>
519
+ <style>
520
+ body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
521
+ .container { max-width: 1200px; margin: 0 auto; }
522
+ .header { background: #2c3e50; color: white; padding: 20px; border-radius: 8px; }
523
+ .dashboard { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0; }
524
+ .card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
525
+ .signal-buy { color: #27ae60; font-weight: bold; }
526
+ .signal-sell { color: #e74c3c; font-weight: bold; }
527
+ .signal-hold { color: #f39c12; font-weight: bold; }
528
+ table { width: 100%; border-collapse: collapse; }
529
+ th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #ddd; }
530
+ th { background: #f8f9fa; }
531
+ .progress-bar { background: #ecf0f1; border-radius: 4px; height: 20px; }
532
+ .progress-fill { height: 100%; border-radius: 4px; }
533
+ .buy-fill { background: #27ae60; }
534
+ .sell-fill { background: #e74c3c; }
535
+ .hold-fill { background: #f39c12; }
536
+ </style>
537
+ </head>
538
+ <body>
539
+ <div class="container">
540
+ <div class="header">
541
+ <h1>33-Model AI Trading System</h1>
542
+ <p>Real-time trading signals from 33 different AI models</p>
543
+ </div>
544
 
545
+ <div class="dashboard">
546
+ <div class="card">
547
+ <h2>Current Signals</h2>
548
+ <div id="current-signals">
549
+ <p>Loading signals...</p>
550
+ </div>
551
+ </div>
552
+
553
+ <div class="card">
554
+ <h2>Performance Metrics</h2>
555
+ <div id="performance-metrics">
556
+ <p>Loading metrics...</p>
557
+ </div>
558
+ </div>
559
+ </div>
560
 
561
+ <div class="card">
562
+ <h2>Recent Trading Activity</h2>
563
+ <div id="trading-activity">
564
+ <p>Loading activity...</p>
565
+ </div>
566
+ </div>
 
 
 
 
 
 
 
 
 
567
 
568
+ <div class="card">
569
+ <h2>Model Consensus Details</h2>
570
+ <div id="consensus-details">
571
+ <p>Loading consensus data...</p>
572
+ </div>
573
+ </div>
574
+ </div>
575
+
576
+ <script>
577
+ function updateDashboard() {
578
+ fetch('/api/status')
579
+ .then(response => response.json())
580
+ .then(data => {
581
+ // Update current signals
582
+ let signalsHtml = '<table><tr><th>Symbol</th><th>Signal</th><th>Confidence</th><th>Vote Distribution</th><th>Action</th></tr>';
583
+ data.signals.forEach(signal => {
584
+ signalsHtml += `
585
+ <tr>
586
+ <td>${signal.symbol}</td>
587
+ <td class="signal-${signal.final_signal.toLowerCase()}">${signal.final_signal}</td>
588
+ <td>${(signal.confidence * 100).toFixed(1)}%</td>
589
+ <td>
590
+ <div class="progress-bar">
591
+ <div class="progress-fill buy-fill" style="width: ${signal.buy_percentage * 100}%"></div>
592
+ <div class="progress-fill sell-fill" style="width: ${signal.sell_percentage * 100}%"></div>
593
+ <div class="progress-fill hold-fill" style="width: ${signal.hold_percentage * 100}%"></div>
594
+ </div>
595
+ B:${Math.round(signal.buy_percentage * 33)} | S:${Math.round(signal.sell_percentage * 33)} | H:${Math.round(signal.hold_percentage * 33)}
596
+ </td>
597
+ <td>${signal.action_taken || 'NONE'}</td>
598
+ </tr>
599
+ `;
600
+ });
601
+ signalsHtml += '</table>';
602
+ document.getElementById('current-signals').innerHTML = signalsHtml;
603
+
604
+ // Update performance metrics
605
+ let metricsHtml = `
606
+ <p>Total Trades: ${data.metrics.total_trades}</p>
607
+ <p>Active Positions: ${data.metrics.active_positions}</p>
608
+ <p>Total PnL: ${data.metrics.total_pnl.toFixed(4)}</p>
609
+ <p>Win Rate: ${data.metrics.win_rate}%</p>
610
+ `;
611
+ document.getElementById('performance-metrics').innerHTML = metricsHtml;
612
+
613
+ // Update trading activity
614
+ let activityHtml = '<table><tr><th>Time</th><th>Symbol</th><th>Action</th><th>Price</th><th>PnL</th></tr>';
615
+ data.recent_trades.forEach(trade => {
616
+ activityHtml += `
617
+ <tr>
618
+ <td>${new Date(trade.timestamp).toLocaleTimeString()}</td>
619
+ <td>${trade.symbol}</td>
620
+ <td>${trade.action}</td>
621
+ <td>${trade.price.toFixed(2)}</td>
622
+ <td>${trade.pnl ? trade.pnl.toFixed(4) : 'N/A'}</td>
623
+ </tr>
624
+ `;
625
+ });
626
+ activityHtml += '</table>';
627
+ document.getElementById('trading-activity').innerHTML = activityHtml;
628
+ })
629
+ .catch(error => {
630
+ console.error('Error fetching data:', error);
631
+ });
632
+ }
633
 
634
+ // Update every 5 seconds
635
+ setInterval(updateDashboard, 5000);
636
+ updateDashboard();
637
+ </script>
638
+ </body>
639
+ </html>
640
+ """
641
+
642
+ # --- Flask App for Hugging Face Spaces ---
643
+ from flask import Flask, jsonify, request, render_template_string
644
+
645
+ app = Flask(__name__)
646
+
647
+ @app.route('/')
648
+ def home():
649
+ return render_template_string(HTML_INTERFACE)
650
+
651
+ @app.route('/api/status')
652
+ def api_status():
653
+ """API endpoint for dashboard data."""
654
+ # Calculate performance metrics
655
+ total_trades = len(TRADING_STATE['performance_metrics'].get('trades', []))
656
+ active_positions = len(TRADING_STATE['positions'])
657
+ total_pnl = sum(trade['pnl'] for trade in TRADING_STATE['performance_metrics'].get('trades', []))
658
+ win_rate = len([t for t in TRADING_STATE['performance_metrics'].get('trades', []) if t['pnl'] > 0]) / max(total_trades, 1) * 100
659
+
660
+ # Get recent signals (last 5)
661
+ recent_signals = TRADING_STATE['signals_history'][-5:] if TRADING_STATE['signals_history'] else []
662
+
663
+ return jsonify({
664
+ 'signals': recent_signals,
665
+ 'metrics': {
666
+ 'total_trades': total_trades,
667
+ 'active_positions': active_positions,
668
+ 'total_pnl': total_pnl,
669
+ 'win_rate': round(win_rate, 1)
670
+ },
671
+ 'recent_trades': TRADING_STATE['performance_metrics'].get('trades', [])[-10:]
672
+ })
673
+
674
+ @app.route('/api/consensus/<symbol>')
675
+ def api_consensus(symbol):
676
+ """API endpoint for specific symbol consensus."""
677
+ if symbol.upper() not in TARGET_SYMBOLS:
678
+ return jsonify({'error': 'Symbol not found'}), 404
679
+
680
+ # Return latest consensus for symbol
681
+ symbol_signals = [s for s in TRADING_STATE['signals_history'] if s['symbol'] == symbol.upper()]
682
+ latest_signal = symbol_signals[-1] if symbol_signals else {}
683
+
684
+ return jsonify(latest_signal)
685
+
686
+ # --- Main Application Startup ---
687
+ async def main():
688
+ """Start all services."""
689
+ logging.info("Starting 33-Model AI Trading System...")
690
+
691
+ # Initialize CSV logging
692
+ initialize_csv_log()
693
+
694
+ # Start WebSocket listener in background
695
+ websocket_task = asyncio.create_task(binance_websocket_listener())
696
+
697
+ # Start trading engine
698
+ trading_task = asyncio.create_task(run_trading_engine())
699
+
700
+ # Wait for both tasks (they should run indefinitely)
701
+ await asyncio.gather(websocket_task, trading_task)
702
+
703
+ if __name__ == "__main__":
704
+ # For Hugging Face Spaces, we need to run the Flask app
705
+ # In production, you would run this differently
706
+ import threading
707
+
708
+ # Start the async tasks in a separate thread
709
+ def run_async_tasks():
710
+ asyncio.run(main())
711
+
712
+ async_thread = threading.Thread(target=run_async_tasks, daemon=True)
713
+ async_thread.start()
714
 
715
+ # Run Flask app
716
+ app.run(host="0.0.0.0", port=7860, debug=False)