execution and training fixes

This commit is contained in:
Dobromir Popov
2025-07-04 20:45:39 +03:00
parent 0c4c682498
commit ed42e7c238
10 changed files with 879 additions and 79 deletions

View File

@ -58,6 +58,7 @@ class TradeRecord:
pnl: float
fees: float
confidence: float
hold_time_seconds: float = 0.0 # Hold time in seconds
class TradingExecutor:
"""Handles trade execution through MEXC API with risk management"""
@ -206,9 +207,9 @@ class TradingExecutor:
# Assert that current_price is not None for type checking
assert current_price is not None, "current_price should not be None at this point"
# --- Balance check before executing trade ---
# Only perform balance check for BUY actions or SHORT (initial sell) actions
if action == 'BUY' or (action == 'SELL' and symbol not in self.positions) or (action == 'SHORT'):
# --- Balance check before executing trade (skip in simulation mode) ---
# Only perform balance check for live trading, not simulation
if not self.simulation_mode and (action == 'BUY' or (action == 'SELL' and symbol not in self.positions) or (action == 'SHORT')):
# Determine the quote asset (e.g., USDT, USDC) from the symbol
if '/' in symbol:
quote_asset = symbol.split('/')[1].upper() # Assuming symbol is like ETH/USDT
@ -244,6 +245,8 @@ class TradingExecutor:
logger.warning(f"Trade blocked for {symbol} {action}: Insufficient {quote_asset} balance. "
f"Required: ${required_capital:.2f}, Available: ${available_balance:.2f}")
return False
elif self.simulation_mode:
logger.debug(f"SIMULATION MODE: Skipping balance check for {symbol} {action} - allowing trade for model training")
# --- End Balance check ---
with self.lock:
@ -318,10 +321,15 @@ class TradingExecutor:
quantity = position_value / current_price
logger.info(f"Executing BUY: {quantity:.6f} {symbol} at ${current_price:.2f} "
f"(value: ${position_value:.2f}, confidence: {confidence:.2f})")
f"(value: ${position_value:.2f}, confidence: {confidence:.2f}) "
f"[{'SIMULATION' if self.simulation_mode else 'LIVE'}]")
if self.simulation_mode:
logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Trade logged but not executed")
# Calculate simulated fees in simulation mode
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
simulated_fees = quantity * current_price * taker_fee_rate
# Create mock position for tracking
self.positions[symbol] = Position(
symbol=symbol,
@ -365,6 +373,10 @@ class TradingExecutor:
)
if order:
# Calculate simulated fees in simulation mode
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
simulated_fees = quantity * current_price * taker_fee_rate
# Create position record
self.positions[symbol] = Position(
symbol=symbol,
@ -398,13 +410,19 @@ class TradingExecutor:
position = self.positions[symbol]
logger.info(f"Executing SELL: {position.quantity:.6f} {symbol} at ${current_price:.2f} "
f"(confidence: {confidence:.2f})")
f"(confidence: {confidence:.2f}) [{'SIMULATION' if self.simulation_mode else 'LIVE'}]")
if self.simulation_mode:
logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Trade logged but not executed")
# Calculate P&L
# Calculate P&L and hold time
pnl = position.calculate_pnl(current_price)
exit_time = datetime.now()
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
# Calculate simulated fees in simulation mode
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
simulated_fees = position.quantity * current_price * taker_fee_rate
# Create trade record
trade_record = TradeRecord(
symbol=symbol,
@ -413,10 +431,11 @@ class TradingExecutor:
entry_price=position.entry_price,
exit_price=current_price,
entry_time=position.entry_time,
exit_time=datetime.now(),
exit_time=exit_time,
pnl=pnl,
fees=0.0,
confidence=confidence
fees=simulated_fees,
confidence=confidence,
hold_time_seconds=hold_time_seconds
)
self.trade_history.append(trade_record)
@ -460,9 +479,15 @@ class TradingExecutor:
)
if order:
# Calculate P&L
# Calculate simulated fees in simulation mode
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
simulated_fees = position.quantity * current_price * taker_fee_rate
# Calculate P&L, fees, and hold time
pnl = position.calculate_pnl(current_price)
fees = self._calculate_trading_fee(order, symbol, position.quantity, current_price)
fees = simulated_fees
exit_time = datetime.now()
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
# Create trade record
trade_record = TradeRecord(
@ -472,10 +497,11 @@ class TradingExecutor:
entry_price=position.entry_price,
exit_price=current_price,
entry_time=position.entry_time,
exit_time=datetime.now(),
exit_time=exit_time,
pnl=pnl - fees,
fees=fees,
confidence=confidence
confidence=confidence,
hold_time_seconds=hold_time_seconds
)
self.trade_history.append(trade_record)
@ -509,10 +535,15 @@ class TradingExecutor:
quantity = position_value / current_price
logger.info(f"Executing SHORT: {quantity:.6f} {symbol} at ${current_price:.2f} "
f"(value: ${position_value:.2f}, confidence: {confidence:.2f})")
f"(value: ${position_value:.2f}, confidence: {confidence:.2f}) "
f"[{'SIMULATION' if self.simulation_mode else 'LIVE'}]")
if self.simulation_mode:
logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Short position logged but not executed")
# Calculate simulated fees in simulation mode
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
simulated_fees = quantity * current_price * taker_fee_rate
# Create mock short position for tracking
self.positions[symbol] = Position(
symbol=symbol,
@ -556,6 +587,10 @@ class TradingExecutor:
)
if order:
# Calculate simulated fees in simulation mode
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
simulated_fees = quantity * current_price * taker_fee_rate
# Create short position record
self.positions[symbol] = Position(
symbol=symbol,
@ -595,8 +630,14 @@ class TradingExecutor:
if self.simulation_mode:
logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Short close logged but not executed")
# Calculate P&L for short position
# Calculate simulated fees in simulation mode
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
simulated_fees = position.quantity * current_price * taker_fee_rate
# Calculate P&L for short position and hold time
pnl = position.calculate_pnl(current_price)
exit_time = datetime.now()
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
# Create trade record
trade_record = TradeRecord(
@ -606,10 +647,11 @@ class TradingExecutor:
entry_price=position.entry_price,
exit_price=current_price,
entry_time=position.entry_time,
exit_time=datetime.now(),
exit_time=exit_time,
pnl=pnl,
fees=0.0,
confidence=confidence
fees=simulated_fees,
confidence=confidence,
hold_time_seconds=hold_time_seconds
)
self.trade_history.append(trade_record)
@ -653,9 +695,15 @@ class TradingExecutor:
)
if order:
# Calculate P&L
# Calculate simulated fees in simulation mode
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
simulated_fees = position.quantity * current_price * taker_fee_rate
# Calculate P&L, fees, and hold time
pnl = position.calculate_pnl(current_price)
fees = self._calculate_trading_fee(order, symbol, position.quantity, current_price)
fees = simulated_fees
exit_time = datetime.now()
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
# Create trade record
trade_record = TradeRecord(
@ -665,10 +713,11 @@ class TradingExecutor:
entry_price=position.entry_price,
exit_price=current_price,
entry_time=position.entry_time,
exit_time=datetime.now(),
exit_time=exit_time,
pnl=pnl - fees,
fees=fees,
confidence=confidence
confidence=confidence,
hold_time_seconds=hold_time_seconds
)
self.trade_history.append(trade_record)
@ -691,15 +740,44 @@ class TradingExecutor:
return False
def _calculate_position_size(self, confidence: float, current_price: float) -> float:
"""Calculate position size based on configuration and confidence"""
max_value = self.mexc_config.get('max_position_value_usd', 1.0)
min_value = self.mexc_config.get('min_position_value_usd', 0.1)
"""Calculate position size based on percentage of account balance, confidence, and leverage"""
# Get account balance (simulation or real)
account_balance = self._get_account_balance_for_sizing()
# Get position sizing percentages
max_percent = self.mexc_config.get('max_position_percent', 20.0) / 100.0
min_percent = self.mexc_config.get('min_position_percent', 2.0) / 100.0
base_percent = self.mexc_config.get('base_position_percent', 5.0) / 100.0
leverage = self.mexc_config.get('leverage', 50.0)
# Scale position size by confidence
base_value = max_value * confidence
position_value = max(min_value, min(base_value, max_value))
position_percent = min(max_percent, max(min_percent, base_percent * confidence))
position_value = account_balance * position_percent
return position_value
# Apply leverage to get effective position size
leveraged_position_value = position_value * leverage
logger.debug(f"Position calculation: account=${account_balance:.2f}, "
f"percent={position_percent*100:.1f}%, base=${position_value:.2f}, "
f"leverage={leverage}x, effective=${leveraged_position_value:.2f}, "
f"confidence={confidence:.2f}")
return leveraged_position_value
def _get_account_balance_for_sizing(self) -> float:
"""Get account balance for position sizing calculations"""
if self.simulation_mode:
return self.mexc_config.get('simulation_account_usd', 100.0)
else:
# For live trading, get actual USDT/USDC balance
try:
balances = self.get_account_balance()
usdt_balance = balances.get('USDT', {}).get('total', 0)
usdc_balance = balances.get('USDC', {}).get('total', 0)
return max(usdt_balance, usdc_balance)
except Exception as e:
logger.warning(f"Failed to get live account balance: {e}, using simulation default")
return self.mexc_config.get('simulation_account_usd', 100.0)
def update_positions(self, symbol: str, current_price: float):
"""Update position P&L with current market price"""
@ -720,15 +798,16 @@ class TradingExecutor:
total_pnl = sum(trade.pnl for trade in self.trade_history)
total_fees = sum(trade.fees for trade in self.trade_history)
gross_pnl = total_pnl + total_fees # P&L before fees
winning_trades = len([t for t in self.trade_history if t.pnl > 0])
losing_trades = len([t for t in self.trade_history if t.pnl < 0])
winning_trades = len([t for t in self.trade_history if t.pnl > 0.001]) # Avoid rounding issues
losing_trades = len([t for t in self.trade_history if t.pnl < -0.001]) # Avoid rounding issues
total_trades = len(self.trade_history)
breakeven_trades = total_trades - winning_trades - losing_trades
# Calculate average trade values
avg_trade_pnl = total_pnl / max(1, total_trades)
avg_trade_fee = total_fees / max(1, total_trades)
avg_winning_trade = sum(t.pnl for t in self.trade_history if t.pnl > 0) / max(1, winning_trades)
avg_losing_trade = sum(t.pnl for t in self.trade_history if t.pnl < 0) / max(1, losing_trades)
avg_winning_trade = sum(t.pnl for t in self.trade_history if t.pnl > 0.001) / max(1, winning_trades)
avg_losing_trade = sum(t.pnl for t in self.trade_history if t.pnl < -0.001) / max(1, losing_trades)
# Enhanced fee analysis from config
fee_structure = self.mexc_config.get('trading_fees', {})
@ -749,6 +828,7 @@ class TradingExecutor:
'total_fees': total_fees,
'winning_trades': winning_trades,
'losing_trades': losing_trades,
'breakeven_trades': breakeven_trades,
'total_trades': total_trades,
'win_rate': winning_trades / max(1, total_trades),
'avg_trade_pnl': avg_trade_pnl,
@ -1147,7 +1227,8 @@ class TradingExecutor:
'exit_time': trade.exit_time,
'pnl': trade.pnl,
'fees': trade.fees,
'confidence': trade.confidence
'confidence': trade.confidence,
'hold_time_seconds': trade.hold_time_seconds
}
trades.append(trade_dict)
return trades
@ -1185,4 +1266,59 @@ class TradingExecutor:
return None
except Exception as e:
logger.error(f"Error getting current position: {e}")
return None
return None
def get_leverage(self) -> float:
"""Get current leverage setting"""
return self.mexc_config.get('leverage', 50.0)
def set_leverage(self, leverage: float) -> bool:
"""Set leverage (for UI control)
Args:
leverage: New leverage value
Returns:
bool: True if successful
"""
try:
# Update in-memory config
self.mexc_config['leverage'] = leverage
logger.info(f"TRADING EXECUTOR: Leverage updated to {leverage}x")
return True
except Exception as e:
logger.error(f"Error setting leverage: {e}")
return False
def get_account_info(self) -> Dict[str, Any]:
"""Get account information for UI display"""
try:
account_balance = self._get_account_balance_for_sizing()
leverage = self.get_leverage()
return {
'account_balance': account_balance,
'leverage': leverage,
'trading_mode': self.trading_mode,
'simulation_mode': self.simulation_mode,
'trading_enabled': self.trading_enabled,
'position_sizing': {
'base_percent': self.mexc_config.get('base_position_percent', 5.0),
'max_percent': self.mexc_config.get('max_position_percent', 20.0),
'min_percent': self.mexc_config.get('min_position_percent', 2.0)
}
}
except Exception as e:
logger.error(f"Error getting account info: {e}")
return {
'account_balance': 100.0,
'leverage': 50.0,
'trading_mode': 'simulation',
'simulation_mode': True,
'trading_enabled': False,
'position_sizing': {
'base_percent': 5.0,
'max_percent': 20.0,
'min_percent': 2.0
}
}