Merge branch 'develop' into feat/pairlistconfig
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user