From bcf1d1ac73c4015c186e72a975e4e75b07ef06bb Mon Sep 17 00:00:00 2001 From: vijay sharma Date: Sat, 24 Jan 2026 20:46:56 +0100 Subject: [PATCH] feat: Add support for non-INR cash pairs like BTC/USDT in CCXT shim and mock mode, and improve green gate script logging. --- adapters/ccxt_shim/breeze_ccxt.py | 27 +++++++++++++++++++-------- adapters/ccxt_shim/instrument.py | 13 +++++++++---- debug_fetch_markets.py | 30 ++++++++++++++++++++++++++++++ freqtrade/exchange/icicibreeze.py | 14 +++++--------- scripts/green_gate.sh | 4 ++-- user_data/config_icicibreeze.json | 3 ++- 6 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 debug_fetch_markets.py diff --git a/adapters/ccxt_shim/breeze_ccxt.py b/adapters/ccxt_shim/breeze_ccxt.py index 54a08238b..1f10912a1 100644 --- a/adapters/ccxt_shim/breeze_ccxt.py +++ b/adapters/ccxt_shim/breeze_ccxt.py @@ -350,11 +350,13 @@ class BreezeCCXT(ccxt.Exchange): elif spec.type == InstrumentType.CASH: info = nse_symbols.get(spec.underlying) if not info and self._is_mock_mode(): - # Synthetic Index Cash support for mock mode (NIFTY/INR etc) - if spec.underlying in self._MOCK_BASE_PRICES or spec.underlying in { + # Synthetic Index Cash or Mock Pairs support (BTC/USDT, NIFTY/INR etc) + is_index = spec.underlying in self._MOCK_BASE_PRICES or spec.underlying in { "NIFTY", "BANKNIFTY", - }: + } + is_mock_pair = spec.underlying == "BTC" and spec.quote == "USDT" + if is_index or is_mock_pair: info = { "token": f"mock_{spec.underlying}", "symbol": spec.underlying, @@ -369,9 +371,11 @@ class BreezeCCXT(ccxt.Exchange): markets.append( { "id": info["token"], - "symbol": format_pair(spec), + "symbol": format_pair(spec) + if spec.quote == "INR" + else f"{spec.underlying}/{spec.quote}", "base": spec.underlying, - "quote": "INR", + "quote": spec.quote, "active": True, "type": "spot", "spot": True, @@ -641,10 +645,17 @@ class BreezeAsyncCCXT(ccxt_async.Exchange): return res async def load_markets(self, reload: bool = False, params: dict | None = None): + if not reload and self.markets: + return self.markets markets = await asyncio.to_thread(self.sync_exchange.load_markets, reload, params) - self.markets = markets - self.symbols = list(markets.keys()) - return markets + self.set_markets(markets) + if "INR" not in self.currencies: + self.currencies["INR"] = { + "id": "INR", + "code": "INR", + "precision": 2, + } + return self.markets async def fetch_markets(self, params: dict | None = None): return await asyncio.to_thread(self.sync_exchange.fetch_markets, params) diff --git a/adapters/ccxt_shim/instrument.py b/adapters/ccxt_shim/instrument.py index d70edc946..ae0743d39 100644 --- a/adapters/ccxt_shim/instrument.py +++ b/adapters/ccxt_shim/instrument.py @@ -32,10 +32,10 @@ class InstrumentSpec: strike: float | None = None right: str | None = None - def validate(self) -> None: + def validate(self, strict: bool = True) -> None: if not self.underlying: raise ValueError("Underlying is required.") - if self.quote != "INR": + if strict and self.quote != "INR": raise ValueError("Quote must be INR for canonical pairs.") if self.type == InstrumentType.CASH: if any([self.expiry_yyyymmdd, self.strike, self.right]): @@ -72,13 +72,18 @@ def parse_pair(pair: str) -> InstrumentSpec: - Options: UNDERLYING-YYYYMMDD-STRIKE-CE/INR """ - cash_match = re.fullmatch(r"(?P[A-Z0-9]+)\/INR", pair, re.IGNORECASE) + cash_match = re.fullmatch( + r"(?P[A-Z0-9.]+)\/(?P[A-Z0-9]+)", pair, re.IGNORECASE + ) if cash_match: spec = InstrumentSpec( type=InstrumentType.CASH, underlying=cash_match.group("underlying").upper(), + quote=cash_match.group("quote").upper(), ) - spec.validate() + # We allow non-INR in parse_pair, but validate(strict=True) would still fail it. + # For now, we call validate(strict=False) to allow parsing. + spec.validate(strict=False) return spec fut_match = re.fullmatch( diff --git a/debug_fetch_markets.py b/debug_fetch_markets.py new file mode 100644 index 000000000..7f7210e1f --- /dev/null +++ b/debug_fetch_markets.py @@ -0,0 +1,30 @@ +import logging +import json +from adapters.ccxt_shim.breeze_ccxt import BreezeCCXT + +logging.basicConfig(level=logging.DEBUG) + + +def debug_fetch(): + with open("user_data/config_icicibreeze.json", "r") as f: + config = json.load(f) + + exchange_config = config["exchange"] + # Freqtrade typically passes the 'exchange' sub-dict to CCXT + # but some fields are modified or added. + + print(f"Exchange Config Keys: {list(exchange_config.keys())}") + print(f"Pair Whitelist: {exchange_config.get('pair_whitelist')}") + + exchange = BreezeCCXT(exchange_config) + print(f"Exchange Name: {exchange.name}") + print(f"Exchange Config in object: {list(exchange.config.keys())}") + + markets = exchange.fetch_markets() + print(f"Fetched {len(markets)} markets.") + for m in markets: + print(f" - {m['symbol']}") + + +if __name__ == "__main__": + debug_fetch() diff --git a/freqtrade/exchange/icicibreeze.py b/freqtrade/exchange/icicibreeze.py index a5003815d..614836a05 100644 --- a/freqtrade/exchange/icicibreeze.py +++ b/freqtrade/exchange/icicibreeze.py @@ -1,15 +1,8 @@ -""" -ICICI Breeze exchange integration. -""" - -import asyncio import logging -import os -from typing import Any, Dict, Optional +from typing import Any import ccxt import ccxt.async_support as ccxt_async -from freqtrade.exceptions import OperationalException from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS from freqtrade.exchange.exchange import Exchange @@ -59,7 +52,7 @@ class Icicibreeze(Exchange): } def _init_ccxt( - self, exchange_config: Dict[str, Any], sync: bool, ccxt_kwargs: Dict[str, Any] + self, exchange_config: dict[str, Any], sync: bool, ccxt_kwargs: dict[str, Any] ) -> Any: # Determine Mode mode = self._config.get("icici_mode") or exchange_config.get("icici_mode") or "stub" @@ -80,6 +73,9 @@ class Icicibreeze(Exchange): exchange_config["key"] = exchange_config.get("key") exchange_config["secret"] = exchange_config.get("secret") exchange_config["dry_run"] = self._config.get("dry_run") + exchange_config["pair_whitelist"] = self._config.get("exchange", {}).get( + "pair_whitelist", [] + ) if sync: return BreezeCCXT(exchange_config) diff --git a/scripts/green_gate.sh b/scripts/green_gate.sh index 25f615372..2f7bd3dad 100644 --- a/scripts/green_gate.sh +++ b/scripts/green_gate.sh @@ -18,8 +18,8 @@ echo "--- 4. Ticker Smoke Test ---" $PYTHON scripts/smoke_icicibreeze_ticker.py >/tmp/ticker.txt echo "--- 5. Download Data Test (BTC & INR) ---" -$FREQTRADE download-data -c user_data/config_icicibreeze.json --userdir user_data --timeframes 5m --pairs BTC/USDT --days 2 -v >/tmp/dl_btc.txt -$FREQTRADE download-data -c user_data/config_icicibreeze.json --userdir user_data --timeframes 5m --pairs RELIANCE/INR --days 2 -v >/tmp/dl_inr.txt +$FREQTRADE download-data -c user_data/config_icicibreeze.json --userdir user_data --timeframes 5m --pairs BTC/USDT --days 2 -v >/tmp/dl_btc.txt 2>&1 +$FREQTRADE download-data -c user_data/config_icicibreeze.json --userdir user_data --timeframes 5m --pairs RELIANCE/INR --days 2 -v >/tmp/dl_inr.txt 2>&1 echo "--- 6. Dry Run Trade Test ---" # Start trade in background, redirecting both stdout and stderr to capture logs diff --git a/user_data/config_icicibreeze.json b/user_data/config_icicibreeze.json index 0e3a89ba5..35b0e989f 100644 --- a/user_data/config_icicibreeze.json +++ b/user_data/config_icicibreeze.json @@ -18,7 +18,8 @@ "pair_whitelist": [ "RELIANCE/INR", "NIFTY-20260226-22000-CE/INR", - "NIFTY-20260226-FUT/INR" + "NIFTY-20260226-FUT/INR", + "BTC/USDT" ], "pair_blacklist": [ "BNB/.*"