OKXTrading/tradingstrategy.py

820 lines
38 KiB
Python

import logging
import time
import threading
import pandas as pd
from technicalanalyzer import TechnicalAnalyzer
from database_manager import DatabaseManager
logger = logging.getLogger(__name__)
class TradingStrategy:
"""Trading Strategy (Comprehensive Optimized Version)"""
def __init__(self, symbol, config, api, risk_manager, deepseek):
self.symbol = symbol
self.config = config
self.api = api
self.risk_manager = risk_manager
self.deepseek = deepseek
self.technical_analyzer = TechnicalAnalyzer()
self.db = DatabaseManager() # Use default configuration
# Position status
self.base_amount = 0.0
self.entry_price = 0.0
# Dynamic stop-loss monitoring - using environment variable configured interval
self.monitor_interval = 300 # Default 5 minutes
if hasattr(risk_manager, 'monitor_interval'):
self.monitor_interval = risk_manager.monitor_interval
self.monitor_running = False
self.monitor_thread = None
# Running status control
self.strategy_running = False
# Load position status
self.load_position()
# Start dynamic stop-loss monitoring
self.start_dynamic_stop_monitor()
def load_position(self):
"""Load position status from database"""
try:
position_data = self.db.load_position(self.symbol)
if position_data:
self.base_amount = position_data['base_amount']
self.entry_price = position_data['entry_price']
logger.info(f"Loaded {self.symbol} position: currency quantity={self.base_amount:.10f}, entry price=${self.entry_price:.2f}")
else:
# If no database record, sync with exchange position
self.sync_with_exchange()
except Exception as e:
logger.error(f"Error loading position status: {e}")
self.sync_with_exchange()
def save_position(self):
"""Save position status to database"""
try:
if self.base_amount > 0:
self.db.save_position(self.symbol, self.base_amount, self.entry_price)
else:
# If position is 0, delete record
self.db.delete_position(self.symbol)
# Also delete dynamic stop record
self.db.delete_dynamic_stop(self.symbol)
except Exception as e:
logger.error(f"Error saving position status: {e}")
def sync_with_exchange(self):
"""Sync with exchange position"""
try:
logger.debug(f"Starting {self.symbol} position status sync")
# Get all currency balances
balances = self.api.get_currency_balances()
if not balances:
logger.warning("Unable to get currency balances")
return False
# Extract base currency
base_currency = self.symbol.split('-')[0]
# Update local balance
if base_currency in balances:
new_base_amount = balances[base_currency].get('amount', 0.0)
# If position quantity changes, print log
if abs(new_base_amount - self.base_amount) > 1e-10:
logger.info(f"Synced {self.symbol} latest balance: {base_currency}={new_base_amount:.10f}")
# If position decreases but entry price not updated, keep original entry price (for calculating profit/loss)
if new_base_amount < self.base_amount and self.entry_price > 0:
# Position decreased, reset entry price to conservative estimate (current price)
if new_base_amount <= 0:
self.entry_price = 0
logger.info(f"{self.symbol} position cleared, reset entry price to 0")
else:
current_price = self.api.get_current_price(self.symbol)
if current_price:
self.entry_price = current_price
logger.info(f"{self.symbol} partial sell, reset entry price to current price: ${current_price:.2f}")
else:
logger.warning(f"{self.symbol} partial sell but unable to get current price, keep original entry price")
# If from no position to having position, and no entry price record
elif self.base_amount == 0 and new_base_amount > 0 and self.entry_price == 0:
# Try to load historical entry price from database
position_data = self.db.load_position(self.symbol)
if position_data and position_data['entry_price'] > 0:
self.entry_price = position_data['entry_price']
logger.info(f"Restored entry price from database: ${self.entry_price:.2f}")
else:
# Get current price as reference (but note this is reference price)
current_price = self.api.get_current_price(self.symbol)
if current_price:
self.entry_price = current_price
logger.info(f"Set reference entry price: ${current_price:.2f} (Note: This is reference price, actual entry price may differ)")
self.base_amount = new_base_amount
else:
self.base_amount = 0.0
# Don't clear entry price, keep for historical record analysis
logger.info(f"Synced {self.symbol}: No position")
# Save position status
self.save_position()
return True
except Exception as e:
logger.error(f"Error syncing exchange position: {e}")
return False
def get_actual_position_from_exchange(self):
"""Get actual position data from exchange"""
try:
balances = self.api.get_currency_balances()
if not balances:
return 0.0
base_currency = self.symbol.split('-')[0]
return balances.get(base_currency, {}).get('amount', 0.0)
except Exception as e:
logger.error(f"Error getting exchange position: {e}")
return 0.0
def analyze_market(self):
"""Comprehensive market analysis (optimized version)"""
try:
# Get market data
df = self.api.get_market_data(self.symbol, self.config['timeframe'])
if df is None or len(df) < 20:
logger.error(f"Failed to get {self.symbol} market data")
return None
# Calculate technical indicators
technical_indicators = self.technical_analyzer.calculate_indicators(df)
if not technical_indicators:
logger.warning(f"Failed to calculate {self.symbol} technical indicators")
return None
# Weighted technical signals
weighted_signals = self.technical_analyzer.generate_weighted_signals(df)
# DeepSeek AI analysis
deepseek_analysis = self.deepseek.analyze_market(self.symbol, df, technical_indicators)
# Technical signals
technical_signals = self.technical_analyzer.generate_signals(df)
return {
'deepseek': deepseek_analysis,
'technical_signals': technical_signals,
'weighted_signals': weighted_signals,
'technical_indicators': technical_indicators,
'current_price': df['close'].iloc[-1],
'market_data': df
}
except Exception as e:
logger.error(f"{self.symbol} market analysis error: {e}")
return None
def make_enhanced_decision(self, analysis):
"""Enhanced trading decision"""
if analysis is None:
return {'action': 'HOLD', 'confidence': 'LOW', 'reason': 'Insufficient analysis data'}
deepseek_signal = analysis['deepseek']
weighted_signals = analysis['weighted_signals']
technical_indicators = analysis['technical_indicators']
market_data = analysis['market_data']
# Build decision matrix
decision_matrix = self._build_decision_matrix(deepseek_signal, weighted_signals, technical_indicators, market_data)
return self._finalize_decision(decision_matrix)
def _build_decision_matrix(self, ai_signal, weighted_signal, technical_indicators, market_data):
"""Build decision matrix"""
matrix = {
'ai_weight': 0.6, # AI analysis weight
'technical_weight': 0.2, # Traditional technical indicator weight
'reversal_weight': 0.05, # Reversal signal weight
'support_resistance_weight': 0.1, # Support resistance weight
'market_weight': 0.05 # Market state weight
}
# AI signal scoring
ai_score = self._score_ai_signal(ai_signal) if ai_signal else 0.5
# Technical signal scoring
tech_score = self._score_technical_signal(weighted_signal)
# Market state scoring
market_score = self._score_market_condition(technical_indicators)
# Reversal signal scoring
reversal_signals = TechnicalAnalyzer.detect_reversal_patterns(market_data)
reversal_score = 0.5
if reversal_signals:
# Score based on reversal signal quantity and strength
strong_signals = [s for s in reversal_signals if s['strength'] == 'STRONG']
reversal_score = 0.5 + (len(strong_signals) * 0.1) + (len(reversal_signals) * 0.05)
reversal_score = min(0.9, reversal_score)
# Support resistance scoring
sr_levels = TechnicalAnalyzer.calculate_support_resistance(market_data)
sr_score = 0.5
if sr_levels:
# Near support level adds points, near resistance level subtracts points
if sr_levels['current_vs_support'] > -2: # Very close to support
sr_score = 0.7
elif sr_levels['current_vs_resistance'] < 2: # Very close to resistance
sr_score = 0.3
# Comprehensive scoring
total_score = (
ai_score * matrix['ai_weight'] +
tech_score * matrix['technical_weight'] +
reversal_score * matrix['reversal_weight'] +
sr_score * matrix['support_resistance_weight'] +
market_score * matrix['market_weight']
)
logger.info(f"[{self.symbol}] Decision matrix details: "
f"AI score={ai_score:.3f}, technical score={tech_score:.3f}, "
f"market score={market_score:.3f}, reversal score={reversal_score:.3f}, "
f"support resistance score={sr_score:.3f}, total score={total_score:.3f}")
# Determine action and confidence
if total_score > 0.6:
action = 'BUY'
confidence = 'HIGH' if total_score > 0.75 else 'MEDIUM'
elif total_score < 0.4:
action = 'SELL'
confidence = 'HIGH' if total_score < 0.25 else 'MEDIUM'
else:
action = 'HOLD'
confidence = 'MEDIUM' if abs(total_score - 0.5) > 0.1 else 'LOW'
return {
'total_score': total_score,
'action': action,
'confidence': confidence,
'components': {
'ai_score': ai_score,
'tech_score': tech_score,
'market_score': market_score,
'reversal_score': reversal_score,
'sr_score': sr_score
}
}
def _score_ai_signal(self, ai_signal):
"""AI signal scoring"""
if not ai_signal:
logger.warning("AI signal is empty, using neutral score")
return 0.5
action = ai_signal.get('action', 'HOLD')
confidence = ai_signal.get('confidence', 'MEDIUM')
# Correction: Redesign scoring logic to ensure score has differentiation
if action == 'BUY':
base_score = 0.7 # Buy base score
elif action == 'SELL':
base_score = 0.3 # Sell base score
else: # HOLD
base_score = 0.5 # Hold neutral score
# Confidence adjustment - ensure final score is within reasonable range
confidence_adjustments = {
'HIGH': 0.2, # High confidence +0.2
'MEDIUM': 0.0, # Medium confidence unchanged
'LOW': -0.2 # Low confidence -0.2
}
adjustment = confidence_adjustments.get(confidence, 0.0)
final_score = base_score + adjustment
# Ensure within 0-1 range
final_score = max(0.1, min(0.9, final_score))
logger.debug(f"AI signal scoring: action={action}, confidence={confidence}, "
f"base={base_score}, adjustment={adjustment}, final={final_score}")
return final_score
def _score_technical_signal(self, weighted_signal):
"""Technical signal scoring"""
if not weighted_signal:
logger.warning("Technical signal is empty, using neutral score")
return 0.5
action = weighted_signal.get('action', 'HOLD')
score = weighted_signal.get('score', 0.5)
signal_count = weighted_signal.get('signal_count', 0)
# Adjust based on signal strength and quantity
if action == 'BUY':
# Buy signal: 0.5-1.0 range
if score > 0.7 and signal_count >= 3:
final_score = 0.8
elif score > 0.6:
final_score = 0.7
else:
final_score = 0.6
elif action == 'SELL':
# Sell signal: 0.0-0.5 range
if score < 0.3 and signal_count >= 3:
final_score = 0.2
elif score < 0.4:
final_score = 0.3
else:
final_score = 0.4
else: # HOLD
final_score = 0.5
logger.debug(f"Technical signal scoring: action={action}, score={score}, "
f"signal_count={signal_count}, final={final_score}")
return final_score
def _score_market_condition(self, technical_indicators):
"""Market state scoring"""
try:
# Assess market state based on multiple indicators
rsi = technical_indicators.get('rsi', 50)
if hasattr(rsi, '__len__'):
rsi = rsi.iloc[-1]
trend_strength = technical_indicators.get('trend_strength', 0)
volatility = technical_indicators.get('volatility', 0)
# RSI scoring (30-70 is neutral)
rsi_score = 0.5
if rsi < 30: # Oversold
rsi_score = 0.8
elif rsi > 70: # Overbought
rsi_score = 0.2
# Trend strength scoring
trend_score = 0.5 + (trend_strength * 0.3) # Strong trend favors buying
# Volatility scoring (medium volatility is best)
vol_score = 0.7 if 0.3 < volatility < 0.6 else 0.5
# Comprehensive scoring
return (rsi_score + trend_score + vol_score) / 3
except Exception as e:
logger.error(f"Market state scoring error: {e}")
return 0.5
def _finalize_decision(self, decision_matrix):
"""Final decision"""
action = decision_matrix['action']
confidence = decision_matrix['confidence']
total_score = decision_matrix['total_score']
reason = f"Comprehensive score: {total_score:.3f} (AI: {decision_matrix['components']['ai_score']:.3f}, "
reason += f"Technical: {decision_matrix['components']['tech_score']:.3f}, "
reason += f"Market: {decision_matrix['components']['market_score']:.3f},"
reason += f"Reversal: {decision_matrix['components']['reversal_score']:.3f},"
reason += f"Support Resistance: {decision_matrix['components']['sr_score']:.3f}),"
return {
'action': action,
'confidence': confidence,
'reason': reason,
'total_score': total_score,
'source': 'Enhanced Decision Matrix'
}
def execute_trade(self, decision, current_price):
"""Execute trade"""
if decision['action'] == 'HOLD':
logger.info(f"[{self.symbol}] Decision is HOLD, not executing trade")
return
# Sync exchange position
self.sync_with_exchange()
actual_position = self.base_amount # Use synced position
logger.info(f"[{self.symbol}] Current position: {self.base_amount:.10f}")
# Only get needed total asset information
total_assets, available_usdt, _ = self.risk_manager.calculate_total_assets()
if decision['action'] == 'BUY':
# Get market analysis for dynamic position calculation
market_analysis = self.analyze_market()
if market_analysis is None:
logger.warning(f"[{self.symbol}] Unable to get market analysis, using base position")
buy_amount = self.risk_manager.get_position_size(self.symbol, decision['confidence'], current_price)
else:
# Use dynamic position calculation
buy_amount = self.risk_manager.get_dynamic_position_size(
self.symbol, decision['confidence'], market_analysis['technical_indicators'], current_price
)
if buy_amount == 0:
logger.warning(f"[{self.symbol}] Calculated position size is 0, skipping buy")
return
# Get exchange minimum trade quantity for buy check
min_sz, _ = self.api.get_instrument_info(self.symbol)
if min_sz is None:
min_sz = self.api.get_default_min_size(self.symbol)
# Calculate base currency quantity to buy
estimated_base_amount = buy_amount / current_price if current_price > 0 else 0
logger.info(f"[{self.symbol}] Pre-buy check: estimated quantity={estimated_base_amount:.10f}, "
f"minimum trade quantity={min_sz}, available USDT=${available_usdt:.2f}, "
f"buy amount=${buy_amount:.2f}")
# Check if buy quantity meets exchange minimum requirement
if estimated_base_amount < min_sz:
logger.warning(f"[{self.symbol}] Estimated buy quantity {estimated_base_amount:.10f} less than minimum trade quantity {min_sz}, canceling buy")
return
# Check if quote currency balance is sufficient
if available_usdt < buy_amount:
logger.warning(f"[{self.symbol}] Quote currency balance insufficient: need ${buy_amount:.2f}, current ${available_usdt:.2f}")
return
logger.info(f"[{self.symbol}] Creating buy order: amount=${buy_amount:.2f}, price=${current_price:.2f}")
order_id = self.api.create_order(
symbol=self.symbol,
side='buy',
amount=buy_amount
)
if order_id:
# Wait for order completion
order_status = self.api.wait_for_order_completion(self.symbol, order_id)
if order_status is None:
logger.error(f"[{self.symbol}] Order not completed")
return
# Get actual fill price and quantity
fill_price = order_status['avgPx']
fill_amount = order_status['accFillSz']
# Update position status
self.base_amount += fill_amount
# Update average entry price (weighted average)
if self.entry_price > 0:
# If adding position, calculate weighted average price
self.entry_price = (self.entry_price * (self.base_amount - fill_amount) + fill_price * fill_amount) / self.base_amount
logger.info(f"[{self.symbol}] Position addition operation, updated average entry price: ${self.entry_price:.2f}")
else:
self.entry_price = fill_price
logger.info(f"[{self.symbol}] Buy successful: quantity={fill_amount:.10f}, price=${fill_price:.2f}")
logger.info(f"[{self.symbol}] Updated position: {self.symbol.split('-')[0]}={self.base_amount:.10f}")
# Save trade record
self.db.save_trade_record(self.symbol, 'buy', fill_amount, fill_price, order_id)
# Sync exchange position
self.sync_with_exchange()
# Set dynamic stop-loss
trailing_percent = self.risk_manager.trailing_stop_percent
self.db.set_dynamic_stop(self.symbol, fill_price, trailing_percent=trailing_percent)
logger.info(f"[{self.symbol}] Set dynamic stop-loss: initial price=${fill_price:.2f}, trailing_percent={trailing_percent:.1%}")
# Note: sync_with_exchange already saves position, no need to save again here
self.risk_manager.increment_trade_count(self.symbol)
else:
logger.error(f"[{self.symbol}] Buy order creation failed")
elif decision['action'] == 'SELL':
if actual_position <= 0:
logger.info(f"[{self.symbol}] Exchange actual position is 0, ignoring sell signal")
# Update local status
self.base_amount = 0
self.entry_price = 0
self.save_position()
return
# Prepare position information for AI analysis
position_info = self._prepare_position_info(actual_position, current_price, total_assets)
# Get market analysis data (for technical indicators)
market_analysis = self.analyze_market()
# Use DeepSeek to analyze sell proportion
try:
sell_proportion = self.deepseek.analyze_sell_proportion(
self.symbol, position_info, market_analysis, current_price
)
except Exception as e:
logger.warning(f"[{self.symbol}] DeepSeek sell proportion analysis failed: {e}, using fallback decision")
sell_proportion = self.deepseek._get_fallback_sell_proportion(position_info, decision['confidence'])
# Calculate sell quantity based on AI suggestion
sell_amount = actual_position * sell_proportion
# Ensure sell quantity doesn't exceed actual position
sell_amount = min(sell_amount, actual_position)
logger.info(f"[{self.symbol}] AI suggested sell proportion: {sell_proportion:.1%}, calculated sell quantity: {sell_amount:.10f}")
# Get exchange minimum trade quantity for sell check
min_sz, _ = self.api.get_instrument_info(self.symbol)
if min_sz is None:
min_sz = self.api.get_default_min_size(self.symbol)
# Check if sell quantity is less than exchange minimum trade quantity
if sell_amount < min_sz:
if sell_proportion < 1.0: # Partial sell
logger.info(f"[{self.symbol}] Sell quantity {sell_amount:.10f} less than minimum trade quantity {min_sz}, ignoring this partial sell")
return
else: # Full sell
logger.info(f"[{self.symbol}] Position quantity {sell_amount:.10f} less than minimum trade quantity {min_sz}, dust position, ignoring sell")
return
# Record sell decision details
logger.info(f"[{self.symbol}] Final sell decision: proportion={sell_proportion:.1%}, quantity={sell_amount:.10f}, "
f"reason={decision.get('reason', 'AI decision')}")
# Execute sell order
order_id = self.api.create_order(
symbol=self.symbol,
side='sell',
amount=sell_amount
)
if order_id:
# Wait for order completion
order_status = self.api.wait_for_order_completion(self.symbol, order_id)
if order_status is None:
logger.error(f"[{self.symbol}] Order not completed")
return
# Get actual fill price and quantity
fill_price = order_status['avgPx']
fill_amount = order_status['accFillSz']
# Calculate profit
profit = (fill_price - self.entry_price) * fill_amount
profit_percent = (fill_price / self.entry_price - 1) * 100 if self.entry_price > 0 else 0
# Sync exchange position
self.sync_with_exchange()
logger.info(f"[{self.symbol}] Sell successful: quantity={fill_amount:.10f}, price=${fill_price:.2f}")
logger.info(f"[{self.symbol}] Profit: ${profit:.2f} ({profit_percent:+.2f}%)")
logger.info(f"[{self.symbol}] Updated position: {self.symbol.split('-')[0]}={self.base_amount:.10f}")
# Save trade record
self.db.save_trade_record(self.symbol, 'sell', fill_amount, fill_price, order_id)
# If fully sold, reset status
if self.base_amount <= 0:
self.entry_price = 0
self.db.delete_dynamic_stop(self.symbol)
logger.info(f"[{self.symbol}] Position cleared, removed dynamic stop-loss")
else:
# After partial sell, update dynamic stop-loss
self.db.set_dynamic_stop(self.symbol, self.entry_price, trailing_percent=0.03, multiplier=2)
logger.info(f"[{self.symbol}] After partial sell, reset dynamic stop-loss")
# Note: sync_with_exchange already saves position, no need to save again here
self.risk_manager.increment_trade_count(self.symbol)
else:
logger.error(f"[{self.symbol}] Sell order creation failed")
def _prepare_position_info(self, actual_position, current_price, total_assets):
"""Prepare position information for AI analysis"""
try:
# Calculate position value
position_value = actual_position * current_price
# Calculate position ratio
position_ratio = position_value / total_assets if total_assets > 0 else 0
# Calculate profit ratio
profit_ratio = (current_price - self.entry_price) / self.entry_price if self.entry_price > 0 else 0
# Calculate distance to stop-loss/take-profit
stop_loss_price = self.entry_price * (1 - self.risk_manager.stop_loss) if self.entry_price > 0 else 0
take_profit_price = self.entry_price * (1 + self.risk_manager.take_profit) if self.entry_price > 0 else 0
distance_to_stop_loss = (current_price - stop_loss_price) / current_price if current_price > 0 else 0
distance_to_take_profit = (take_profit_price - current_price) / current_price if current_price > 0 else 0
# Get market volatility
market_data = self.api.get_market_data(self.symbol, '1H', limit=24)
if market_data is not None and len(market_data) > 0:
market_volatility = market_data['close'].pct_change().std() * 100
else:
market_volatility = 0
# Get trend strength
market_analysis = self.analyze_market()
trend_strength = 0
if market_analysis and 'technical_indicators' in market_analysis:
trend_strength = market_analysis['technical_indicators'].get('trend_strength', 0)
position_info = {
'base_amount': actual_position,
'entry_price': self.entry_price,
'current_price': current_price,
'position_value': position_value,
'position_ratio': position_ratio,
'profit_ratio': profit_ratio,
'distance_to_stop_loss': distance_to_stop_loss,
'distance_to_take_profit': distance_to_take_profit,
'market_volatility': market_volatility,
'trend_strength': trend_strength,
'total_assets': total_assets
}
logger.debug(f"[{self.symbol}] Position analysis information: ratio={position_ratio:.2%}, profit={profit_ratio:+.2%}, "
f"distance to stop-loss={distance_to_stop_loss:.2%}, volatility={market_volatility:.2f}%")
return position_info
except Exception as e:
logger.error(f"[{self.symbol}] Error preparing position information: {e}")
# Return basic position information
return {
'base_amount': actual_position,
'entry_price': self.entry_price,
'current_price': current_price,
'position_value': actual_position * current_price,
'position_ratio': 0,
'profit_ratio': 0,
'distance_to_stop_loss': 0,
'distance_to_take_profit': 0,
'market_volatility': 0,
'trend_strength': 0,
'total_assets': total_assets
}
def start_dynamic_stop_monitor(self):
"""Start dynamic stop-loss monitoring"""
if self.monitor_running and self.monitor_thread and self.monitor_thread.is_alive():
logger.info(f"[{self.symbol}] Dynamic stop-loss monitoring already running")
return
self.monitor_running = True
self.monitor_thread = threading.Thread(target=self._dynamic_stop_monitor_loop, name=f"StopMonitor-{self.symbol}")
self.monitor_thread.daemon = True
self.monitor_thread.start()
logger.info(f"[{self.symbol}] Dynamic stop-loss monitoring started")
def stop_dynamic_stop_monitor(self):
"""Stop dynamic stop-loss monitoring"""
self.monitor_running = False
if self.monitor_thread and self.monitor_thread.is_alive():
self.monitor_thread.join(timeout=5)
if self.monitor_thread.is_alive():
logger.warning(f"{self.symbol} dynamic stop-loss monitoring thread didn't exit normally")
else:
logger.info(f"{self.symbol} dynamic stop-loss monitoring stopped")
def _dynamic_stop_monitor_loop(self):
"""Dynamic stop-loss monitoring loop"""
while self.monitor_running:
try:
# Only monitor when there is position
if self.base_amount > 0:
self.check_dynamic_stops()
# Monitor every 5 minutes
time.sleep(self.monitor_interval)
except Exception as e:
logger.error(f"[{self.symbol}] Dynamic stop-loss monitoring error: {e}")
time.sleep(60) # Wait 1 minute before continuing when error occurs
def check_dynamic_stops(self):
"""Check dynamic stop-loss conditions"""
try:
# Get current price
current_price = self.api.get_current_price(self.symbol)
if not current_price:
logger.warning(f"[{self.symbol}] Unable to get current price, skipping stop-loss check")
return
# Get dynamic stop-loss information
stop_info = self.db.get_dynamic_stop(self.symbol)
if not stop_info:
logger.debug(f"[{self.symbol}] No dynamic stop-loss settings")
return
stop_loss_price = stop_info['current_stop_loss']
take_profit_price = stop_info['current_take_profit']
trailing_percent = stop_info['trailing_percent']
logger.debug(f"[{self.symbol}] Dynamic stop-loss check: current price=${current_price:.2f}, stop-loss=${stop_loss_price:.2f}, take-profit=${take_profit_price:.2f}")
# Check stop-loss condition
if current_price <= stop_loss_price:
logger.warning(f"⚠️ {self.symbol} triggered dynamic stop-loss! Current price=${current_price:.2f} <= stop-loss price=${stop_loss_price:.2f}")
self.execute_trade({
'action': 'SELL',
'confidence': 'HIGH',
'reason': f'Dynamic stop-loss triggered: {current_price:.2f} <= {stop_loss_price:.2f}'
}, current_price)
return
# Check take-profit condition
if current_price >= take_profit_price:
logger.info(f"🎯 {self.symbol} triggered dynamic take-profit! Current price=${current_price:.2f} >= take-profit price=${take_profit_price:.2f}")
self.execute_trade({
'action': 'SELL',
'confidence': 'HIGH',
'reason': f'Dynamic take-profit triggered: {current_price:.2f} >= {take_profit_price:.2f}'
}, current_price)
return
# Update trailing stop-loss (only moves upward) - fix logic
if stop_info and current_price > stop_info.get('highest_price', 0):
new_stop_loss = self.db.update_dynamic_stop(self.symbol, current_price, trailing_percent=trailing_percent, multiplier=2)
if new_stop_loss:
logger.info(f"[{self.symbol}] Trailing stop-loss updated: new stop-loss price=${new_stop_loss:.2f}")
except Exception as e:
logger.error(f"[{self.symbol}] Error checking dynamic stop-loss: {e}")
def run(self):
"""Run strategy (using enhanced decision)"""
self.strategy_running = True
while self.strategy_running:
try:
logger.info(f"\n=== {self.symbol} Strategy Auto Execution ===")
# Market analysis
analysis = self.analyze_market()
if analysis is None:
logger.warning("Analysis failed, waiting for next execution")
time.sleep(self.config['interval'])
continue
# Make trading decision
decision = self.make_enhanced_decision(analysis)
logger.info(f"{self.symbol} Decision: {decision['action']} (Confidence: {decision['confidence']}, Score: {decision['total_score']:.3f})")
if decision['reason']:
logger.info(f"Reason: {decision['reason']}")
# Execute trade
self.execute_trade(decision, analysis['current_price'])
# Risk management check
self.check_risk_management(analysis['current_price'])
time.sleep(self.config['interval'])
except Exception as e:
logger.error(f"Strategy execution error: {e}")
if "Network" in str(e) or "Connection" in str(e):
wait_time = min(300, self.config['interval'] * 2) # Network error wait longer
else:
wait_time = self.config['interval']
if self.strategy_running:
time.sleep(wait_time)
def stop_strategy(self):
"""Stop strategy running"""
with threading.Lock(): # Add thread lock
self.strategy_running = False
logger.info(f"{self.symbol} strategy stop signal sent")
def check_risk_management(self, current_price):
"""Check risk management (fixed stop-loss/take-profit)"""
if self.base_amount == 0:
return
# Calculate profit/loss percentage
pnl_pct = (current_price - self.entry_price) / self.entry_price if self.entry_price > 0 else 0
# Fixed stop-loss/take-profit check
if pnl_pct <= -self.risk_manager.stop_loss:
logger.warning(f"⚠️ {self.symbol} triggered fixed stop-loss ({pnl_pct:.2%})")
self.execute_trade({
'action': 'SELL',
'confidence': 'HIGH',
'reason': 'Fixed stop-loss triggered'
}, current_price)
elif pnl_pct >= self.risk_manager.take_profit:
logger.info(f"🎯 {self.symbol} triggered fixed take-profit ({pnl_pct:.2%})")
self.execute_trade({
'action': 'SELL',
'confidence': 'HIGH',
'reason': 'Fixed take-profit triggered'
}, current_price)