Merge branch 'develop' of github.com:lolongcovas/freqtrade into feat/freqai
This commit is contained in:
commit
1c5f2d653c
@ -15,7 +15,7 @@ repos:
|
|||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- types-cachetools==5.2.1
|
- types-cachetools==5.2.1
|
||||||
- types-filelock==3.2.7
|
- types-filelock==3.2.7
|
||||||
- types-requests==2.28.8
|
- types-requests==2.28.9
|
||||||
- types-tabulate==0.8.11
|
- types-tabulate==0.8.11
|
||||||
- types-python-dateutil==2.8.19
|
- types-python-dateutil==2.8.19
|
||||||
# stages: [push]
|
# stages: [push]
|
||||||
|
@ -186,7 +186,7 @@ Freqtrade currently supports 3 data-formats for both OHLCV and trades data:
|
|||||||
By default, OHLCV data is stored as `json` data, while trades data is stored as `jsongz` data.
|
By default, OHLCV data is stored as `json` data, while trades data is stored as `jsongz` data.
|
||||||
|
|
||||||
This can be changed via the `--data-format-ohlcv` and `--data-format-trades` command line arguments respectively.
|
This can be changed via the `--data-format-ohlcv` and `--data-format-trades` command line arguments respectively.
|
||||||
To persist this change, you can should also add the following snippet to your configuration, so you don't have to insert the above arguments each time:
|
To persist this change, you should also add the following snippet to your configuration, so you don't have to insert the above arguments each time:
|
||||||
|
|
||||||
``` jsonc
|
``` jsonc
|
||||||
// ...
|
// ...
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
markdown==3.3.7
|
markdown==3.3.7
|
||||||
mkdocs==1.3.1
|
mkdocs==1.3.1
|
||||||
mkdocs-material==8.4.0
|
mkdocs-material==8.4.1
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==9.5
|
pymdown-extensions==9.5
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
|
@ -317,7 +317,7 @@ whitelist
|
|||||||
### OpenAPI interface
|
### OpenAPI interface
|
||||||
|
|
||||||
To enable the builtin openAPI interface (Swagger UI), specify `"enable_openapi": true` in the api_server configuration.
|
To enable the builtin openAPI interface (Swagger UI), specify `"enable_openapi": true` in the api_server configuration.
|
||||||
This will enable the Swagger UI at the `/docs` endpoint. By default, that's running at http://localhost:8080/docs/ - but it'll depend on your settings.
|
This will enable the Swagger UI at the `/docs` endpoint. By default, that's running at http://localhost:8080/docs - but it'll depend on your settings.
|
||||||
|
|
||||||
### Advanced API usage using JWT tokens
|
### Advanced API usage using JWT tokens
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Stake size management
|
## Stake size management
|
||||||
|
|
||||||
Called before entering a trade, makes it possible to manage your position size when placing a new trade.
|
Called before entering a trade, makes it possible to manage your position size when placing a new trade.
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ def start_download_data(args: Dict[str, Any]) -> None:
|
|||||||
data_format_trades=config['dataformat_trades'],
|
data_format_trades=config['dataformat_trades'],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if not exchange._ft_has.get('ohlcv_has_history', True):
|
if not exchange.get_option('ohlcv_has_history', True):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f"Historic klines not available for {exchange.name}. "
|
f"Historic klines not available for {exchange.name}. "
|
||||||
"Please use `--dl-trades` instead for this exchange "
|
"Please use `--dl-trades` instead for this exchange "
|
||||||
|
@ -302,8 +302,8 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
|
|||||||
if trading_mode == 'futures':
|
if trading_mode == 'futures':
|
||||||
# Predefined candletype (and timeframe) depending on exchange
|
# Predefined candletype (and timeframe) depending on exchange
|
||||||
# Downloads what is necessary to backtest based on futures data.
|
# Downloads what is necessary to backtest based on futures data.
|
||||||
tf_mark = exchange._ft_has['mark_ohlcv_timeframe']
|
tf_mark = exchange.get_option('mark_ohlcv_timeframe')
|
||||||
fr_candle_type = CandleType.from_string(exchange._ft_has['mark_ohlcv_price'])
|
fr_candle_type = CandleType.from_string(exchange.get_option('mark_ohlcv_price'))
|
||||||
# All exchanges need FundingRate for futures trading.
|
# All exchanges need FundingRate for futures trading.
|
||||||
# The timeframe is aligned to the mark-price timeframe.
|
# The timeframe is aligned to the mark-price timeframe.
|
||||||
for funding_candle_type in (CandleType.FUNDING_RATE, fr_candle_type):
|
for funding_candle_type in (CandleType.FUNDING_RATE, fr_candle_type):
|
||||||
@ -330,13 +330,12 @@ def _download_trades_history(exchange: Exchange,
|
|||||||
try:
|
try:
|
||||||
|
|
||||||
until = None
|
until = None
|
||||||
|
since = 0
|
||||||
if timerange:
|
if timerange:
|
||||||
if timerange.starttype == 'date':
|
if timerange.starttype == 'date':
|
||||||
since = timerange.startts * 1000
|
since = timerange.startts * 1000
|
||||||
if timerange.stoptype == 'date':
|
if timerange.stoptype == 'date':
|
||||||
until = timerange.stopts * 1000
|
until = timerange.stopts * 1000
|
||||||
else:
|
|
||||||
since = arrow.utcnow().shift(days=-new_pairs_days).int_timestamp * 1000
|
|
||||||
|
|
||||||
trades = data_handler.trades_load(pair)
|
trades = data_handler.trades_load(pair)
|
||||||
|
|
||||||
@ -349,6 +348,9 @@ def _download_trades_history(exchange: Exchange,
|
|||||||
logger.info(f"Start earlier than available data. Redownloading trades for {pair}...")
|
logger.info(f"Start earlier than available data. Redownloading trades for {pair}...")
|
||||||
trades = []
|
trades = []
|
||||||
|
|
||||||
|
if not since:
|
||||||
|
since = arrow.utcnow().shift(days=-new_pairs_days).int_timestamp * 1000
|
||||||
|
|
||||||
from_id = trades[-1][1] if trades else None
|
from_id = trades[-1][1] if trades else None
|
||||||
if trades and since < trades[-1][0]:
|
if trades and since < trades[-1][0]:
|
||||||
# Reset since to the last available point
|
# Reset since to the last available point
|
||||||
|
@ -9,7 +9,8 @@ from freqtrade.exchange.bitpanda import Bitpanda
|
|||||||
from freqtrade.exchange.bittrex import Bittrex
|
from freqtrade.exchange.bittrex import Bittrex
|
||||||
from freqtrade.exchange.bybit import Bybit
|
from freqtrade.exchange.bybit import Bybit
|
||||||
from freqtrade.exchange.coinbasepro import Coinbasepro
|
from freqtrade.exchange.coinbasepro import Coinbasepro
|
||||||
from freqtrade.exchange.exchange import (amount_to_precision, available_exchanges, ccxt_exchanges,
|
from freqtrade.exchange.exchange import (amount_to_contracts, amount_to_precision,
|
||||||
|
available_exchanges, ccxt_exchanges, contracts_to_amount,
|
||||||
date_minus_candles, is_exchange_known_ccxt,
|
date_minus_candles, is_exchange_known_ccxt,
|
||||||
is_exchange_officially_supported, market_is_active,
|
is_exchange_officially_supported, market_is_active,
|
||||||
price_to_precision, timeframe_to_minutes,
|
price_to_precision, timeframe_to_minutes,
|
||||||
|
@ -54,8 +54,8 @@ class Exchange:
|
|||||||
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
|
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
|
||||||
_params: Dict = {}
|
_params: Dict = {}
|
||||||
|
|
||||||
# Additional headers - added to the ccxt object
|
# Additional parameters - added to the ccxt object
|
||||||
_headers: Dict = {}
|
_ccxt_params: Dict = {}
|
||||||
|
|
||||||
# Dict to specify which options each exchange implements
|
# Dict to specify which options each exchange implements
|
||||||
# This defines defaults, which can be selectively overridden by subclasses using _ft_has
|
# This defines defaults, which can be selectively overridden by subclasses using _ft_has
|
||||||
@ -242,9 +242,9 @@ class Exchange:
|
|||||||
}
|
}
|
||||||
if ccxt_kwargs:
|
if ccxt_kwargs:
|
||||||
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
|
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
|
||||||
if self._headers:
|
if self._ccxt_params:
|
||||||
# Inject static headers after the above output to not confuse users.
|
# Inject static options after the above output to not confuse users.
|
||||||
ccxt_kwargs = deep_merge_dicts({'headers': self._headers}, ccxt_kwargs)
|
ccxt_kwargs = deep_merge_dicts(self._ccxt_params, ccxt_kwargs)
|
||||||
if ccxt_kwargs:
|
if ccxt_kwargs:
|
||||||
ex_config.update(ccxt_kwargs)
|
ex_config.update(ccxt_kwargs)
|
||||||
try:
|
try:
|
||||||
@ -408,7 +408,7 @@ class Exchange:
|
|||||||
else:
|
else:
|
||||||
return DataFrame()
|
return DataFrame()
|
||||||
|
|
||||||
def _get_contract_size(self, pair: str) -> float:
|
def get_contract_size(self, pair: str) -> float:
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
market = self.markets[pair]
|
market = self.markets[pair]
|
||||||
contract_size: float = 1.0
|
contract_size: float = 1.0
|
||||||
@ -421,7 +421,7 @@ class Exchange:
|
|||||||
|
|
||||||
def _trades_contracts_to_amount(self, trades: List) -> List:
|
def _trades_contracts_to_amount(self, trades: List) -> List:
|
||||||
if len(trades) > 0 and 'symbol' in trades[0]:
|
if len(trades) > 0 and 'symbol' in trades[0]:
|
||||||
contract_size = self._get_contract_size(trades[0]['symbol'])
|
contract_size = self.get_contract_size(trades[0]['symbol'])
|
||||||
if contract_size != 1:
|
if contract_size != 1:
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
trade['amount'] = trade['amount'] * contract_size
|
trade['amount'] = trade['amount'] * contract_size
|
||||||
@ -429,7 +429,7 @@ class Exchange:
|
|||||||
|
|
||||||
def _order_contracts_to_amount(self, order: Dict) -> Dict:
|
def _order_contracts_to_amount(self, order: Dict) -> Dict:
|
||||||
if 'symbol' in order and order['symbol'] is not None:
|
if 'symbol' in order and order['symbol'] is not None:
|
||||||
contract_size = self._get_contract_size(order['symbol'])
|
contract_size = self.get_contract_size(order['symbol'])
|
||||||
if contract_size != 1:
|
if contract_size != 1:
|
||||||
for prop in self._ft_has.get('order_props_in_contracts', []):
|
for prop in self._ft_has.get('order_props_in_contracts', []):
|
||||||
if prop in order and order[prop] is not None:
|
if prop in order and order[prop] is not None:
|
||||||
@ -438,19 +438,13 @@ class Exchange:
|
|||||||
|
|
||||||
def _amount_to_contracts(self, pair: str, amount: float) -> float:
|
def _amount_to_contracts(self, pair: str, amount: float) -> float:
|
||||||
|
|
||||||
contract_size = self._get_contract_size(pair)
|
contract_size = self.get_contract_size(pair)
|
||||||
if contract_size and contract_size != 1:
|
return amount_to_contracts(amount, contract_size)
|
||||||
return amount / contract_size
|
|
||||||
else:
|
|
||||||
return amount
|
|
||||||
|
|
||||||
def _contracts_to_amount(self, pair: str, num_contracts: float) -> float:
|
def _contracts_to_amount(self, pair: str, num_contracts: float) -> float:
|
||||||
|
|
||||||
contract_size = self._get_contract_size(pair)
|
contract_size = self.get_contract_size(pair)
|
||||||
if contract_size and contract_size != 1:
|
return contracts_to_amount(num_contracts, contract_size)
|
||||||
return num_contracts * contract_size
|
|
||||||
else:
|
|
||||||
return num_contracts
|
|
||||||
|
|
||||||
def set_sandbox(self, api: ccxt.Exchange, exchange_config: dict, name: str) -> None:
|
def set_sandbox(self, api: ccxt.Exchange, exchange_config: dict, name: str) -> None:
|
||||||
if exchange_config.get('sandbox'):
|
if exchange_config.get('sandbox'):
|
||||||
@ -674,6 +668,12 @@ class Exchange:
|
|||||||
f"Freqtrade does not support {mm_value} {trading_mode.value} on {self.name}"
|
f"Freqtrade does not support {mm_value} {trading_mode.value} on {self.name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_option(self, param: str, default: Any = None) -> Any:
|
||||||
|
"""
|
||||||
|
Get parameter value from _ft_has
|
||||||
|
"""
|
||||||
|
return self._ft_has.get(param, default)
|
||||||
|
|
||||||
def exchange_has(self, endpoint: str) -> bool:
|
def exchange_has(self, endpoint: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks if exchange implements a specific API endpoint.
|
Checks if exchange implements a specific API endpoint.
|
||||||
@ -2892,6 +2892,33 @@ def market_is_active(market: Dict) -> bool:
|
|||||||
return market.get('active', True) is not False
|
return market.get('active', True) is not False
|
||||||
|
|
||||||
|
|
||||||
|
def amount_to_contracts(amount: float, contract_size: Optional[float]) -> float:
|
||||||
|
"""
|
||||||
|
Convert amount to contracts.
|
||||||
|
:param amount: amount to convert
|
||||||
|
:param contract_size: contract size - taken from exchange.get_contract_size(pair)
|
||||||
|
:return: num-contracts
|
||||||
|
"""
|
||||||
|
if contract_size and contract_size != 1:
|
||||||
|
return amount / contract_size
|
||||||
|
else:
|
||||||
|
return amount
|
||||||
|
|
||||||
|
|
||||||
|
def contracts_to_amount(num_contracts: float, contract_size: Optional[float]) -> float:
|
||||||
|
"""
|
||||||
|
Takes num-contracts and converts it to contract size
|
||||||
|
:param num_contracts: number of contracts
|
||||||
|
:param contract_size: contract size - taken from exchange.get_contract_size(pair)
|
||||||
|
:return: Amount
|
||||||
|
"""
|
||||||
|
|
||||||
|
if contract_size and contract_size != 1:
|
||||||
|
return num_contracts * contract_size
|
||||||
|
else:
|
||||||
|
return num_contracts
|
||||||
|
|
||||||
|
|
||||||
def amount_to_precision(amount: float, amount_precision: Optional[float],
|
def amount_to_precision(amount: float, amount_precision: Optional[float],
|
||||||
precisionMode: Optional[int]) -> float:
|
precisionMode: Optional[int]) -> float:
|
||||||
"""
|
"""
|
||||||
|
@ -25,7 +25,6 @@ class Gateio(Exchange):
|
|||||||
|
|
||||||
_ft_has: Dict = {
|
_ft_has: Dict = {
|
||||||
"ohlcv_candle_limit": 1000,
|
"ohlcv_candle_limit": 1000,
|
||||||
"ohlcv_volume_currency": "quote",
|
|
||||||
"time_in_force_parameter": "timeInForce",
|
"time_in_force_parameter": "timeInForce",
|
||||||
"order_time_in_force": ['gtc', 'ioc'],
|
"order_time_in_force": ['gtc', 'ioc'],
|
||||||
"stoploss_order_types": {"limit": "limit"},
|
"stoploss_order_types": {"limit": "limit"},
|
||||||
@ -34,7 +33,6 @@ class Gateio(Exchange):
|
|||||||
|
|
||||||
_ft_has_futures: Dict = {
|
_ft_has_futures: Dict = {
|
||||||
"needs_trading_fees": True,
|
"needs_trading_fees": True,
|
||||||
"ohlcv_volume_currency": "base",
|
|
||||||
"fee_cost_in_contracts": False, # Set explicitly to false for clarity
|
"fee_cost_in_contracts": False, # Set explicitly to false for clarity
|
||||||
"order_props_in_contracts": ['amount', 'filled', 'remaining'],
|
"order_props_in_contracts": ['amount', 'filled', 'remaining'],
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,8 @@ class Okx(Exchange):
|
|||||||
|
|
||||||
net_only = True
|
net_only = True
|
||||||
|
|
||||||
|
_ccxt_params: Dict = {'options': {'brokerId': 'ffb5405ad327SUDE'}}
|
||||||
|
|
||||||
def ohlcv_candle_limit(
|
def ohlcv_candle_limit(
|
||||||
self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None) -> int:
|
self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None) -> int:
|
||||||
"""
|
"""
|
||||||
|
@ -421,7 +421,7 @@ class FreqaiDataDrawer:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# if self.live:
|
# if self.live:
|
||||||
self.model_dictionary[dk.model_filename] = model
|
self.model_dictionary[coin] = model
|
||||||
self.pair_dict[coin]["model_filename"] = dk.model_filename
|
self.pair_dict[coin]["model_filename"] = dk.model_filename
|
||||||
self.pair_dict[coin]["data_path"] = str(dk.data_path)
|
self.pair_dict[coin]["data_path"] = str(dk.data_path)
|
||||||
self.save_drawer_to_disk()
|
self.save_drawer_to_disk()
|
||||||
@ -460,8 +460,8 @@ class FreqaiDataDrawer:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# try to access model in memory instead of loading object from disk to save time
|
# try to access model in memory instead of loading object from disk to save time
|
||||||
if dk.live and dk.model_filename in self.model_dictionary:
|
if dk.live and coin in self.model_dictionary:
|
||||||
model = self.model_dictionary[dk.model_filename]
|
model = self.model_dictionary[coin]
|
||||||
elif not dk.keras:
|
elif not dk.keras:
|
||||||
model = load(dk.data_path / f"{dk.model_filename}_model.joblib")
|
model = load(dk.data_path / f"{dk.model_filename}_model.joblib")
|
||||||
else:
|
else:
|
||||||
|
@ -601,6 +601,8 @@ class FreqaiDataKitchen:
|
|||||||
is an outlier.
|
is an outlier.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from math import cos, sin
|
||||||
|
|
||||||
if predict:
|
if predict:
|
||||||
train_ft_df = self.data_dictionary['train_features']
|
train_ft_df = self.data_dictionary['train_features']
|
||||||
pred_ft_df = self.data_dictionary['prediction_features']
|
pred_ft_df = self.data_dictionary['prediction_features']
|
||||||
@ -619,23 +621,47 @@ class FreqaiDataKitchen:
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
def normalise_distances(distances):
|
||||||
|
normalised_distances = (distances - distances.min()) / \
|
||||||
|
(distances.max() - distances.min())
|
||||||
|
return normalised_distances
|
||||||
|
|
||||||
|
def rotate_point(origin, point, angle):
|
||||||
|
# rotate a point counterclockwise by a given angle (in radians)
|
||||||
|
# around a given origin
|
||||||
|
x = origin[0] + cos(angle) * (point[0] - origin[0]) - \
|
||||||
|
sin(angle) * (point[1] - origin[1])
|
||||||
|
y = origin[1] + sin(angle) * (point[0] - origin[0]) + \
|
||||||
|
cos(angle) * (point[1] - origin[1])
|
||||||
|
return (x, y)
|
||||||
|
|
||||||
MinPts = len(self.data_dictionary['train_features'].columns) * 2
|
MinPts = len(self.data_dictionary['train_features'].columns) * 2
|
||||||
# measure pairwise distances to train_features.shape[1]*2 nearest neighbours
|
# measure pairwise distances to train_features.shape[1]*2 nearest neighbours
|
||||||
neighbors = NearestNeighbors(
|
neighbors = NearestNeighbors(
|
||||||
n_neighbors=MinPts, n_jobs=self.thread_count)
|
n_neighbors=MinPts, n_jobs=self.thread_count)
|
||||||
neighbors_fit = neighbors.fit(self.data_dictionary['train_features'])
|
neighbors_fit = neighbors.fit(self.data_dictionary['train_features'])
|
||||||
distances, _ = neighbors_fit.kneighbors(self.data_dictionary['train_features'])
|
distances, _ = neighbors_fit.kneighbors(self.data_dictionary['train_features'])
|
||||||
distances = np.sort(distances, axis=0)
|
distances = np.sort(distances, axis=0).mean(axis=1)
|
||||||
index_ten_pct = int(len(distances[:, 1]) * 0.1)
|
|
||||||
distances = distances[index_ten_pct:, 1]
|
normalised_distances = normalise_distances(distances)
|
||||||
epsilon = distances[-1]
|
x_range = np.linspace(0, 1, len(distances))
|
||||||
|
line = np.linspace(normalised_distances[0],
|
||||||
|
normalised_distances[-1], len(normalised_distances))
|
||||||
|
deflection = np.abs(normalised_distances - line)
|
||||||
|
max_deflection_loc = np.where(deflection == deflection.max())[0][0]
|
||||||
|
origin = x_range[max_deflection_loc], line[max_deflection_loc]
|
||||||
|
point = x_range[max_deflection_loc], normalised_distances[max_deflection_loc]
|
||||||
|
rot_angle = np.pi / 4
|
||||||
|
elbow_loc = rotate_point(origin, point, rot_angle)
|
||||||
|
|
||||||
|
epsilon = elbow_loc[1] * (distances[-1] - distances[0]) + distances[0]
|
||||||
|
|
||||||
clustering = DBSCAN(eps=epsilon, min_samples=MinPts,
|
clustering = DBSCAN(eps=epsilon, min_samples=MinPts,
|
||||||
n_jobs=int(self.thread_count)).fit(
|
n_jobs=int(self.thread_count)).fit(
|
||||||
self.data_dictionary['train_features']
|
self.data_dictionary['train_features']
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f'DBSCAN found eps of {epsilon}.')
|
logger.info(f'DBSCAN found eps of {epsilon:.2f}.')
|
||||||
|
|
||||||
self.data['DBSCAN_eps'] = epsilon
|
self.data['DBSCAN_eps'] = epsilon
|
||||||
self.data['DBSCAN_min_samples'] = MinPts
|
self.data['DBSCAN_min_samples'] = MinPts
|
||||||
@ -806,7 +832,7 @@ class FreqaiDataKitchen:
|
|||||||
|
|
||||||
if (len(do_predict) - do_predict.sum()) > 0:
|
if (len(do_predict) - do_predict.sum()) > 0:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"DI tossed {len(do_predict) - do_predict.sum():.2f} predictions for "
|
f"DI tossed {len(do_predict) - do_predict.sum()} predictions for "
|
||||||
"being too far from training data"
|
"being too far from training data"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -981,13 +1007,6 @@ class FreqaiDataKitchen:
|
|||||||
data_load_timerange.stopts = int(time)
|
data_load_timerange.stopts = int(time)
|
||||||
retrain = True
|
retrain = True
|
||||||
|
|
||||||
# logger.info(
|
|
||||||
# f"downloading data for "
|
|
||||||
# f"{(data_load_timerange.stopts-data_load_timerange.startts)/SECONDS_IN_DAY:.2f} "
|
|
||||||
# " days. "
|
|
||||||
# f"Extension of {additional_seconds/SECONDS_IN_DAY:.2f} days"
|
|
||||||
# )
|
|
||||||
|
|
||||||
return retrain, trained_timerange, data_load_timerange
|
return retrain, trained_timerange, data_load_timerange
|
||||||
|
|
||||||
def set_new_model_names(self, pair: str, trained_timerange: TimeRange):
|
def set_new_model_names(self, pair: str, trained_timerange: TimeRange):
|
||||||
|
@ -82,12 +82,15 @@ class IFreqaiModel(ABC):
|
|||||||
if self.ft_params.get("inlier_metric_window", 0):
|
if self.ft_params.get("inlier_metric_window", 0):
|
||||||
self.CONV_WIDTH = self.ft_params.get("inlier_metric_window", 0) * 2
|
self.CONV_WIDTH = self.ft_params.get("inlier_metric_window", 0) * 2
|
||||||
self.pair_it = 0
|
self.pair_it = 0
|
||||||
|
self.pair_it_train = 0
|
||||||
self.total_pairs = len(self.config.get("exchange", {}).get("pair_whitelist"))
|
self.total_pairs = len(self.config.get("exchange", {}).get("pair_whitelist"))
|
||||||
self.last_trade_database_summary: DataFrame = {}
|
self.last_trade_database_summary: DataFrame = {}
|
||||||
self.current_trade_database_summary: DataFrame = {}
|
self.current_trade_database_summary: DataFrame = {}
|
||||||
self.analysis_lock = Lock()
|
self.analysis_lock = Lock()
|
||||||
self.inference_time: float = 0
|
self.inference_time: float = 0
|
||||||
|
self.train_time: float = 0
|
||||||
self.begin_time: float = 0
|
self.begin_time: float = 0
|
||||||
|
self.begin_time_train: float = 0
|
||||||
self.base_tf_seconds = timeframe_to_seconds(self.config['timeframe'])
|
self.base_tf_seconds = timeframe_to_seconds(self.config['timeframe'])
|
||||||
|
|
||||||
def assert_config(self, config: Dict[str, Any]) -> None:
|
def assert_config(self, config: Dict[str, Any]) -> None:
|
||||||
@ -130,11 +133,20 @@ class IFreqaiModel(ABC):
|
|||||||
dk = self.start_backtesting(dataframe, metadata, self.dk)
|
dk = self.start_backtesting(dataframe, metadata, self.dk)
|
||||||
|
|
||||||
dataframe = dk.remove_features_from_df(dk.return_dataframe)
|
dataframe = dk.remove_features_from_df(dk.return_dataframe)
|
||||||
del dk
|
self.clean_up()
|
||||||
if self.live:
|
if self.live:
|
||||||
self.inference_timer('stop')
|
self.inference_timer('stop')
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
def clean_up(self):
|
||||||
|
"""
|
||||||
|
Objects that should be handled by GC already between coins, but
|
||||||
|
are explicitly shown here to help demonstrate the non-persistence of these
|
||||||
|
objects.
|
||||||
|
"""
|
||||||
|
self.model = None
|
||||||
|
self.dk = None
|
||||||
|
|
||||||
@threaded
|
@threaded
|
||||||
def start_scanning(self, strategy: IStrategy) -> None:
|
def start_scanning(self, strategy: IStrategy) -> None:
|
||||||
"""
|
"""
|
||||||
@ -161,9 +173,11 @@ class IFreqaiModel(ABC):
|
|||||||
dk.set_paths(pair, new_trained_timerange.stopts)
|
dk.set_paths(pair, new_trained_timerange.stopts)
|
||||||
|
|
||||||
if retrain:
|
if retrain:
|
||||||
|
self.train_timer('start')
|
||||||
self.train_model_in_series(
|
self.train_model_in_series(
|
||||||
new_trained_timerange, pair, strategy, dk, data_load_timerange
|
new_trained_timerange, pair, strategy, dk, data_load_timerange
|
||||||
)
|
)
|
||||||
|
self.train_timer('stop')
|
||||||
|
|
||||||
self.dd.save_historic_predictions_to_disk()
|
self.dd.save_historic_predictions_to_disk()
|
||||||
|
|
||||||
@ -490,8 +504,7 @@ class IFreqaiModel(ABC):
|
|||||||
data_load_timerange: TimeRange,
|
data_load_timerange: TimeRange,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Retrieve data and train model in single threaded mode (only used if model directory is empty
|
Retrieve data and train model.
|
||||||
upon startup for dry/live )
|
|
||||||
:param new_trained_timerange: TimeRange = the timerange to train the model on
|
:param new_trained_timerange: TimeRange = the timerange to train the model on
|
||||||
:param metadata: dict = strategy provided metadata
|
:param metadata: dict = strategy provided metadata
|
||||||
:param strategy: IStrategy = user defined strategy object
|
:param strategy: IStrategy = user defined strategy object
|
||||||
@ -622,6 +635,24 @@ class IFreqaiModel(ABC):
|
|||||||
self.inference_time = 0
|
self.inference_time = 0
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def train_timer(self, do='start'):
|
||||||
|
"""
|
||||||
|
Timer designed to track the cumulative time spent training the full pairlist in
|
||||||
|
FreqAI.
|
||||||
|
"""
|
||||||
|
if do == 'start':
|
||||||
|
self.pair_it_train += 1
|
||||||
|
self.begin_time_train = time.time()
|
||||||
|
elif do == 'stop':
|
||||||
|
end = time.time()
|
||||||
|
self.train_time += (end - self.begin_time_train)
|
||||||
|
if self.pair_it_train == self.total_pairs:
|
||||||
|
logger.info(
|
||||||
|
f'Total time spent training pairlist {self.train_time:.2f} seconds')
|
||||||
|
self.pair_it_train = 0
|
||||||
|
self.train_time = 0
|
||||||
|
return
|
||||||
|
|
||||||
# Following methods which are overridden by user made prediction models.
|
# Following methods which are overridden by user made prediction models.
|
||||||
# See freqai/prediction_models/CatboostPredictionModel.py for an example.
|
# See freqai/prediction_models/CatboostPredictionModel.py for an example.
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
Return the number of free open trades slots or 0 if
|
Return the number of free open trades slots or 0 if
|
||||||
max number of open trades reached
|
max number of open trades reached
|
||||||
"""
|
"""
|
||||||
open_trades = len(Trade.get_open_trades())
|
open_trades = Trade.get_open_trade_count()
|
||||||
return max(0, self.config['max_open_trades'] - open_trades)
|
return max(0, self.config['max_open_trades'] - open_trades)
|
||||||
|
|
||||||
def update_funding_fees(self):
|
def update_funding_fees(self):
|
||||||
@ -290,13 +290,14 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
def startup_backpopulate_precision(self):
|
def startup_backpopulate_precision(self):
|
||||||
|
|
||||||
trades = Trade.get_trades([Trade.precision_mode.is_(None)])
|
trades = Trade.get_trades([Trade.contract_size.is_(None)])
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
if trade.exchange != self.exchange.id:
|
if trade.exchange != self.exchange.id:
|
||||||
continue
|
continue
|
||||||
trade.precision_mode = self.exchange.precisionMode
|
trade.precision_mode = self.exchange.precisionMode
|
||||||
trade.amount_precision = self.exchange.get_precision_amount(trade.pair)
|
trade.amount_precision = self.exchange.get_precision_amount(trade.pair)
|
||||||
trade.price_precision = self.exchange.get_precision_price(trade.pair)
|
trade.price_precision = self.exchange.get_precision_price(trade.pair)
|
||||||
|
trade.contract_size = self.exchange.get_contract_size(trade.pair)
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
def startup_update_open_orders(self):
|
def startup_update_open_orders(self):
|
||||||
@ -755,6 +756,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
amount_precision=self.exchange.get_precision_amount(pair),
|
amount_precision=self.exchange.get_precision_amount(pair),
|
||||||
price_precision=self.exchange.get_precision_price(pair),
|
price_precision=self.exchange.get_precision_price(pair),
|
||||||
precision_mode=self.exchange.precisionMode,
|
precision_mode=self.exchange.precisionMode,
|
||||||
|
contract_size=self.exchange.get_contract_size(pair),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# This is additional buy, we reset fee_open_currency so timeout checking can work
|
# This is additional buy, we reset fee_open_currency so timeout checking can work
|
||||||
|
@ -24,7 +24,8 @@ from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType
|
|||||||
TradingMode)
|
TradingMode)
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||||
from freqtrade.exchange.exchange import amount_to_precision
|
from freqtrade.exchange.exchange import (amount_to_contracts, amount_to_precision,
|
||||||
|
contracts_to_amount)
|
||||||
from freqtrade.mixins import LoggingMixin
|
from freqtrade.mixins import LoggingMixin
|
||||||
from freqtrade.optimize.backtest_caching import get_strategy_run_id
|
from freqtrade.optimize.backtest_caching import get_strategy_run_id
|
||||||
from freqtrade.optimize.bt_progress import BTProgress
|
from freqtrade.optimize.bt_progress import BTProgress
|
||||||
@ -267,7 +268,7 @@ class Backtesting:
|
|||||||
funding_rates_dict = history.load_data(
|
funding_rates_dict = history.load_data(
|
||||||
datadir=self.config['datadir'],
|
datadir=self.config['datadir'],
|
||||||
pairs=self.pairlists.whitelist,
|
pairs=self.pairlists.whitelist,
|
||||||
timeframe=self.exchange._ft_has['mark_ohlcv_timeframe'],
|
timeframe=self.exchange.get_option('mark_ohlcv_timeframe'),
|
||||||
timerange=self.timerange,
|
timerange=self.timerange,
|
||||||
startup_candles=0,
|
startup_candles=0,
|
||||||
fail_without_data=True,
|
fail_without_data=True,
|
||||||
@ -279,12 +280,12 @@ class Backtesting:
|
|||||||
mark_rates_dict = history.load_data(
|
mark_rates_dict = history.load_data(
|
||||||
datadir=self.config['datadir'],
|
datadir=self.config['datadir'],
|
||||||
pairs=self.pairlists.whitelist,
|
pairs=self.pairlists.whitelist,
|
||||||
timeframe=self.exchange._ft_has['mark_ohlcv_timeframe'],
|
timeframe=self.exchange.get_option('mark_ohlcv_timeframe'),
|
||||||
timerange=self.timerange,
|
timerange=self.timerange,
|
||||||
startup_candles=0,
|
startup_candles=0,
|
||||||
fail_without_data=True,
|
fail_without_data=True,
|
||||||
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
||||||
candle_type=CandleType.from_string(self.exchange._ft_has["mark_ohlcv_price"])
|
candle_type=CandleType.from_string(self.exchange.get_option("mark_ohlcv_price"))
|
||||||
)
|
)
|
||||||
# Combine data to avoid combining the data per trade.
|
# Combine data to avoid combining the data per trade.
|
||||||
unavailable_pairs = []
|
unavailable_pairs = []
|
||||||
@ -823,11 +824,13 @@ class Backtesting:
|
|||||||
self.order_id_counter += 1
|
self.order_id_counter += 1
|
||||||
base_currency = self.exchange.get_pair_base_currency(pair)
|
base_currency = self.exchange.get_pair_base_currency(pair)
|
||||||
amount_p = (stake_amount / propose_rate) * leverage
|
amount_p = (stake_amount / propose_rate) * leverage
|
||||||
amount = self.exchange._contracts_to_amount(
|
contract_size = self.exchange.get_contract_size(pair)
|
||||||
pair, amount_to_precision(
|
precision_amount = self.exchange.get_precision_amount(pair)
|
||||||
self.exchange._amount_to_contracts(pair, amount_p),
|
amount = contracts_to_amount(
|
||||||
self.exchange.get_precision_amount(pair), self.precision_mode)
|
amount_to_precision(
|
||||||
)
|
amount_to_contracts(amount_p, contract_size),
|
||||||
|
precision_amount, self.precision_mode),
|
||||||
|
contract_size)
|
||||||
# Backcalculate actual stake amount.
|
# Backcalculate actual stake amount.
|
||||||
stake_amount = amount * propose_rate / leverage
|
stake_amount = amount * propose_rate / leverage
|
||||||
|
|
||||||
@ -859,9 +862,10 @@ class Backtesting:
|
|||||||
trading_mode=self.trading_mode,
|
trading_mode=self.trading_mode,
|
||||||
leverage=leverage,
|
leverage=leverage,
|
||||||
# interest_rate=interest_rate,
|
# interest_rate=interest_rate,
|
||||||
amount_precision=self.exchange.get_precision_amount(pair),
|
amount_precision=precision_amount,
|
||||||
price_precision=self.exchange.get_precision_price(pair),
|
price_precision=self.exchange.get_precision_price(pair),
|
||||||
precision_mode=self.precision_mode,
|
precision_mode=self.precision_mode,
|
||||||
|
contract_size=contract_size,
|
||||||
orders=[],
|
orders=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -133,6 +133,7 @@ def migrate_trades_and_orders_table(
|
|||||||
amount_precision = get_column_def(cols, 'amount_precision', 'null')
|
amount_precision = get_column_def(cols, 'amount_precision', 'null')
|
||||||
price_precision = get_column_def(cols, 'price_precision', 'null')
|
price_precision = get_column_def(cols, 'price_precision', 'null')
|
||||||
precision_mode = get_column_def(cols, 'precision_mode', 'null')
|
precision_mode = get_column_def(cols, 'precision_mode', 'null')
|
||||||
|
contract_size = get_column_def(cols, 'contract_size', 'null')
|
||||||
|
|
||||||
# Schema migration necessary
|
# Schema migration necessary
|
||||||
with engine.begin() as connection:
|
with engine.begin() as connection:
|
||||||
@ -161,7 +162,7 @@ def migrate_trades_and_orders_table(
|
|||||||
timeframe, open_trade_value, close_profit_abs,
|
timeframe, open_trade_value, close_profit_abs,
|
||||||
trading_mode, leverage, liquidation_price, is_short,
|
trading_mode, leverage, liquidation_price, is_short,
|
||||||
interest_rate, funding_fees, realized_profit,
|
interest_rate, funding_fees, realized_profit,
|
||||||
amount_precision, price_precision, precision_mode
|
amount_precision, price_precision, precision_mode, contract_size
|
||||||
)
|
)
|
||||||
select id, lower(exchange), pair, {base_currency} base_currency,
|
select id, lower(exchange), pair, {base_currency} base_currency,
|
||||||
{stake_currency} stake_currency,
|
{stake_currency} stake_currency,
|
||||||
@ -189,7 +190,7 @@ def migrate_trades_and_orders_table(
|
|||||||
{is_short} is_short, {interest_rate} interest_rate,
|
{is_short} is_short, {interest_rate} interest_rate,
|
||||||
{funding_fees} funding_fees, {realized_profit} realized_profit,
|
{funding_fees} funding_fees, {realized_profit} realized_profit,
|
||||||
{amount_precision} amount_precision, {price_precision} price_precision,
|
{amount_precision} amount_precision, {price_precision} price_precision,
|
||||||
{precision_mode} precision_mode
|
{precision_mode} precision_mode, {contract_size} contract_size
|
||||||
from {trade_back_name}
|
from {trade_back_name}
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
@ -308,7 +309,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
|
|||||||
# if ('orders' not in previous_tables
|
# if ('orders' not in previous_tables
|
||||||
# or not has_column(cols_orders, 'stop_price')):
|
# or not has_column(cols_orders, 'stop_price')):
|
||||||
migrating = False
|
migrating = False
|
||||||
if not has_column(cols_trades, 'precision_mode'):
|
if not has_column(cols_trades, 'contract_size'):
|
||||||
migrating = True
|
migrating = True
|
||||||
logger.info(f"Running database migration for trades - "
|
logger.info(f"Running database migration for trades - "
|
||||||
f"backup: {table_back_name}, {order_table_bak_name}")
|
f"backup: {table_back_name}, {order_table_bak_name}")
|
||||||
|
@ -15,6 +15,7 @@ from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPE
|
|||||||
from freqtrade.enums import ExitType, TradingMode
|
from freqtrade.enums import ExitType, TradingMode
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.exchange import amount_to_precision, price_to_precision
|
from freqtrade.exchange import amount_to_precision, price_to_precision
|
||||||
|
from freqtrade.exchange.exchange import amount_to_contracts, contracts_to_amount
|
||||||
from freqtrade.leverage import interest
|
from freqtrade.leverage import interest
|
||||||
from freqtrade.persistence.base import _DECL_BASE
|
from freqtrade.persistence.base import _DECL_BASE
|
||||||
from freqtrade.util import FtPrecise
|
from freqtrade.util import FtPrecise
|
||||||
@ -296,6 +297,7 @@ class LocalTrade():
|
|||||||
amount_precision: Optional[float] = None
|
amount_precision: Optional[float] = None
|
||||||
price_precision: Optional[float] = None
|
price_precision: Optional[float] = None
|
||||||
precision_mode: Optional[int] = None
|
precision_mode: Optional[int] = None
|
||||||
|
contract_size: Optional[float] = None
|
||||||
|
|
||||||
# Leverage trading properties
|
# Leverage trading properties
|
||||||
liquidation_price: Optional[float] = None
|
liquidation_price: Optional[float] = None
|
||||||
@ -623,7 +625,11 @@ class LocalTrade():
|
|||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'Got different open_order_id {self.open_order_id} != {order.order_id}')
|
f'Got different open_order_id {self.open_order_id} != {order.order_id}')
|
||||||
amount_tr = amount_to_precision(self.amount, self.amount_precision, self.precision_mode)
|
amount_tr = contracts_to_amount(
|
||||||
|
amount_to_precision(
|
||||||
|
amount_to_contracts(self.amount, self.contract_size),
|
||||||
|
self.amount_precision, self.precision_mode),
|
||||||
|
self.contract_size)
|
||||||
if isclose(order.safe_amount_after_fee, amount_tr, abs_tol=MATH_CLOSE_PREC):
|
if isclose(order.safe_amount_after_fee, amount_tr, abs_tol=MATH_CLOSE_PREC):
|
||||||
self.close(order.safe_price)
|
self.close(order.safe_price)
|
||||||
else:
|
else:
|
||||||
@ -1044,6 +1050,16 @@ class LocalTrade():
|
|||||||
"""
|
"""
|
||||||
return Trade.get_trades_proxy(is_open=True)
|
return Trade.get_trades_proxy(is_open=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_open_trade_count() -> int:
|
||||||
|
"""
|
||||||
|
get open trade count
|
||||||
|
"""
|
||||||
|
if Trade.use_db:
|
||||||
|
return Trade.query.filter(Trade.is_open.is_(True)).count()
|
||||||
|
else:
|
||||||
|
return len(LocalTrade.trades_open)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def stoploss_reinitialization(desired_stoploss):
|
def stoploss_reinitialization(desired_stoploss):
|
||||||
"""
|
"""
|
||||||
@ -1132,6 +1148,7 @@ class Trade(_DECL_BASE, LocalTrade):
|
|||||||
amount_precision = Column(Float, nullable=True)
|
amount_precision = Column(Float, nullable=True)
|
||||||
price_precision = Column(Float, nullable=True)
|
price_precision = Column(Float, nullable=True)
|
||||||
precision_mode = Column(Integer, nullable=True)
|
precision_mode = Column(Integer, nullable=True)
|
||||||
|
contract_size = Column(Float, nullable=True)
|
||||||
|
|
||||||
# Leverage trading properties
|
# Leverage trading properties
|
||||||
leverage = Column(Float, nullable=True, default=1.0)
|
leverage = Column(Float, nullable=True, default=1.0)
|
||||||
|
@ -73,7 +73,7 @@ class VolumePairList(IPairList):
|
|||||||
|
|
||||||
if (not self._use_range and not (
|
if (not self._use_range and not (
|
||||||
self._exchange.exchange_has('fetchTickers')
|
self._exchange.exchange_has('fetchTickers')
|
||||||
and self._exchange._ft_has["tickers_have_quoteVolume"])):
|
and self._exchange.get_option("tickers_have_quoteVolume"))):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"Exchange does not support dynamic whitelist in this configuration. "
|
"Exchange does not support dynamic whitelist in this configuration. "
|
||||||
"Please edit your config and either remove Volumepairlist, "
|
"Please edit your config and either remove Volumepairlist, "
|
||||||
@ -193,7 +193,7 @@ class VolumePairList(IPairList):
|
|||||||
) in candles else None
|
) in candles else None
|
||||||
# in case of candle data calculate typical price and quoteVolume for candle
|
# in case of candle data calculate typical price and quoteVolume for candle
|
||||||
if pair_candles is not None and not pair_candles.empty:
|
if pair_candles is not None and not pair_candles.empty:
|
||||||
if self._exchange._ft_has["ohlcv_volume_currency"] == "base":
|
if self._exchange.get_option("ohlcv_volume_currency") == "base":
|
||||||
pair_candles['typical_price'] = (pair_candles['high'] + pair_candles['low']
|
pair_candles['typical_price'] = (pair_candles['high'] + pair_candles['low']
|
||||||
+ pair_candles['close']) / 3
|
+ pair_candles['close']) / 3
|
||||||
|
|
||||||
|
@ -193,7 +193,10 @@ class IResolver:
|
|||||||
:return: List of dicts containing 'name', 'class' and 'location' entries
|
:return: List of dicts containing 'name', 'class' and 'location' entries
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'")
|
logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'")
|
||||||
objects = []
|
objects: List[Dict[str, Any]] = []
|
||||||
|
if not directory.is_dir():
|
||||||
|
logger.info(f"'{directory}' is not a directory, skipping.")
|
||||||
|
return objects
|
||||||
for entry in directory.iterdir():
|
for entry in directory.iterdir():
|
||||||
if (
|
if (
|
||||||
recursive and entry.is_dir()
|
recursive and entry.is_dir()
|
||||||
|
@ -17,7 +17,7 @@ pytest-mock==3.8.2
|
|||||||
pytest-random-order==1.0.4
|
pytest-random-order==1.0.4
|
||||||
isort==5.10.1
|
isort==5.10.1
|
||||||
# For datetime mocking
|
# For datetime mocking
|
||||||
time-machine==2.7.1
|
time-machine==2.8.1
|
||||||
|
|
||||||
# Convert jupyter notebooks to markdown documents
|
# Convert jupyter notebooks to markdown documents
|
||||||
nbconvert==6.5.3
|
nbconvert==6.5.3
|
||||||
@ -25,6 +25,6 @@ nbconvert==6.5.3
|
|||||||
# mypy types
|
# mypy types
|
||||||
types-cachetools==5.2.1
|
types-cachetools==5.2.1
|
||||||
types-filelock==3.2.7
|
types-filelock==3.2.7
|
||||||
types-requests==2.28.8
|
types-requests==2.28.9
|
||||||
types-tabulate==0.8.11
|
types-tabulate==0.8.11
|
||||||
types-python-dateutil==2.8.19
|
types-python-dateutil==2.8.19
|
||||||
|
@ -2,7 +2,7 @@ numpy==1.23.2
|
|||||||
pandas==1.4.3
|
pandas==1.4.3
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==1.92.20
|
ccxt==1.92.52
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==37.0.4
|
cryptography==37.0.4
|
||||||
aiohttp==3.8.1
|
aiohttp==3.8.1
|
||||||
@ -12,7 +12,7 @@ arrow==1.2.2
|
|||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
requests==2.28.1
|
requests==2.28.1
|
||||||
urllib3==1.26.11
|
urllib3==1.26.11
|
||||||
jsonschema==4.9.1
|
jsonschema==4.14.0
|
||||||
TA-Lib==0.4.24
|
TA-Lib==0.4.24
|
||||||
technical==1.3.0
|
technical==1.3.0
|
||||||
tabulate==0.8.10
|
tabulate==0.8.10
|
||||||
@ -34,7 +34,7 @@ orjson==3.7.12
|
|||||||
sdnotify==0.3.2
|
sdnotify==0.3.2
|
||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
fastapi==0.79.0
|
fastapi==0.79.1
|
||||||
uvicorn==0.18.2
|
uvicorn==0.18.2
|
||||||
pyjwt==2.4.0
|
pyjwt==2.4.0
|
||||||
aiofiles==0.8.0
|
aiofiles==0.8.0
|
||||||
|
@ -409,14 +409,14 @@ class TestCCXTExchange():
|
|||||||
assert (isinstance(futures_leverage, float) or isinstance(futures_leverage, int))
|
assert (isinstance(futures_leverage, float) or isinstance(futures_leverage, int))
|
||||||
assert futures_leverage >= 1.0
|
assert futures_leverage >= 1.0
|
||||||
|
|
||||||
def test_ccxt__get_contract_size(self, exchange_futures):
|
def test_ccxt_get_contract_size(self, exchange_futures):
|
||||||
futures, futures_name = exchange_futures
|
futures, futures_name = exchange_futures
|
||||||
if futures:
|
if futures:
|
||||||
futures_pair = EXCHANGES[futures_name].get(
|
futures_pair = EXCHANGES[futures_name].get(
|
||||||
'futures_pair',
|
'futures_pair',
|
||||||
EXCHANGES[futures_name]['pair']
|
EXCHANGES[futures_name]['pair']
|
||||||
)
|
)
|
||||||
contract_size = futures._get_contract_size(futures_pair)
|
contract_size = futures.get_contract_size(futures_pair)
|
||||||
assert (isinstance(contract_size, float) or isinstance(contract_size, int))
|
assert (isinstance(contract_size, float) or isinstance(contract_size, int))
|
||||||
assert contract_size >= 0.0
|
assert contract_size >= 0.0
|
||||||
|
|
||||||
|
@ -181,11 +181,11 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
|
|||||||
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
||||||
assert log_has(asynclogmsg, caplog)
|
assert log_has(asynclogmsg, caplog)
|
||||||
# Test additional headers case
|
# Test additional headers case
|
||||||
Exchange._headers = {'hello': 'world'}
|
Exchange._ccxt_params = {'hello': 'world'}
|
||||||
ex = Exchange(conf)
|
ex = Exchange(conf)
|
||||||
|
|
||||||
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
||||||
assert ex._api.headers == {'hello': 'world'}
|
assert ex._api.hello == 'world'
|
||||||
assert ex._ccxt_config == {}
|
assert ex._ccxt_config == {}
|
||||||
Exchange._headers = {}
|
Exchange._headers = {}
|
||||||
|
|
||||||
@ -2352,10 +2352,11 @@ def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name)
|
|||||||
order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=val)
|
order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=val)
|
||||||
assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC'
|
assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC'
|
||||||
# Not all exchanges support all limits for orderbook
|
# Not all exchanges support all limits for orderbook
|
||||||
if not exchange._ft_has['l2_limit_range'] or val in exchange._ft_has['l2_limit_range']:
|
if (not exchange.get_option('l2_limit_range')
|
||||||
|
or val in exchange.get_option('l2_limit_range')):
|
||||||
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == val
|
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == val
|
||||||
else:
|
else:
|
||||||
next_limit = exchange.get_next_limit_in_list(val, exchange._ft_has['l2_limit_range'])
|
next_limit = exchange.get_next_limit_in_list(val, exchange.get_option('l2_limit_range'))
|
||||||
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == next_limit
|
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == next_limit
|
||||||
|
|
||||||
|
|
||||||
@ -3311,16 +3312,16 @@ def test_merge_ft_has_dict(default_conf, mocker):
|
|||||||
|
|
||||||
ex = Kraken(default_conf)
|
ex = Kraken(default_conf)
|
||||||
assert ex._ft_has != Exchange._ft_has_default
|
assert ex._ft_has != Exchange._ft_has_default
|
||||||
assert ex._ft_has['trades_pagination'] == 'id'
|
assert ex.get_option('trades_pagination') == 'id'
|
||||||
assert ex._ft_has['trades_pagination_arg'] == 'since'
|
assert ex.get_option('trades_pagination_arg') == 'since'
|
||||||
|
|
||||||
# Binance defines different values
|
# Binance defines different values
|
||||||
ex = Binance(default_conf)
|
ex = Binance(default_conf)
|
||||||
assert ex._ft_has != Exchange._ft_has_default
|
assert ex._ft_has != Exchange._ft_has_default
|
||||||
assert ex._ft_has['stoploss_on_exchange']
|
assert ex.get_option('stoploss_on_exchange')
|
||||||
assert ex._ft_has['order_time_in_force'] == ['gtc', 'fok', 'ioc']
|
assert ex.get_option('order_time_in_force') == ['gtc', 'fok', 'ioc']
|
||||||
assert ex._ft_has['trades_pagination'] == 'id'
|
assert ex.get_option('trades_pagination') == 'id'
|
||||||
assert ex._ft_has['trades_pagination_arg'] == 'fromId'
|
assert ex.get_option('trades_pagination_arg') == 'fromId'
|
||||||
|
|
||||||
conf = copy.deepcopy(default_conf)
|
conf = copy.deepcopy(default_conf)
|
||||||
conf['exchange']['_ft_has_params'] = {"DeadBeef": 20,
|
conf['exchange']['_ft_has_params'] = {"DeadBeef": 20,
|
||||||
@ -4287,7 +4288,7 @@ def test__fetch_and_calculate_funding_fees_datetime_called(
|
|||||||
('XLTCUSDT', 0.01, 'futures'),
|
('XLTCUSDT', 0.01, 'futures'),
|
||||||
('ETH/USDT:USDT', 10, 'futures')
|
('ETH/USDT:USDT', 10, 'futures')
|
||||||
])
|
])
|
||||||
def test__get_contract_size(mocker, default_conf, pair, expected_size, trading_mode):
|
def est__get_contract_size(mocker, default_conf, pair, expected_size, trading_mode):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
default_conf['trading_mode'] = trading_mode
|
default_conf['trading_mode'] = trading_mode
|
||||||
default_conf['margin_mode'] = 'isolated'
|
default_conf['margin_mode'] = 'isolated'
|
||||||
@ -4306,7 +4307,7 @@ def test__get_contract_size(mocker, default_conf, pair, expected_size, trading_m
|
|||||||
'contractSize': '10',
|
'contractSize': '10',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
size = exchange._get_contract_size(pair)
|
size = exchange.get_contract_size(pair)
|
||||||
assert expected_size == size
|
assert expected_size == size
|
||||||
|
|
||||||
|
|
||||||
@ -5145,7 +5146,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
exchange._get_contract_size = MagicMock(return_value=contract_size)
|
exchange.get_contract_size = MagicMock(return_value=contract_size)
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
order = exchange.stoploss(
|
order = exchange.stoploss(
|
||||||
|
@ -48,6 +48,10 @@ def test_search_all_strategies_with_failed():
|
|||||||
assert len([x for x in strategies if x['class'] is not None]) == 9
|
assert len([x for x in strategies if x['class'] is not None]) == 9
|
||||||
assert len([x for x in strategies if x['class'] is None]) == 1
|
assert len([x for x in strategies if x['class'] is None]) == 1
|
||||||
|
|
||||||
|
directory = Path(__file__).parent / "strats_nonexistingdir"
|
||||||
|
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
|
||||||
|
assert len(strategies) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_load_strategy(default_conf, result):
|
def test_load_strategy(default_conf, result):
|
||||||
default_conf.update({'strategy': 'SampleStrategy',
|
default_conf.update({'strategy': 'SampleStrategy',
|
||||||
|
@ -473,8 +473,6 @@ def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None:
|
|||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
|
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
|
||||||
|
|
||||||
Trade.query = MagicMock()
|
|
||||||
Trade.query.filter = MagicMock()
|
|
||||||
assert not freqtrade.create_trade('ETH/USDT')
|
assert not freqtrade.create_trade('ETH/USDT')
|
||||||
|
|
||||||
|
|
||||||
|
@ -1689,6 +1689,7 @@ def test_get_open(fee, is_short, use_db):
|
|||||||
|
|
||||||
create_mock_trades(fee, is_short, use_db)
|
create_mock_trades(fee, is_short, use_db)
|
||||||
assert len(Trade.get_open_trades()) == 4
|
assert len(Trade.get_open_trades()) == 4
|
||||||
|
assert Trade.get_open_trade_count() == 4
|
||||||
|
|
||||||
Trade.use_db = True
|
Trade.use_db = True
|
||||||
|
|
||||||
@ -1701,6 +1702,7 @@ def test_get_open_lev(fee, use_db):
|
|||||||
|
|
||||||
create_mock_trades_with_leverage(fee, use_db)
|
create_mock_trades_with_leverage(fee, use_db)
|
||||||
assert len(Trade.get_open_trades()) == 5
|
assert len(Trade.get_open_trades()) == 5
|
||||||
|
assert Trade.get_open_trade_count() == 5
|
||||||
|
|
||||||
Trade.use_db = True
|
Trade.use_db = True
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user