mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-11-29 00:23:07 +00:00
Compare commits
12 Commits
1bd60c2afb
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfe9aac16b | ||
|
|
1770a68457 | ||
|
|
57fd455adf | ||
|
|
61ecaa4c41 | ||
|
|
682c4137b4 | ||
|
|
759c18df3d | ||
|
|
04fda255de | ||
|
|
26f23c10b5 | ||
|
|
001baa07b1 | ||
|
|
72724037af | ||
|
|
77e8a53572 | ||
|
|
f98efdbe07 |
@@ -675,7 +675,7 @@ Should you experience problems you suspect are caused by websockets, you can dis
|
|||||||
Should you be required to use a proxy, please refer to the [proxy section](#using-a-proxy-with-freqtrade) for more information.
|
Should you be required to use a proxy, please refer to the [proxy section](#using-a-proxy-with-freqtrade) for more information.
|
||||||
|
|
||||||
!!! Info "Rollout"
|
!!! Info "Rollout"
|
||||||
We're implementing this out slowly, ensuring stability of your bots.
|
We're rolling this out slowly, ensuring stability of your bots.
|
||||||
Currently, usage is limited to ohlcv data streams.
|
Currently, usage is limited to ohlcv data streams.
|
||||||
It's also limited to a few exchanges, with new exchanges being added on an ongoing basis.
|
It's also limited to a few exchanges, with new exchanges being added on an ongoing basis.
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,19 @@ Alternatively (e.g. if your system is not supported by the setup.sh script), fol
|
|||||||
|
|
||||||
This will install all required tools for development, including `pytest`, `ruff`, `mypy`, and `coveralls`.
|
This will install all required tools for development, including `pytest`, `ruff`, `mypy`, and `coveralls`.
|
||||||
|
|
||||||
Then install the git hook scripts by running `pre-commit install`, so your changes will be verified locally before committing.
|
Run the following command to install the git hook scripts:
|
||||||
This avoids a lot of waiting for CI already, as some basic formatting checks are done locally on your machine.
|
|
||||||
|
|
||||||
Before opening a pull request, please familiarize yourself with our [Contributing Guidelines](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md).
|
``` bash
|
||||||
|
pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
|
These pre-commit scripts check your changes automatically before each commit.
|
||||||
|
If any formatting issues are found, the commit will fail and will prompt for fixes.
|
||||||
|
This reduces unnecessary CI failures, reduces maintenance burden, and improves code quality.
|
||||||
|
|
||||||
|
You can run the checks manually when necessary with `pre-commit run -a`.
|
||||||
|
|
||||||
|
Before opening a pull request, please also familiarize yourself with our [Contributing Guidelines](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md).
|
||||||
|
|
||||||
### Devcontainer setup
|
### Devcontainer setup
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1769,7 +1769,7 @@ class Exchange:
|
|||||||
balances.pop("total", None)
|
balances.pop("total", None)
|
||||||
balances.pop("used", None)
|
balances.pop("used", None)
|
||||||
|
|
||||||
self._log_exchange_response("fetch_balances", balances)
|
self._log_exchange_response("fetch_balance", balances)
|
||||||
return balances
|
return balances
|
||||||
except ccxt.DDoSProtection as e:
|
except ccxt.DDoSProtection as e:
|
||||||
raise DDosProtection(e) from e
|
raise DDosProtection(e) from e
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from freqtrade.constants import BuySell
|
from freqtrade.constants import BuySell
|
||||||
from freqtrade.enums import MarginMode, TradingMode
|
from freqtrade.enums import MarginMode, TradingMode
|
||||||
@@ -56,6 +57,13 @@ class Hyperliquid(Exchange):
|
|||||||
config.update(super()._ccxt_config)
|
config.update(super()._ccxt_config)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
def market_is_tradable(self, market: dict[str, Any]) -> bool:
|
||||||
|
parent_check = super().market_is_tradable(market)
|
||||||
|
|
||||||
|
# Exclude hip3 markets for now - which have the format XYZ:GOOGL/USDT:USDT -
|
||||||
|
# and XYZ:GOOGL as base
|
||||||
|
return parent_check and ":" not in market["base"]
|
||||||
|
|
||||||
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
|
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
|
||||||
# There are no leverage tiers
|
# There are no leverage tiers
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class Kraken(Exchange):
|
|||||||
balances.pop("free", None)
|
balances.pop("free", None)
|
||||||
balances.pop("total", None)
|
balances.pop("total", None)
|
||||||
balances.pop("used", None)
|
balances.pop("used", None)
|
||||||
self._log_exchange_response("fetch_balances", balances)
|
self._log_exchange_response("fetch_balance", balances)
|
||||||
|
|
||||||
# Consolidate balances
|
# Consolidate balances
|
||||||
balances = self.consolidate_balances(balances)
|
balances = self.consolidate_balances(balances)
|
||||||
@@ -104,7 +104,7 @@ class Kraken(Exchange):
|
|||||||
balances[bal]["used"] = sum(order[1] for order in order_list if order[0] == bal)
|
balances[bal]["used"] = sum(order[1] for order in order_list if order[0] == bal)
|
||||||
balances[bal]["free"] = balances[bal]["total"] - balances[bal]["used"]
|
balances[bal]["free"] = balances[bal]["total"] - balances[bal]["used"]
|
||||||
|
|
||||||
self._log_exchange_response("fetch_balances2", balances)
|
self._log_exchange_response("fetch_balance2", balances)
|
||||||
return balances
|
return balances
|
||||||
except ccxt.DDoSProtection as e:
|
except ccxt.DDoSProtection as e:
|
||||||
raise DDosProtection(e) from e
|
raise DDosProtection(e) from e
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ class Backtesting:
|
|||||||
|
|
||||||
self.config["dry_run"] = True
|
self.config["dry_run"] = True
|
||||||
self.price_pair_prec: dict[str, Series] = {}
|
self.price_pair_prec: dict[str, Series] = {}
|
||||||
|
self.available_pairs: list[str] = []
|
||||||
self.run_ids: dict[str, str] = {}
|
self.run_ids: dict[str, str] = {}
|
||||||
self.strategylist: list[IStrategy] = []
|
self.strategylist: list[IStrategy] = []
|
||||||
self.all_bt_content: dict[str, BacktestContentType] = {}
|
self.all_bt_content: dict[str, BacktestContentType] = {}
|
||||||
@@ -176,7 +177,8 @@ class Backtesting:
|
|||||||
self._validate_pairlists_for_backtesting()
|
self._validate_pairlists_for_backtesting()
|
||||||
|
|
||||||
self.dataprovider.add_pairlisthandler(self.pairlists)
|
self.dataprovider.add_pairlisthandler(self.pairlists)
|
||||||
self.pairlists.refresh_pairlist()
|
self.dynamic_pairlist: bool = self.config.get("enable_dynamic_pairlist", False)
|
||||||
|
self.pairlists.refresh_pairlist(only_first=self.dynamic_pairlist)
|
||||||
|
|
||||||
if len(self.pairlists.whitelist) == 0:
|
if len(self.pairlists.whitelist) == 0:
|
||||||
raise OperationalException("No pair in whitelist.")
|
raise OperationalException("No pair in whitelist.")
|
||||||
@@ -211,7 +213,6 @@ class Backtesting:
|
|||||||
self._can_short = self.trading_mode != TradingMode.SPOT
|
self._can_short = self.trading_mode != TradingMode.SPOT
|
||||||
self._position_stacking: bool = self.config.get("position_stacking", False)
|
self._position_stacking: bool = self.config.get("position_stacking", False)
|
||||||
self.enable_protections: bool = self.config.get("enable_protections", False)
|
self.enable_protections: bool = self.config.get("enable_protections", False)
|
||||||
self.dynamic_pairlist: bool = self.config.get("enable_dynamic_pairlist", False)
|
|
||||||
migrate_data(config, self.exchange)
|
migrate_data(config, self.exchange)
|
||||||
|
|
||||||
self.init_backtest()
|
self.init_backtest()
|
||||||
@@ -335,10 +336,12 @@ class Backtesting:
|
|||||||
self.progress.set_new_value(1)
|
self.progress.set_new_value(1)
|
||||||
self._load_bt_data_detail()
|
self._load_bt_data_detail()
|
||||||
self.price_pair_prec = {}
|
self.price_pair_prec = {}
|
||||||
|
|
||||||
for pair in self.pairlists.whitelist:
|
for pair in self.pairlists.whitelist:
|
||||||
if pair in data:
|
if pair in data:
|
||||||
# Load price precision logic
|
# Load price precision logic
|
||||||
self.price_pair_prec[pair] = get_tick_size_over_time(data[pair])
|
self.price_pair_prec[pair] = get_tick_size_over_time(data[pair])
|
||||||
|
self.available_pairs.append(pair)
|
||||||
return data, self.timerange
|
return data, self.timerange
|
||||||
|
|
||||||
def _load_bt_data_detail(self) -> None:
|
def _load_bt_data_detail(self) -> None:
|
||||||
@@ -1587,7 +1590,7 @@ class Backtesting:
|
|||||||
self.check_abort()
|
self.check_abort()
|
||||||
|
|
||||||
if self.dynamic_pairlist and self.pairlists:
|
if self.dynamic_pairlist and self.pairlists:
|
||||||
self.pairlists.refresh_pairlist()
|
self.pairlists.refresh_pairlist(pairs=self.available_pairs)
|
||||||
pairs = self.pairlists.whitelist
|
pairs = self.pairlists.whitelist
|
||||||
|
|
||||||
# Reset open trade count for this candle
|
# Reset open trade count for this candle
|
||||||
|
|||||||
@@ -75,11 +75,11 @@ def init_plotscript(config, markets: list, startup_candles: int = 0):
|
|||||||
)
|
)
|
||||||
|
|
||||||
no_trades = False
|
no_trades = False
|
||||||
filename = config.get("exportfilename")
|
filename = config.get("exportfilename") or config.get("exportdirectory")
|
||||||
if config.get("no_trades", False):
|
if config.get("no_trades", False):
|
||||||
no_trades = True
|
no_trades = True
|
||||||
elif config["trade_source"] == "file":
|
elif config["trade_source"] == "file":
|
||||||
if not filename.is_dir() and not filename.is_file():
|
if not filename or (not filename.is_dir() and not filename.is_file()):
|
||||||
logger.warning("Backtest file is missing skipping trades.")
|
logger.warning("Backtest file is missing skipping trades.")
|
||||||
no_trades = True
|
no_trades = True
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -134,8 +134,20 @@ class PairListManager(LoggingMixin):
|
|||||||
def _get_cached_tickers(self) -> Tickers:
|
def _get_cached_tickers(self) -> Tickers:
|
||||||
return self._exchange.get_tickers()
|
return self._exchange.get_tickers()
|
||||||
|
|
||||||
def refresh_pairlist(self) -> None:
|
def refresh_pairlist(self, only_first: bool = False, pairs: list[str] | None = None) -> None:
|
||||||
"""Run pairlist through all configured Pairlist Handlers."""
|
"""
|
||||||
|
Run pairlist through all configured Pairlist Handlers.
|
||||||
|
|
||||||
|
:param only_first: If True, only run the first PairList handler (the generator)
|
||||||
|
and skip all subsequent filters. Used during backtesting startup to ensure
|
||||||
|
historic data is loaded for the complete universe of pairs that the
|
||||||
|
generator can produce (even if later filters would reduce the list size).
|
||||||
|
Prevents missing data when a filter returns a variable number of pairs
|
||||||
|
across refresh cycles.
|
||||||
|
:param pairs: Optional list of pairs to intersect with the generated pairlist.
|
||||||
|
Only pairs present both in the generated list and this parameter are kept.
|
||||||
|
Used in backtesting to filter out pairs with no available data.
|
||||||
|
"""
|
||||||
# Tickers should be cached to avoid calling the exchange on each call.
|
# Tickers should be cached to avoid calling the exchange on each call.
|
||||||
tickers: dict = {}
|
tickers: dict = {}
|
||||||
if self._tickers_needed:
|
if self._tickers_needed:
|
||||||
@@ -144,10 +156,15 @@ class PairListManager(LoggingMixin):
|
|||||||
# Generate the pairlist with first Pairlist Handler in the chain
|
# Generate the pairlist with first Pairlist Handler in the chain
|
||||||
pairlist = self._pairlist_handlers[0].gen_pairlist(tickers)
|
pairlist = self._pairlist_handlers[0].gen_pairlist(tickers)
|
||||||
|
|
||||||
# Process all Pairlist Handlers in the chain
|
# Optional intersection with an explicit list of pairs (used in backtesting)
|
||||||
# except for the first one, which is the generator.
|
if pairs is not None:
|
||||||
for pairlist_handler in self._pairlist_handlers[1:]:
|
pairlist = [p for p in pairlist if p in pairs]
|
||||||
pairlist = pairlist_handler.filter_pairlist(pairlist, tickers)
|
|
||||||
|
if not only_first:
|
||||||
|
# Process all Pairlist Handlers in the chain
|
||||||
|
# except for the first one, which is the generator.
|
||||||
|
for pairlist_handler in self._pairlist_handlers[1:]:
|
||||||
|
pairlist = pairlist_handler.filter_pairlist(pairlist, tickers)
|
||||||
|
|
||||||
# Validation against blacklist happens after the chain of Pairlist Handlers
|
# Validation against blacklist happens after the chain of Pairlist Handlers
|
||||||
# to ensure blacklist is respected.
|
# to ensure blacklist is respected.
|
||||||
|
|||||||
@@ -2772,7 +2772,7 @@ def test_time_pair_generator_open_trades_first(mocker, default_conf, dynamic_pai
|
|||||||
dummy_row = (end_date, 1.0, 1.1, 0.9, 1.0, 0, 0, 0, 0, None, None)
|
dummy_row = (end_date, 1.0, 1.1, 0.9, 1.0, 0, 0, 0, 0, None, None)
|
||||||
data = {pair: [dummy_row] for pair in pairs}
|
data = {pair: [dummy_row] for pair in pairs}
|
||||||
|
|
||||||
def mock_refresh(self):
|
def mock_refresh(self, **kwargs):
|
||||||
# Simulate shuffle
|
# Simulate shuffle
|
||||||
self._whitelist = pairs[::-1] # ['ETH/BTC', 'NEO/BTC', 'LTC/BTC', 'XRP/BTC']
|
self._whitelist = pairs[::-1] # ['ETH/BTC', 'NEO/BTC', 'LTC/BTC', 'XRP/BTC']
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user