Merge branch 'develop' into feat/pairlistconfig

This commit is contained in:
Matthias
2023-05-28 10:01:43 +02:00
14 changed files with 47 additions and 43 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
""" Freqtrade bot """ """ Freqtrade bot """
__version__ = '2023.5.dev' __version__ = '2023.6.dev'
if 'dev' in __version__: if 'dev' in __version__:
from pathlib import Path from pathlib import Path
+1 -1
View File
@@ -174,7 +174,7 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
return return
for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]): for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]):
if (pl.get('method') == 'StaticPairList' if (isinstance(pl, dict) and pl.get('method') == 'StaticPairList'
and not conf.get('exchange', {}).get('pair_whitelist')): and not conf.get('exchange', {}).get('pair_whitelist')):
raise OperationalException("StaticPairList requires pair_whitelist to be set.") raise OperationalException("StaticPairList requires pair_whitelist to be set.")
+1 -1
View File
@@ -65,7 +65,7 @@ class Binance(Exchange):
""" """
try: try:
if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']: if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']:
position_side = self._api.fapiPrivateGetPositionsideDual() position_side = self._api.fapiPrivateGetPositionSideDual()
self._log_exchange_response('position_side_setting', position_side) self._log_exchange_response('position_side_setting', position_side)
assets_margin = self._api.fapiPrivateGetMultiAssetsMargin() assets_margin = self._api.fapiPrivateGetMultiAssetsMargin()
self._log_exchange_response('multi_asset_margin', assets_margin) self._log_exchange_response('multi_asset_margin', assets_margin)
+26 -21
View File
@@ -169,27 +169,11 @@ class Okx(Exchange):
params['posSide'] = self._get_posSide(side, True) params['posSide'] = self._get_posSide(side, True)
return params return params
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict: def _convert_stop_order(self, pair: str, order_id: str, order: Dict) -> Dict:
if self._config['dry_run']: if (
return self.fetch_dry_run_order(order_id) order['status'] == 'closed'
and (real_order_id := order.get('info', {}).get('ordId')) is not None
try: ):
params1 = {'stop': True}
order_reg = self._api.fetch_order(order_id, pair, params=params1)
self._log_exchange_response('fetch_stoploss_order', order_reg)
return order_reg
except ccxt.OrderNotFound:
pass
params2 = {'stop': True, 'ordType': 'conditional'}
for method in (self._api.fetch_open_orders, self._api.fetch_closed_orders,
self._api.fetch_canceled_orders):
try:
orders = method(pair, params=params2)
orders_f = [order for order in orders if order['id'] == order_id]
if orders_f:
order = orders_f[0]
if (order['status'] == 'closed'
and (real_order_id := order.get('info', {}).get('ordId')) is not None):
# Once a order triggered, we fetch the regular followup order. # Once a order triggered, we fetch the regular followup order.
order_reg = self.fetch_order(real_order_id, pair) order_reg = self.fetch_order(real_order_id, pair)
self._log_exchange_response('fetch_stoploss_order1', order_reg) self._log_exchange_response('fetch_stoploss_order1', order_reg)
@@ -200,6 +184,27 @@ class Okx(Exchange):
return order_reg return order_reg
order['type'] = 'stoploss' order['type'] = 'stoploss'
return order return order
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
if self._config['dry_run']:
return self.fetch_dry_run_order(order_id)
try:
params1 = {'stop': True}
order_reg = self._api.fetch_order(order_id, pair, params=params1)
self._log_exchange_response('fetch_stoploss_order', order_reg)
return self._convert_stop_order(pair, order_id, order_reg)
except ccxt.OrderNotFound:
pass
params2 = {'stop': True, 'ordType': 'conditional'}
for method in (self._api.fetch_open_orders, self._api.fetch_closed_orders,
self._api.fetch_canceled_orders):
try:
orders = method(pair, params=params2)
orders_f = [order for order in orders if order['id'] == order_id]
if orders_f:
order = orders_f[0]
return self._convert_stop_order(pair, order_id, order)
except ccxt.BaseError: except ccxt.BaseError:
pass pass
raise RetryableOrderError( raise RetryableOrderError(
+1 -1
View File
@@ -180,7 +180,7 @@ class BaseEnvironment(gym.Env):
def reset_tensorboard_log(self): def reset_tensorboard_log(self):
self.tensorboard_metrics = {} self.tensorboard_metrics = {}
def reset(self): def reset(self, seed=None):
""" """
Reset is called at the beginning of every episode Reset is called at the beginning of every episode
""" """
@@ -10,8 +10,7 @@ logger = logging.getLogger(__name__)
class BaseTensorboardLogger: class BaseTensorboardLogger:
def __init__(self, logdir: Path, activate: bool = True): def __init__(self, logdir: Path, activate: bool = True):
logger.warning("Tensorboard is not installed, no logs will be written." pass
"Ensure torch is installed, or use the torch/RL docker images")
def log_scalar(self, tag: str, scalar_value: Any, step: int): def log_scalar(self, tag: str, scalar_value: Any, step: int):
return return
@@ -23,8 +22,7 @@ class BaseTensorboardLogger:
class BaseTensorBoardCallback(TrainingCallback): class BaseTensorBoardCallback(TrainingCallback):
def __init__(self, logdir: Path, activate: bool = True): def __init__(self, logdir: Path, activate: bool = True):
logger.warning("Tensorboard is not installed, no logs will be written." pass
"Ensure torch is installed, or use the torch/RL docker images")
def after_iteration( def after_iteration(
self, model, epoch: int, evals_log: TrainingCallback.EvalsLog self, model, epoch: int, evals_log: TrainingCallback.EvalsLog
+1 -1
View File
@@ -1075,7 +1075,7 @@ class FreqtradeBot(LoggingMixin):
trades_closed = 0 trades_closed = 0
for trade in trades: for trade in trades:
if not self.wallets.check_exit_amount(trade): if trade.open_order_id is None and not self.wallets.check_exit_amount(trade):
logger.warning( logger.warning(
f'Not enough {trade.safe_base_currency} in wallet to exit {trade}. ' f'Not enough {trade.safe_base_currency} in wallet to exit {trade}. '
'Trying to recover.') 'Trying to recover.')
+1 -12
View File
@@ -8,7 +8,6 @@ from pathlib import Path
from typing import Any, Dict, Iterator, List, Mapping, Optional, TextIO, Union from typing import Any, Dict, Iterator, List, Mapping, Optional, TextIO, Union
from urllib.parse import urlparse from urllib.parse import urlparse
import orjson
import pandas as pd import pandas as pd
import rapidjson import rapidjson
@@ -249,17 +248,7 @@ def dataframe_to_json(dataframe: pd.DataFrame) -> str:
:param dataframe: A pandas DataFrame :param dataframe: A pandas DataFrame
:returns: A JSON string of the pandas DataFrame :returns: A JSON string of the pandas DataFrame
""" """
# https://github.com/pandas-dev/pandas/issues/24889 return dataframe.to_json(orient='split')
# https://github.com/pandas-dev/pandas/issues/40443
# We need to convert to a dict to avoid mem leak
def default(z):
if isinstance(z, pd.Timestamp):
return z.timestamp() * 1e3
if z is pd.NaT:
return 'NaT'
raise TypeError
return str(orjson.dumps(dataframe.to_dict(orient='split'), default=default), 'utf-8')
def json_to_dataframe(data: str) -> pd.DataFrame: def json_to_dataframe(data: str) -> pd.DataFrame:
@@ -98,7 +98,7 @@ class VolatilityFilter(IPairList):
needed_pairs: ListPairsWithTimeframes = [ needed_pairs: ListPairsWithTimeframes = [
(p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache] (p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache]
since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days - 1)) since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days))
# Get all candles # Get all candles
candles = {} candles = {}
if needed_pairs: if needed_pairs:
@@ -127,7 +127,7 @@ class VolatilityFilter(IPairList):
result = False result = False
if daily_candles is not None and not daily_candles.empty: if daily_candles is not None and not daily_candles.empty:
returns = (np.log(daily_candles.close / daily_candles.close.shift(-1))) returns = (np.log(daily_candles["close"].shift(1) / daily_candles["close"]))
returns.fillna(0, inplace=True) returns.fillna(0, inplace=True)
volatility_series = returns.rolling(window=self._days).std() * np.sqrt(self._days) volatility_series = returns.rolling(window=self._days).std() * np.sqrt(self._days)
+2 -2
View File
@@ -5,7 +5,7 @@
torch==2.0.1 torch==2.0.1
#until these branches will be released we can use this #until these branches will be released we can use this
gymnasium==0.28.1 gymnasium==0.28.1
stable_baselines3==2.0.0a5 stable_baselines3==2.0.0a10
sb3_contrib>=2.0.0a4 sb3_contrib>=2.0.0a9
# Progress bar for stable-baselines3 and sb3-contrib # Progress bar for stable-baselines3 and sb3-contrib
tqdm==4.65.0 tqdm==4.65.0
+1 -1
View File
@@ -12,7 +12,7 @@ python-telegram-bot==20.3
httpx>=0.23.3 httpx>=0.23.3
arrow==1.2.3 arrow==1.2.3
cachetools==5.3.0 cachetools==5.3.0
requests==2.30.0 requests==2.31.0
urllib3==2.0.2 urllib3==2.0.2
jsonschema==4.17.3 jsonschema==4.17.3
TA-Lib==0.4.26 TA-Lib==0.4.26
+2 -1
View File
@@ -279,8 +279,9 @@ class FtRestClient():
""" """
data = {"pair": pair, data = {"pair": pair,
"side": side, "side": side,
"price": price,
} }
if price:
data['price'] = price
return self._post("forceenter", data=data) return self._post("forceenter", data=data)
def forceexit(self, tradeid, ordertype=None, amount=None): def forceexit(self, tradeid, ordertype=None, amount=None):
+3 -3
View File
@@ -514,7 +514,7 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers
def test_additional_exchange_init_binance(default_conf, mocker): def test_additional_exchange_init_binance(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fapiPrivateGetPositionsideDual = MagicMock(return_value={"dualSidePosition": True}) api_mock.fapiPrivateGetPositionSideDual = MagicMock(return_value={"dualSidePosition": True})
api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": True}) api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": True})
default_conf['dry_run'] = False default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES default_conf['trading_mode'] = TradingMode.FUTURES
@@ -522,12 +522,12 @@ def test_additional_exchange_init_binance(default_conf, mocker):
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r"Hedge Mode is not supported.*\nMulti-Asset Mode is not supported.*"): match=r"Hedge Mode is not supported.*\nMulti-Asset Mode is not supported.*"):
get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock) get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock)
api_mock.fapiPrivateGetPositionsideDual = MagicMock(return_value={"dualSidePosition": False}) api_mock.fapiPrivateGetPositionSideDual = MagicMock(return_value={"dualSidePosition": False})
api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": False}) api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": False})
exchange = get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock) exchange = get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock)
assert exchange assert exchange
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'binance', ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'binance',
"additional_exchange_init", "fapiPrivateGetPositionsideDual") "additional_exchange_init", "fapiPrivateGetPositionSideDual")
def test__set_leverage_binance(mocker, default_conf): def test__set_leverage_binance(mocker, default_conf):
+11
View File
@@ -43,6 +43,10 @@ EXCHANGES = {
'hasQuoteVolumeFutures': True, 'hasQuoteVolumeFutures': True,
'leverage_tiers_public': False, 'leverage_tiers_public': False,
'leverage_in_spot_market': False, 'leverage_in_spot_market': False,
'private_methods': [
'fapiPrivateGetPositionSideDual',
'fapiPrivateGetMultiAssetsMargin'
],
'sample_order': [{ 'sample_order': [{
"symbol": "SOLUSDT", "symbol": "SOLUSDT",
"orderId": 3551312894, "orderId": 3551312894,
@@ -221,11 +225,13 @@ EXCHANGES = {
'hasQuoteVolumeFutures': False, 'hasQuoteVolumeFutures': False,
'leverage_tiers_public': True, 'leverage_tiers_public': True,
'leverage_in_spot_market': True, 'leverage_in_spot_market': True,
'private_methods': ['fetch_accounts'],
}, },
'bybit': { 'bybit': {
'pair': 'BTC/USDT', 'pair': 'BTC/USDT',
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'hasQuoteVolume': True, 'hasQuoteVolume': True,
'use_ci_proxy': True,
'timeframe': '1h', 'timeframe': '1h',
'futures_pair': 'BTC/USDT:USDT', 'futures_pair': 'BTC/USDT:USDT',
'futures': True, 'futures': True,
@@ -755,3 +761,8 @@ class TestCCXTExchange():
max_stake_amount = futures.get_max_pair_stake_amount(futures_pair, 40000) max_stake_amount = futures.get_max_pair_stake_amount(futures_pair, 40000)
assert (isinstance(max_stake_amount, float)) assert (isinstance(max_stake_amount, float))
assert max_stake_amount >= 0.0 assert max_stake_amount >= 0.0
def test_private_method_presence(self, exchange: EXCHANGE_FIXTURE_TYPE):
exch, exchangename = exchange
for method in EXCHANGES[exchangename].get('private_methods', []):
assert hasattr(exch._api, method)