Compare commits
2 Commits
6d55061e86
...
23f0caea74
Author | SHA1 | Date | |
---|---|---|---|
23f0caea74 | |||
26d440f772 |
@ -40,12 +40,40 @@ class Position:
|
|||||||
order_id: str
|
order_id: str
|
||||||
unrealized_pnl: float = 0.0
|
unrealized_pnl: float = 0.0
|
||||||
|
|
||||||
def calculate_pnl(self, current_price: float) -> float:
|
def calculate_pnl(self, current_price: float, leverage: float = 1.0, include_fees: bool = True) -> float:
|
||||||
"""Calculate unrealized P&L for the position"""
|
"""Calculate unrealized P&L for the position with leverage and fees
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current_price: Current market price
|
||||||
|
leverage: Leverage multiplier (default: 1.0)
|
||||||
|
include_fees: Whether to subtract fees from PnL (default: True)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: Unrealized PnL including leverage and fees
|
||||||
|
"""
|
||||||
|
# Calculate position value
|
||||||
|
position_value = self.entry_price * self.quantity
|
||||||
|
|
||||||
|
# Calculate base PnL
|
||||||
if self.side == 'LONG':
|
if self.side == 'LONG':
|
||||||
self.unrealized_pnl = (current_price - self.entry_price) * self.quantity
|
base_pnl = (current_price - self.entry_price) * self.quantity
|
||||||
else: # SHORT
|
else: # SHORT
|
||||||
self.unrealized_pnl = (self.entry_price - current_price) * self.quantity
|
base_pnl = (self.entry_price - current_price) * self.quantity
|
||||||
|
|
||||||
|
# Apply leverage
|
||||||
|
leveraged_pnl = base_pnl * leverage
|
||||||
|
|
||||||
|
# Calculate fees (0.1% open + 0.1% close = 0.2% total)
|
||||||
|
fees = 0.0
|
||||||
|
if include_fees:
|
||||||
|
# Open fee already paid
|
||||||
|
open_fee = position_value * 0.001
|
||||||
|
# Close fee will be paid when position is closed
|
||||||
|
close_fee = (current_price * self.quantity) * 0.001
|
||||||
|
fees = open_fee + close_fee
|
||||||
|
|
||||||
|
# Final PnL after fees
|
||||||
|
self.unrealized_pnl = leveraged_pnl - fees
|
||||||
return self.unrealized_pnl
|
return self.unrealized_pnl
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -62,6 +90,10 @@ class TradeRecord:
|
|||||||
fees: float
|
fees: float
|
||||||
confidence: float
|
confidence: float
|
||||||
hold_time_seconds: float = 0.0 # Hold time in seconds
|
hold_time_seconds: float = 0.0 # Hold time in seconds
|
||||||
|
leverage: float = 1.0 # Leverage used for the trade
|
||||||
|
position_size_usd: float = 0.0 # Position size in USD
|
||||||
|
gross_pnl: float = 0.0 # PnL before fees
|
||||||
|
net_pnl: float = 0.0 # PnL after fees
|
||||||
|
|
||||||
class TradingExecutor:
|
class TradingExecutor:
|
||||||
"""Handles trade execution through multiple exchange APIs with risk management"""
|
"""Handles trade execution through multiple exchange APIs with risk management"""
|
||||||
@ -79,19 +111,22 @@ class TradingExecutor:
|
|||||||
# Set primary exchange as main interface
|
# Set primary exchange as main interface
|
||||||
self.exchange = self.primary_exchange
|
self.exchange = self.primary_exchange
|
||||||
|
|
||||||
|
# Get primary exchange name and config first
|
||||||
|
primary_name = self.exchanges_config.get('primary', 'deribit')
|
||||||
|
primary_config = self.exchanges_config.get(primary_name, {})
|
||||||
|
|
||||||
|
# Determine trading and simulation modes
|
||||||
|
trading_mode = primary_config.get('trading_mode', 'simulation')
|
||||||
|
self.trading_enabled = self.trading_config.get('enabled', True)
|
||||||
|
self.simulation_mode = trading_mode == 'simulation'
|
||||||
|
|
||||||
if not self.exchange:
|
if not self.exchange:
|
||||||
logger.error("Failed to initialize primary exchange")
|
if self.simulation_mode:
|
||||||
self.trading_enabled = False
|
logger.info("Failed to initialize primary exchange, but simulation mode is enabled - trading allowed")
|
||||||
self.simulation_mode = True
|
else:
|
||||||
|
logger.error("Failed to initialize primary exchange and not in simulation mode - trading disabled")
|
||||||
|
self.trading_enabled = False
|
||||||
else:
|
else:
|
||||||
primary_name = self.exchanges_config.get('primary', 'deribit')
|
|
||||||
primary_config = self.exchanges_config.get(primary_name, {})
|
|
||||||
|
|
||||||
# Determine trading and simulation modes
|
|
||||||
trading_mode = primary_config.get('trading_mode', 'simulation')
|
|
||||||
self.trading_enabled = self.trading_config.get('enabled', True)
|
|
||||||
self.simulation_mode = trading_mode == 'simulation'
|
|
||||||
|
|
||||||
logger.info(f"Trading Executor initialized with {primary_name} as primary exchange")
|
logger.info(f"Trading Executor initialized with {primary_name} as primary exchange")
|
||||||
logger.info(f"Trading mode: {trading_mode}, Simulation: {self.simulation_mode}")
|
logger.info(f"Trading mode: {trading_mode}, Simulation: {self.simulation_mode}")
|
||||||
|
|
||||||
@ -121,6 +156,13 @@ class TradingExecutor:
|
|||||||
# Store trading mode for compatibility
|
# Store trading mode for compatibility
|
||||||
self.trading_mode = self.primary_config.get('trading_mode', 'simulation')
|
self.trading_mode = self.primary_config.get('trading_mode', 'simulation')
|
||||||
|
|
||||||
|
# Safety feature: Auto-disable live trading after consecutive losses
|
||||||
|
self.max_consecutive_losses = 5 # Disable live trading after 5 consecutive losses
|
||||||
|
self.min_success_rate_to_reenable = 0.55 # Require 55% success rate to re-enable
|
||||||
|
self.trades_to_evaluate = 20 # Evaluate last 20 trades for success rate
|
||||||
|
self.original_trading_mode = self.trading_mode # Store original mode
|
||||||
|
self.safety_triggered = False # Track if safety feature was triggered
|
||||||
|
|
||||||
# Initialize session stats
|
# Initialize session stats
|
||||||
self.session_start_time = datetime.now()
|
self.session_start_time = datetime.now()
|
||||||
self.session_trades = 0
|
self.session_trades = 0
|
||||||
@ -130,7 +172,19 @@ class TradingExecutor:
|
|||||||
self.positions = {} # symbol -> Position object
|
self.positions = {} # symbol -> Position object
|
||||||
self.trade_records = [] # List of TradeRecord objects
|
self.trade_records = [] # List of TradeRecord objects
|
||||||
|
|
||||||
|
# Simulation balance tracking
|
||||||
|
self.simulation_balance = self.trading_config.get('simulation_account_usd', 100.0)
|
||||||
|
self.simulation_positions = {} # symbol -> position data with real entry prices
|
||||||
|
|
||||||
|
# Trading fees configuration (0.1% for both open and close)
|
||||||
|
self.trading_fees = {
|
||||||
|
'open_fee_percent': 0.001, # 0.1% fee when opening position
|
||||||
|
'close_fee_percent': 0.001, # 0.1% fee when closing position
|
||||||
|
'total_round_trip_fee': 0.002 # 0.2% total for round trip
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(f"TradingExecutor initialized - Trading: {self.trading_enabled}, Mode: {self.trading_mode}")
|
logger.info(f"TradingExecutor initialized - Trading: {self.trading_enabled}, Mode: {self.trading_mode}")
|
||||||
|
logger.info(f"Simulation balance: ${self.simulation_balance:.2f}")
|
||||||
|
|
||||||
# Legacy compatibility (deprecated)
|
# Legacy compatibility (deprecated)
|
||||||
self.dry_run = self.simulation_mode
|
self.dry_run = self.simulation_mode
|
||||||
@ -152,10 +206,13 @@ class TradingExecutor:
|
|||||||
|
|
||||||
# Connect to exchange
|
# Connect to exchange
|
||||||
if self.trading_enabled:
|
if self.trading_enabled:
|
||||||
logger.info("TRADING EXECUTOR: Attempting to connect to exchange...")
|
if self.simulation_mode:
|
||||||
if not self._connect_exchange():
|
logger.info("TRADING EXECUTOR: Simulation mode - trading enabled without exchange connection")
|
||||||
logger.error("TRADING EXECUTOR: Failed initial exchange connection. Trading will be disabled.")
|
else:
|
||||||
self.trading_enabled = False
|
logger.info("TRADING EXECUTOR: Attempting to connect to exchange...")
|
||||||
|
if not self._connect_exchange():
|
||||||
|
logger.error("TRADING EXECUTOR: Failed initial exchange connection. Trading will be disabled.")
|
||||||
|
self.trading_enabled = False
|
||||||
else:
|
else:
|
||||||
logger.info("TRADING EXECUTOR: Trading is explicitly disabled in config.")
|
logger.info("TRADING EXECUTOR: Trading is explicitly disabled in config.")
|
||||||
|
|
||||||
@ -210,6 +267,67 @@ class TradingExecutor:
|
|||||||
logger.error(f"Error calling {method_name}: {e}")
|
logger.error(f"Error calling {method_name}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_real_current_price(self, symbol: str) -> Optional[float]:
|
||||||
|
"""Get real current price from data provider - NEVER use simulated data"""
|
||||||
|
try:
|
||||||
|
# Try to get from data provider first (most reliable)
|
||||||
|
from core.data_provider import DataProvider
|
||||||
|
data_provider = DataProvider()
|
||||||
|
|
||||||
|
# Try multiple timeframes to get the most recent price
|
||||||
|
for timeframe in ['1m', '5m', '1h']:
|
||||||
|
try:
|
||||||
|
df = data_provider.get_historical_data(symbol, timeframe, limit=1, refresh=True)
|
||||||
|
if df is not None and not df.empty:
|
||||||
|
price = float(df['close'].iloc[-1])
|
||||||
|
if price > 0:
|
||||||
|
logger.debug(f"Got real price for {symbol} from {timeframe}: ${price:.2f}")
|
||||||
|
return price
|
||||||
|
except Exception as tf_error:
|
||||||
|
logger.debug(f"Failed to get {timeframe} data for {symbol}: {tf_error}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Try exchange ticker if available
|
||||||
|
if self.exchange:
|
||||||
|
try:
|
||||||
|
ticker = self.exchange.get_ticker(symbol)
|
||||||
|
if ticker and 'last' in ticker:
|
||||||
|
price = float(ticker['last'])
|
||||||
|
if price > 0:
|
||||||
|
logger.debug(f"Got real price for {symbol} from exchange: ${price:.2f}")
|
||||||
|
return price
|
||||||
|
except Exception as ex_error:
|
||||||
|
logger.debug(f"Failed to get price from exchange: {ex_error}")
|
||||||
|
|
||||||
|
# Try external API as last resort
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
if symbol == 'ETH/USDT':
|
||||||
|
response = requests.get('https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT', timeout=2)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
price = float(data['price'])
|
||||||
|
if price > 0:
|
||||||
|
logger.debug(f"Got real price for {symbol} from Binance API: ${price:.2f}")
|
||||||
|
return price
|
||||||
|
elif symbol == 'BTC/USDT':
|
||||||
|
response = requests.get('https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT', timeout=2)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
price = float(data['price'])
|
||||||
|
if price > 0:
|
||||||
|
logger.debug(f"Got real price for {symbol} from Binance API: ${price:.2f}")
|
||||||
|
return price
|
||||||
|
except Exception as api_error:
|
||||||
|
logger.debug(f"Failed to get price from external API: {api_error}")
|
||||||
|
|
||||||
|
logger.error(f"Failed to get real current price for {symbol} from all sources")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting real current price for {symbol}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _connect_exchange(self) -> bool:
|
def _connect_exchange(self) -> bool:
|
||||||
"""Connect to the primary exchange"""
|
"""Connect to the primary exchange"""
|
||||||
if not self.exchange:
|
if not self.exchange:
|
||||||
@ -250,11 +368,11 @@ class TradingExecutor:
|
|||||||
|
|
||||||
# Get current price if not provided
|
# Get current price if not provided
|
||||||
if current_price is None:
|
if current_price is None:
|
||||||
ticker = self.exchange.get_ticker(symbol)
|
# Always get real current price - never use simulated data
|
||||||
if not ticker or 'last' not in ticker:
|
current_price = self._get_real_current_price(symbol)
|
||||||
logger.error(f"Failed to get current price for {symbol} or ticker is malformed.")
|
if current_price is None:
|
||||||
|
logger.error(f"Failed to get real current price for {symbol}")
|
||||||
return False
|
return False
|
||||||
current_price = ticker['last']
|
|
||||||
|
|
||||||
# Assert that current_price is not None for type checking
|
# 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"
|
assert current_price is not None, "current_price should not be None at this point"
|
||||||
@ -504,12 +622,96 @@ class TradingExecutor:
|
|||||||
logger.error(f"Error cancelling open orders for {symbol}: {e}")
|
logger.error(f"Error cancelling open orders for {symbol}: {e}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def _can_reenable_live_trading(self) -> bool:
|
||||||
|
"""Check if trading performance has improved enough to re-enable live trading
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if performance meets criteria to re-enable live trading
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Need enough trades to evaluate
|
||||||
|
if len(self.trade_history) < self.trades_to_evaluate:
|
||||||
|
logger.debug(f"Not enough trades to evaluate for re-enabling live trading: {len(self.trade_history)}/{self.trades_to_evaluate}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get the most recent trades for evaluation
|
||||||
|
recent_trades = self.trade_history[-self.trades_to_evaluate:]
|
||||||
|
|
||||||
|
# Calculate success rate
|
||||||
|
winning_trades = sum(1 for trade in recent_trades if trade.pnl > 0.001)
|
||||||
|
success_rate = winning_trades / len(recent_trades)
|
||||||
|
|
||||||
|
# Calculate average PnL
|
||||||
|
avg_pnl = sum(trade.pnl for trade in recent_trades) / len(recent_trades)
|
||||||
|
|
||||||
|
# Calculate win/loss ratio
|
||||||
|
losing_trades = sum(1 for trade in recent_trades if trade.pnl < -0.001)
|
||||||
|
win_loss_ratio = winning_trades / max(1, losing_trades) # Avoid division by zero
|
||||||
|
|
||||||
|
logger.info(f"SAFETY FEATURE: Performance evaluation - Success rate: {success_rate:.2%}, Avg PnL: ${avg_pnl:.2f}, Win/Loss ratio: {win_loss_ratio:.2f}")
|
||||||
|
|
||||||
|
# Criteria to re-enable live trading:
|
||||||
|
# 1. Success rate must exceed minimum threshold
|
||||||
|
# 2. Average PnL must be positive
|
||||||
|
# 3. Win/loss ratio must be at least 1.0 (equal wins and losses)
|
||||||
|
if (success_rate >= self.min_success_rate_to_reenable and
|
||||||
|
avg_pnl > 0 and
|
||||||
|
win_loss_ratio >= 1.0):
|
||||||
|
logger.info(f"SAFETY FEATURE: Performance criteria met for re-enabling live trading")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.debug(f"SAFETY FEATURE: Performance criteria not yet met for re-enabling live trading")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error evaluating trading performance: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error evaluating trading performance: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def _check_safety_conditions(self, symbol: str, action: str) -> bool:
|
def _check_safety_conditions(self, symbol: str, action: str) -> bool:
|
||||||
"""Check if it's safe to execute a trade"""
|
"""Check if it's safe to execute a trade"""
|
||||||
# Check if trading is stopped
|
# Check if trading is stopped
|
||||||
if self.exchange_config.get('emergency_stop', False):
|
if self.exchange_config.get('emergency_stop', False):
|
||||||
logger.warning("Emergency stop is active - no trades allowed")
|
logger.warning("Emergency stop is active - no trades allowed")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Safety feature: Check consecutive losses and switch to simulation mode if needed
|
||||||
|
if not self.simulation_mode and self.consecutive_losses >= self.max_consecutive_losses:
|
||||||
|
logger.warning(f"SAFETY FEATURE ACTIVATED: {self.consecutive_losses} consecutive losses detected")
|
||||||
|
logger.warning(f"Switching from live trading to simulation mode for safety")
|
||||||
|
|
||||||
|
# Store original mode and switch to simulation
|
||||||
|
self.original_trading_mode = self.trading_mode
|
||||||
|
self.trading_mode = 'simulation'
|
||||||
|
self.simulation_mode = True
|
||||||
|
self.safety_triggered = True
|
||||||
|
|
||||||
|
# Log the event
|
||||||
|
logger.info(f"Trading mode changed to SIMULATION due to safety feature")
|
||||||
|
logger.info(f"Will continue to monitor performance and re-enable live trading when success rate improves")
|
||||||
|
|
||||||
|
# Continue allowing trades in simulation mode
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if we should try to re-enable live trading after safety feature was triggered
|
||||||
|
if self.simulation_mode and self.safety_triggered and self.original_trading_mode != 'simulation':
|
||||||
|
# Check if performance has improved enough to re-enable live trading
|
||||||
|
if self._can_reenable_live_trading():
|
||||||
|
logger.info(f"SAFETY FEATURE: Performance has improved, re-enabling live trading")
|
||||||
|
|
||||||
|
# Switch back to original mode
|
||||||
|
self.trading_mode = self.original_trading_mode
|
||||||
|
self.simulation_mode = (self.trading_mode == 'simulation')
|
||||||
|
self.safety_triggered = False
|
||||||
|
self.consecutive_losses = 0 # Reset consecutive losses counter
|
||||||
|
|
||||||
|
logger.info(f"Trading mode restored to {self.trading_mode}")
|
||||||
|
|
||||||
|
# Continue with the trade
|
||||||
|
return True
|
||||||
|
|
||||||
# Check symbol allowlist
|
# Check symbol allowlist
|
||||||
allowed_symbols = self.exchange_config.get('allowed_symbols', [])
|
allowed_symbols = self.exchange_config.get('allowed_symbols', [])
|
||||||
@ -961,7 +1163,22 @@ class TradingExecutor:
|
|||||||
exit_time = datetime.now()
|
exit_time = datetime.now()
|
||||||
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
|
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
|
||||||
|
|
||||||
# Create trade record
|
# Get current leverage setting
|
||||||
|
leverage = self.trading_config.get('leverage', 1.0)
|
||||||
|
|
||||||
|
# Calculate position size in USD
|
||||||
|
position_size_usd = position.quantity * position.entry_price
|
||||||
|
|
||||||
|
# Calculate gross PnL (before fees) with leverage
|
||||||
|
if position.side == 'SHORT':
|
||||||
|
gross_pnl = (position.entry_price - current_price) * position.quantity * leverage
|
||||||
|
else: # LONG
|
||||||
|
gross_pnl = (current_price - position.entry_price) * position.quantity * leverage
|
||||||
|
|
||||||
|
# Calculate net PnL (after fees)
|
||||||
|
net_pnl = gross_pnl - simulated_fees
|
||||||
|
|
||||||
|
# Create trade record with enhanced PnL calculations
|
||||||
trade_record = TradeRecord(
|
trade_record = TradeRecord(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
side='SHORT',
|
side='SHORT',
|
||||||
@ -970,10 +1187,14 @@ class TradingExecutor:
|
|||||||
exit_price=current_price,
|
exit_price=current_price,
|
||||||
entry_time=position.entry_time,
|
entry_time=position.entry_time,
|
||||||
exit_time=exit_time,
|
exit_time=exit_time,
|
||||||
pnl=pnl,
|
pnl=net_pnl, # Store net PnL as the main PnL value
|
||||||
fees=simulated_fees,
|
fees=simulated_fees,
|
||||||
confidence=confidence,
|
confidence=confidence,
|
||||||
hold_time_seconds=hold_time_seconds
|
hold_time_seconds=hold_time_seconds,
|
||||||
|
leverage=leverage,
|
||||||
|
position_size_usd=position_size_usd,
|
||||||
|
gross_pnl=gross_pnl,
|
||||||
|
net_pnl=net_pnl
|
||||||
)
|
)
|
||||||
|
|
||||||
self.trade_history.append(trade_record)
|
self.trade_history.append(trade_record)
|
||||||
@ -1033,7 +1254,22 @@ class TradingExecutor:
|
|||||||
exit_time = datetime.now()
|
exit_time = datetime.now()
|
||||||
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
|
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
|
||||||
|
|
||||||
# Create trade record
|
# Get current leverage setting
|
||||||
|
leverage = self.trading_config.get('leverage', 1.0)
|
||||||
|
|
||||||
|
# Calculate position size in USD
|
||||||
|
position_size_usd = position.quantity * position.entry_price
|
||||||
|
|
||||||
|
# Calculate gross PnL (before fees) with leverage
|
||||||
|
if position.side == 'SHORT':
|
||||||
|
gross_pnl = (position.entry_price - current_price) * position.quantity * leverage
|
||||||
|
else: # LONG
|
||||||
|
gross_pnl = (current_price - position.entry_price) * position.quantity * leverage
|
||||||
|
|
||||||
|
# Calculate net PnL (after fees)
|
||||||
|
net_pnl = gross_pnl - fees
|
||||||
|
|
||||||
|
# Create trade record with enhanced PnL calculations
|
||||||
trade_record = TradeRecord(
|
trade_record = TradeRecord(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
side='SHORT',
|
side='SHORT',
|
||||||
@ -1042,10 +1278,14 @@ class TradingExecutor:
|
|||||||
exit_price=current_price,
|
exit_price=current_price,
|
||||||
entry_time=position.entry_time,
|
entry_time=position.entry_time,
|
||||||
exit_time=exit_time,
|
exit_time=exit_time,
|
||||||
pnl=pnl - fees,
|
pnl=net_pnl, # Store net PnL as the main PnL value
|
||||||
fees=fees,
|
fees=fees,
|
||||||
confidence=confidence,
|
confidence=confidence,
|
||||||
hold_time_seconds=hold_time_seconds
|
hold_time_seconds=hold_time_seconds,
|
||||||
|
leverage=leverage,
|
||||||
|
position_size_usd=position_size_usd,
|
||||||
|
gross_pnl=gross_pnl,
|
||||||
|
net_pnl=net_pnl
|
||||||
)
|
)
|
||||||
|
|
||||||
self.trade_history.append(trade_record)
|
self.trade_history.append(trade_record)
|
||||||
@ -1243,7 +1483,7 @@ class TradingExecutor:
|
|||||||
def _get_account_balance_for_sizing(self) -> float:
|
def _get_account_balance_for_sizing(self) -> float:
|
||||||
"""Get account balance for position sizing calculations"""
|
"""Get account balance for position sizing calculations"""
|
||||||
if self.simulation_mode:
|
if self.simulation_mode:
|
||||||
return self.mexc_config.get('simulation_account_usd', 100.0)
|
return self.simulation_balance
|
||||||
else:
|
else:
|
||||||
# For live trading, get actual USDT/USDC balance
|
# For live trading, get actual USDT/USDC balance
|
||||||
try:
|
try:
|
||||||
@ -1253,7 +1493,179 @@ class TradingExecutor:
|
|||||||
return max(usdt_balance, usdc_balance)
|
return max(usdt_balance, usdc_balance)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to get live account balance: {e}, using simulation default")
|
logger.warning(f"Failed to get live account balance: {e}, using simulation default")
|
||||||
return self.mexc_config.get('simulation_account_usd', 100.0)
|
return self.simulation_balance
|
||||||
|
|
||||||
|
def _calculate_pnl_with_fees(self, entry_price: float, exit_price: float, quantity: float, side: str) -> Dict[str, float]:
|
||||||
|
"""Calculate PnL including trading fees (0.1% open + 0.1% close = 0.2% total)"""
|
||||||
|
try:
|
||||||
|
# Calculate position value
|
||||||
|
position_value = entry_price * quantity
|
||||||
|
|
||||||
|
# Calculate fees
|
||||||
|
open_fee = position_value * self.trading_fees['open_fee_percent']
|
||||||
|
close_fee = (exit_price * quantity) * self.trading_fees['close_fee_percent']
|
||||||
|
total_fees = open_fee + close_fee
|
||||||
|
|
||||||
|
# Calculate gross PnL (before fees)
|
||||||
|
if side.upper() == 'LONG':
|
||||||
|
gross_pnl = (exit_price - entry_price) * quantity
|
||||||
|
else: # SHORT
|
||||||
|
gross_pnl = (entry_price - exit_price) * quantity
|
||||||
|
|
||||||
|
# Calculate net PnL (after fees)
|
||||||
|
net_pnl = gross_pnl - total_fees
|
||||||
|
|
||||||
|
# Calculate percentage returns
|
||||||
|
gross_pnl_percent = (gross_pnl / position_value) * 100
|
||||||
|
net_pnl_percent = (net_pnl / position_value) * 100
|
||||||
|
fee_percent = (total_fees / position_value) * 100
|
||||||
|
|
||||||
|
return {
|
||||||
|
'gross_pnl': gross_pnl,
|
||||||
|
'net_pnl': net_pnl,
|
||||||
|
'total_fees': total_fees,
|
||||||
|
'open_fee': open_fee,
|
||||||
|
'close_fee': close_fee,
|
||||||
|
'gross_pnl_percent': gross_pnl_percent,
|
||||||
|
'net_pnl_percent': net_pnl_percent,
|
||||||
|
'fee_percent': fee_percent,
|
||||||
|
'position_value': position_value
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating PnL with fees: {e}")
|
||||||
|
return {
|
||||||
|
'gross_pnl': 0.0,
|
||||||
|
'net_pnl': 0.0,
|
||||||
|
'total_fees': 0.0,
|
||||||
|
'open_fee': 0.0,
|
||||||
|
'close_fee': 0.0,
|
||||||
|
'gross_pnl_percent': 0.0,
|
||||||
|
'net_pnl_percent': 0.0,
|
||||||
|
'fee_percent': 0.0,
|
||||||
|
'position_value': 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
def _calculate_pivot_points(self, symbol: str) -> Dict[str, float]:
|
||||||
|
"""Calculate pivot points for the symbol using real market data"""
|
||||||
|
try:
|
||||||
|
from core.data_provider import DataProvider
|
||||||
|
data_provider = DataProvider()
|
||||||
|
|
||||||
|
# Get daily data for pivot calculation
|
||||||
|
df = data_provider.get_historical_data(symbol, '1d', limit=2, refresh=True)
|
||||||
|
if df is None or len(df) < 2:
|
||||||
|
logger.warning(f"Insufficient data for pivot calculation for {symbol}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Use previous day's data for pivot calculation
|
||||||
|
prev_day = df.iloc[-2]
|
||||||
|
high = float(prev_day['high'])
|
||||||
|
low = float(prev_day['low'])
|
||||||
|
close = float(prev_day['close'])
|
||||||
|
|
||||||
|
# Calculate pivot point
|
||||||
|
pivot = (high + low + close) / 3
|
||||||
|
|
||||||
|
# Calculate support and resistance levels
|
||||||
|
r1 = (2 * pivot) - low
|
||||||
|
s1 = (2 * pivot) - high
|
||||||
|
r2 = pivot + (high - low)
|
||||||
|
s2 = pivot - (high - low)
|
||||||
|
r3 = high + 2 * (pivot - low)
|
||||||
|
s3 = low - 2 * (high - pivot)
|
||||||
|
|
||||||
|
pivots = {
|
||||||
|
'pivot': pivot,
|
||||||
|
'r1': r1, 'r2': r2, 'r3': r3,
|
||||||
|
's1': s1, 's2': s2, 's3': s3,
|
||||||
|
'prev_high': high,
|
||||||
|
'prev_low': low,
|
||||||
|
'prev_close': close
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(f"Pivot points for {symbol}: P={pivot:.2f}, R1={r1:.2f}, S1={s1:.2f}")
|
||||||
|
return pivots
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating pivot points for {symbol}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _get_pivot_signal_strength(self, symbol: str, current_price: float, action: str) -> float:
|
||||||
|
"""Get signal strength based on proximity to pivot points"""
|
||||||
|
try:
|
||||||
|
pivots = self._calculate_pivot_points(symbol)
|
||||||
|
if not pivots:
|
||||||
|
return 1.0 # Default strength if no pivots available
|
||||||
|
|
||||||
|
pivot = pivots['pivot']
|
||||||
|
r1, r2, r3 = pivots['r1'], pivots['r2'], pivots['r3']
|
||||||
|
s1, s2, s3 = pivots['s1'], pivots['s2'], pivots['s3']
|
||||||
|
|
||||||
|
# Calculate distance to nearest pivot levels
|
||||||
|
distances = {
|
||||||
|
'pivot': abs(current_price - pivot),
|
||||||
|
'r1': abs(current_price - r1),
|
||||||
|
'r2': abs(current_price - r2),
|
||||||
|
'r3': abs(current_price - r3),
|
||||||
|
's1': abs(current_price - s1),
|
||||||
|
's2': abs(current_price - s2),
|
||||||
|
's3': abs(current_price - s3)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find nearest level
|
||||||
|
nearest_level = min(distances.keys(), key=lambda k: distances[k])
|
||||||
|
nearest_distance = distances[nearest_level]
|
||||||
|
nearest_price = pivots[nearest_level]
|
||||||
|
|
||||||
|
# Calculate signal strength based on action and pivot context
|
||||||
|
strength = 1.0
|
||||||
|
|
||||||
|
if action == 'BUY':
|
||||||
|
# Stronger buy signals near support levels
|
||||||
|
if nearest_level in ['s1', 's2', 's3'] and current_price <= nearest_price:
|
||||||
|
strength = 1.5 # Boost buy signals at support
|
||||||
|
elif nearest_level in ['r1', 'r2', 'r3'] and current_price >= nearest_price:
|
||||||
|
strength = 0.7 # Reduce buy signals at resistance
|
||||||
|
|
||||||
|
elif action == 'SELL':
|
||||||
|
# Stronger sell signals near resistance levels
|
||||||
|
if nearest_level in ['r1', 'r2', 'r3'] and current_price >= nearest_price:
|
||||||
|
strength = 1.5 # Boost sell signals at resistance
|
||||||
|
elif nearest_level in ['s1', 's2', 's3'] and current_price <= nearest_price:
|
||||||
|
strength = 0.7 # Reduce sell signals at support
|
||||||
|
|
||||||
|
logger.debug(f"Pivot signal strength for {symbol} {action}: {strength:.2f} "
|
||||||
|
f"(near {nearest_level} at ${nearest_price:.2f}, current ${current_price:.2f})")
|
||||||
|
|
||||||
|
return strength
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating pivot signal strength: {e}")
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
def _get_current_price_from_data_provider(self, symbol: str) -> Optional[float]:
|
||||||
|
"""Get current price from data provider for most up-to-date information"""
|
||||||
|
try:
|
||||||
|
from core.data_provider import DataProvider
|
||||||
|
data_provider = DataProvider()
|
||||||
|
|
||||||
|
# Try to get real-time price first
|
||||||
|
current_price = data_provider.get_current_price(symbol)
|
||||||
|
if current_price and current_price > 0:
|
||||||
|
return float(current_price)
|
||||||
|
|
||||||
|
# Fallback to latest 1m candle
|
||||||
|
df = data_provider.get_historical_data(symbol, '1m', limit=1, refresh=True)
|
||||||
|
if df is not None and len(df) > 0:
|
||||||
|
return float(df.iloc[-1]['close'])
|
||||||
|
|
||||||
|
logger.warning(f"Could not get current price for {symbol} from data provider")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting current price from data provider for {symbol}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _check_position_size_limit(self) -> bool:
|
def _check_position_size_limit(self) -> bool:
|
||||||
"""Check if total open position value exceeds the maximum allowed percentage of balance"""
|
"""Check if total open position value exceeds the maximum allowed percentage of balance"""
|
||||||
@ -1272,8 +1684,12 @@ class TradingExecutor:
|
|||||||
for symbol, position in self.positions.items():
|
for symbol, position in self.positions.items():
|
||||||
# Get current price for the symbol
|
# Get current price for the symbol
|
||||||
try:
|
try:
|
||||||
ticker = self.exchange.get_ticker(symbol) if self.exchange else None
|
if self.exchange:
|
||||||
current_price = ticker['last'] if ticker and 'last' in ticker else position.entry_price
|
ticker = self.exchange.get_ticker(symbol)
|
||||||
|
current_price = ticker['last'] if ticker and 'last' in ticker else position.entry_price
|
||||||
|
else:
|
||||||
|
# Simulation mode - use entry price or default
|
||||||
|
current_price = position.entry_price
|
||||||
except Exception:
|
except Exception:
|
||||||
# Fallback to entry price if we can't get current price
|
# Fallback to entry price if we can't get current price
|
||||||
current_price = position.entry_price
|
current_price = position.entry_price
|
||||||
@ -1393,9 +1809,13 @@ class TradingExecutor:
|
|||||||
if not self.dry_run:
|
if not self.dry_run:
|
||||||
for symbol, position in self.positions.items():
|
for symbol, position in self.positions.items():
|
||||||
try:
|
try:
|
||||||
ticker = self.exchange.get_ticker(symbol)
|
if self.exchange:
|
||||||
if ticker:
|
ticker = self.exchange.get_ticker(symbol)
|
||||||
self._execute_sell(symbol, 1.0, ticker['last'])
|
if ticker:
|
||||||
|
self._execute_sell(symbol, 1.0, ticker['last'])
|
||||||
|
else:
|
||||||
|
# Simulation mode - use entry price for closing
|
||||||
|
self._execute_sell(symbol, 1.0, position.entry_price)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error closing position {symbol} during emergency stop: {e}")
|
logger.error(f"Error closing position {symbol} during emergency stop: {e}")
|
||||||
|
|
||||||
@ -1746,11 +2166,10 @@ class TradingExecutor:
|
|||||||
try:
|
try:
|
||||||
# Get current price
|
# Get current price
|
||||||
current_price = None
|
current_price = None
|
||||||
ticker = self.exchange.get_ticker(symbol)
|
# Always get real current price - never use simulated data
|
||||||
if ticker:
|
current_price = self._get_real_current_price(symbol)
|
||||||
current_price = ticker['last']
|
if current_price is None:
|
||||||
else:
|
logger.error(f"Failed to get real current price for {symbol}")
|
||||||
logger.error(f"Failed to get current price for {symbol}")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Calculate confidence based on manual trade (high confidence)
|
# Calculate confidence based on manual trade (high confidence)
|
||||||
@ -1881,6 +2300,88 @@ class TradingExecutor:
|
|||||||
logger.info("TRADING EXECUTOR: Test mode enabled - bypassing safety checks")
|
logger.info("TRADING EXECUTOR: Test mode enabled - bypassing safety checks")
|
||||||
else:
|
else:
|
||||||
logger.info("TRADING EXECUTOR: Test mode disabled - normal safety checks active")
|
logger.info("TRADING EXECUTOR: Test mode disabled - normal safety checks active")
|
||||||
|
|
||||||
|
def get_status(self) -> Dict[str, Any]:
|
||||||
|
"""Get trading executor status with safety feature information"""
|
||||||
|
try:
|
||||||
|
# Get account balance
|
||||||
|
if self.simulation_mode:
|
||||||
|
balance = self.simulation_balance
|
||||||
|
else:
|
||||||
|
balance = self.exchange.get_balance('USDT') if self.exchange else 0.0
|
||||||
|
|
||||||
|
# Get open positions
|
||||||
|
positions = self.get_positions()
|
||||||
|
|
||||||
|
# Calculate total fees paid
|
||||||
|
total_fees = sum(trade.fees for trade in self.trade_history)
|
||||||
|
total_volume = sum(trade.quantity * trade.exit_price for trade in self.trade_history)
|
||||||
|
|
||||||
|
# Estimate fee breakdown (since we don't track maker vs taker separately)
|
||||||
|
maker_fee_rate = self.exchange_config.get('maker_fee', 0.0002)
|
||||||
|
taker_fee_rate = self.exchange_config.get('taker_fee', 0.0006)
|
||||||
|
avg_fee_rate = (maker_fee_rate + taker_fee_rate) / 2
|
||||||
|
|
||||||
|
# Fee impact analysis
|
||||||
|
total_pnl = sum(trade.pnl for trade in self.trade_history)
|
||||||
|
gross_pnl = total_pnl + total_fees
|
||||||
|
fee_impact_percent = (total_fees / max(1, abs(gross_pnl))) * 100 if gross_pnl != 0 else 0
|
||||||
|
|
||||||
|
# Calculate success rate for recent trades
|
||||||
|
recent_trades = self.trade_history[-self.trades_to_evaluate:] if len(self.trade_history) >= self.trades_to_evaluate else self.trade_history
|
||||||
|
winning_trades = sum(1 for trade in recent_trades if trade.pnl > 0.001) if recent_trades else 0
|
||||||
|
success_rate = (winning_trades / len(recent_trades)) if recent_trades else 0
|
||||||
|
|
||||||
|
# Safety feature status
|
||||||
|
safety_status = {
|
||||||
|
'active': self.safety_triggered,
|
||||||
|
'consecutive_losses': self.consecutive_losses,
|
||||||
|
'max_consecutive_losses': self.max_consecutive_losses,
|
||||||
|
'original_mode': self.original_trading_mode if self.safety_triggered else self.trading_mode,
|
||||||
|
'success_rate': success_rate,
|
||||||
|
'min_success_rate_to_reenable': self.min_success_rate_to_reenable,
|
||||||
|
'trades_evaluated': len(recent_trades),
|
||||||
|
'trades_needed': self.trades_to_evaluate,
|
||||||
|
'can_reenable': self._can_reenable_live_trading() if self.safety_triggered else False
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'trading_enabled': self.trading_enabled,
|
||||||
|
'simulation_mode': self.simulation_mode,
|
||||||
|
'trading_mode': self.trading_mode,
|
||||||
|
'balance': balance,
|
||||||
|
'positions': len(positions),
|
||||||
|
'daily_trades': self.daily_trades,
|
||||||
|
'daily_pnl': self.daily_pnl,
|
||||||
|
'daily_loss': self.daily_loss,
|
||||||
|
'consecutive_losses': self.consecutive_losses,
|
||||||
|
'total_trades': len(self.trade_history),
|
||||||
|
'safety_feature': safety_status,
|
||||||
|
'pnl': {
|
||||||
|
'total': total_pnl,
|
||||||
|
'gross': gross_pnl,
|
||||||
|
'fees': total_fees,
|
||||||
|
'fee_impact_percent': fee_impact_percent,
|
||||||
|
'pnl_after_fees': total_pnl,
|
||||||
|
'pnl_before_fees': gross_pnl,
|
||||||
|
'avg_fee_per_trade': total_fees / max(1, len(self.trade_history))
|
||||||
|
},
|
||||||
|
'fee_efficiency': {
|
||||||
|
'total_volume': total_volume,
|
||||||
|
'total_fees': total_fees,
|
||||||
|
'effective_fee_rate': (total_fees / max(0.01, total_volume)) if total_volume > 0 else 0,
|
||||||
|
'expected_fee_rate': avg_fee_rate,
|
||||||
|
'fee_efficiency': (avg_fee_rate / ((total_fees / max(0.01, total_volume)) if total_volume > 0 else 1)) if avg_fee_rate > 0 else 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting trading executor status: {e}")
|
||||||
|
return {
|
||||||
|
'trading_enabled': self.trading_enabled,
|
||||||
|
'simulation_mode': self.simulation_mode,
|
||||||
|
'trading_mode': self.trading_mode,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
def sync_position_with_mexc(self, symbol: str, desired_state: str) -> bool:
|
def sync_position_with_mexc(self, symbol: str, desired_state: str) -> bool:
|
||||||
"""Synchronize dashboard position state with actual MEXC account positions
|
"""Synchronize dashboard position state with actual MEXC account positions
|
||||||
@ -2015,9 +2516,13 @@ class TradingExecutor:
|
|||||||
def _get_current_price_for_sync(self, symbol: str) -> Optional[float]:
|
def _get_current_price_for_sync(self, symbol: str) -> Optional[float]:
|
||||||
"""Get current price for position synchronization"""
|
"""Get current price for position synchronization"""
|
||||||
try:
|
try:
|
||||||
ticker = self.exchange.get_ticker(symbol)
|
if self.exchange:
|
||||||
if ticker and 'last' in ticker:
|
ticker = self.exchange.get_ticker(symbol)
|
||||||
return float(ticker['last'])
|
if ticker and 'last' in ticker:
|
||||||
|
return float(ticker['last'])
|
||||||
|
else:
|
||||||
|
# Get real current price - never use simulated data
|
||||||
|
return self._get_real_current_price(symbol)
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting current price for sync: {e}")
|
logger.error(f"Error getting current price for sync: {e}")
|
||||||
|
@ -91,33 +91,79 @@ class RewardCalculator:
|
|||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
def calculate_enhanced_reward(self, action, price_change, position_held_time=0, volatility=None, is_profitable=False, confidence=0.0, predicted_change=0.0, actual_change=0.0, current_pnl=0.0, symbol='UNKNOWN'):
|
def calculate_enhanced_reward(self, action, price_change, position_held_time=0, volatility=None, is_profitable=False, confidence=0.0, predicted_change=0.0, actual_change=0.0, current_pnl=0.0, symbol='UNKNOWN'):
|
||||||
"""Calculate enhanced reward for trading actions"""
|
"""Calculate enhanced reward for trading actions with shifted neutral point
|
||||||
|
|
||||||
|
Neutral reward is shifted to require profits that exceed double the fees,
|
||||||
|
which penalizes small profit trades and encourages holding for larger moves.
|
||||||
|
Current PnL is given more weight in the decision-making process.
|
||||||
|
"""
|
||||||
fee = self.base_fee_rate
|
fee = self.base_fee_rate
|
||||||
|
double_fee = fee * 4 # Double the fees (2x open + 2x close = 4x base fee)
|
||||||
frequency_penalty = self._calculate_frequency_penalty()
|
frequency_penalty = self._calculate_frequency_penalty()
|
||||||
|
|
||||||
if action == 0: # Buy
|
if action == 0: # Buy
|
||||||
|
# Penalize buying more when already in profit
|
||||||
reward = -fee - frequency_penalty
|
reward = -fee - frequency_penalty
|
||||||
|
if current_pnl > 0:
|
||||||
|
# Reduce incentive to close profitable positions
|
||||||
|
reward -= current_pnl * 0.2
|
||||||
elif action == 1: # Sell
|
elif action == 1: # Sell
|
||||||
profit_pct = price_change
|
profit_pct = price_change
|
||||||
net_profit = profit_pct - (fee * 2)
|
|
||||||
reward = net_profit * self.reward_scaling
|
# Shift neutral point - require profit > double fees to be considered positive
|
||||||
|
net_profit = profit_pct - double_fee
|
||||||
|
|
||||||
|
# Scale reward based on profit size
|
||||||
|
if net_profit > 0:
|
||||||
|
# Exponential reward for larger profits
|
||||||
|
reward = (net_profit ** 1.5) * self.reward_scaling
|
||||||
|
else:
|
||||||
|
# Linear penalty for losses
|
||||||
|
reward = net_profit * self.reward_scaling
|
||||||
|
|
||||||
reward -= frequency_penalty
|
reward -= frequency_penalty
|
||||||
self.record_pnl(net_profit)
|
self.record_pnl(net_profit)
|
||||||
|
|
||||||
|
# Add extra penalty for very small profits (less than 3x fees)
|
||||||
|
if 0 < profit_pct < (fee * 6):
|
||||||
|
reward -= 0.5 # Discourage tiny profit-taking
|
||||||
else: # Hold
|
else: # Hold
|
||||||
if is_profitable:
|
if is_profitable:
|
||||||
reward = self._calculate_holding_reward(position_held_time, price_change)
|
# Increase reward for holding profitable positions
|
||||||
|
profit_factor = min(5.0, current_pnl * 20) # Cap at 5x
|
||||||
|
reward = self._calculate_holding_reward(position_held_time, price_change) * (1.0 + profit_factor)
|
||||||
|
|
||||||
|
# Add bonus for holding through volatility when profitable
|
||||||
|
if volatility is not None and volatility > 0.001:
|
||||||
|
reward += 0.1 * volatility * 100
|
||||||
else:
|
else:
|
||||||
reward = -0.0001
|
# Small penalty for holding losing positions
|
||||||
|
loss_factor = min(1.0, abs(current_pnl) * 10)
|
||||||
|
reward = -0.0001 * (1.0 + loss_factor)
|
||||||
|
|
||||||
|
# But reduce penalty for very recent positions (give them time)
|
||||||
|
if position_held_time < 30: # Less than 30 seconds
|
||||||
|
reward *= 0.5
|
||||||
|
|
||||||
|
# Prediction accuracy reward component
|
||||||
if action in [0, 1] and predicted_change != 0:
|
if action in [0, 1] and predicted_change != 0:
|
||||||
if (action == 0 and actual_change > 0) or (action == 1 and actual_change < 0):
|
if (action == 0 and actual_change > 0) or (action == 1 and actual_change < 0):
|
||||||
reward += abs(actual_change) * 5.0
|
reward += abs(actual_change) * 5.0
|
||||||
else:
|
else:
|
||||||
reward -= abs(predicted_change) * 2.0
|
reward -= abs(predicted_change) * 2.0
|
||||||
reward += current_pnl * 0.1
|
|
||||||
|
# Increase weight of current PnL in decision making (3x more than before)
|
||||||
|
reward += current_pnl * 0.3
|
||||||
|
|
||||||
|
# Volatility penalty
|
||||||
if volatility is not None:
|
if volatility is not None:
|
||||||
reward -= abs(volatility) * 100
|
reward -= abs(volatility) * 100
|
||||||
|
|
||||||
|
# Risk adjustment
|
||||||
if self.risk_aversion > 0 and len(self.returns) > 1:
|
if self.risk_aversion > 0 and len(self.returns) > 1:
|
||||||
returns_std = np.std(self.returns)
|
returns_std = np.std(self.returns)
|
||||||
reward -= returns_std * self.risk_aversion
|
reward -= returns_std * self.risk_aversion
|
||||||
|
|
||||||
self.record_trade(action)
|
self.record_trade(action)
|
||||||
return reward
|
return reward
|
||||||
|
|
||||||
|
@ -186,14 +186,24 @@ class DashboardComponentManager:
|
|||||||
pnl_class = "text-success" if pnl >= 0 else "text-danger"
|
pnl_class = "text-success" if pnl >= 0 else "text-danger"
|
||||||
side_class = "text-success" if side == "BUY" else "text-danger"
|
side_class = "text-success" if side == "BUY" else "text-danger"
|
||||||
|
|
||||||
|
# Calculate position size in USD
|
||||||
|
position_size_usd = size * entry_price
|
||||||
|
|
||||||
|
# Get leverage from trade or use default
|
||||||
|
leverage = trade.get('leverage', 1.0) if not hasattr(trade, 'entry_time') else getattr(trade, 'leverage', 1.0)
|
||||||
|
|
||||||
|
# Calculate leveraged PnL (already included in pnl value, but ensure it's displayed correctly)
|
||||||
|
# Ensure fees are subtracted from PnL for accurate profitability
|
||||||
|
net_pnl = pnl - fees
|
||||||
|
|
||||||
row = html.Tr([
|
row = html.Tr([
|
||||||
html.Td(time_str, className="small"),
|
html.Td(time_str, className="small"),
|
||||||
html.Td(side, className=f"small {side_class}"),
|
html.Td(side, className=f"small {side_class}"),
|
||||||
html.Td(f"{size:.3f}", className="small"),
|
html.Td(f"${position_size_usd:.2f}", className="small"), # Show size in USD
|
||||||
html.Td(f"${entry_price:.2f}", className="small"),
|
html.Td(f"${entry_price:.2f}", className="small"),
|
||||||
html.Td(f"${exit_price:.2f}", className="small"),
|
html.Td(f"${exit_price:.2f}", className="small"),
|
||||||
html.Td(f"{hold_time_seconds:.0f}", className="small text-info"),
|
html.Td(f"{hold_time_seconds:.0f}", className="small text-info"),
|
||||||
html.Td(f"${pnl:.2f}", className=f"small {pnl_class}"),
|
html.Td(f"${net_pnl:.2f}", className=f"small {pnl_class}"), # Show net PnL after fees
|
||||||
html.Td(f"${fees:.3f}", className="small text-muted")
|
html.Td(f"${fees:.3f}", className="small text-muted")
|
||||||
])
|
])
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
Reference in New Issue
Block a user