Skip to content

Commit 8718418

Browse files
committed
fix: resolve CI failures - ccxtstore staticmethod, async tests, optimization multiprocessing
- ccxtstore.py: move retry from @staticmethod to module-level _ccxt_retry() to fix 'staticmethod object is not callable' on Python 3.10+ Windows - setup.py: add pytest-asyncio to dev dependencies - pytest.ini: add asyncio_mode = auto for async test support - test_strategy.py/v2/test_improved_examples.py: use maxcpus=1 in optstrategy tests to avoid multiprocessing pickle errors in CI
1 parent 71a885d commit 8718418

File tree

6 files changed

+48
-49
lines changed

6 files changed

+48
-49
lines changed

backtrader/stores/ccxtstore.py

Lines changed: 43 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,39 @@
3030

3131
logger = logging.getLogger(__name__)
3232

33+
34+
def _ccxt_retry(method):
35+
"""Module-level retry decorator for CCXTStore methods."""
36+
37+
@wraps(method)
38+
def retry_method(self, *args, **kwargs):
39+
for i in range(self.retries):
40+
if self.debug:
41+
logger.debug("%s - %s - Attempt %d", datetime.now(), method.__name__, i)
42+
43+
if self._rate_limiter:
44+
self._rate_limiter.acquire()
45+
else:
46+
time.sleep(self.exchange.rateLimit / 1000)
47+
48+
try:
49+
result = method(self, *args, **kwargs)
50+
if self._rate_limiter and hasattr(self._rate_limiter, "on_success"):
51+
self._rate_limiter.on_success()
52+
if self._connection_manager:
53+
self._connection_manager.mark_success()
54+
return result
55+
except (NetworkError, ExchangeError) as e:
56+
if self._rate_limiter and hasattr(self._rate_limiter, "on_rate_limit_error"):
57+
if "rate" in str(e).lower() or "429" in str(e):
58+
self._rate_limiter.on_rate_limit_error()
59+
if self._connection_manager:
60+
self._connection_manager.mark_failure()
61+
if i == self.retries - 1:
62+
raise
63+
64+
return retry_method
65+
3366
# TimeFrame constants to avoid circular import with backtrader
3467
# Values match backtrader.dataseries.TimeFrame
3568
_TF_MINUTES = 4
@@ -239,43 +272,7 @@ def get_granularity(self, timeframe, compression):
239272

240273
return granularity
241274

242-
@staticmethod
243-
def retry(method):
244-
"""Decorator for retry with rate limiting."""
245-
246-
@wraps(method)
247-
def retry_method(self, *args, **kwargs):
248-
for i in range(self.retries):
249-
if self.debug:
250-
logger.debug("%s - %s - Attempt %d", datetime.now(), method.__name__, i)
251-
252-
# Use rate limiter if available, otherwise fall back to basic sleep
253-
if self._rate_limiter:
254-
self._rate_limiter.acquire()
255-
else:
256-
time.sleep(self.exchange.rateLimit / 1000)
257-
258-
try:
259-
result = method(self, *args, **kwargs)
260-
# Mark success for adaptive rate limiter
261-
if self._rate_limiter and hasattr(self._rate_limiter, "on_success"):
262-
self._rate_limiter.on_success()
263-
if self._connection_manager:
264-
self._connection_manager.mark_success()
265-
return result
266-
except (NetworkError, ExchangeError) as e:
267-
# Mark failure for adaptive rate limiter
268-
if self._rate_limiter and hasattr(self._rate_limiter, "on_rate_limit_error"):
269-
if "rate" in str(e).lower() or "429" in str(e):
270-
self._rate_limiter.on_rate_limit_error()
271-
if self._connection_manager:
272-
self._connection_manager.mark_failure()
273-
if i == self.retries - 1:
274-
raise
275-
276-
return retry_method
277-
278-
@retry
275+
@_ccxt_retry
279276
def get_wallet_balance(self, params=None):
280277
"""Fetch the wallet balance from the exchange.
281278
@@ -290,7 +287,7 @@ def get_wallet_balance(self, params=None):
290287
balance = self.exchange.fetch_balance(params)
291288
return balance
292289

293-
@retry
290+
@_ccxt_retry
294291
def get_balance(self):
295292
"""Fetch and update the current balance from the exchange.
296293
@@ -305,7 +302,7 @@ def get_balance(self):
305302
self._cash = cash if cash else 0
306303
self._value = value if value else 0
307304

308-
@retry
305+
@_ccxt_retry
309306
def getposition(self):
310307
"""Get the current position value.
311308
@@ -314,7 +311,7 @@ def getposition(self):
314311
"""
315312
return self._value
316313

317-
@retry
314+
@_ccxt_retry
318315
def create_order(self, symbol, order_type, side, amount, price, params):
319316
"""Create an order on the exchange.
320317
@@ -334,7 +331,7 @@ def create_order(self, symbol, order_type, side, amount, price, params):
334331
symbol=symbol, type=order_type, side=side, amount=amount, price=price, params=params
335332
)
336333

337-
@retry
334+
@_ccxt_retry
338335
def cancel_order(self, order_id, symbol):
339336
"""Cancel an existing order on the exchange.
340337
@@ -347,7 +344,7 @@ def cancel_order(self, order_id, symbol):
347344
"""
348345
return self.exchange.cancel_order(order_id, symbol)
349346

350-
@retry
347+
@_ccxt_retry
351348
def fetch_trades(self, symbol):
352349
"""Fetch recent trades for a symbol from the exchange.
353350
@@ -359,7 +356,7 @@ def fetch_trades(self, symbol):
359356
"""
360357
return self.exchange.fetch_trades(symbol)
361358

362-
@retry
359+
@_ccxt_retry
363360
def fetch_ohlcv(self, symbol, timeframe, since, limit, params=None):
364361
"""Fetch OHLCV (candlestick) data from the exchange.
365362
@@ -382,7 +379,7 @@ def fetch_ohlcv(self, symbol, timeframe, since, limit, params=None):
382379
symbol, timeframe=timeframe, since=since, limit=limit, params=params
383380
)
384381

385-
@retry
382+
@_ccxt_retry
386383
def fetch_order(self, oid, symbol):
387384
"""Fetch details of a specific order from the exchange.
388385
@@ -395,7 +392,7 @@ def fetch_order(self, oid, symbol):
395392
"""
396393
return self.exchange.fetch_order(oid, symbol)
397394

398-
@retry
395+
@_ccxt_retry
399396
def fetch_open_orders(self):
400397
"""Fetch all open orders from the exchange.
401398
@@ -404,7 +401,7 @@ def fetch_open_orders(self):
404401
"""
405402
return self.exchange.fetchOpenOrders()
406403

407-
@retry
404+
@_ccxt_retry
408405
def private_end_point(self, type, endpoint, params):
409406
"""Call any private endpoint on the exchange.
410407

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ markers =
1313
trading: Tests that place real orders on sandbox exchange
1414
live: Tests that require live production environment
1515
slow: Tests that take a long time to execute
16+
asyncio_mode = auto
1617
filterwarnings =
1718
ignore::RuntimeWarning
1819
ignore::DeprecationWarning

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"pytest-xdist",
3131
"pytest-html",
3232
"pytest-timeout",
33+
"pytest-asyncio",
3334
"ruff",
3435
"black",
3536
"isort",

tests/integration/test_improved_examples.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def test_2_3_UT_001_strategy_optimization(cerebro_with_data):
352352
cerebro_with_data.optstrategy(_OptSMAStrategy, period=range(10, 20, 5))
353353

354354
# Act
355-
results = cerebro_with_data.run()
355+
results = cerebro_with_data.run(maxcpus=1)
356356

357357
# Assert - Should have multiple results from optimization
358358
assert len(results) > 1, "Optimization should return multiple results"

tests/unit/core/test_strategy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def test_strategy_optimization(main=False):
246246
cerebro.adddata(data)
247247
cerebro.optstrategy(SampleStrategy1, period=range(10, 20, 5))
248248

249-
results = cerebro.run()
249+
results = cerebro.run(maxcpus=1)
250250

251251
if main:
252252
print(f"Optimization tested {len(results)} parameter combinations")

tests/unit/core/test_strategy_v2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def test_2_3_UT_001_strategy_optimization(cerebro_engine):
216216
cerebro_engine.optstrategy(SampleStrategy1, period=range(10, 20, 5))
217217

218218
# Act
219-
results = cerebro_engine.run()
219+
results = cerebro_engine.run(maxcpus=1)
220220

221221
# Assert - Should have multiple results from optimization
222222
assert len(results) > 1 # More than one parameter combination

0 commit comments

Comments
 (0)