diff --git a/env_example.txt b/env_example.txt index ea9a7e01..38e6ee50 100644 --- a/env_example.txt +++ b/env_example.txt @@ -62,6 +62,13 @@ LOG_FILE=trading_log.csv TIMEZONE=Asia/Shanghai +# Ticker映射配置 (对冲模式专用) +# 用于解决不同交易所使用不同ticker名称的问题 +# 格式: {"源交易所_to_目标交易所": {"源ticker": "目标ticker"}} +# 例子: Extended使用"EUR",Lighter使用"EURUSD" +# 注意: lighter上交易EUR要使用Isolated保证金模式,请先手动切换 +TICKER_MAPPING={"extended_to_lighter": {"EUR":"EURUSD","GBP":"GBPUSD","JPY":"JPYUSD","AUD":"AUDUSD"}, "backpack_to_lighter": {"EUR":"EURUSD"}, "grvt_to_lighter": {"EUR":"EURUSD"}} + # EdgeX API Endpoints EDGEX_BASE_URL=https://pro.edgex.exchange EDGEX_WS_URL=wss://quote.edgex.exchange \ No newline at end of file diff --git a/exchanges/extended.py b/exchanges/extended.py index c26ad0e0..903dfd04 100644 --- a/exchanges/extended.py +++ b/exchanges/extended.py @@ -13,7 +13,7 @@ from helpers.logger import TradingLogger from x10.perpetual.trading_client import PerpetualTradingClient -from x10.perpetual.configuration import STARKNET_MAINNET_CONFIG +from x10.perpetual.configuration import MAINNET_CONFIG as STARKNET_MAINNET_CONFIG from x10.perpetual.accounts import StarkPerpetualAccount from x10.perpetual.orders import TimeInForce, OrderSide diff --git a/hedge/hedge_mode_ext.py b/hedge/hedge_mode_ext.py index 29a4d9ff..c2a75596 100644 --- a/hedge/hedge_mode_ext.py +++ b/hedge/hedge_mode_ext.py @@ -147,6 +147,9 @@ def __init__(self, ticker: str, order_quantity: Decimal, fill_timeout: int = 5, self.extended_vault = os.getenv('EXTENDED_VAULT') self.extended_stark_key_private = os.getenv('EXTENDED_STARK_KEY_PRIVATE') self.extended_stark_key_public = os.getenv('EXTENDED_STARK_KEY_PUBLIC') + + # Load ticker mapping configuration + self._load_ticker_mapping() self.extended_api_key = os.getenv('EXTENDED_API_KEY') def shutdown(self, signum=None, frame=None): @@ -179,6 +182,52 @@ def shutdown(self, signum=None, frame=None): except Exception: pass + def _load_ticker_mapping(self): + """Load ticker mapping configuration from environment variables.""" + # Default fallback mapping for common currency pairs + default_mapping = { + "extended_to_lighter": { + "EUR": "EURUSD", + "GBP": "GBPUSD", + "JPY": "JPYUSD", + "AUD": "AUDUSD" + } + } + + try: + # Try to load from environment variable + ticker_mapping_str = os.getenv('TICKER_MAPPING') + + if ticker_mapping_str: + self.ticker_mapping = json.loads(ticker_mapping_str) + self.logger.info(f"✅ Loaded ticker mapping from .env: {self.ticker_mapping}") + else: + self.ticker_mapping = default_mapping + self.logger.info(f"⚠️ TICKER_MAPPING not found in .env, using default mapping: {default_mapping}") + + except json.JSONDecodeError as e: + self.ticker_mapping = default_mapping + self.logger.error(f"⚠️ Error parsing TICKER_MAPPING JSON: {e}, using default mapping: {default_mapping}") + except Exception as e: + self.ticker_mapping = default_mapping + self.logger.error(f"⚠️ Unexpected error loading ticker mapping: {e}, using default mapping: {default_mapping}") + + def get_lighter_ticker(self): + """Get the corresponding Lighter ticker for the current ticker.""" + mapping_key = "extended_to_lighter" + + # Check if mapping exists for this exchange combination + if mapping_key in self.ticker_mapping: + exchange_mapping = self.ticker_mapping[mapping_key] + if self.ticker in exchange_mapping: + lighter_ticker = exchange_mapping[self.ticker] + self.logger.info(f"🔄 Ticker mapping: Extended '{self.ticker}' → Lighter '{lighter_ticker}'") + return lighter_ticker + + # If no mapping found, use original ticker and log warning + self.logger.warning(f"⚠️ No ticker mapping found for Extended '{self.ticker}' to Lighter, using original ticker") + return self.ticker + def _initialize_csv_file(self): """Initialize CSV file with headers if it doesn't exist.""" if not os.path.exists(self.csv_filename): @@ -551,6 +600,9 @@ def get_lighter_market_config(self) -> Tuple[int, int, int, Decimal]: url = f"{self.lighter_base_url}/api/v1/orderBooks" headers = {"accept": "application/json"} + # Get the mapped ticker for Lighter + lighter_ticker = self.get_lighter_ticker() + try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() @@ -564,15 +616,16 @@ def get_lighter_market_config(self) -> Tuple[int, int, int, Decimal]: raise Exception("Unexpected response format") for market in data["order_books"]: - if market["symbol"] == self.ticker: + if market["symbol"] == lighter_ticker: price_multiplier = pow(10, market["supported_price_decimals"]) - return (market["market_id"], - pow(10, market["supported_size_decimals"]), + self.logger.info(f"✅ Found Lighter market config for {lighter_ticker}: market_id={market['market_id']}") + return (market["market_id"], + pow(10, market["supported_size_decimals"]), price_multiplier, Decimal("1") / (Decimal("10") ** market["supported_price_decimals"]) ) - raise Exception(f"Ticker {self.ticker} not found") + raise Exception(f"Ticker {lighter_ticker} not found") except Exception as e: self.logger.error(f"⚠️ Error getting market config: {e}")