DS Query
DS Query
import pandas as pd
import numpy as np
import google.generativeai as genai
import os
import sys
import time
import traceback
from datetime import datetime, timedelta
import re
import inspect
def debug_print_disabled():
"""Control debug printing for specific functions"""
stack = inspect.stack()[1][3]
return DEBUG_LOG and not any(x in stack for x in ['ATR', 'volume', 'Volume'])
# Trade configuration
TRADE_CONFIG = {
'magic': 12345678901, # Unique magic number for Tri-Point Trading System
'comment': 'GeminiBot', # Simplified static comment
'lot': 0.01
}
# Execution parameters
MAX_SLIPPAGE_PIPS = 3
MARKET_DEVIATION = 30
def get_order_expiration(timeframe):
"""Calculate order expiration as (timeframe duration - 30 seconds)"""
timeframe_minutes = {
'M1': 1,
'M5': 5,
'M15': 15,
'M30': 30,
'H1': 60,
'H4': 240
}.get(timeframe, 1)
# Timeframe setup
time_frame_order = ['M1', 'M5', 'M15', 'M30', 'H1', 'H4']
mt5_timeframes = {
'M1': mt5.TIMEFRAME_M1,
'M5': mt5.TIMEFRAME_M5,
'M15': mt5.TIMEFRAME_M15,
'M30': mt5.TIMEFRAME_M30,
'H1': mt5.TIMEFRAME_H1,
'H4': mt5.TIMEFRAME_H4
}
# Market correlations
CORRELATION_SYMBOLS = ['DXY', 'USDJPY', 'EURUSD', 'AUDUSD', 'USDCHF']
# Risk management
RISK_PERCENT = 1.0
MIN_CONFIDENCE = 4
PARTIAL_TP_RATIO = 0.5
MIN_LOT_SIZE = 0.01
MAX_LOT_SIZE = 0.1
# Indicator parameters
MFI_PERIOD = 14
ROC_PERIOD = 14
CMF_PERIOD = 20
VOLUME_MA_PERIOD = 20
ATR_PERIOD = 14
VOLUME_THRESHOLD = 1.5
def format_price(price):
"""Format price to specified precision"""
return f"{price:.2f}"
# Format templates
SIGNAL_TEMPLATE = """
Signal {num}: [{description}]
└─ Direction: {direction}
└─ 🎯 Entry: {entry:.2f}
└─ ⛔ SL: {sl:.2f}
└─ 🎯 TP: {tp:.2f}
└─ 📌 Confidence: {confidence_stars} ({confidence}/5)
└─ 💡 Rationale: {rationale}
"""
ANALYSIS_TEMPLATE = """
════════════════════════════════════════════════════════════
📊 MARKET ANALYSIS REPORT 📊
════════════════════════════════════════════════════════════
└─ 📈 Symbol: {symbol}
└─ 💵 Current Price: {price:.2f}
└─ ⏰ Time: {timestamp}
{technical}
{fundamental}
{correlation}
════════════════════════════════════════════════════════════
🚀 TRADE SIGNALS 🚀
════════════════════════════════════════════════════════════
{signals}
"""
# Regular expressions
SIGNAL_PATTERN = re.compile(r"""
Signal\s*(\d+)\s*:.*?
Direction\s*:\s*(BUY|SELL)\s*
(?:.*?Entry)[\s*:\-]*(\d+\.?\d*)
.*?SL[\s*:\-]*(\d+\.?\d*)
.*?TP[\s*:\-]*(\d+\.?\d*)
.*?Confidence\s*:\s*(⭐+)
.*?Rationale\s*:\s*(.*?)
(?=Signal|\Z)
""", re.DOTALL | re.VERBOSE)
# System constants
DEBUG_LOG = True
LOG_FILE = "ai_trading_log.txt"
MIN_RISK_REWARD = 2.0
MIN_STOP_DISTANCE_POINTS = 100
ORDER_DEVIATION = 20
PRICE_TOLERANCE = 0.001
MIN_DISTANCE_POINTS = 150 # Further reduced to allow closer stop orders
MIN_CONFIDENCE_TO_TRADE = 4
SWING_LOOKBACK = 100
MIN_TOUCHES = 3
MAX_LEVELS = 5
MAX_OPEN_TRADES = 3
MAX_RETRIES = 3
RETRY_DELAY = 5
API_TIMEOUT = 30
def print_title_bar():
print("\n" + "═"*60)
print("🔱 TRI-POINT GEMINI TRADING BOT 🔱".center(60))
print("═"*60 + "\n")
return "gemini"
def initialize_mt5():
"""Initialize MT5 with proper timeout settings"""
mt5.shutdown() # Ensure clean state
time.sleep(1) # Brief pause before initialization
if not mt5.initialize(timeout=TERMINAL_TIMEOUT):
print("❌ MT5 initialization failed")
return False
def verify_mt5_connection():
"""Enhanced connection verification"""
try:
if not mt5.terminal_info() or not mt5.terminal_info().connected:
print("📡 MT5 terminal not responding, reconnecting...")
mt5.shutdown()
time.sleep(CONNECTION_CHECK_DELAY)
if initialize_mt5():
time.sleep(CONNECTION_STABLE_TIME) # Wait for connection to
stabilize
return True
return False
return True
except Exception as e:
print(f"Connection verification error: {str(e)}")
return False
if verify_mt5_connection():
return True # Connection restored
return False
return True # No timeout error
# Technical indicators
def calculate_mfi(data, period=MFI_PERIOD):
"""Calculate Money Flow Index"""
typical_price = (data['high'] + data['low'] + data['close']) / 3
raw_money_flow = typical_price * data['tick_volume']
positive_flow = np.where(typical_price > typical_price.shift(1),
raw_money_flow, 0)
negative_flow = np.where(typical_price < typical_price.shift(1),
raw_money_flow, 0)
mf_ratio = (pd.Series(positive_flow).rolling(period).sum() /
pd.Series(negative_flow).rolling(period).sum().replace(0, 1e-10))
return 100 - (100 / (1 + mf_ratio))
def calculate_adl(data):
"""Calculate Accumulation/Distribution Line"""
clv = ((2 * data['close'] - data['high'] - data['low']) /
(data['high'] - data['low']).replace(0, 1e-10))
return (clv * data['tick_volume']).cumsum()
plus_dm = high.diff()
minus_dm = low.diff()
plus_dm[plus_dm < 0] = 0
minus_dm[minus_dm > 0] = 0
def analyze_volume(data):
vol_ma = data['tick_volume'].rolling(window=VOLUME_MA_PERIOD).mean()
volume_spikes = data['tick_volume'] > (vol_ma * VOLUME_THRESHOLD)
recent_vol_trend = 'increasing' if
data['tick_volume'].tail(5).is_monotonic_increasing else \
'decreasing' if
data['tick_volume'].tail(5).is_monotonic_decreasing else \
'neutral'
price_changes = data['close'].diff()
vol_price_corr = data['tick_volume'].corr(abs(price_changes))
return {
'volume_ma': vol_ma,
'volume_spikes': volume_spikes,
'vol_trend': recent_vol_trend,
'vol_price_correlation': vol_price_corr
}
def calculate_obv(data):
obv = pd.Series(index=data.index, dtype=float)
obv.iloc[0] = 0
for i in range(1, len(data)):
if data['close'].iloc[i] > data['close'].iloc[i-1]:
obv.iloc[i] = obv.iloc[i-1] + data['tick_volume'].iloc[i]
elif data['close'].iloc[i] < data['close'].iloc[i-1]:
obv.iloc[i] = obv.iloc[i-1] - data['tick_volume'].iloc[i]
else:
obv.iloc[i] = obv.iloc[i-1]
return obv
timeframe_data = data[timeframe]
if timeframe_data is None or len(timeframe_data) < 20:
return None
# Validate values
if pd.isna(price) or pd.isna(ma_short_val) or pd.isna(ma_long_val):
return None
# Determine bias
if price > ma_short_val > ma_long_val:
return "BULLISH"
elif price < ma_short_val < ma_long_val:
return "BEARISH"
return None
except Exception as e:
print(f"Error calculating MA bias: {str(e)}")
return None
df = pd.DataFrame(rates)
df['time'] = pd.to_datetime(df['time'], unit='s')
df.set_index('time', inplace=True)
# Moving averages
for ma_period in [8, 20, 50, 100, 200]:
df[f'MA{ma_period}'] = df['close'].rolling(ma_period).mean()
# Core indicators
df['RSI'] = calculate_rsi(df)
df['MACD'], df['MACD_Signal'] = calculate_macd(df)
df['Stoch_K'], df['Stoch_D'] = calculate_stochastic(df)
df['ADX'], df['Plus_DI'], df['Minus_DI'] = calculate_adx(df)
df['ATR'] = calculate_atr(df)
df['CCI'] = calculate_cci(df)
df['OBV'] = calculate_obv(df)
df['Williams_R'] = calculate_williams_r(df)
# New indicators
df['MFI'] = calculate_mfi(df)
df['ADL'] = calculate_adl(df)
df['ROC'] = calculate_roc(df)
df['CMF'] = calculate_cmf(df)
# Volume analysis
volume_metrics = analyze_volume(df)
df['Volume_MA'] = volume_metrics['volume_ma']
df['Volume_Spikes'] = volume_metrics['volume_spikes']
all_data[tf] = df
return all_data
def get_current_price(symbol):
try:
tick = mt5.symbol_info_tick(symbol)
return {'bid': tick.bid, 'ask': tick.ask} if tick else None
except Exception as e:
print(f"Price check error: {str(e)}")
return None
def check_existing_trade(trade_type):
positions = mt5.positions_get()
orders = mt5.orders_get()
all_trades = []
if positions: all_trades.extend(positions)
if orders: all_trades.extend(orders)
point = symbol_info.point
min_stop_distance = 1500 * point
request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": symbol,
"volume": volume,
"type": order_type,
"price": price_info['ask'] if direction == 'buy' else price_info['bid'],
"sl": sl,
"tp": tp,
"deviation": 20,
"magic": TRADE_CONFIG['magic'],
"comment": comment,
"type_time": mt5.ORDER_TIME_GTC,
"type_filling": mt5.ORDER_FILLING_FOK,
}
result = mt5.order_send(request)
if not result:
error_message = mt5.last_error() # Capture last error
print(f"No response from MT5. MT5 Error: {error_message}") # Log detailed
error
return False
if result.retcode != mt5.TRADE_RETCODE_DONE:
print(f"Market order failed: {mt5.last_error()}")
return False
def check_existing_positions(symbol):
positions = mt5.positions_get(symbol=symbol)
return positions if positions is not None else []
symbol_info = mt5.symbol_info(symbol)
if not symbol_info:
return MIN_LOT_SIZE
return position_size
except Exception as e:
print(f"Error calculating position size: {str(e)}")
return 0.01
def evaluate_signal_quality(signal_params):
try:
confidence = int(signal_params['confidence'])
if confidence < MIN_CONFIDENCE_TO_TRADE:
return False, f"Confidence ({confidence}) below minimum required
({MIN_CONFIDENCE_TO_TRADE})"
return True, "Signal meets confidence criteria"
except Exception as e:
return False, f"Error evaluating signal: {str(e)}"
point = symbol_info.point
sl_distance = abs(entry - sl) / point
current_price = get_current_price(symbol)
if not current_price:
return False, "Current price not available"
ask = current_price['ask']
bid = current_price['bid']
point = symbol_info.point
if order_type == mt5.ORDER_TYPE_BUY_STOP:
min_price = ask + (min_distance * point)
if price <= min_price:
# Suggest price with additional buffer
suggested_price = min_price + (200 * point) # Increased buffer to
200 points
return False, (f"Buy stop price ({price}) must be above
{min_price:.5f} (ask + {min_distance:.0f} points). "
f"Spread: {spread:.0f} points, ATR: {atr/point:.0f}
points. "
f"Suggested price: {suggested_price:.5f}")
except Exception as e:
return False, f"Price validation error: {str(e)}"
def group_levels(levels):
grouped = []
for price in levels:
found_group = False
for group in grouped:
if abs(price - sum(group)/len(group)) <= price *
PRICE_TOLERANCE:
group.append(price)
found_group = True
break
if not found_group:
grouped.append([price])
return [sum(group)/len(group) for group in grouped if len(group) >=
min_touches]
except Exception as e:
log_debug("Error detecting S/R levels", str(e))
return {'support': [], 'resistance': []}
if direction.lower() == 'buy':
valid_sl = [s for s in supports if s < entry_price]
valid_tp = [r for r in resistances if r > entry_price]
sl = max(valid_sl)
min_tp_distance = (entry_price - sl) * risk_reward_min
valid_tp = [tp for tp in valid_tp if tp - entry_price >=
min_tp_distance]
if not valid_tp:
return None, None
tp = min(valid_tp)
else:
valid_sl = [r for r in resistances if r > entry_price]
valid_tp = [s for s in supports if s < entry_price]
sl = min(valid_sl)
min_tp_distance = (sl - entry_price) * risk_reward_min
valid_tp = [tp for tp in valid_tp if entry_price - tp >=
min_tp_distance]
if not valid_tp:
return None, None
tp = max(valid_tp)
return sl, tp
except Exception as e:
log_debug("Error finding optimal SL/TP", str(e))
return None, None
if not sl_valid:
return False, "Stop loss not aligned with S/R levels"
if not tp_valid:
return False, "Take profit not aligned with S/R levels"
except Exception as e:
return False, f"Error validating against S/R: {str(e)}"
df = fetch_intraday_data(symbol, num_candles=SWING_LOOKBACK)[tf]
sr_levels = detect_support_resistance(df)
structure[tf] = {
'sr_levels': sr_levels,
'trend': 'bullish' if df['close'].iloc[-1] > df['MA20'].iloc[-1]
else 'bearish',
'volatility': df['high'].std() / df['close'].mean()
}
return structure
except Exception as e:
log_debug("Error analyzing market structure", str(e))
return None
def count_total_trades(symbol=None):
try:
positions = mt5.positions_get(symbol=symbol)
orders = mt5.orders_get(symbol=symbol)
total_trades = 0
if positions:
total_trades += sum(1 for pos in positions if pos.magic ==
TRADE_CONFIG['magic'])
if orders:
total_trades += sum(1 for order in orders if order.magic ==
TRADE_CONFIG['magic'])
return total_trades
except Exception as e:
print(f"Error counting trades: {str(e)}")
return 0
def parse_suggested_price(error_message):
"""Extract suggested price from error message"""
pattern = r"Suggested price: ([\d\.]+)"
match = re.search(pattern, error_message)
if match:
return float(match.group(1))
return None
direction = signal_params['direction']
entry_price = float(signal_params['entry'])
sl_price = float(signal_params['sl'])
tp_price = float(signal_params['tp'])
symbol_info = mt5.symbol_info(symbol)
price_info = get_current_price(symbol)
if not symbol_info or not price_info:
print("❌ Failed to get market info")
return False
result = mt5.order_send(request)
return False
except Exception as e:
print(f"❌ Trade execution error: {str(e)}")
return False
symbol_info = mt5.symbol_info(symbol)
if not symbol_info:
return price
if direction.lower() == 'buy':
return current_price['ask'] + (spread * 2)
else:
return current_price['bid'] - (spread * 2)
except Exception as e:
print(f"Price adjustment error: {str(e)}")
return price
for tf in time_frame_order:
if tf in all_data:
df = all_data[tf]
last_row = df.iloc[-1]
vol_analysis = analyze_volume(df)
if correlation_data:
prompt += "\n📊 CORRELATION ANALYSIS:\n"
for corr_symbol, data in correlation_data.items():
if 'H1' in data:
last_row = data['H1'].iloc[-1]
prompt += f"\n{corr_symbol} Analysis (H1):\n"
prompt += f"Current: {last_row['close']:.5f}\n"
prompt += f"MA20: {last_row['MA20']:.5f}\n"
prompt += f"RSI: {last_row['RSI']:.2f}\n"
prompt += f"Trend: {'Bullish' if last_row['close'] >
last_row['MA20'] else 'Bearish'}\n"
analysis_template = f"""
Based on the main symbol and correlation analysis above, provide exactly 3 high-
probability trade setups in order of confidence level:
🌐 Market Overview:
🔭 Technical: [Key technical factors across timeframes]
📅 Fundamental: [Current or upcoming market events]
🏛 Institutional: [Major price levels and order blocks]
📊 Correlations: [How DXY, USDJPY, EURUSD are affecting {symbol}]
REQUIREMENTS:
- Entry prices must be logical relative to current price {current_price:.5f}
- Each signal must have clear technical and fundamental justification
- Minimum 1:2 risk-reward ratio required
- Only provide high-probability setups (minimum 3-star confidence)
- Prioritize quality over timeframe - focus on best available setups
"""
strict_format_template = """
STRICT SIGNAL FORMAT REQUIREMENTS:
================================
1. Use exactly this structure for each signal:
Signal N:
Direction: BUY or SELL
Entry: PRICE
SL: PRICE
TP: PRICE
Confidence: ⭐⭐⭐⭐⭐
Rationale: Your analysis here
2. Rules:
- No emojis in signal parameters
- Only numerical values for prices
- Exact labels as shown above
- One line per parameter
- Clear separation between signals
Example Signal:
Signal 1:
Direction: BUY
Entry: 2015.50
SL: 2010.25
TP: 2025.75
Confidence: ⭐⭐⭐⭐
Rationale: Strong support at entry with bullish RSI divergence
"""
formatted_signals += SIGNAL_TEMPLATE.format(
num=i+1,
direction=signal_data['direction'].upper(),
entry=signal_data['entry'],
sl=signal_data['sl'],
tp=signal_data['tp'],
confidence_stars=signal_data['confidence_stars'],
rationale=signal_data['rationale'] + status
)
except Exception as e:
print(f"Error formatting analysis: {str(e)}")
print(response_text)
emoji_map = {
'Sending prompt to AI': '🤖',
'AI Response received': '📝',
'Original AI Response': '📄',
'Parsed Signal': '🎯',
'Signal parsing error': '⚠️',
'AI API Error': '🔌',
'Order request': '📝',
}
if should_print:
if data and 'Order request' not in message:
print(f"\n{message}")
elif 'Parsed Signal' in message:
print(f"\n{message}: {data['direction'].upper()} @ {data['entry']}")
else:
print(f"\n{message}")
def parse_ai_response(response_text):
signals = {}
seen_signals = set() # Track seen signals to prevent duplicates
try:
# Skip logging original AI response
cleaned_text = (response_text.replace('*', '')
.replace('[', '')
.replace(']', '')
.replace('\r', '\n')
.replace('→', '')
.replace('•', '')
.strip())
cleaned_text = re.sub(r'\s+', ' ', cleaned_text)
if signal_hash in seen_signals:
continue
seen_signals.add(signal_hash)
signal_data = {
'direction': match.group(2).lower(),
'entry': float(match.group(3)),
'sl': float(match.group(4)),
'tp': float(match.group(5)),
'confidence_stars': match.group(6),
'confidence': len(match.group(6)),
'rationale': match.group(7).strip()
}
signal_num = match.group(1)
signals[f'signal{signal_num}'] = signal_data
except Exception as e:
continue
except Exception as e:
pass
return signals
technical = format_section(
response_text.split('🔭 Technical:')[1].split('📅 Fundamental:')
[0].strip()
if '🔭 Technical:' in response_text else "",
"📊 TECHNICAL ANALYSIS"
)
fundamental = format_section(
response_text.split('📅 Fundamental:')[1].split('📊 Correlations:')
[0].strip()
if '📅 Fundamental:' in response_text else "",
"🌍 FUNDAMENTAL ANALYSIS"
)
correlation = format_section(
response_text.split('📊 Correlations:')[1].split('🚀 High-Probability
Trade Signals:')[0].strip()
if '📊 Correlations:' in response_text else "",
"🔗 CORRELATION ANALYSIS"
)
formatted_signals += SIGNAL_TEMPLATE.format(
num=i+1,
description=description,
direction=signal_data['direction'].upper(),
entry=float(signal_data['entry']),
sl=float(signal_data['sl']),
tp=float(signal_data['tp']),
confidence_stars="⭐" * signal_data['confidence'],
confidence=signal_data['confidence'],
rationale=signal_data['rationale'].strip()
)
except Exception as e:
print(f"❌ Error formatting response: {str(e)}")
return response_text
except Exception as e:
print(f"🔌 Gemini API Error (Attempt {attempt + 1}/{retries}):
{str(e)}")
if attempt < retries - 1:
print(f"⏳ Retrying in {RETRY_DELAY} seconds...")
time.sleep(RETRY_DELAY)
else:
raise Exception("❌ Failed to get AI response after maximum
retries")
current_price = get_current_price(symbol)
if not current_price:
print("❌ Failed to get current price")
return
try:
prompt = generate_analysis_prompt(symbol, all_data[symbol],
current_price['bid'], correlation_data)
response_text = get_ai_response(prompt, retries=MAX_RETRIES)
formatted_response = format_ai_response(response_text, symbol,
current_price['bid'], timeframe)
print(formatted_response)
print("\n" + "═"*60 + "\n")
signals = parse_ai_response(formatted_response)
if not signals:
print("No valid signals to process")
return
for signal_num, signal_params in signals.items():
try:
all_signals = formatted_response.split("Signal ")
signal_idx = int(signal_num[-1])
if signal_idx < len(all_signals):
signal_text = all_signals[signal_idx]
else:
signal_text = ""
except Exception as e:
print(f"Error processing signal {signal_num}: {str(e)}")
continue
print("─────────────────────────────────────────────────────────\n")
except Exception as e:
print(f"\n❌ AI Analysis Error: {str(e)}")
print("Waiting for next cycle...")
def main_loop():
print_title_bar()
# Timeframe selection UI
print("\n" + "═"*60)
print("⏰ TIME FRAME SELECTION".center(60))
print("═"*60)
print("\nAvailable Trading Timeframes:")
print("┌───────────────┬──────────────────────────────────────┐")
print("│ Timeframe │ Description │")
print("├───────────────┼──────────────────────────────────────┤")
print("│ M1 │ 1 Minute - Ultra Short-Term Trading │")
print("│ M5 │ 5 Minutes - Short-Term Trading │")
print("│ M15 │ 15 Minutes - Intraday Trading │")
print("│ M30 │ 30 Minutes - Swing Trading │")
print("│ H1 │ 1 Hour - Medium-Term Trading │")
print("│ H4 │ 4 Hours - Long-Term Trading │")
print("└───────────────┴──────────────────────────────────────┘")
if not initialize_mt5():
return
while True:
try:
analysis_cycle(symbol, timeframe)
if not hasattr(analysis_cycle, 'next_run'):
timeframe_minutes = {
'M1': 1,
'M5': 5,
'M15': 15,
'M30': 30,
'H1': 60,
'H4': 240
}[timeframe]
analysis_cycle.next_run = datetime.now() +
timedelta(minutes=timeframe_minutes)
print(f"\n⏰ Next analysis scheduled at
{analysis_cycle.next_run.strftime('%Y-%m-%d %H:%M:%S')}")
except Exception as e:
print(f"\n❌ Error in main loop: {str(e)}")
time.sleep(60)
except KeyboardInterrupt:
print("\n🛑 Trading bot stopped by user.")
break # Exit loop if Ctrl+C is pressed
except Exception as e:
print(f"\n❌ Error in main loop: {str(e)}")
time.sleep(60)
finally:
mt5.shutdown()
if __name__ == "__main__":
main_loop()