sync fees from API. usdc. trade works
This commit is contained in:
@ -19,6 +19,7 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'NN'))
|
||||
|
||||
from NN.exchanges import MEXCInterface
|
||||
from .config import get_config
|
||||
from .config_sync import ConfigSynchronizer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -109,6 +110,35 @@ class TradingExecutor:
|
||||
# Connect to exchange
|
||||
if self.trading_enabled:
|
||||
self._connect_exchange()
|
||||
|
||||
logger.info(f"Trading Executor initialized - Mode: {self.trading_mode}, Enabled: {self.trading_enabled}")
|
||||
|
||||
# Initialize config synchronizer for automatic fee updates
|
||||
self.config_synchronizer = ConfigSynchronizer(
|
||||
config_path=config_path,
|
||||
mexc_interface=self.exchange if self.trading_enabled else None
|
||||
)
|
||||
|
||||
# Perform initial fee sync on startup if trading is enabled
|
||||
if self.trading_enabled and self.exchange:
|
||||
try:
|
||||
logger.info("TRADING EXECUTOR: Performing initial fee synchronization with MEXC API")
|
||||
sync_result = self.config_synchronizer.sync_trading_fees(force=True)
|
||||
if sync_result.get('status') == 'success':
|
||||
logger.info("TRADING EXECUTOR: Fee synchronization completed successfully")
|
||||
if sync_result.get('changes_made'):
|
||||
logger.info(f"TRADING EXECUTOR: Fee changes applied: {list(sync_result['changes'].keys())}")
|
||||
# Reload config to get updated fees
|
||||
self.config = get_config(config_path)
|
||||
self.mexc_config = self.config.get('mexc_trading', {})
|
||||
elif sync_result.get('status') == 'warning':
|
||||
logger.warning("TRADING EXECUTOR: Fee sync completed with warnings")
|
||||
else:
|
||||
logger.warning(f"TRADING EXECUTOR: Fee sync failed: {sync_result.get('status')}")
|
||||
except Exception as e:
|
||||
logger.warning(f"TRADING EXECUTOR: Initial fee sync failed: {e}")
|
||||
|
||||
logger.info(f"Trading Executor initialized - Mode: {self.trading_mode}, Enabled: {self.trading_enabled}")
|
||||
|
||||
def _connect_exchange(self) -> bool:
|
||||
"""Connect to the MEXC exchange"""
|
||||
@ -242,12 +272,22 @@ class TradingExecutor:
|
||||
return True
|
||||
|
||||
try:
|
||||
# Place market buy order
|
||||
# Get order type from config
|
||||
order_type = self.mexc_config.get('order_type', 'market').lower()
|
||||
|
||||
# For limit orders, set price slightly above market for immediate execution
|
||||
limit_price = None
|
||||
if order_type == 'limit':
|
||||
# Set buy price slightly above market to ensure immediate execution
|
||||
limit_price = current_price * 1.001 # 0.1% above market
|
||||
|
||||
# Place buy order
|
||||
order = self.exchange.place_order(
|
||||
symbol=symbol,
|
||||
side='buy',
|
||||
order_type='market',
|
||||
quantity=quantity
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
price=limit_price
|
||||
)
|
||||
|
||||
if order:
|
||||
@ -317,18 +357,28 @@ class TradingExecutor:
|
||||
return True
|
||||
|
||||
try:
|
||||
# Place market sell order
|
||||
# Get order type from config
|
||||
order_type = self.mexc_config.get('order_type', 'market').lower()
|
||||
|
||||
# For limit orders, set price slightly below market for immediate execution
|
||||
limit_price = None
|
||||
if order_type == 'limit':
|
||||
# Set sell price slightly below market to ensure immediate execution
|
||||
limit_price = current_price * 0.999 # 0.1% below market
|
||||
|
||||
# Place sell order
|
||||
order = self.exchange.place_order(
|
||||
symbol=symbol,
|
||||
side='sell',
|
||||
order_type='market',
|
||||
quantity=position.quantity
|
||||
order_type=order_type,
|
||||
quantity=position.quantity,
|
||||
price=limit_price
|
||||
)
|
||||
|
||||
if order:
|
||||
# Calculate P&L
|
||||
pnl = position.calculate_pnl(current_price)
|
||||
fees = current_price * position.quantity * self.mexc_config.get('trading_fee', 0.0002)
|
||||
fees = self._calculate_trading_fee(order, symbol, position.quantity, current_price)
|
||||
|
||||
# Create trade record
|
||||
trade_record = TradeRecord(
|
||||
@ -389,19 +439,58 @@ class TradingExecutor:
|
||||
return self.trade_history.copy()
|
||||
|
||||
def get_daily_stats(self) -> Dict[str, Any]:
|
||||
"""Get daily trading statistics"""
|
||||
"""Get daily trading statistics with enhanced fee analysis"""
|
||||
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])
|
||||
total_trades = len(self.trade_history)
|
||||
|
||||
# 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)
|
||||
|
||||
# Enhanced fee analysis from config
|
||||
fee_structure = self.mexc_config.get('trading_fees', {})
|
||||
maker_fee_rate = fee_structure.get('maker', 0.0000)
|
||||
taker_fee_rate = fee_structure.get('taker', 0.0005)
|
||||
default_fee_rate = fee_structure.get('default', 0.0005)
|
||||
|
||||
# Calculate fee efficiency
|
||||
total_volume = sum(trade.quantity * trade.exit_price for trade in self.trade_history)
|
||||
effective_fee_rate = (total_fees / max(0.01, total_volume)) if total_volume > 0 else 0
|
||||
fee_impact_on_pnl = (total_fees / max(0.01, abs(gross_pnl))) * 100 if gross_pnl != 0 else 0
|
||||
|
||||
return {
|
||||
'daily_trades': self.daily_trades,
|
||||
'daily_loss': self.daily_loss,
|
||||
'total_pnl': total_pnl,
|
||||
'gross_pnl': gross_pnl,
|
||||
'total_fees': total_fees,
|
||||
'winning_trades': winning_trades,
|
||||
'losing_trades': losing_trades,
|
||||
'win_rate': winning_trades / max(1, len(self.trade_history)),
|
||||
'positions_count': len(self.positions)
|
||||
'total_trades': total_trades,
|
||||
'win_rate': winning_trades / max(1, total_trades),
|
||||
'avg_trade_pnl': avg_trade_pnl,
|
||||
'avg_trade_fee': avg_trade_fee,
|
||||
'avg_winning_trade': avg_winning_trade,
|
||||
'avg_losing_trade': avg_losing_trade,
|
||||
'positions_count': len(self.positions),
|
||||
'fee_rates': {
|
||||
'maker': f"{maker_fee_rate*100:.3f}%",
|
||||
'taker': f"{taker_fee_rate*100:.3f}%",
|
||||
'default': f"{default_fee_rate*100:.3f}%",
|
||||
'effective': f"{effective_fee_rate*100:.3f}%" # Actual rate based on trades
|
||||
},
|
||||
'fee_analysis': {
|
||||
'total_volume': total_volume,
|
||||
'fee_impact_percent': fee_impact_on_pnl,
|
||||
'is_fee_efficient': fee_impact_on_pnl < 5.0, # Less than 5% impact is good
|
||||
'fee_savings_vs_market': (0.001 - effective_fee_rate) * total_volume if effective_fee_rate < 0.001 else 0
|
||||
}
|
||||
}
|
||||
|
||||
def emergency_stop(self):
|
||||
@ -467,3 +556,250 @@ class TradingExecutor:
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting account balance: {e}")
|
||||
return {}
|
||||
|
||||
def _calculate_trading_fee(self, order_result: Dict[str, Any], symbol: str,
|
||||
quantity: float, price: float) -> float:
|
||||
"""Calculate trading fee based on order execution details with enhanced MEXC API support
|
||||
|
||||
Args:
|
||||
order_result: Order result from exchange API
|
||||
symbol: Trading symbol
|
||||
quantity: Order quantity
|
||||
price: Execution price
|
||||
|
||||
Returns:
|
||||
float: Trading fee amount in quote currency
|
||||
"""
|
||||
try:
|
||||
# 1. Try to get actual fee from API response (most accurate)
|
||||
# MEXC API can return fees in different formats depending on the endpoint
|
||||
|
||||
# Check for 'fills' array (most common for filled orders)
|
||||
if order_result and 'fills' in order_result:
|
||||
total_commission = 0.0
|
||||
commission_asset = None
|
||||
for fill in order_result['fills']:
|
||||
commission = float(fill.get('commission', 0))
|
||||
commission_asset = fill.get('commissionAsset', '')
|
||||
total_commission += commission
|
||||
|
||||
if total_commission > 0:
|
||||
logger.info(f"Using actual API fee from fills: {total_commission} {commission_asset}")
|
||||
# If commission is in different asset, we might need conversion
|
||||
# For now, assume it's in quote currency (USDC/USDT)
|
||||
return total_commission
|
||||
|
||||
# 2. Check if order result has fee information directly
|
||||
fee_fields = ['fee', 'commission', 'tradeFee', 'fees']
|
||||
for field in fee_fields:
|
||||
if order_result and field in order_result:
|
||||
fee = float(order_result[field])
|
||||
if fee > 0:
|
||||
logger.info(f"Using API fee field '{field}': {fee}")
|
||||
return fee
|
||||
|
||||
# 3. Check for executedQty and cummulativeQuoteQty for more accurate calculation
|
||||
if order_result and 'executedQty' in order_result and 'cummulativeQuoteQty' in order_result:
|
||||
executed_qty = float(order_result['executedQty'])
|
||||
executed_value = float(order_result['cummulativeQuoteQty'])
|
||||
if executed_qty > 0 and executed_value > 0:
|
||||
# Use executed values instead of provided price/quantity
|
||||
quantity = executed_qty
|
||||
price = executed_value / executed_qty
|
||||
logger.info(f"Using executed order data: {quantity} @ {price:.6f}")
|
||||
|
||||
# 4. Fall back to config-based fee calculation with enhanced logic
|
||||
trading_fees = self.mexc_config.get('trading_fees', {})
|
||||
|
||||
# Determine if this was a maker or taker trade
|
||||
order_type = order_result.get('type', 'MARKET') if order_result else 'MARKET'
|
||||
order_status = order_result.get('status', 'UNKNOWN') if order_result else 'UNKNOWN'
|
||||
time_in_force = order_result.get('timeInForce', 'GTC') if order_result else 'GTC'
|
||||
|
||||
# Enhanced maker/taker detection logic
|
||||
if order_type.upper() == 'LIMIT':
|
||||
# For limit orders, check execution speed and market conditions
|
||||
if order_status == 'FILLED':
|
||||
# If it's an IOC (Immediate or Cancel) order, it's likely a taker
|
||||
if time_in_force == 'IOC' or time_in_force == 'FOK':
|
||||
fee_rate = trading_fees.get('taker', 0.0005)
|
||||
logger.info(f"Using taker fee rate for {time_in_force} limit order: {fee_rate*100:.3f}%")
|
||||
else:
|
||||
# For GTC orders, assume taker if aggressive pricing is used
|
||||
# This is a heuristic based on our trading strategy
|
||||
fee_rate = trading_fees.get('taker', 0.0005)
|
||||
logger.info(f"Using taker fee rate for aggressive limit order: {fee_rate*100:.3f}%")
|
||||
else:
|
||||
# If not immediately filled, likely a maker (though we don't usually reach here)
|
||||
fee_rate = trading_fees.get('maker', 0.0000)
|
||||
logger.info(f"Using maker fee rate for pending/partial limit order: {fee_rate*100:.3f}%")
|
||||
elif order_type.upper() == 'LIMIT_MAKER':
|
||||
# LIMIT_MAKER orders are guaranteed to be makers
|
||||
fee_rate = trading_fees.get('maker', 0.0000)
|
||||
logger.info(f"Using maker fee rate for LIMIT_MAKER order: {fee_rate*100:.3f}%")
|
||||
else:
|
||||
# Market orders and other types are always takers
|
||||
fee_rate = trading_fees.get('taker', 0.0005)
|
||||
logger.info(f"Using taker fee rate for {order_type} order: {fee_rate*100:.3f}%")
|
||||
|
||||
# Calculate fee amount
|
||||
trade_value = quantity * price
|
||||
fee_amount = trade_value * fee_rate
|
||||
|
||||
logger.info(f"Calculated fee: ${fee_amount:.6f} ({fee_rate*100:.3f}% of ${trade_value:.2f})")
|
||||
return fee_amount
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error calculating trading fee: {e}")
|
||||
# Ultimate fallback using default rate
|
||||
default_fee_rate = self.mexc_config.get('trading_fees', {}).get('default', 0.0005)
|
||||
fallback_rate = self.mexc_config.get('trading_fee', default_fee_rate) # Legacy support
|
||||
fee_amount = quantity * price * fallback_rate
|
||||
logger.info(f"Using fallback fee: ${fee_amount:.6f} ({fallback_rate*100:.3f}%)")
|
||||
return fee_amount
|
||||
|
||||
def get_fee_analysis(self) -> Dict[str, Any]:
|
||||
"""Get detailed fee analysis and statistics
|
||||
|
||||
Returns:
|
||||
Dict with fee breakdowns, rates, and impact analysis
|
||||
"""
|
||||
try:
|
||||
fee_structure = self.mexc_config.get('trading_fees', {})
|
||||
maker_rate = fee_structure.get('maker', 0.0000)
|
||||
taker_rate = fee_structure.get('taker', 0.0005)
|
||||
default_rate = fee_structure.get('default', 0.0005)
|
||||
|
||||
# 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)
|
||||
# Assume most of our limit orders are takers due to our pricing strategy
|
||||
estimated_taker_volume = total_volume * 0.9 # 90% taker assumption
|
||||
estimated_maker_volume = total_volume * 0.1 # 10% maker assumption
|
||||
|
||||
estimated_taker_fees = estimated_taker_volume * taker_rate
|
||||
estimated_maker_fees = estimated_maker_volume * maker_rate
|
||||
|
||||
# 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
|
||||
|
||||
return {
|
||||
'fee_rates': {
|
||||
'maker': {
|
||||
'rate': maker_rate,
|
||||
'rate_percent': f"{maker_rate*100:.3f}%"
|
||||
},
|
||||
'taker': {
|
||||
'rate': taker_rate,
|
||||
'rate_percent': f"{taker_rate*100:.3f}%"
|
||||
},
|
||||
'default': {
|
||||
'rate': default_rate,
|
||||
'rate_percent': f"{default_rate*100:.3f}%"
|
||||
}
|
||||
},
|
||||
'total_fees': total_fees,
|
||||
'total_volume': total_volume,
|
||||
'estimated_breakdown': {
|
||||
'taker_fees': estimated_taker_fees,
|
||||
'maker_fees': estimated_maker_fees,
|
||||
'taker_volume': estimated_taker_volume,
|
||||
'maker_volume': estimated_maker_volume
|
||||
},
|
||||
'impact_analysis': {
|
||||
'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': {
|
||||
'volume_to_fee_ratio': total_volume / max(0.01, total_fees),
|
||||
'is_efficient': fee_impact_percent < 5.0 # Less than 5% impact is good
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating fee analysis: {e}")
|
||||
return {
|
||||
'error': str(e),
|
||||
'fee_rates': {
|
||||
'maker': {'rate': 0.0000, 'rate_percent': '0.000%'},
|
||||
'taker': {'rate': 0.0005, 'rate_percent': '0.050%'}
|
||||
}
|
||||
}
|
||||
|
||||
def sync_fees_with_api(self, force: bool = False) -> Dict[str, Any]:
|
||||
"""Manually trigger fee synchronization with MEXC API
|
||||
|
||||
Args:
|
||||
force: Force sync even if last sync was recent
|
||||
|
||||
Returns:
|
||||
dict: Sync result with status and details
|
||||
"""
|
||||
if not self.config_synchronizer:
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': 'Config synchronizer not initialized'
|
||||
}
|
||||
|
||||
try:
|
||||
logger.info("TRADING EXECUTOR: Manual fee sync requested")
|
||||
sync_result = self.config_synchronizer.sync_trading_fees(force=force)
|
||||
|
||||
# If fees were updated, reload config
|
||||
if sync_result.get('changes_made'):
|
||||
logger.info("TRADING EXECUTOR: Reloading config after fee sync")
|
||||
self.config = get_config(self.config_synchronizer.config_path)
|
||||
self.mexc_config = self.config.get('mexc_trading', {})
|
||||
|
||||
return sync_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"TRADING EXECUTOR: Error in manual fee sync: {e}")
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def auto_sync_fees_if_needed(self) -> bool:
|
||||
"""Automatically sync fees if needed (called periodically)
|
||||
|
||||
Returns:
|
||||
bool: True if sync was performed successfully
|
||||
"""
|
||||
if not self.config_synchronizer:
|
||||
return False
|
||||
|
||||
try:
|
||||
return self.config_synchronizer.auto_sync_fees()
|
||||
except Exception as e:
|
||||
logger.error(f"TRADING EXECUTOR: Error in auto fee sync: {e}")
|
||||
return False
|
||||
|
||||
def get_fee_sync_status(self) -> Dict[str, Any]:
|
||||
"""Get current fee synchronization status
|
||||
|
||||
Returns:
|
||||
dict: Fee sync status and history
|
||||
"""
|
||||
if not self.config_synchronizer:
|
||||
return {
|
||||
'sync_available': False,
|
||||
'error': 'Config synchronizer not initialized'
|
||||
}
|
||||
|
||||
try:
|
||||
status = self.config_synchronizer.get_sync_status()
|
||||
status['sync_available'] = True
|
||||
return status
|
||||
except Exception as e:
|
||||
logger.error(f"TRADING EXECUTOR: Error getting sync status: {e}")
|
||||
return {
|
||||
'sync_available': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
Reference in New Issue
Block a user