fix merge
This commit is contained in:
@@ -34,15 +34,11 @@ class COBIntegration:
|
||||
"""
|
||||
Integration layer for Multi-Exchange COB data with gogo2 trading system
|
||||
"""
|
||||
|
||||
<<<<<<< HEAD
|
||||
def __init__(self, data_provider: Optional[DataProvider] = None, symbols: Optional[List[str]] = None, initial_data_limit=None, **kwargs):
|
||||
=======
|
||||
|
||||
def __init__(self, data_provider: Optional['DataProvider'] = None, symbols: Optional[List[str]] = None):
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
"""
|
||||
Initialize COB Integration
|
||||
|
||||
|
||||
Args:
|
||||
data_provider: Existing DataProvider instance
|
||||
symbols: List of symbols to monitor
|
||||
@@ -98,23 +94,8 @@ class COBIntegration:
|
||||
|
||||
# Initialize Enhanced WebSocket first
|
||||
try:
|
||||
<<<<<<< HEAD
|
||||
logger.info("Starting COB provider streaming...")
|
||||
await self.cob_provider.start_streaming()
|
||||
=======
|
||||
self.enhanced_websocket = EnhancedCOBWebSocket(
|
||||
symbols=self.symbols,
|
||||
dashboard_callback=self._on_websocket_status_update
|
||||
)
|
||||
|
||||
# Add COB data callback
|
||||
self.enhanced_websocket.add_cob_callback(self._on_enhanced_cob_update)
|
||||
|
||||
# Start enhanced WebSocket
|
||||
await self.enhanced_websocket.start()
|
||||
logger.info(" Enhanced WebSocket started successfully")
|
||||
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
# Enhanced WebSocket initialization would go here
|
||||
logger.info("Enhanced WebSocket initialized successfully")
|
||||
except Exception as e:
|
||||
logger.error(f" Error starting Enhanced WebSocket: {e}")
|
||||
|
||||
@@ -281,16 +262,12 @@ class COBIntegration:
|
||||
|
||||
# Stop COB provider if it exists (should be None with current optimization)
|
||||
if self.cob_provider:
|
||||
<<<<<<< HEAD
|
||||
await self.cob_provider.stop_streaming()
|
||||
=======
|
||||
try:
|
||||
await self.cob_provider.stop_streaming()
|
||||
logger.info("COB provider stopped")
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping COB provider: {e}")
|
||||
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
logger.info("COB Integration stopped")
|
||||
|
||||
def add_cnn_callback(self, callback: Callable[[str, Dict], None]):
|
||||
@@ -595,11 +572,6 @@ class COBIntegration:
|
||||
logger.error(f"Error getting real-time stats for {symbol}: {e}")
|
||||
stats['realtime_1s'] = {}
|
||||
stats['realtime_5s'] = {}
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
return {
|
||||
'type': 'cob_update',
|
||||
'data': {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,64 +84,6 @@ class MEXCInterface(ExchangeInterface):
|
||||
# This method is included for completeness but should not be used for spot trading
|
||||
return symbol.replace('/', '_').upper()
|
||||
|
||||
<<<<<<< HEAD:NN/exchanges/mexc_interface.py
|
||||
def _generate_signature(self, timestamp: str, method: str, endpoint: str, params: Dict[str, Any]) -> str:
|
||||
"""Generate signature for private API calls using MEXC's official method"""
|
||||
# MEXC signature format varies by method:
|
||||
# For GET/DELETE: URL-encoded query string of alphabetically sorted parameters.
|
||||
# For POST: JSON string of parameters (no sorting needed).
|
||||
# The API-Secret is used as the HMAC SHA256 key.
|
||||
|
||||
# Remove signature from params to avoid circular inclusion
|
||||
clean_params = {k: v for k, v in params.items() if k != 'signature'}
|
||||
|
||||
parameter_string: str
|
||||
|
||||
if method.upper() == "POST":
|
||||
# For POST requests, the signature parameter is a JSON string
|
||||
# Ensure sorting keys for consistent JSON string generation across runs
|
||||
# even though MEXC says sorting is not required for POST params, it's good practice.
|
||||
parameter_string = json.dumps(clean_params, sort_keys=True, separators=(',', ':'))
|
||||
else:
|
||||
# For GET/DELETE requests, parameters are spliced in dictionary order with & interval
|
||||
sorted_params = sorted(clean_params.items())
|
||||
parameter_string = '&'.join(f"{key}={str(value)}" for key, value in sorted_params)
|
||||
|
||||
# The string to be signed is: accessKey + timestamp + obtained parameter string.
|
||||
string_to_sign = f"{self.api_key}{timestamp}{parameter_string}"
|
||||
|
||||
logger.debug(f"MEXC string to sign (method {method}): {string_to_sign}")
|
||||
|
||||
=======
|
||||
def _generate_signature(self, params: Dict[str, Any]) -> str:
|
||||
"""Generate signature for private API calls using MEXC's parameter ordering"""
|
||||
# MEXC uses specific parameter ordering for signature generation
|
||||
# Based on working Postman collection: symbol, side, type, quantity, price, timestamp, recvWindow, then others
|
||||
|
||||
# Remove signature if present
|
||||
clean_params = {k: v for k, v in params.items() if k != 'signature'}
|
||||
|
||||
# MEXC parameter order (from working Postman collection)
|
||||
mexc_order = ['symbol', 'side', 'type', 'quantity', 'price', 'timestamp', 'recvWindow']
|
||||
|
||||
ordered_params = []
|
||||
|
||||
# Add parameters in MEXC's expected order
|
||||
for param_name in mexc_order:
|
||||
if param_name in clean_params:
|
||||
ordered_params.append(f"{param_name}={clean_params[param_name]}")
|
||||
del clean_params[param_name]
|
||||
|
||||
# Add any remaining parameters in alphabetical order
|
||||
for key in sorted(clean_params.keys()):
|
||||
ordered_params.append(f"{key}={clean_params[key]}")
|
||||
|
||||
# Create query string
|
||||
query_string = '&'.join(ordered_params)
|
||||
|
||||
logger.debug(f"MEXC signature query string: {query_string}")
|
||||
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b:core/exchanges/mexc_interface.py
|
||||
# Generate HMAC SHA256 signature
|
||||
signature = hmac.new(
|
||||
self.api_secret.encode('utf-8'),
|
||||
@@ -180,11 +122,6 @@ class MEXCInterface(ExchangeInterface):
|
||||
return {}
|
||||
|
||||
def _send_private_request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
||||
<<<<<<< HEAD:NN/exchanges/mexc_interface.py
|
||||
"""Send a private request to the exchange with proper signature"""
|
||||
=======
|
||||
"""Send a private request to the exchange with proper signature and MEXC error handling"""
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b:core/exchanges/mexc_interface.py
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
@@ -211,19 +148,7 @@ class MEXCInterface(ExchangeInterface):
|
||||
if method.upper() == "GET":
|
||||
response = self.session.get(url, headers=headers, params=params, timeout=10)
|
||||
elif method.upper() == "POST":
|
||||
<<<<<<< HEAD:NN/exchanges/mexc_interface.py
|
||||
# MEXC expects POST parameters as JSON in the request body, not as query string
|
||||
# The signature is generated from the JSON string of parameters.
|
||||
# We need to exclude 'signature' from the JSON body sent, as it's for the header.
|
||||
params_for_body = {k: v for k, v in params.items() if k != 'signature'}
|
||||
response = self.session.post(url, headers=headers, json=params_for_body, timeout=10)
|
||||
=======
|
||||
# For POST requests, MEXC expects parameters as query parameters, not form data
|
||||
# Based on Postman collection: Content-Type header is disabled
|
||||
response = self.session.post(url, headers=headers, params=params, timeout=10)
|
||||
elif method.upper() == "DELETE":
|
||||
response = self.session.delete(url, headers=headers, params=params, timeout=10)
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b:core/exchanges/mexc_interface.py
|
||||
else:
|
||||
logger.error(f"Unsupported method: {method}")
|
||||
return None
|
||||
@@ -312,31 +237,6 @@ class MEXCInterface(ExchangeInterface):
|
||||
|
||||
response = self._send_public_request('GET', endpoint, params)
|
||||
|
||||
<<<<<<< HEAD:NN/exchanges/mexc_interface.py
|
||||
if isinstance(response, dict):
|
||||
ticker_data: Dict[str, Any] = response
|
||||
elif isinstance(response, list) and len(response) > 0:
|
||||
found_ticker = next((item for item in response if item.get('symbol') == formatted_symbol), None)
|
||||
if found_ticker:
|
||||
ticker_data = found_ticker
|
||||
=======
|
||||
if response:
|
||||
# MEXC ticker returns a dictionary if single symbol, list if all symbols
|
||||
if isinstance(response, dict):
|
||||
ticker_data = response
|
||||
elif isinstance(response, list) and len(response) > 0:
|
||||
# If the response is a list, try to find the specific symbol
|
||||
found_ticker = None
|
||||
for item in response:
|
||||
if isinstance(item, dict) and item.get('symbol') == formatted_symbol:
|
||||
found_ticker = item
|
||||
break
|
||||
if found_ticker:
|
||||
ticker_data = found_ticker
|
||||
else:
|
||||
logger.error(f"Ticker data for {formatted_symbol} not found in response list.")
|
||||
return None
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b:core/exchanges/mexc_interface.py
|
||||
else:
|
||||
logger.error(f"Ticker data for {formatted_symbol} not found in response list.")
|
||||
return None
|
||||
@@ -396,71 +296,6 @@ class MEXCInterface(ExchangeInterface):
|
||||
|
||||
def place_order(self, symbol: str, side: str, order_type: str, quantity: float, price: Optional[float] = None) -> Dict[str, Any]:
|
||||
"""Place a new order on MEXC."""
|
||||
<<<<<<< HEAD:NN/exchanges/mexc_interface.py
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
|
||||
# Check if symbol is supported for API trading
|
||||
if not self.is_symbol_supported(symbol):
|
||||
supported_symbols = self.get_api_symbols()
|
||||
logger.error(f"Symbol {formatted_symbol} is not supported for API trading")
|
||||
logger.info(f"Supported symbols include: {supported_symbols[:10]}...") # Show first 10
|
||||
return {}
|
||||
|
||||
# Format quantity according to symbol precision requirements
|
||||
formatted_quantity = self._format_quantity_for_symbol(formatted_symbol, quantity)
|
||||
if formatted_quantity is None:
|
||||
logger.error(f"MEXC: Failed to format quantity {quantity} for {formatted_symbol}")
|
||||
return {}
|
||||
|
||||
# Handle order type restrictions for specific symbols
|
||||
final_order_type = self._adjust_order_type_for_symbol(formatted_symbol, order_type.upper())
|
||||
|
||||
# Get price for limit orders
|
||||
final_price = price
|
||||
if final_order_type == 'LIMIT' and price is None:
|
||||
# Get current market price
|
||||
ticker = self.get_ticker(symbol)
|
||||
if ticker and 'last' in ticker:
|
||||
final_price = ticker['last']
|
||||
logger.info(f"MEXC: Using market price ${final_price:.2f} for LIMIT order")
|
||||
else:
|
||||
logger.error(f"MEXC: Could not get market price for LIMIT order on {formatted_symbol}")
|
||||
return {}
|
||||
|
||||
endpoint = "order"
|
||||
|
||||
params: Dict[str, Any] = {
|
||||
'symbol': formatted_symbol,
|
||||
'side': side.upper(),
|
||||
'type': final_order_type,
|
||||
'quantity': str(formatted_quantity) # Quantity must be a string
|
||||
}
|
||||
if final_price is not None:
|
||||
params['price'] = str(final_price) # Price must be a string for limit orders
|
||||
|
||||
logger.info(f"MEXC: Placing {side.upper()} {final_order_type} order for {formatted_quantity} {formatted_symbol} at price {final_price}")
|
||||
|
||||
try:
|
||||
# MEXC API endpoint for placing orders is /api/v3/order (POST)
|
||||
order_result = self._send_private_request('POST', endpoint, params)
|
||||
if order_result is not None:
|
||||
logger.info(f"MEXC: Order placed successfully: {order_result}")
|
||||
return order_result
|
||||
else:
|
||||
logger.error(f"MEXC: Error placing order: request returned None")
|
||||
=======
|
||||
try:
|
||||
logger.info(f"MEXC: place_order called with symbol={symbol}, side={side}, order_type={order_type}, quantity={quantity}, price={price}")
|
||||
|
||||
formatted_symbol = self._format_spot_symbol(symbol)
|
||||
logger.info(f"MEXC: Formatted symbol: {symbol} -> {formatted_symbol}")
|
||||
|
||||
# Check if symbol is supported for API trading
|
||||
if not self.is_symbol_supported(symbol):
|
||||
supported_symbols = self.get_api_symbols()
|
||||
logger.error(f"Symbol {formatted_symbol} is not supported for API trading")
|
||||
logger.info(f"Supported symbols include: {supported_symbols[:10]}...") # Show first 10
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b:core/exchanges/mexc_interface.py
|
||||
return {}
|
||||
|
||||
# Round quantity to MEXC precision requirements and ensure minimum order value
|
||||
|
||||
@@ -47,57 +47,6 @@ import aiohttp.resolver
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
<<<<<<< HEAD
|
||||
# goal: use top 10 exchanges
|
||||
# https://www.coingecko.com/en/exchanges
|
||||
=======
|
||||
class SimpleRateLimiter:
|
||||
"""Simple rate limiter to prevent 418 errors"""
|
||||
|
||||
def __init__(self, requests_per_second: float = 0.5): # Much more conservative
|
||||
self.requests_per_second = requests_per_second
|
||||
self.last_request_time = 0
|
||||
self.min_interval = 1.0 / requests_per_second
|
||||
self.consecutive_errors = 0
|
||||
self.blocked_until = 0
|
||||
|
||||
def can_make_request(self) -> bool:
|
||||
"""Check if we can make a request"""
|
||||
now = time.time()
|
||||
|
||||
# Check if we're in a blocked state
|
||||
if now < self.blocked_until:
|
||||
return False
|
||||
|
||||
return (now - self.last_request_time) >= self.min_interval
|
||||
|
||||
def record_request(self, success: bool = True):
|
||||
"""Record that a request was made"""
|
||||
self.last_request_time = time.time()
|
||||
|
||||
if success:
|
||||
self.consecutive_errors = 0
|
||||
else:
|
||||
self.consecutive_errors += 1
|
||||
# Exponential backoff for errors
|
||||
if self.consecutive_errors >= 3:
|
||||
backoff_time = min(300, 10 * (2 ** (self.consecutive_errors - 3))) # Max 5 min
|
||||
self.blocked_until = time.time() + backoff_time
|
||||
logger.warning(f"Rate limiter blocked for {backoff_time}s after {self.consecutive_errors} errors")
|
||||
|
||||
def get_wait_time(self) -> float:
|
||||
"""Get time to wait before next request"""
|
||||
now = time.time()
|
||||
|
||||
# Check if blocked
|
||||
if now < self.blocked_until:
|
||||
return self.blocked_until - now
|
||||
|
||||
time_since_last = now - self.last_request_time
|
||||
if time_since_last < self.min_interval:
|
||||
return self.min_interval - time_since_last
|
||||
return 0.0
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
class ExchangeType(Enum):
|
||||
BINANCE = "binance"
|
||||
@@ -105,12 +54,6 @@ class ExchangeType(Enum):
|
||||
KRAKEN = "kraken"
|
||||
HUOBI = "huobi"
|
||||
BITFINEX = "bitfinex"
|
||||
<<<<<<< HEAD
|
||||
BYBIT = "bybit"
|
||||
BITGET = "bitget"
|
||||
=======
|
||||
COINAPI = "coinapi"
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
@dataclass
|
||||
class ExchangeOrderBookLevel:
|
||||
@@ -170,74 +113,18 @@ class MultiExchangeCOBProvider:
|
||||
Aggregates real-time order book data from multiple cryptocurrency exchanges
|
||||
to create a consolidated view of market liquidity and pricing.
|
||||
"""
|
||||
|
||||
<<<<<<< HEAD
|
||||
|
||||
def __init__(self, symbols: Optional[List[str]] = None, bucket_size_bps: float = 1.0):
|
||||
"""
|
||||
Initialize Multi-Exchange COB Provider
|
||||
|
||||
|
||||
Args:
|
||||
symbols: List of symbols to monitor (e.g., ['BTC/USDT', 'ETH/USDT'])
|
||||
bucket_size_bps: Price bucket size in basis points for fine-grain analysis
|
||||
"""
|
||||
self.symbols = symbols or ['BTC/USDT', 'ETH/USDT']
|
||||
self.bucket_size_bps = bucket_size_bps
|
||||
self.bucket_update_frequency = 100 # ms
|
||||
self.consolidation_frequency = 100 # ms
|
||||
|
||||
# REST API configuration for deep order book
|
||||
self.rest_api_frequency = 2000 # ms - full snapshot every 2 seconds (reduced frequency for deeper data)
|
||||
self.rest_depth_limit = 1000 # Increased to 1000 levels via REST for maximum depth
|
||||
|
||||
# Exchange configurations
|
||||
self.exchange_configs = self._initialize_exchange_configs()
|
||||
|
||||
# Order book storage - now with deep and live separation
|
||||
self.exchange_order_books = {
|
||||
symbol: {
|
||||
exchange.value: {
|
||||
'bids': {},
|
||||
'asks': {},
|
||||
'timestamp': None,
|
||||
'connected': False,
|
||||
'deep_bids': {}, # Full depth from REST API
|
||||
'deep_asks': {}, # Full depth from REST API
|
||||
'deep_timestamp': None,
|
||||
'last_update_id': None # For managing diff updates
|
||||
}
|
||||
for exchange in ExchangeType
|
||||
}
|
||||
for symbol in self.symbols
|
||||
}
|
||||
|
||||
# Consolidated order books
|
||||
self.consolidated_order_books: Dict[str, COBSnapshot] = {}
|
||||
|
||||
# Real-time statistics tracking
|
||||
self.realtime_stats: Dict[str, Dict] = {symbol: {} for symbol in self.symbols}
|
||||
self.realtime_snapshots: Dict[str, deque] = {
|
||||
symbol: deque(maxlen=1000) for symbol in self.symbols
|
||||
}
|
||||
|
||||
# Session tracking for SVP
|
||||
self.session_start_time = datetime.now()
|
||||
self.session_trades: Dict[str, List[Dict]] = {symbol: [] for symbol in self.symbols}
|
||||
self.svp_cache: Dict[str, Dict] = {symbol: {} for symbol in self.symbols}
|
||||
|
||||
# Fixed USD bucket sizes for different symbols as requested
|
||||
self.fixed_usd_buckets = {
|
||||
'BTC/USDT': 10.0, # $10 buckets for BTC
|
||||
'ETH/USDT': 1.0, # $1 buckets for ETH
|
||||
}
|
||||
|
||||
# WebSocket management
|
||||
=======
|
||||
def __init__(self, symbols: List[str], exchange_configs: Dict[str, ExchangeConfig]):
|
||||
"""Initialize multi-exchange COB provider"""
|
||||
self.symbols = symbols
|
||||
self.exchange_configs = exchange_configs
|
||||
self.active_exchanges = ['binance'] # Focus on Binance for now
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
self.is_streaming = False
|
||||
self.cob_data_cache = {} # Cache for COB data
|
||||
self.cob_subscribers = [] # List of callback functions
|
||||
@@ -263,86 +150,6 @@ class MultiExchangeCOBProvider:
|
||||
|
||||
logger.info(f"Multi-exchange COB provider initialized for symbols: {symbols}")
|
||||
|
||||
<<<<<<< HEAD
|
||||
def _initialize_exchange_configs(self) -> Dict[str, ExchangeConfig]:
|
||||
"""Initialize exchange configurations"""
|
||||
configs = {}
|
||||
|
||||
# Binance configuration
|
||||
configs[ExchangeType.BINANCE.value] = ExchangeConfig(
|
||||
exchange_type=ExchangeType.BINANCE,
|
||||
weight=0.3, # Higher weight due to volume
|
||||
websocket_url="wss://stream.binance.com:9443/ws/",
|
||||
rest_api_url="https://api.binance.com",
|
||||
symbols_mapping={'BTC/USDT': 'BTCUSDT', 'ETH/USDT': 'ETHUSDT'},
|
||||
rate_limits={'requests_per_minute': 1200, 'weight_per_minute': 6000}
|
||||
)
|
||||
|
||||
# Coinbase Pro configuration
|
||||
configs[ExchangeType.COINBASE.value] = ExchangeConfig(
|
||||
exchange_type=ExchangeType.COINBASE,
|
||||
weight=0.25,
|
||||
websocket_url="wss://ws-feed.exchange.coinbase.com",
|
||||
rest_api_url="https://api.exchange.coinbase.com",
|
||||
symbols_mapping={'BTC/USDT': 'BTC-USD', 'ETH/USDT': 'ETH-USD'},
|
||||
rate_limits={'requests_per_minute': 600}
|
||||
)
|
||||
|
||||
# Kraken configuration
|
||||
configs[ExchangeType.KRAKEN.value] = ExchangeConfig(
|
||||
exchange_type=ExchangeType.KRAKEN,
|
||||
weight=0.2,
|
||||
websocket_url="wss://ws.kraken.com",
|
||||
rest_api_url="https://api.kraken.com",
|
||||
symbols_mapping={'BTC/USDT': 'XBT/USDT', 'ETH/USDT': 'ETH/USDT'},
|
||||
rate_limits={'requests_per_minute': 900}
|
||||
)
|
||||
|
||||
# Huobi configuration
|
||||
configs[ExchangeType.HUOBI.value] = ExchangeConfig(
|
||||
exchange_type=ExchangeType.HUOBI,
|
||||
weight=0.15,
|
||||
websocket_url="wss://api.huobi.pro/ws",
|
||||
rest_api_url="https://api.huobi.pro",
|
||||
symbols_mapping={'BTC/USDT': 'btcusdt', 'ETH/USDT': 'ethusdt'},
|
||||
rate_limits={'requests_per_minute': 2000}
|
||||
)
|
||||
|
||||
# Bitfinex configuration
|
||||
configs[ExchangeType.BITFINEX.value] = ExchangeConfig(
|
||||
exchange_type=ExchangeType.BITFINEX,
|
||||
weight=0.1,
|
||||
websocket_url="wss://api-pub.bitfinex.com/ws/2",
|
||||
rest_api_url="https://api-pub.bitfinex.com",
|
||||
symbols_mapping={'BTC/USDT': 'tBTCUST', 'ETH/USDT': 'tETHUST'},
|
||||
rate_limits={'requests_per_minute': 1000}
|
||||
)
|
||||
|
||||
# Bybit configuration
|
||||
configs[ExchangeType.BYBIT.value] = ExchangeConfig(
|
||||
exchange_type=ExchangeType.BYBIT,
|
||||
weight=0.18,
|
||||
websocket_url="wss://stream.bybit.com/v5/public/spot",
|
||||
rest_api_url="https://api.bybit.com",
|
||||
symbols_mapping={'BTC/USDT': 'BTCUSDT', 'ETH/USDT': 'ETHUSDT'},
|
||||
rate_limits={'requests_per_minute': 1200}
|
||||
)
|
||||
# Bitget configuration
|
||||
configs[ExchangeType.BITGET.value] = ExchangeConfig(
|
||||
exchange_type=ExchangeType.BITGET,
|
||||
weight=0.12,
|
||||
websocket_url="wss://ws.bitget.com/spot/v1/stream",
|
||||
rest_api_url="https://api.bitget.com",
|
||||
symbols_mapping={'BTC/USDT': 'BTCUSDT_SPBL', 'ETH/USDT': 'ETHUSDT_SPBL'},
|
||||
rate_limits={'requests_per_minute': 1200}
|
||||
)
|
||||
return configs
|
||||
=======
|
||||
def subscribe_to_cob_updates(self, callback):
|
||||
"""Subscribe to COB data updates"""
|
||||
self.cob_subscribers.append(callback)
|
||||
logger.debug(f"Added COB subscriber, total: {len(self.cob_subscribers)}")
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
async def _notify_cob_subscribers(self, symbol: str, cob_snapshot: Dict):
|
||||
"""Notify all subscribers of COB data updates"""
|
||||
|
||||
8858
core/orchestrator.py
8858
core/orchestrator.py
File diff suppressed because it is too large
Load Diff
@@ -96,14 +96,6 @@ class TradeRecord:
|
||||
fees: float
|
||||
confidence: float
|
||||
hold_time_seconds: float = 0.0 # Hold time in seconds
|
||||
<<<<<<< HEAD
|
||||
leverage: float = 1.0 # Leverage applied to this trade
|
||||
=======
|
||||
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
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
class TradingExecutor:
|
||||
"""Handles trade execution through multiple exchange APIs with risk management"""
|
||||
@@ -229,13 +221,6 @@ class TradingExecutor:
|
||||
# Connect to exchange - skip connection check in simulation mode
|
||||
if self.trading_enabled:
|
||||
if self.simulation_mode:
|
||||
<<<<<<< HEAD
|
||||
logger.info("TRADING EXECUTOR: Simulation mode - skipping exchange connection check")
|
||||
# In simulation mode, we don't need a real exchange connection
|
||||
# Trading should remain enabled for simulation trades
|
||||
=======
|
||||
logger.info("TRADING EXECUTOR: Simulation mode - trading enabled without exchange connection")
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
else:
|
||||
logger.info("TRADING EXECUTOR: Attempting to connect to exchange...")
|
||||
if not self._connect_exchange():
|
||||
@@ -548,37 +533,6 @@ class TradingExecutor:
|
||||
# For simplicity, assume required capital is the full position value in USD
|
||||
required_capital = self._calculate_position_size(confidence, current_price)
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Get available balance for the quote asset
|
||||
# For MEXC, prioritize USDT over USDC since most accounts have USDT
|
||||
if quote_asset == 'USDC':
|
||||
# Check USDT first (most common balance)
|
||||
usdt_balance = self.exchange.get_balance('USDT')
|
||||
usdc_balance = self.exchange.get_balance('USDC')
|
||||
|
||||
if usdt_balance >= required_capital:
|
||||
available_balance = usdt_balance
|
||||
quote_asset = 'USDT' # Use USDT for trading
|
||||
logger.info(f"BALANCE CHECK: Using USDT balance for {symbol} (preferred)")
|
||||
elif usdc_balance >= required_capital:
|
||||
available_balance = usdc_balance
|
||||
logger.info(f"BALANCE CHECK: Using USDC balance for {symbol}")
|
||||
else:
|
||||
# Use the larger balance for reporting
|
||||
available_balance = max(usdt_balance, usdc_balance)
|
||||
quote_asset = 'USDT' if usdt_balance > usdc_balance else 'USDC'
|
||||
=======
|
||||
# Get available balance for the quote asset (try USDT first, then USDC as fallback)
|
||||
if quote_asset == 'USDT':
|
||||
available_balance = self.exchange.get_balance('USDT')
|
||||
if available_balance < required_capital:
|
||||
# If USDT balance is insufficient, check USDC as fallback
|
||||
usdc_balance = self.exchange.get_balance('USDC')
|
||||
if usdc_balance >= required_capital:
|
||||
available_balance = usdc_balance
|
||||
quote_asset = 'USDC' # Use USDC instead
|
||||
logger.info(f"BALANCE CHECK: Using USDC fallback balance for {symbol}")
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
else:
|
||||
available_balance = self.exchange.get_balance(quote_asset)
|
||||
|
||||
@@ -1040,33 +994,6 @@ class TradingExecutor:
|
||||
logger.warning(f"POSITION SAFETY: Already have LONG position in {symbol} - blocking duplicate trade")
|
||||
return False
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Calculate position size
|
||||
position_value = self._calculate_position_size(confidence, current_price)
|
||||
|
||||
# CRITICAL: Check for zero price to prevent division by zero
|
||||
if current_price <= 0:
|
||||
logger.error(f"Invalid price {current_price} for {symbol} - cannot calculate quantity")
|
||||
return False
|
||||
|
||||
quantity = position_value / current_price
|
||||
=======
|
||||
# ADDITIONAL SAFETY: Double-check with exchange if not in simulation mode
|
||||
if not self.simulation_mode and self.exchange:
|
||||
try:
|
||||
exchange_positions = self.exchange.get_positions(symbol)
|
||||
if exchange_positions:
|
||||
for pos in exchange_positions:
|
||||
if float(pos.get('size', 0)) > 0:
|
||||
logger.warning(f"POSITION SAFETY: Found existing position on exchange for {symbol} - blocking duplicate trade")
|
||||
logger.warning(f"Position details: {pos}")
|
||||
# Sync this position to local state
|
||||
self._sync_single_position_from_exchange(symbol, pos)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.debug(f"Error checking exchange positions for {symbol}: {e}")
|
||||
# Don't block trade if we can't check - but log it
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
# Cancel any existing open orders before placing new order
|
||||
if not self.simulation_mode:
|
||||
@@ -1079,17 +1006,6 @@ class TradingExecutor:
|
||||
logger.info(f"Executing BUY: {quantity:.6f} {symbol} at ${current_price:.2f} (value: ${position_size:.2f}, confidence: {confidence:.2f}) [{'SIM' if self.simulation_mode else 'LIVE'}]")
|
||||
|
||||
if self.simulation_mode:
|
||||
<<<<<<< HEAD
|
||||
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)
|
||||
current_leverage = self.get_leverage()
|
||||
simulated_fees = quantity * current_price * taker_fee_rate * current_leverage
|
||||
|
||||
# Create mock position for tracking
|
||||
=======
|
||||
# Create simulated position
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
self.positions[symbol] = Position(
|
||||
symbol=symbol,
|
||||
side='LONG',
|
||||
@@ -1109,47 +1025,6 @@ class TradingExecutor:
|
||||
logger.error(f"BUY order blocked: {result['message']}")
|
||||
return False
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Place buy order
|
||||
if order_type == 'market':
|
||||
order = self.exchange.place_order(
|
||||
symbol=symbol,
|
||||
side='buy',
|
||||
order_type=order_type,
|
||||
quantity=quantity
|
||||
)
|
||||
else:
|
||||
# For limit orders, price is required
|
||||
assert limit_price is not None, "limit_price required for limit orders"
|
||||
order = self.exchange.place_order(
|
||||
symbol=symbol,
|
||||
side='buy',
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
price=limit_price
|
||||
)
|
||||
|
||||
if order:
|
||||
# Calculate simulated fees in simulation mode
|
||||
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
|
||||
current_leverage = self.get_leverage()
|
||||
simulated_fees = quantity * current_price * taker_fee_rate * current_leverage
|
||||
|
||||
# Create position record
|
||||
self.positions[symbol] = Position(
|
||||
symbol=symbol,
|
||||
side='LONG',
|
||||
quantity=quantity,
|
||||
entry_price=current_price,
|
||||
entry_time=datetime.now(),
|
||||
order_id=order.get('orderId', 'unknown')
|
||||
)
|
||||
=======
|
||||
if result and 'orderId' in result:
|
||||
# Use actual fill information if available, otherwise fall back to order parameters
|
||||
filled_quantity = result.get('executedQty', quantity)
|
||||
fill_price = result.get('avgPrice', current_price)
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
# Only create position if order was actually filled
|
||||
if result.get('filled', True): # Assume filled for backward compatibility
|
||||
@@ -1185,146 +1060,6 @@ class TradingExecutor:
|
||||
# No position to sell, open short position
|
||||
logger.info(f"No position to sell in {symbol}. Opening short position")
|
||||
return self._execute_short(symbol, confidence, current_price)
|
||||
<<<<<<< HEAD
|
||||
|
||||
position = self.positions[symbol]
|
||||
current_leverage = self.get_leverage()
|
||||
|
||||
logger.info(f"Executing SELL: {position.quantity:.6f} {symbol} at ${current_price:.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 and hold time
|
||||
pnl = position.calculate_pnl(current_price) * current_leverage # Apply leverage to PnL
|
||||
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 * current_leverage # Apply leverage to fees
|
||||
|
||||
# Create trade record
|
||||
trade_record = TradeRecord(
|
||||
symbol=symbol,
|
||||
side='LONG',
|
||||
quantity=position.quantity,
|
||||
entry_price=position.entry_price,
|
||||
exit_price=current_price,
|
||||
entry_time=position.entry_time,
|
||||
exit_time=exit_time,
|
||||
pnl=pnl - simulated_fees,
|
||||
fees=simulated_fees,
|
||||
confidence=confidence,
|
||||
hold_time_seconds=hold_time_seconds,
|
||||
leverage=current_leverage # Store leverage
|
||||
)
|
||||
|
||||
self.trade_history.append(trade_record)
|
||||
self.daily_loss += max(0, -(pnl - simulated_fees)) # Add to daily loss if negative
|
||||
|
||||
# Update consecutive losses
|
||||
if pnl < -0.001: # A losing trade
|
||||
self.consecutive_losses += 1
|
||||
elif pnl > 0.001: # A winning trade
|
||||
self.consecutive_losses = 0
|
||||
else: # Breakeven trade
|
||||
self.consecutive_losses = 0
|
||||
|
||||
# Remove position
|
||||
del self.positions[symbol]
|
||||
self.last_trade_time[symbol] = datetime.now()
|
||||
self.daily_trades += 1
|
||||
|
||||
logger.info(f"Position closed - P&L: ${pnl - simulated_fees:.2f}")
|
||||
return True
|
||||
|
||||
try:
|
||||
# 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
|
||||
if order_type == 'market':
|
||||
order = self.exchange.place_order(
|
||||
symbol=symbol,
|
||||
side='sell',
|
||||
order_type=order_type,
|
||||
quantity=position.quantity
|
||||
)
|
||||
else:
|
||||
# For limit orders, price is required
|
||||
assert limit_price is not None, "limit_price required for limit orders"
|
||||
order = self.exchange.place_order(
|
||||
symbol=symbol,
|
||||
side='sell',
|
||||
order_type=order_type,
|
||||
quantity=position.quantity,
|
||||
price=limit_price
|
||||
)
|
||||
|
||||
if order:
|
||||
# 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 * current_leverage # Apply leverage
|
||||
|
||||
# Calculate P&L, fees, and hold time
|
||||
pnl = position.calculate_pnl(current_price) * current_leverage # Apply leverage to PnL
|
||||
fees = simulated_fees
|
||||
exit_time = datetime.now()
|
||||
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
|
||||
|
||||
# Create trade record
|
||||
trade_record = TradeRecord(
|
||||
symbol=symbol,
|
||||
side='LONG',
|
||||
quantity=position.quantity,
|
||||
entry_price=position.entry_price,
|
||||
exit_price=current_price,
|
||||
entry_time=position.entry_time,
|
||||
exit_time=exit_time,
|
||||
pnl=pnl - fees,
|
||||
fees=fees,
|
||||
confidence=confidence,
|
||||
hold_time_seconds=hold_time_seconds,
|
||||
leverage=current_leverage # Store leverage
|
||||
)
|
||||
|
||||
self.trade_history.append(trade_record)
|
||||
self.daily_loss += max(0, -(pnl - fees)) # Add to daily loss if negative
|
||||
|
||||
# Update consecutive losses
|
||||
if pnl < -0.001: # A losing trade
|
||||
self.consecutive_losses += 1
|
||||
elif pnl > 0.001: # A winning trade
|
||||
self.consecutive_losses = 0
|
||||
else: # Breakeven trade
|
||||
self.consecutive_losses = 0
|
||||
|
||||
# Remove position
|
||||
del self.positions[symbol]
|
||||
self.last_trade_time[symbol] = datetime.now()
|
||||
self.daily_trades += 1
|
||||
|
||||
logger.info(f"SELL order executed: {order}")
|
||||
logger.info(f"Position closed - P&L: ${pnl - fees:.2f}")
|
||||
return True
|
||||
else:
|
||||
logger.error("Failed to place SELL order")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing SELL order: {e}")
|
||||
return False
|
||||
|
||||
=======
|
||||
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
def _execute_short(self, symbol: str, confidence: float, current_price: float) -> bool:
|
||||
"""Execute a short order (sell without holding the asset) with enhanced position management"""
|
||||
# CRITICAL: Check for any existing positions before opening SHORT
|
||||
@@ -1352,34 +1087,10 @@ class TradingExecutor:
|
||||
self._cancel_open_orders(symbol)
|
||||
|
||||
# Calculate position size
|
||||
<<<<<<< HEAD
|
||||
position_value = self._calculate_position_size(confidence, current_price)
|
||||
|
||||
# CRITICAL: Check for zero price to prevent division by zero
|
||||
if current_price <= 0:
|
||||
logger.error(f"Invalid price {current_price} for {symbol} - cannot calculate quantity")
|
||||
return False
|
||||
|
||||
quantity = position_value / current_price
|
||||
=======
|
||||
position_size = self._calculate_position_size(confidence, current_price)
|
||||
quantity = position_size / current_price
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
logger.info(f"Executing SHORT: {quantity:.6f} {symbol} at ${current_price:.2f} (value: ${position_size:.2f}, confidence: {confidence:.2f}) [{'SIM' if self.simulation_mode else 'LIVE'}]")
|
||||
|
||||
if self.simulation_mode:
|
||||
<<<<<<< HEAD
|
||||
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)
|
||||
current_leverage = self.get_leverage()
|
||||
simulated_fees = quantity * current_price * taker_fee_rate * current_leverage
|
||||
|
||||
# Create mock short position for tracking
|
||||
=======
|
||||
# Create simulated short position
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
self.positions[symbol] = Position(
|
||||
symbol=symbol,
|
||||
side='SHORT',
|
||||
@@ -1399,47 +1110,6 @@ class TradingExecutor:
|
||||
logger.error(f"SHORT order blocked: {result['message']}")
|
||||
return False
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Place short sell order
|
||||
if order_type == 'market':
|
||||
order = self.exchange.place_order(
|
||||
symbol=symbol,
|
||||
side='sell', # Short selling starts with a sell order
|
||||
order_type=order_type,
|
||||
quantity=quantity
|
||||
)
|
||||
else:
|
||||
# For limit orders, price is required
|
||||
assert limit_price is not None, "limit_price required for limit orders"
|
||||
order = self.exchange.place_order(
|
||||
symbol=symbol,
|
||||
side='sell', # Short selling starts with a sell order
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
price=limit_price
|
||||
)
|
||||
|
||||
if order:
|
||||
# Calculate simulated fees in simulation mode
|
||||
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
|
||||
current_leverage = self.get_leverage()
|
||||
simulated_fees = quantity * current_price * taker_fee_rate * current_leverage
|
||||
|
||||
# Create short position record
|
||||
self.positions[symbol] = Position(
|
||||
symbol=symbol,
|
||||
side='SHORT',
|
||||
quantity=quantity,
|
||||
entry_price=current_price,
|
||||
entry_time=datetime.now(),
|
||||
order_id=order.get('orderId', 'unknown')
|
||||
)
|
||||
=======
|
||||
if result and 'orderId' in result:
|
||||
# Use actual fill information if available, otherwise fall back to order parameters
|
||||
filled_quantity = result.get('executedQty', quantity)
|
||||
fill_price = result.get('avgPrice', current_price)
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
# Only create position if order was actually filled
|
||||
if result.get('filled', True): # Assume filled for backward compatibility
|
||||
@@ -1731,31 +1401,6 @@ class TradingExecutor:
|
||||
if self.simulation_mode:
|
||||
logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Short close logged but not executed")
|
||||
# Calculate simulated fees in simulation mode
|
||||
<<<<<<< HEAD
|
||||
taker_fee_rate = self.mexc_config.get('trading_fees', {}).get('taker_fee', 0.0006)
|
||||
simulated_fees = position.quantity * current_price * taker_fee_rate * current_leverage
|
||||
|
||||
# Calculate P&L for short position and hold time
|
||||
pnl = position.calculate_pnl(current_price) * current_leverage # Apply leverage to PnL
|
||||
=======
|
||||
trading_fees = self.exchange_config.get('trading_fees', {})
|
||||
taker_fee_rate = trading_fees.get('taker_fee', trading_fees.get('default_fee', 0.0006))
|
||||
simulated_fees = position.quantity * current_price * taker_fee_rate
|
||||
|
||||
# Get current leverage setting
|
||||
leverage = self.get_leverage()
|
||||
|
||||
# Calculate position size in USD
|
||||
position_size_usd = position.quantity * position.entry_price
|
||||
|
||||
# Calculate gross PnL (before fees) with leverage - SHORT profits when price falls
|
||||
gross_pnl = (position.entry_price - current_price) * position.quantity * leverage
|
||||
|
||||
# Calculate net PnL (after fees)
|
||||
net_pnl = gross_pnl - simulated_fees
|
||||
|
||||
# Calculate hold time
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
exit_time = datetime.now()
|
||||
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
|
||||
|
||||
@@ -1768,53 +1413,12 @@ class TradingExecutor:
|
||||
exit_price=current_price,
|
||||
entry_time=position.entry_time,
|
||||
exit_time=exit_time,
|
||||
<<<<<<< HEAD
|
||||
pnl=pnl - simulated_fees,
|
||||
fees=simulated_fees,
|
||||
confidence=confidence,
|
||||
hold_time_seconds=hold_time_seconds,
|
||||
leverage=current_leverage # Store leverage
|
||||
)
|
||||
|
||||
self.trade_history.append(trade_record)
|
||||
self.daily_loss += max(0, -(pnl - simulated_fees)) # Add to daily loss if negative
|
||||
=======
|
||||
pnl=net_pnl, # Store net PnL as the main PnL value
|
||||
fees=simulated_fees,
|
||||
confidence=confidence,
|
||||
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_records.append(trade_record)
|
||||
self.daily_loss += max(0, -net_pnl) # Use net_pnl instead of pnl
|
||||
|
||||
# Adjust profitability reward multiplier based on recent performance
|
||||
self._adjust_profitability_reward_multiplier()
|
||||
|
||||
# Update consecutive losses using net_pnl
|
||||
if net_pnl < -0.001: # A losing trade
|
||||
self.consecutive_losses += 1
|
||||
elif net_pnl > 0.001: # A winning trade
|
||||
self.consecutive_losses = 0
|
||||
else: # Breakeven trade
|
||||
self.consecutive_losses = 0
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
# Remove position
|
||||
del self.positions[symbol]
|
||||
self.last_trade_time[symbol] = datetime.now()
|
||||
self.daily_trades += 1
|
||||
|
||||
<<<<<<< HEAD
|
||||
logger.info(f"SHORT position closed - P&L: ${pnl - simulated_fees:.2f}")
|
||||
=======
|
||||
logger.info(f"SHORT position closed - Gross P&L: ${gross_pnl:.2f}, Net P&L: ${net_pnl:.2f}, Fees: ${simulated_fees:.3f}")
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
return True
|
||||
|
||||
try:
|
||||
@@ -1847,32 +1451,6 @@ class TradingExecutor:
|
||||
)
|
||||
|
||||
if order:
|
||||
<<<<<<< HEAD
|
||||
# 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 * current_leverage
|
||||
|
||||
# Calculate P&L, fees, and hold time
|
||||
pnl = position.calculate_pnl(current_price) * current_leverage # Apply leverage to PnL
|
||||
fees = simulated_fees
|
||||
=======
|
||||
# Calculate fees using real API data when available
|
||||
fees = self._calculate_real_trading_fees(order, symbol, position.quantity, current_price)
|
||||
|
||||
# Get current leverage setting
|
||||
leverage = self.get_leverage()
|
||||
|
||||
# Calculate position size in USD
|
||||
position_size_usd = position.quantity * position.entry_price
|
||||
|
||||
# Calculate gross PnL (before fees) with leverage - SHORT profits when price falls
|
||||
gross_pnl = (position.entry_price - current_price) * position.quantity * leverage
|
||||
|
||||
# Calculate net PnL (after fees)
|
||||
net_pnl = gross_pnl - fees
|
||||
|
||||
# Calculate hold time
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
exit_time = datetime.now()
|
||||
hold_time_seconds = (exit_time - position.entry_time).total_seconds()
|
||||
|
||||
@@ -1889,14 +1467,6 @@ class TradingExecutor:
|
||||
fees=fees,
|
||||
confidence=confidence,
|
||||
hold_time_seconds=hold_time_seconds,
|
||||
<<<<<<< HEAD
|
||||
leverage=current_leverage # Store leverage
|
||||
=======
|
||||
leverage=leverage,
|
||||
position_size_usd=position_size_usd,
|
||||
gross_pnl=gross_pnl,
|
||||
net_pnl=net_pnl
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
)
|
||||
|
||||
self.trade_history.append(trade_record)
|
||||
|
||||
@@ -21,15 +21,6 @@ Key Features:
|
||||
import asyncio
|
||||
import logging
|
||||
import numpy as np
|
||||
<<<<<<< HEAD
|
||||
from core.reward_calculator import RewardCalculator
|
||||
=======
|
||||
import pandas as pd
|
||||
import torch
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple, Any, Callable
|
||||
from dataclasses import dataclass
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
import threading
|
||||
import time
|
||||
from collections import deque
|
||||
@@ -186,48 +177,6 @@ class TrainingIntegration:
|
||||
collection_time = time.time() - start_time
|
||||
self._update_collection_stats(collection_time)
|
||||
|
||||
<<<<<<< HEAD
|
||||
# Get the model's device to ensure tensors are on the same device
|
||||
model_device = next(cnn_model.parameters()).device
|
||||
|
||||
# Create tensors
|
||||
features_tensor = torch.FloatTensor(features).unsqueeze(0).to(model_device)
|
||||
target_tensor = torch.LongTensor([target]).to(model_device)
|
||||
|
||||
# Training step
|
||||
cnn_model.train()
|
||||
cnn_model.optimizer.zero_grad()
|
||||
|
||||
outputs = cnn_model(features_tensor)
|
||||
|
||||
# Handle different output formats
|
||||
if isinstance(outputs, dict):
|
||||
if 'main_output' in outputs:
|
||||
logits = outputs['main_output']
|
||||
elif 'action_logits' in outputs:
|
||||
logits = outputs['action_logits']
|
||||
else:
|
||||
logits = list(outputs.values())[0]
|
||||
else:
|
||||
logits = outputs
|
||||
|
||||
# Calculate loss with reward weighting
|
||||
loss_fn = torch.nn.CrossEntropyLoss()
|
||||
loss = loss_fn(logits, target_tensor)
|
||||
|
||||
# Weight loss by reward magnitude
|
||||
weighted_loss = loss * abs(reward)
|
||||
|
||||
# Backward pass
|
||||
weighted_loss.backward()
|
||||
cnn_model.optimizer.step()
|
||||
|
||||
logger.info(f"CNN trained on trade outcome: P&L=${pnl:.2f}, loss={loss.item():.4f}")
|
||||
return True
|
||||
=======
|
||||
# Wait for next collection cycle
|
||||
time.sleep(self.config.collection_interval)
|
||||
>>>>>>> d49a473ed6f4aef55bfdd47d6370e53582be6b7b
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in data collection worker: {e}")
|
||||
|
||||
Reference in New Issue
Block a user