Merge branch 'freqtrade:feat/short' into feat/short
This commit is contained in:
commit
91cc8212f6
@ -98,6 +98,38 @@ class MyAwesomeStrategy(IStrategy):
|
|||||||
!!! Note
|
!!! Note
|
||||||
All overrides are optional and can be mixed/matched as necessary.
|
All overrides are optional and can be mixed/matched as necessary.
|
||||||
|
|
||||||
|
### Overriding Base estimator
|
||||||
|
|
||||||
|
You can define your own estimator for Hyperopt by implementing `generate_estimator()` in the Hyperopt subclass.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyAwesomeStrategy(IStrategy):
|
||||||
|
class HyperOpt:
|
||||||
|
def generate_estimator():
|
||||||
|
return "RF"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Possible values are either one of "GP", "RF", "ET", "GBRT" (Details can be found in the [scikit-optimize documentation](https://scikit-optimize.github.io/)), or "an instance of a class that inherits from `RegressorMixin` (from sklearn) and where the `predict` method has an optional `return_std` argument, which returns `std(Y | x)` along with `E[Y | x]`".
|
||||||
|
|
||||||
|
Some research will be necessary to find additional Regressors.
|
||||||
|
|
||||||
|
Example for `ExtraTreesRegressor` ("ET") with additional parameters:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyAwesomeStrategy(IStrategy):
|
||||||
|
class HyperOpt:
|
||||||
|
def generate_estimator():
|
||||||
|
from skopt.learning import ExtraTreesRegressor
|
||||||
|
# Corresponds to "ET" - but allows additional parameters.
|
||||||
|
return ExtraTreesRegressor(n_estimators=100)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
While custom estimators can be provided, it's up to you as User to do research on possible parameters and analyze / understand which ones should be used.
|
||||||
|
If you're unsure about this, best use one of the Defaults (`"ET"` has proven to be the most versatile) without further parameters.
|
||||||
|
|
||||||
## Space options
|
## Space options
|
||||||
|
|
||||||
For the additional spaces, scikit-optimize (in combination with Freqtrade) provides the following space types:
|
For the additional spaces, scikit-optimize (in combination with Freqtrade) provides the following space types:
|
||||||
|
@ -677,7 +677,7 @@ If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace f
|
|||||||
|
|
||||||
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used.
|
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used.
|
||||||
|
|
||||||
If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default.
|
If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default.
|
||||||
|
|
||||||
Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps).
|
Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps).
|
||||||
|
|
||||||
|
19
freqtrade/configuration/PeriodicCache.py
Normal file
19
freqtrade/configuration/PeriodicCache.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from cachetools.ttl import TTLCache
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicCache(TTLCache):
|
||||||
|
"""
|
||||||
|
Special cache that expires at "straight" times
|
||||||
|
A timer with ttl of 3600 (1h) will expire at every full hour (:00).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, maxsize, ttl, getsizeof=None):
|
||||||
|
def local_timer():
|
||||||
|
ts = datetime.now(timezone.utc).timestamp()
|
||||||
|
offset = (ts % ttl)
|
||||||
|
return ts - offset
|
||||||
|
|
||||||
|
# Init with smlight offset
|
||||||
|
super().__init__(maxsize=maxsize, ttl=ttl-1e-5, timer=local_timer, getsizeof=getsizeof)
|
@ -4,4 +4,5 @@ from freqtrade.configuration.check_exchange import check_exchange
|
|||||||
from freqtrade.configuration.config_setup import setup_utils_configuration
|
from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||||
from freqtrade.configuration.config_validation import validate_config_consistency
|
from freqtrade.configuration.config_validation import validate_config_consistency
|
||||||
from freqtrade.configuration.configuration import Configuration
|
from freqtrade.configuration.configuration import Configuration
|
||||||
|
from freqtrade.configuration.PeriodicCache import PeriodicCache
|
||||||
from freqtrade.configuration.timerange import TimeRange
|
from freqtrade.configuration.timerange import TimeRange
|
||||||
|
@ -45,7 +45,7 @@ progressbar.streams.wrap_stdout()
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
INITIAL_POINTS = 30
|
INITIAL_POINTS = 5
|
||||||
|
|
||||||
# Keep no more than SKOPT_MODEL_QUEUE_SIZE models
|
# Keep no more than SKOPT_MODEL_QUEUE_SIZE models
|
||||||
# in the skopt model queue, to optimize memory consumption
|
# in the skopt model queue, to optimize memory consumption
|
||||||
@ -241,7 +241,7 @@ class Hyperopt:
|
|||||||
|
|
||||||
if HyperoptTools.has_space(self.config, 'buy'):
|
if HyperoptTools.has_space(self.config, 'buy'):
|
||||||
logger.debug("Hyperopt has 'buy' space")
|
logger.debug("Hyperopt has 'buy' space")
|
||||||
self.buy_space = self.custom_hyperopt.indicator_space()
|
self.buy_space = self.custom_hyperopt.buy_indicator_space()
|
||||||
|
|
||||||
if HyperoptTools.has_space(self.config, 'sell'):
|
if HyperoptTools.has_space(self.config, 'sell'):
|
||||||
logger.debug("Hyperopt has 'sell' space")
|
logger.debug("Hyperopt has 'sell' space")
|
||||||
@ -365,10 +365,20 @@ class Hyperopt:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer:
|
def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer:
|
||||||
|
estimator = self.custom_hyperopt.generate_estimator()
|
||||||
|
|
||||||
|
acq_optimizer = "sampling"
|
||||||
|
if isinstance(estimator, str):
|
||||||
|
if estimator not in ("GP", "RF", "ET", "GBRT"):
|
||||||
|
raise OperationalException(f"Estimator {estimator} not supported.")
|
||||||
|
else:
|
||||||
|
acq_optimizer = "auto"
|
||||||
|
|
||||||
|
logger.info(f"Using estimator {estimator}.")
|
||||||
return Optimizer(
|
return Optimizer(
|
||||||
dimensions,
|
dimensions,
|
||||||
base_estimator="ET",
|
base_estimator=estimator,
|
||||||
acq_optimizer="auto",
|
acq_optimizer=acq_optimizer,
|
||||||
n_initial_points=INITIAL_POINTS,
|
n_initial_points=INITIAL_POINTS,
|
||||||
acq_optimizer_kwargs={'n_jobs': cpu_count},
|
acq_optimizer_kwargs={'n_jobs': cpu_count},
|
||||||
random_state=self.random_state,
|
random_state=self.random_state,
|
||||||
|
@ -12,7 +12,7 @@ from freqtrade.exceptions import OperationalException
|
|||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
from skopt.space import Dimension
|
from skopt.space import Dimension
|
||||||
|
|
||||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
from freqtrade.optimize.hyperopt_interface import EstimatorType, IHyperOpt
|
||||||
|
|
||||||
|
|
||||||
def _format_exception_message(space: str) -> str:
|
def _format_exception_message(space: str) -> str:
|
||||||
@ -56,7 +56,7 @@ class HyperOptAuto(IHyperOpt):
|
|||||||
else:
|
else:
|
||||||
_format_exception_message(category)
|
_format_exception_message(category)
|
||||||
|
|
||||||
def indicator_space(self) -> List['Dimension']:
|
def buy_indicator_space(self) -> List['Dimension']:
|
||||||
return self._get_indicator_space('buy')
|
return self._get_indicator_space('buy')
|
||||||
|
|
||||||
def sell_indicator_space(self) -> List['Dimension']:
|
def sell_indicator_space(self) -> List['Dimension']:
|
||||||
@ -79,3 +79,6 @@ class HyperOptAuto(IHyperOpt):
|
|||||||
|
|
||||||
def trailing_space(self) -> List['Dimension']:
|
def trailing_space(self) -> List['Dimension']:
|
||||||
return self._get_func('trailing_space')()
|
return self._get_func('trailing_space')()
|
||||||
|
|
||||||
|
def generate_estimator(self) -> EstimatorType:
|
||||||
|
return self._get_func('generate_estimator')()
|
||||||
|
@ -5,8 +5,9 @@ This module defines the interface to apply for hyperopt
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from typing import Dict, List
|
from typing import Dict, List, Union
|
||||||
|
|
||||||
|
from sklearn.base import RegressorMixin
|
||||||
from skopt.space import Categorical, Dimension, Integer
|
from skopt.space import Categorical, Dimension, Integer
|
||||||
|
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
@ -17,6 +18,8 @@ from freqtrade.strategy import IStrategy
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
EstimatorType = Union[RegressorMixin, str]
|
||||||
|
|
||||||
|
|
||||||
class IHyperOpt(ABC):
|
class IHyperOpt(ABC):
|
||||||
"""
|
"""
|
||||||
@ -37,6 +40,14 @@ class IHyperOpt(ABC):
|
|||||||
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
|
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
|
||||||
IHyperOpt.timeframe = str(config['timeframe'])
|
IHyperOpt.timeframe = str(config['timeframe'])
|
||||||
|
|
||||||
|
def generate_estimator(self) -> EstimatorType:
|
||||||
|
"""
|
||||||
|
Return base_estimator.
|
||||||
|
Can be any of "GP", "RF", "ET", "GBRT" or an instance of a class
|
||||||
|
inheriting from RegressorMixin (from sklearn).
|
||||||
|
"""
|
||||||
|
return 'ET'
|
||||||
|
|
||||||
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
|
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
|
||||||
"""
|
"""
|
||||||
Create a ROI table.
|
Create a ROI table.
|
||||||
|
@ -320,6 +320,7 @@ class LocalTrade():
|
|||||||
if self.isolated_liq:
|
if self.isolated_liq:
|
||||||
self.set_isolated_liq(self.isolated_liq)
|
self.set_isolated_liq(self.isolated_liq)
|
||||||
self.recalc_open_trade_value()
|
self.recalc_open_trade_value()
|
||||||
|
# TODO-lev: Throw exception if on margin and interest_rate is none
|
||||||
|
|
||||||
def _set_stop_loss(self, stop_loss: float, percent: float):
|
def _set_stop_loss(self, stop_loss: float, percent: float):
|
||||||
"""
|
"""
|
||||||
|
@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional
|
|||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from freqtrade.configuration import PeriodicCache
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.misc import plural
|
from freqtrade.misc import plural
|
||||||
from freqtrade.plugins.pairlist.IPairList import IPairList
|
from freqtrade.plugins.pairlist.IPairList import IPairList
|
||||||
@ -18,14 +19,15 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class AgeFilter(IPairList):
|
class AgeFilter(IPairList):
|
||||||
|
|
||||||
# Checked symbols cache (dictionary of ticker symbol => timestamp)
|
|
||||||
_symbolsChecked: Dict[str, int] = {}
|
|
||||||
|
|
||||||
def __init__(self, exchange, pairlistmanager,
|
def __init__(self, exchange, pairlistmanager,
|
||||||
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
|
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
|
||||||
pairlist_pos: int) -> None:
|
pairlist_pos: int) -> None:
|
||||||
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
|
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
|
||||||
|
|
||||||
|
# Checked symbols cache (dictionary of ticker symbol => timestamp)
|
||||||
|
self._symbolsChecked: Dict[str, int] = {}
|
||||||
|
self._symbolsCheckFailed = PeriodicCache(maxsize=1000, ttl=86_400)
|
||||||
|
|
||||||
self._min_days_listed = pairlistconfig.get('min_days_listed', 10)
|
self._min_days_listed = pairlistconfig.get('min_days_listed', 10)
|
||||||
self._max_days_listed = pairlistconfig.get('max_days_listed', None)
|
self._max_days_listed = pairlistconfig.get('max_days_listed', None)
|
||||||
|
|
||||||
@ -69,9 +71,12 @@ class AgeFilter(IPairList):
|
|||||||
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
||||||
:return: new allowlist
|
:return: new allowlist
|
||||||
"""
|
"""
|
||||||
needed_pairs = [(p, '1d') for p in pairlist if p not in self._symbolsChecked]
|
needed_pairs = [
|
||||||
|
(p, '1d') for p in pairlist
|
||||||
|
if p not in self._symbolsChecked and p not in self._symbolsCheckFailed]
|
||||||
if not needed_pairs:
|
if not needed_pairs:
|
||||||
return pairlist
|
# Remove pairs that have been removed before
|
||||||
|
return [p for p in pairlist if p not in self._symbolsCheckFailed]
|
||||||
|
|
||||||
since_days = -(
|
since_days = -(
|
||||||
self._max_days_listed if self._max_days_listed else self._min_days_listed
|
self._max_days_listed if self._max_days_listed else self._min_days_listed
|
||||||
@ -118,5 +123,6 @@ class AgeFilter(IPairList):
|
|||||||
" or more than "
|
" or more than "
|
||||||
f"{self._max_days_listed} {plural(self._max_days_listed, 'day')}"
|
f"{self._max_days_listed} {plural(self._max_days_listed, 'day')}"
|
||||||
) if self._max_days_listed else ''), logger.info)
|
) if self._max_days_listed else ''), logger.info)
|
||||||
|
self._symbolsCheckFailed[pair] = arrow.utcnow().int_timestamp * 1000
|
||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
@ -786,10 +786,11 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
Does not run advise_buy or advise_sell!
|
Does not run advise_buy or advise_sell!
|
||||||
Used by optimize operations only, not during dry / live runs.
|
Used by optimize operations only, not during dry / live runs.
|
||||||
Using .copy() to get a fresh copy of the dataframe for every strategy run.
|
Using .copy() to get a fresh copy of the dataframe for every strategy run.
|
||||||
|
Also copy on output to avoid PerformanceWarnings pandas 1.3.0 started to show.
|
||||||
Has positive effects on memory usage for whatever reason - also when
|
Has positive effects on memory usage for whatever reason - also when
|
||||||
using only one strategy.
|
using only one strategy.
|
||||||
"""
|
"""
|
||||||
return {pair: self.advise_indicators(pair_data.copy(), {'pair': pair})
|
return {pair: self.advise_indicators(pair_data.copy(), {'pair': pair}).copy()
|
||||||
for pair, pair_data in data.items()}
|
for pair, pair_data in data.items()}
|
||||||
|
|
||||||
def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
@ -14,6 +14,8 @@ pytest-cov==2.12.1
|
|||||||
pytest-mock==3.6.1
|
pytest-mock==3.6.1
|
||||||
pytest-random-order==1.0.4
|
pytest-random-order==1.0.4
|
||||||
isort==5.9.3
|
isort==5.9.3
|
||||||
|
# For datetime mocking
|
||||||
|
time-machine==2.4.0
|
||||||
|
|
||||||
# Convert jupyter notebooks to markdown documents
|
# Convert jupyter notebooks to markdown documents
|
||||||
nbconvert==6.1.0
|
nbconvert==6.1.0
|
||||||
|
@ -6,6 +6,7 @@ from copy import deepcopy
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Tuple
|
||||||
from unittest.mock import MagicMock, Mock, PropertyMock
|
from unittest.mock import MagicMock, Mock, PropertyMock
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
@ -262,6 +263,10 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
|
|||||||
Trade.query.session.flush()
|
Trade.query.session.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def get_sides(is_short: bool) -> Tuple[str, str]:
|
||||||
|
return ("sell", "buy") if is_short else ("buy", "sell")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def patch_coingekko(mocker) -> None:
|
def patch_coingekko(mocker) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -884,6 +884,10 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
|||||||
assert hyperopt.backtesting.strategy.buy_rsi.value != 35
|
assert hyperopt.backtesting.strategy.buy_rsi.value != 35
|
||||||
assert hyperopt.backtesting.strategy.sell_rsi.value != 74
|
assert hyperopt.backtesting.strategy.sell_rsi.value != 74
|
||||||
|
|
||||||
|
hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: 'ET1'
|
||||||
|
with pytest.raises(OperationalException, match="Estimator ET1 not supported."):
|
||||||
|
hyperopt.get_optimizer([], 2)
|
||||||
|
|
||||||
|
|
||||||
def test_SKDecimal():
|
def test_SKDecimal():
|
||||||
space = SKDecimal(1, 2, decimals=2)
|
space = SKDecimal(1, 2, decimals=2)
|
||||||
|
@ -4,6 +4,7 @@ import time
|
|||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import time_machine
|
||||||
|
|
||||||
from freqtrade.constants import AVAILABLE_PAIRLISTS
|
from freqtrade.constants import AVAILABLE_PAIRLISTS
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
@ -815,18 +816,17 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick
|
|||||||
|
|
||||||
|
|
||||||
def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history):
|
def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history):
|
||||||
|
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||||
ohlcv_data = {
|
ohlcv_data = {
|
||||||
('ETH/BTC', '1d'): ohlcv_history,
|
('ETH/BTC', '1d'): ohlcv_history,
|
||||||
('TKN/BTC', '1d'): ohlcv_history,
|
('TKN/BTC', '1d'): ohlcv_history,
|
||||||
('LTC/BTC', '1d'): ohlcv_history,
|
('LTC/BTC', '1d'): ohlcv_history,
|
||||||
}
|
}
|
||||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
|
||||||
markets=PropertyMock(return_value=markets),
|
|
||||||
exchange_has=MagicMock(return_value=True),
|
|
||||||
get_tickers=tickers
|
|
||||||
)
|
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
|
markets=PropertyMock(return_value=markets),
|
||||||
|
exchange_has=MagicMock(return_value=True),
|
||||||
|
get_tickers=tickers,
|
||||||
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -836,11 +836,43 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o
|
|||||||
assert len(freqtrade.pairlists.whitelist) == 3
|
assert len(freqtrade.pairlists.whitelist) == 3
|
||||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count > 0
|
assert freqtrade.exchange.refresh_latest_ohlcv.call_count > 0
|
||||||
|
|
||||||
previous_call_count = freqtrade.exchange.refresh_latest_ohlcv.call_count
|
|
||||||
freqtrade.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
assert len(freqtrade.pairlists.whitelist) == 3
|
assert len(freqtrade.pairlists.whitelist) == 3
|
||||||
|
# Call to XRP/BTC cached
|
||||||
|
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 2
|
||||||
|
|
||||||
|
ohlcv_data = {
|
||||||
|
('ETH/BTC', '1d'): ohlcv_history,
|
||||||
|
('TKN/BTC', '1d'): ohlcv_history,
|
||||||
|
('LTC/BTC', '1d'): ohlcv_history,
|
||||||
|
('XRP/BTC', '1d'): ohlcv_history.iloc[[0]],
|
||||||
|
}
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||||
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
|
assert len(freqtrade.pairlists.whitelist) == 3
|
||||||
|
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||||
|
|
||||||
|
# Move to next day
|
||||||
|
t.move_to("2021-09-02 01:00:00 +00:00")
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||||
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
|
assert len(freqtrade.pairlists.whitelist) == 3
|
||||||
|
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||||
|
|
||||||
|
# Move another day with fresh mocks (now the pair is old enough)
|
||||||
|
t.move_to("2021-09-03 01:00:00 +00:00")
|
||||||
# Called once for XRP/BTC
|
# Called once for XRP/BTC
|
||||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == previous_call_count + 1
|
ohlcv_data = {
|
||||||
|
('ETH/BTC', '1d'): ohlcv_history,
|
||||||
|
('TKN/BTC', '1d'): ohlcv_history,
|
||||||
|
('LTC/BTC', '1d'): ohlcv_history,
|
||||||
|
('XRP/BTC', '1d'): ohlcv_history,
|
||||||
|
}
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||||
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
|
assert len(freqtrade.pairlists.whitelist) == 4
|
||||||
|
# Called once (only for XRP/BTC)
|
||||||
|
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_OffsetFilter_error(mocker, whitelist_conf) -> None:
|
def test_OffsetFilter_error(mocker, whitelist_conf) -> None:
|
||||||
|
32
tests/test_periodiccache.py
Normal file
32
tests/test_periodiccache.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import time_machine
|
||||||
|
|
||||||
|
from freqtrade.configuration import PeriodicCache
|
||||||
|
|
||||||
|
|
||||||
|
def test_ttl_cache():
|
||||||
|
|
||||||
|
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||||
|
|
||||||
|
cache = PeriodicCache(5, ttl=60)
|
||||||
|
cache1h = PeriodicCache(5, ttl=3600)
|
||||||
|
|
||||||
|
assert cache.timer() == 1630472400.0
|
||||||
|
cache['a'] = 1235
|
||||||
|
cache1h['a'] = 555123
|
||||||
|
assert 'a' in cache
|
||||||
|
assert 'a' in cache1h
|
||||||
|
|
||||||
|
t.move_to("2021-09-01 05:00:59 +00:00")
|
||||||
|
assert 'a' in cache
|
||||||
|
assert 'a' in cache1h
|
||||||
|
|
||||||
|
# Cache expired
|
||||||
|
t.move_to("2021-09-01 05:01:00 +00:00")
|
||||||
|
assert 'a' not in cache
|
||||||
|
assert 'a' in cache1h
|
||||||
|
|
||||||
|
t.move_to("2021-09-01 05:59:59 +00:00")
|
||||||
|
assert 'a' in cache1h
|
||||||
|
|
||||||
|
t.move_to("2021-09-01 06:00:00 +00:00")
|
||||||
|
assert 'a' not in cache1h
|
@ -13,7 +13,8 @@ from sqlalchemy import create_engine, inspect, text
|
|||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
|
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
|
||||||
from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re
|
from tests.conftest import (create_mock_trades, create_mock_trades_with_leverage, get_sides,
|
||||||
|
log_has, log_has_re)
|
||||||
|
|
||||||
|
|
||||||
def test_init_create_session(default_conf):
|
def test_init_create_session(default_conf):
|
||||||
@ -64,8 +65,10 @@ def test_init_dryrun_db(default_conf, tmpdir):
|
|||||||
assert Path(filename).is_file()
|
assert Path(filename).is_file()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('is_short', [False, True])
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_enter_exit_side(fee):
|
def test_enter_exit_side(fee, is_short):
|
||||||
|
enter_side, exit_side = get_sides(is_short)
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
id=2,
|
id=2,
|
||||||
pair='ADA/USDT',
|
pair='ADA/USDT',
|
||||||
@ -77,16 +80,11 @@ def test_enter_exit_side(fee):
|
|||||||
fee_open=fee.return_value,
|
fee_open=fee.return_value,
|
||||||
fee_close=fee.return_value,
|
fee_close=fee.return_value,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
is_short=False,
|
is_short=is_short,
|
||||||
leverage=2.0
|
leverage=2.0
|
||||||
)
|
)
|
||||||
assert trade.enter_side == 'buy'
|
assert trade.enter_side == enter_side
|
||||||
assert trade.exit_side == 'sell'
|
assert trade.exit_side == exit_side
|
||||||
|
|
||||||
trade.is_short = True
|
|
||||||
|
|
||||||
assert trade.enter_side == 'sell'
|
|
||||||
assert trade.exit_side == 'buy'
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
@ -170,8 +168,32 @@ def test_set_stop_loss_isolated_liq(fee):
|
|||||||
assert trade.initial_stop_loss == 0.09
|
assert trade.initial_stop_loss == 0.09
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest', [
|
||||||
|
("binance", False, 3, 10, 0.0005, round(0.0008333333333333334, 8)),
|
||||||
|
("binance", True, 3, 10, 0.0005, 0.000625),
|
||||||
|
("binance", False, 3, 295, 0.0005, round(0.004166666666666667, 8)),
|
||||||
|
("binance", True, 3, 295, 0.0005, round(0.0031249999999999997, 8)),
|
||||||
|
("binance", False, 3, 295, 0.00025, round(0.0020833333333333333, 8)),
|
||||||
|
("binance", True, 3, 295, 0.00025, round(0.0015624999999999999, 8)),
|
||||||
|
("binance", False, 5, 295, 0.0005, 0.005),
|
||||||
|
("binance", True, 5, 295, 0.0005, round(0.0031249999999999997, 8)),
|
||||||
|
("binance", False, 1, 295, 0.0005, 0.0),
|
||||||
|
("binance", True, 1, 295, 0.0005, 0.003125),
|
||||||
|
|
||||||
|
("kraken", False, 3, 10, 0.0005, 0.040),
|
||||||
|
("kraken", True, 3, 10, 0.0005, 0.030),
|
||||||
|
("kraken", False, 3, 295, 0.0005, 0.06),
|
||||||
|
("kraken", True, 3, 295, 0.0005, 0.045),
|
||||||
|
("kraken", False, 3, 295, 0.00025, 0.03),
|
||||||
|
("kraken", True, 3, 295, 0.00025, 0.0225),
|
||||||
|
("kraken", False, 5, 295, 0.0005, round(0.07200000000000001, 8)),
|
||||||
|
("kraken", True, 5, 295, 0.0005, 0.045),
|
||||||
|
("kraken", False, 1, 295, 0.0005, 0.0),
|
||||||
|
("kraken", True, 1, 295, 0.0005, 0.045),
|
||||||
|
|
||||||
|
])
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_interest(market_buy_order_usdt, fee):
|
def test_interest(market_buy_order_usdt, fee, exchange, is_short, lev, minutes, rate, interest):
|
||||||
"""
|
"""
|
||||||
10min, 5hr limit trade on Binance/Kraken at 3x,5x leverage
|
10min, 5hr limit trade on Binance/Kraken at 3x,5x leverage
|
||||||
fee: 0.25 % quote
|
fee: 0.25 % quote
|
||||||
@ -230,114 +252,27 @@ def test_interest(market_buy_order_usdt, fee):
|
|||||||
stake_amount=20.0,
|
stake_amount=20.0,
|
||||||
amount=30.0,
|
amount=30.0,
|
||||||
open_rate=2.0,
|
open_rate=2.0,
|
||||||
open_date=datetime.utcnow() - timedelta(hours=0, minutes=10),
|
open_date=datetime.utcnow() - timedelta(minutes=minutes),
|
||||||
fee_open=fee.return_value,
|
fee_open=fee.return_value,
|
||||||
fee_close=fee.return_value,
|
fee_close=fee.return_value,
|
||||||
exchange='binance',
|
exchange=exchange,
|
||||||
leverage=3.0,
|
leverage=lev,
|
||||||
interest_rate=0.0005,
|
interest_rate=rate,
|
||||||
|
is_short=is_short
|
||||||
)
|
)
|
||||||
|
|
||||||
# 10min, 3x leverage
|
assert round(float(trade.calculate_interest()), 8) == interest
|
||||||
# binance
|
|
||||||
assert round(float(trade.calculate_interest()), 8) == round(0.0008333333333333334, 8)
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert float(trade.calculate_interest()) == 0.040
|
|
||||||
# Short
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# binace
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert float(trade.calculate_interest()) == 0.000625
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert isclose(float(trade.calculate_interest()), 0.030)
|
|
||||||
|
|
||||||
# 5hr, long
|
|
||||||
trade.open_date = datetime.utcnow() - timedelta(hours=4, minutes=55)
|
|
||||||
trade.is_short = False
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# binance
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert round(float(trade.calculate_interest()), 8) == round(0.004166666666666667, 8)
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert float(trade.calculate_interest()) == 0.06
|
|
||||||
# short
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# binace
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert round(float(trade.calculate_interest()), 8) == round(0.0031249999999999997, 8)
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert float(trade.calculate_interest()) == 0.045
|
|
||||||
|
|
||||||
# 0.00025 interest, 5hr, long
|
|
||||||
trade.is_short = False
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# binance
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert round(float(trade.calculate_interest(interest_rate=0.00025)),
|
|
||||||
8) == round(0.0020833333333333333, 8)
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert isclose(float(trade.calculate_interest(interest_rate=0.00025)), 0.03)
|
|
||||||
# short
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# binace
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert round(float(trade.calculate_interest(interest_rate=0.00025)),
|
|
||||||
8) == round(0.0015624999999999999, 8)
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert float(trade.calculate_interest(interest_rate=0.00025)) == 0.0225
|
|
||||||
|
|
||||||
# 5x leverage, 0.0005 interest, 5hr, long
|
|
||||||
trade.is_short = False
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
trade.leverage = 5.0
|
|
||||||
# binance
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert round(float(trade.calculate_interest()), 8) == 0.005
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert float(trade.calculate_interest()) == round(0.07200000000000001, 8)
|
|
||||||
# short
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# binace
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert round(float(trade.calculate_interest()), 8) == round(0.0031249999999999997, 8)
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert float(trade.calculate_interest()) == 0.045
|
|
||||||
|
|
||||||
# 1x leverage, 0.0005 interest, 5hr
|
|
||||||
trade.is_short = False
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
trade.leverage = 1.0
|
|
||||||
# binance
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert float(trade.calculate_interest()) == 0.0
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert float(trade.calculate_interest()) == 0.0
|
|
||||||
# short
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# binace
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert float(trade.calculate_interest()) == 0.003125
|
|
||||||
# kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert float(trade.calculate_interest()) == 0.045
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('is_short,lev,borrowed', [
|
||||||
|
(False, 1.0, 0.0),
|
||||||
|
(True, 1.0, 30.0),
|
||||||
|
(False, 3.0, 40.0),
|
||||||
|
(True, 3.0, 30.0),
|
||||||
|
])
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog):
|
def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee,
|
||||||
|
caplog, is_short, lev, borrowed):
|
||||||
"""
|
"""
|
||||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||||
fee: 0.25% quote
|
fee: 0.25% quote
|
||||||
@ -411,20 +346,19 @@ def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog):
|
|||||||
fee_open=fee.return_value,
|
fee_open=fee.return_value,
|
||||||
fee_close=fee.return_value,
|
fee_close=fee.return_value,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
|
is_short=is_short,
|
||||||
|
leverage=lev
|
||||||
)
|
)
|
||||||
assert trade.borrowed == 0
|
assert trade.borrowed == borrowed
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
assert trade.borrowed == 30.0
|
|
||||||
trade.leverage = 3.0
|
|
||||||
assert trade.borrowed == 30.0
|
|
||||||
trade.is_short = False
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
assert trade.borrowed == 40.0
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('is_short,open_rate,close_rate,lev,profit', [
|
||||||
|
(False, 2.0, 2.2, 1.0, round(0.0945137157107232, 8)),
|
||||||
|
(True, 2.2, 2.0, 3.0, round(0.2589996297562085, 8))
|
||||||
|
])
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog):
|
def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt,
|
||||||
|
is_short, open_rate, close_rate, lev, profit):
|
||||||
"""
|
"""
|
||||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||||
fee: 0.25% quote
|
fee: 0.25% quote
|
||||||
@ -494,84 +428,52 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
enter_order = limit_sell_order_usdt if is_short else limit_buy_order_usdt
|
||||||
|
exit_order = limit_buy_order_usdt if is_short else limit_sell_order_usdt
|
||||||
|
enter_side, exit_side = get_sides(is_short)
|
||||||
|
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
id=2,
|
id=2,
|
||||||
pair='ADA/USDT',
|
pair='ADA/USDT',
|
||||||
stake_amount=60.0,
|
stake_amount=60.0,
|
||||||
open_rate=2.0,
|
open_rate=open_rate,
|
||||||
amount=30.0,
|
|
||||||
is_open=True,
|
|
||||||
open_date=arrow.utcnow().datetime,
|
|
||||||
fee_open=fee.return_value,
|
|
||||||
fee_close=fee.return_value,
|
|
||||||
exchange='binance'
|
|
||||||
)
|
|
||||||
assert trade.open_order_id is None
|
|
||||||
assert trade.close_profit is None
|
|
||||||
assert trade.close_date is None
|
|
||||||
|
|
||||||
trade.open_order_id = 'something'
|
|
||||||
trade.update(limit_buy_order_usdt)
|
|
||||||
assert trade.open_order_id is None
|
|
||||||
assert trade.open_rate == 2.00
|
|
||||||
assert trade.close_profit is None
|
|
||||||
assert trade.close_date is None
|
|
||||||
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
|
|
||||||
r'pair=ADA/USDT, amount=30.00000000, '
|
|
||||||
r"is_short=False, leverage=1.0, open_rate=2.00000000, open_since=.*\).",
|
|
||||||
caplog)
|
|
||||||
|
|
||||||
caplog.clear()
|
|
||||||
trade.open_order_id = 'something'
|
|
||||||
trade.update(limit_sell_order_usdt)
|
|
||||||
assert trade.open_order_id is None
|
|
||||||
assert trade.close_rate == 2.20
|
|
||||||
assert trade.close_profit == round(0.0945137157107232, 8)
|
|
||||||
assert trade.close_date is not None
|
|
||||||
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
|
|
||||||
r"pair=ADA/USDT, amount=30.00000000, "
|
|
||||||
r"is_short=False, leverage=1.0, open_rate=2.00000000, open_since=.*\).",
|
|
||||||
caplog)
|
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
trade = Trade(
|
|
||||||
id=226531,
|
|
||||||
pair='ADA/USDT',
|
|
||||||
stake_amount=20.0,
|
|
||||||
open_rate=2.0,
|
|
||||||
amount=30.0,
|
amount=30.0,
|
||||||
is_open=True,
|
is_open=True,
|
||||||
open_date=arrow.utcnow().datetime,
|
open_date=arrow.utcnow().datetime,
|
||||||
fee_open=fee.return_value,
|
fee_open=fee.return_value,
|
||||||
fee_close=fee.return_value,
|
fee_close=fee.return_value,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
is_short=True,
|
is_short=is_short,
|
||||||
leverage=3.0,
|
|
||||||
interest_rate=0.0005,
|
interest_rate=0.0005,
|
||||||
|
leverage=lev
|
||||||
)
|
)
|
||||||
trade.open_order_id = 'something'
|
|
||||||
trade.update(limit_sell_order_usdt)
|
|
||||||
|
|
||||||
assert trade.open_order_id is None
|
assert trade.open_order_id is None
|
||||||
assert trade.open_rate == 2.20
|
|
||||||
assert trade.close_profit is None
|
assert trade.close_profit is None
|
||||||
assert trade.close_date is None
|
assert trade.close_date is None
|
||||||
|
|
||||||
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=226531, "
|
|
||||||
r"pair=ADA/USDT, amount=30.00000000, "
|
|
||||||
r"is_short=True, leverage=3.0, open_rate=2.20000000, open_since=.*\).",
|
|
||||||
caplog)
|
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
trade.open_order_id = 'something'
|
trade.open_order_id = 'something'
|
||||||
trade.update(limit_buy_order_usdt)
|
trade.update(enter_order)
|
||||||
assert trade.open_order_id is None
|
assert trade.open_order_id is None
|
||||||
assert trade.close_rate == 2.00
|
assert trade.open_rate == open_rate
|
||||||
assert trade.close_profit == round(0.2589996297562085, 8)
|
assert trade.close_profit is None
|
||||||
|
assert trade.close_date is None
|
||||||
|
assert log_has_re(f"LIMIT_{enter_side.upper()} has been fulfilled for "
|
||||||
|
r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, "
|
||||||
|
f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, "
|
||||||
|
r"open_since=.*\).",
|
||||||
|
caplog)
|
||||||
|
|
||||||
|
caplog.clear()
|
||||||
|
trade.open_order_id = 'something'
|
||||||
|
trade.update(exit_order)
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
assert trade.close_rate == close_rate
|
||||||
|
assert trade.close_profit == profit
|
||||||
assert trade.close_date is not None
|
assert trade.close_date is not None
|
||||||
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=226531, "
|
assert log_has_re(f"LIMIT_{exit_side.upper()} has been fulfilled for "
|
||||||
r"pair=ADA/USDT, amount=30.00000000, "
|
r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, "
|
||||||
r"is_short=True, leverage=3.0, open_rate=2.20000000, open_since=.*\).",
|
f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, "
|
||||||
|
r"open_since=.*\).",
|
||||||
caplog)
|
caplog)
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
@ -616,9 +518,21 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
|||||||
caplog)
|
caplog)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exchange,is_short,lev,open_value,close_value,profit,profit_ratio', [
|
||||||
|
("binance", False, 1, 60.15, 65.835, 5.685, 0.0945137157107232),
|
||||||
|
("binance", True, 1, 59.850, 66.1663784375, -6.316378437500013, -0.1055368159983292),
|
||||||
|
("binance", False, 3, 60.15, 65.83416667, 5.684166670000003, 0.2834995845386534),
|
||||||
|
("binance", True, 3, 59.85, 66.1663784375, -6.316378437500013, -0.3166104479949876),
|
||||||
|
|
||||||
|
("kraken", False, 1, 60.15, 65.835, 5.685, 0.0945137157107232),
|
||||||
|
("kraken", True, 1, 59.850, 66.231165, -6.381165, -0.106619298245614),
|
||||||
|
("kraken", False, 3, 60.15, 65.795, 5.645, 0.2815461346633419),
|
||||||
|
("kraken", True, 3, 59.850, 66.231165, -6.381165000000003, -0.319857894736842),
|
||||||
|
])
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee, exchange,
|
||||||
trade = Trade(
|
is_short, lev, open_value, close_value, profit, profit_ratio):
|
||||||
|
trade: Trade = Trade(
|
||||||
pair='ADA/USDT',
|
pair='ADA/USDT',
|
||||||
stake_amount=60.0,
|
stake_amount=60.0,
|
||||||
open_rate=2.0,
|
open_rate=2.0,
|
||||||
@ -627,55 +541,22 @@ def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt
|
|||||||
interest_rate=0.0005,
|
interest_rate=0.0005,
|
||||||
fee_open=fee.return_value,
|
fee_open=fee.return_value,
|
||||||
fee_close=fee.return_value,
|
fee_close=fee.return_value,
|
||||||
exchange='binance',
|
exchange=exchange,
|
||||||
|
is_short=is_short,
|
||||||
|
leverage=lev
|
||||||
)
|
)
|
||||||
|
|
||||||
trade.open_order_id = 'something'
|
trade.open_order_id = f'something-{is_short}-{lev}-{exchange}'
|
||||||
|
|
||||||
trade.update(limit_buy_order_usdt)
|
trade.update(limit_buy_order_usdt)
|
||||||
trade.update(limit_sell_order_usdt)
|
trade.update(limit_sell_order_usdt)
|
||||||
# 1x leverage, binance
|
trade.open_rate = 2.0
|
||||||
assert trade._calc_open_trade_value() == 60.15
|
trade.close_rate = 2.2
|
||||||
assert isclose(trade.calc_close_trade_value(), 65.835)
|
|
||||||
assert trade.calc_profit() == 5.685
|
|
||||||
assert trade.calc_profit_ratio() == round(0.0945137157107232, 8)
|
|
||||||
# 3x leverage, binance
|
|
||||||
trade.leverage = 3
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert trade._calc_open_trade_value() == 60.15
|
|
||||||
assert round(trade.calc_close_trade_value(), 8) == 65.83416667
|
|
||||||
assert trade.calc_profit() == round(5.684166670000003, 8)
|
|
||||||
assert trade.calc_profit_ratio() == round(0.2834995845386534, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
# 3x leverage, kraken
|
|
||||||
assert trade._calc_open_trade_value() == 60.15
|
|
||||||
assert trade.calc_close_trade_value() == 65.795
|
|
||||||
assert trade.calc_profit() == 5.645
|
|
||||||
assert trade.calc_profit_ratio() == round(0.2815461346633419, 8)
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
trade.recalc_open_trade_value()
|
||||||
# 3x leverage, short, kraken
|
assert isclose(trade._calc_open_trade_value(), open_value)
|
||||||
assert trade._calc_open_trade_value() == 59.850
|
assert isclose(trade.calc_close_trade_value(), close_value)
|
||||||
assert trade.calc_close_trade_value() == 66.231165
|
assert isclose(trade.calc_profit(), round(profit, 8))
|
||||||
assert trade.calc_profit() == round(-6.381165000000003, 8)
|
assert isclose(trade.calc_profit_ratio(), round(profit_ratio, 8))
|
||||||
assert trade.calc_profit_ratio() == round(-0.319857894736842, 8)
|
|
||||||
trade.exchange = "binance"
|
|
||||||
# 3x leverage, short, binance
|
|
||||||
assert trade._calc_open_trade_value() == 59.85
|
|
||||||
assert trade.calc_close_trade_value() == 66.1663784375
|
|
||||||
assert trade.calc_profit() == round(-6.316378437500013, 8)
|
|
||||||
assert trade.calc_profit_ratio() == round(-0.3166104479949876, 8)
|
|
||||||
# 1x leverage, short, binance
|
|
||||||
trade.leverage = 1.0
|
|
||||||
assert trade._calc_open_trade_value() == 59.850
|
|
||||||
assert trade.calc_close_trade_value() == 66.1663784375
|
|
||||||
assert trade.calc_profit() == round(-6.316378437500013, 8)
|
|
||||||
assert trade.calc_profit_ratio() == round(-0.1055368159983292, 8)
|
|
||||||
# 1x leverage, short, kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade._calc_open_trade_value() == 59.850
|
|
||||||
assert trade.calc_close_trade_value() == 66.231165
|
|
||||||
assert trade.calc_profit() == -6.381165
|
|
||||||
assert trade.calc_profit_ratio() == round(-0.106619298245614, 8)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
@ -766,8 +647,27 @@ def test_update_invalid_order(limit_buy_order_usdt):
|
|||||||
trade.update(limit_buy_order_usdt)
|
trade.update(limit_buy_order_usdt)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exchange', ['binance', 'kraken'])
|
||||||
|
@pytest.mark.parametrize('lev', [1, 3])
|
||||||
|
@pytest.mark.parametrize('is_short,fee_rate,result', [
|
||||||
|
(False, 0.003, 60.18),
|
||||||
|
(False, 0.0025, 60.15),
|
||||||
|
(False, 0.003, 60.18),
|
||||||
|
(False, 0.0025, 60.15),
|
||||||
|
(True, 0.003, 59.82),
|
||||||
|
(True, 0.0025, 59.85),
|
||||||
|
(True, 0.003, 59.82),
|
||||||
|
(True, 0.0025, 59.85)
|
||||||
|
])
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_calc_open_trade_value(limit_buy_order_usdt, fee):
|
def test_calc_open_trade_value(
|
||||||
|
limit_buy_order_usdt,
|
||||||
|
exchange,
|
||||||
|
lev,
|
||||||
|
is_short,
|
||||||
|
fee_rate,
|
||||||
|
result
|
||||||
|
):
|
||||||
# 10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
# 10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||||
# fee: 0.25 %, 0.3% quote
|
# fee: 0.25 %, 0.3% quote
|
||||||
# open_rate: 2.00 quote
|
# open_rate: 2.00 quote
|
||||||
@ -787,90 +687,104 @@ def test_calc_open_trade_value(limit_buy_order_usdt, fee):
|
|||||||
stake_amount=60.0,
|
stake_amount=60.0,
|
||||||
amount=30.0,
|
amount=30.0,
|
||||||
open_rate=2.0,
|
open_rate=2.0,
|
||||||
fee_open=fee.return_value,
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||||
fee_close=fee.return_value,
|
fee_open=fee_rate,
|
||||||
exchange='binance',
|
fee_close=fee_rate,
|
||||||
|
exchange=exchange,
|
||||||
|
leverage=lev,
|
||||||
|
is_short=is_short
|
||||||
)
|
)
|
||||||
trade.open_order_id = 'open_trade'
|
trade.open_order_id = 'open_trade'
|
||||||
trade.update(limit_buy_order_usdt)
|
|
||||||
|
|
||||||
# Get the open rate price with the standard fee rate
|
# Get the open rate price with the standard fee rate
|
||||||
assert trade._calc_open_trade_value() == 60.15
|
assert trade._calc_open_trade_value() == result
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
assert trade._calc_open_trade_value() == 59.85
|
|
||||||
trade.leverage = 3
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert trade._calc_open_trade_value() == 59.85
|
|
||||||
trade.is_short = False
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
assert trade._calc_open_trade_value() == 60.15
|
|
||||||
|
|
||||||
# Get the open rate price with a custom fee rate
|
|
||||||
trade.fee_open = 0.003
|
|
||||||
|
|
||||||
assert trade._calc_open_trade_value() == 60.18
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
assert trade._calc_open_trade_value() == 59.82
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exchange,is_short,lev,open_rate,close_rate,fee_rate,result', [
|
||||||
|
('binance', False, 1, 2.0, 2.5, 0.0025, 74.8125),
|
||||||
|
('binance', False, 1, 2.0, 2.5, 0.003, 74.775),
|
||||||
|
('binance', False, 1, 2.0, 2.2, 0.005, 65.67),
|
||||||
|
('binance', False, 3, 2.0, 2.5, 0.0025, 74.81166667),
|
||||||
|
('binance', False, 3, 2.0, 2.5, 0.003, 74.77416667),
|
||||||
|
('kraken', False, 3, 2.0, 2.5, 0.0025, 74.7725),
|
||||||
|
('kraken', False, 3, 2.0, 2.5, 0.003, 74.735),
|
||||||
|
('kraken', True, 3, 2.2, 2.5, 0.0025, 75.2626875),
|
||||||
|
('kraken', True, 3, 2.2, 2.5, 0.003, 75.300225),
|
||||||
|
('binance', True, 3, 2.2, 2.5, 0.0025, 75.18906641),
|
||||||
|
('binance', True, 3, 2.2, 2.5, 0.003, 75.22656719),
|
||||||
|
('binance', True, 1, 2.2, 2.5, 0.0025, 75.18906641),
|
||||||
|
('binance', True, 1, 2.2, 2.5, 0.003, 75.22656719),
|
||||||
|
('kraken', True, 1, 2.2, 2.5, 0.0025, 75.2626875),
|
||||||
|
('kraken', True, 1, 2.2, 2.5, 0.003, 75.300225),
|
||||||
|
])
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, open_rate,
|
||||||
|
exchange, is_short, lev, close_rate, fee_rate, result):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='ADA/USDT',
|
pair='ADA/USDT',
|
||||||
stake_amount=60.0,
|
stake_amount=60.0,
|
||||||
amount=30.0,
|
amount=30.0,
|
||||||
open_rate=2.0,
|
open_rate=open_rate,
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||||
fee_open=fee.return_value,
|
fee_open=fee_rate,
|
||||||
fee_close=fee.return_value,
|
fee_close=fee_rate,
|
||||||
exchange='binance',
|
exchange=exchange,
|
||||||
interest_rate=0.0005,
|
interest_rate=0.0005,
|
||||||
|
is_short=is_short,
|
||||||
|
leverage=lev
|
||||||
)
|
)
|
||||||
trade.open_order_id = 'close_trade'
|
trade.open_order_id = 'close_trade'
|
||||||
trade.update(limit_buy_order_usdt)
|
assert round(trade.calc_close_trade_value(rate=close_rate, fee=fee_rate), 8) == result
|
||||||
|
|
||||||
# 1x leverage binance
|
|
||||||
assert trade.calc_close_trade_value(rate=2.5) == 74.8125
|
|
||||||
assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 74.775
|
|
||||||
trade.update(limit_sell_order_usdt)
|
|
||||||
assert trade.calc_close_trade_value(fee=0.005) == 65.67
|
|
||||||
|
|
||||||
# 3x leverage binance
|
|
||||||
trade.leverage = 3.0
|
|
||||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 74.81166667
|
|
||||||
assert round(trade.calc_close_trade_value(rate=2.5, fee=0.003), 8) == 74.77416667
|
|
||||||
|
|
||||||
# 3x leverage kraken
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_close_trade_value(rate=2.5) == 74.7725
|
|
||||||
assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 74.735
|
|
||||||
|
|
||||||
# 3x leverage kraken, short
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 75.2626875
|
|
||||||
assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 75.300225
|
|
||||||
|
|
||||||
# 3x leverage binance, short
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 75.18906641
|
|
||||||
assert round(trade.calc_close_trade_value(rate=2.5, fee=0.003), 8) == 75.22656719
|
|
||||||
|
|
||||||
trade.leverage = 1.0
|
|
||||||
# 1x leverage binance, short
|
|
||||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 75.18906641
|
|
||||||
assert round(trade.calc_close_trade_value(rate=2.5, fee=0.003), 8) == 75.22656719
|
|
||||||
|
|
||||||
# 1x leverage kraken, short
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 75.2626875
|
|
||||||
assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 75.300225
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exchange,is_short,lev,close_rate,fee_close,profit,profit_ratio', [
|
||||||
|
('binance', False, 1, 2.1, 0.0025, 2.6925, 0.04476309226932673),
|
||||||
|
('binance', False, 3, 2.1, 0.0025, 2.69166667, 0.13424771421446402),
|
||||||
|
('binance', True, 1, 2.1, 0.0025, -3.308815781249997, -0.05528514254385963),
|
||||||
|
('binance', True, 3, 2.1, 0.0025, -3.308815781249997, -0.1658554276315789),
|
||||||
|
|
||||||
|
('binance', False, 1, 1.9, 0.0025, -3.2925, -0.05473815461346632),
|
||||||
|
('binance', False, 3, 1.9, 0.0025, -3.29333333, -0.16425602643391513),
|
||||||
|
('binance', True, 1, 1.9, 0.0025, 2.7063095312499996, 0.045218204365079395),
|
||||||
|
('binance', True, 3, 1.9, 0.0025, 2.7063095312499996, 0.13565461309523819),
|
||||||
|
|
||||||
|
('binance', False, 1, 2.2, 0.0025, 5.685, 0.0945137157107232),
|
||||||
|
('binance', False, 3, 2.2, 0.0025, 5.68416667, 0.2834995845386534),
|
||||||
|
('binance', True, 1, 2.2, 0.0025, -6.316378437499999, -0.1055368159983292),
|
||||||
|
('binance', True, 3, 2.2, 0.0025, -6.316378437499999, -0.3166104479949876),
|
||||||
|
|
||||||
|
('kraken', False, 1, 2.1, 0.0025, 2.6925, 0.04476309226932673),
|
||||||
|
('kraken', False, 3, 2.1, 0.0025, 2.6525, 0.13229426433915248),
|
||||||
|
('kraken', True, 1, 2.1, 0.0025, -3.3706575, -0.05631842105263152),
|
||||||
|
('kraken', True, 3, 2.1, 0.0025, -3.3706575, -0.16895526315789455),
|
||||||
|
|
||||||
|
('kraken', False, 1, 1.9, 0.0025, -3.2925, -0.05473815461346632),
|
||||||
|
('kraken', False, 3, 1.9, 0.0025, -3.3325, -0.16620947630922667),
|
||||||
|
('kraken', True, 1, 1.9, 0.0025, 2.6503575, 0.04428333333333334),
|
||||||
|
('kraken', True, 3, 1.9, 0.0025, 2.6503575, 0.13285000000000002),
|
||||||
|
|
||||||
|
('kraken', False, 1, 2.2, 0.0025, 5.685, 0.0945137157107232),
|
||||||
|
('kraken', False, 3, 2.2, 0.0025, 5.645, 0.2815461346633419),
|
||||||
|
('kraken', True, 1, 2.2, 0.0025, -6.381165, -0.106619298245614),
|
||||||
|
('kraken', True, 3, 2.2, 0.0025, -6.381165, -0.319857894736842),
|
||||||
|
|
||||||
|
('binance', False, 1, 2.1, 0.003, 2.6610000000000014, 0.04423940149625927),
|
||||||
|
('binance', False, 1, 1.9, 0.003, -3.320999999999998, -0.05521197007481293),
|
||||||
|
('binance', False, 1, 2.2, 0.003, 5.652000000000008, 0.09396508728179565),
|
||||||
|
])
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
def test_calc_profit(
|
||||||
|
limit_buy_order_usdt,
|
||||||
|
limit_sell_order_usdt,
|
||||||
|
fee,
|
||||||
|
exchange,
|
||||||
|
is_short,
|
||||||
|
lev,
|
||||||
|
close_rate,
|
||||||
|
fee_close,
|
||||||
|
profit,
|
||||||
|
profit_ratio
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||||
arguments:
|
arguments:
|
||||||
@ -1007,198 +921,16 @@ def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
|||||||
open_rate=2.0,
|
open_rate=2.0,
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||||
interest_rate=0.0005,
|
interest_rate=0.0005,
|
||||||
fee_open=fee.return_value,
|
exchange=exchange,
|
||||||
fee_close=fee.return_value,
|
is_short=is_short,
|
||||||
exchange='binance'
|
leverage=lev,
|
||||||
|
fee_open=0.0025,
|
||||||
|
fee_close=fee_close
|
||||||
)
|
)
|
||||||
trade.open_order_id = 'something'
|
trade.open_order_id = 'something'
|
||||||
trade.update(limit_buy_order_usdt) # Buy @ 2.0
|
|
||||||
|
|
||||||
# 1x Leverage, long
|
assert trade.calc_profit(rate=close_rate) == round(profit, 8)
|
||||||
# Custom closing rate and regular fee rate
|
assert trade.calc_profit_ratio(rate=close_rate) == round(profit_ratio, 8)
|
||||||
# Higher than open rate - 2.1 quote
|
|
||||||
assert trade.calc_profit(rate=2.1) == 2.6925
|
|
||||||
# Lower than open rate - 1.9 quote
|
|
||||||
assert trade.calc_profit(rate=1.9) == round(-3.292499999999997, 8)
|
|
||||||
|
|
||||||
# fee 0.003
|
|
||||||
# Higher than open rate - 2.1 quote
|
|
||||||
assert trade.calc_profit(rate=2.1, fee=0.003) == 2.661
|
|
||||||
# Lower than open rate - 1.9 quote
|
|
||||||
assert trade.calc_profit(rate=1.9, fee=0.003) == round(-3.320999999999998, 8)
|
|
||||||
|
|
||||||
# Test when we apply a Sell order. Sell higher than open rate @ 2.2
|
|
||||||
trade.update(limit_sell_order_usdt)
|
|
||||||
assert trade.calc_profit() == round(5.684999999999995, 8)
|
|
||||||
|
|
||||||
# Test with a custom fee rate on the close trade
|
|
||||||
assert trade.calc_profit(fee=0.003) == round(5.652000000000008, 8)
|
|
||||||
|
|
||||||
trade.open_trade_value = 0.0
|
|
||||||
trade.open_trade_value = trade._calc_open_trade_value()
|
|
||||||
|
|
||||||
# 3x leverage, long ###################################################
|
|
||||||
trade.leverage = 3.0
|
|
||||||
# Higher than open rate - 2.1 quote
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == 2.69166667
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == 2.6525
|
|
||||||
|
|
||||||
# 1.9 quote
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == -3.29333333
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == -3.3325
|
|
||||||
|
|
||||||
# 2.2 quote
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(fee=0.0025) == 5.68416667
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(fee=0.0025) == 5.645
|
|
||||||
|
|
||||||
# 3x leverage, short ###################################################
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# 2.1 quote - Higher than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == round(-3.308815781249997, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == -3.3706575
|
|
||||||
|
|
||||||
# 1.9 quote - Lower than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == round(2.7063095312499996, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == 2.6503575
|
|
||||||
|
|
||||||
# Test when we apply a Sell order. Uses sell order used above
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(fee=0.0025) == round(-6.316378437499999, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(fee=0.0025) == -6.381165
|
|
||||||
|
|
||||||
# 1x leverage, short ###################################################
|
|
||||||
trade.leverage = 1.0
|
|
||||||
# 2.1 quote - Higher than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == round(-3.308815781249997, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == -3.3706575
|
|
||||||
|
|
||||||
# 1.9 quote - Lower than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == round(2.7063095312499996, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == 2.6503575
|
|
||||||
|
|
||||||
# Test when we apply a Sell order. Uses sell order used above
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit(fee=0.0025) == round(-6.316378437499999, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit(fee=0.0025) == -6.381165
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
|
||||||
def test_calc_profit_ratio(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
|
||||||
trade = Trade(
|
|
||||||
pair='ADA/USDT',
|
|
||||||
stake_amount=60.0,
|
|
||||||
amount=30.0,
|
|
||||||
open_rate=2.0,
|
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
|
||||||
interest_rate=0.0005,
|
|
||||||
fee_open=fee.return_value,
|
|
||||||
fee_close=fee.return_value,
|
|
||||||
exchange='binance'
|
|
||||||
)
|
|
||||||
trade.open_order_id = 'something'
|
|
||||||
trade.update(limit_buy_order_usdt) # Buy @ 2.0
|
|
||||||
|
|
||||||
# 1x Leverage, long
|
|
||||||
# Custom closing rate and regular fee rate
|
|
||||||
# Higher than open rate - 2.1 quote
|
|
||||||
assert trade.calc_profit_ratio(rate=2.1) == round(0.04476309226932673, 8)
|
|
||||||
# Lower than open rate - 1.9 quote
|
|
||||||
assert trade.calc_profit_ratio(rate=1.9) == round(-0.05473815461346632, 8)
|
|
||||||
|
|
||||||
# fee 0.003
|
|
||||||
# Higher than open rate - 2.1 quote
|
|
||||||
assert trade.calc_profit_ratio(rate=2.1, fee=0.003) == round(0.04423940149625927, 8)
|
|
||||||
# Lower than open rate - 1.9 quote
|
|
||||||
assert trade.calc_profit_ratio(rate=1.9, fee=0.003) == round(-0.05521197007481293, 8)
|
|
||||||
|
|
||||||
# Test when we apply a Sell order. Sell higher than open rate @ 2.2
|
|
||||||
trade.update(limit_sell_order_usdt)
|
|
||||||
assert trade.calc_profit_ratio() == round(0.0945137157107232, 8)
|
|
||||||
|
|
||||||
# Test with a custom fee rate on the close trade
|
|
||||||
assert trade.calc_profit_ratio(fee=0.003) == round(0.09396508728179565, 8)
|
|
||||||
|
|
||||||
trade.open_trade_value = 0.0
|
|
||||||
assert trade.calc_profit_ratio(fee=0.003) == 0.0
|
|
||||||
trade.open_trade_value = trade._calc_open_trade_value()
|
|
||||||
|
|
||||||
# 3x leverage, long ###################################################
|
|
||||||
trade.leverage = 3.0
|
|
||||||
# 2.1 quote - Higher than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit_ratio(rate=2.1) == round(0.13424771421446402, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio(rate=2.1) == round(0.13229426433915248, 8)
|
|
||||||
|
|
||||||
# 1.9 quote - Lower than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit_ratio(rate=1.9) == round(-0.16425602643391513, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio(rate=1.9) == round(-0.16620947630922667, 8)
|
|
||||||
|
|
||||||
# Test when we apply a Sell order. Uses sell order used above
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit_ratio() == round(0.2834995845386534, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio() == round(0.2815461346633419, 8)
|
|
||||||
|
|
||||||
# 3x leverage, short ###################################################
|
|
||||||
trade.is_short = True
|
|
||||||
trade.recalc_open_trade_value()
|
|
||||||
# 2.1 quote - Higher than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit_ratio(rate=2.1) == round(-0.1658554276315789, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio(rate=2.1) == round(-0.16895526315789455, 8)
|
|
||||||
|
|
||||||
# 1.9 quote - Lower than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit_ratio(rate=1.9) == round(0.13565461309523819, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio(rate=1.9) == round(0.13285000000000002, 8)
|
|
||||||
|
|
||||||
# Test when we apply a Sell order. Uses sell order used above
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit_ratio() == round(-0.3166104479949876, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio() == round(-0.319857894736842, 8)
|
|
||||||
|
|
||||||
# 1x leverage, short ###################################################
|
|
||||||
trade.leverage = 1.0
|
|
||||||
# 2.1 quote - Higher than open rate
|
|
||||||
trade.exchange = "binance" # binance
|
|
||||||
assert trade.calc_profit_ratio(rate=2.1) == round(-0.05528514254385963, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio(rate=2.1) == round(-0.05631842105263152, 8)
|
|
||||||
|
|
||||||
# 1.9 quote - Lower than open rate
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert trade.calc_profit_ratio(rate=1.9) == round(0.045218204365079395, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio(rate=1.9) == round(0.04428333333333334, 8)
|
|
||||||
|
|
||||||
# Test when we apply a Sell order. Uses sell order used above
|
|
||||||
trade.exchange = "binance"
|
|
||||||
assert trade.calc_profit_ratio() == round(-0.1055368159983292, 8)
|
|
||||||
trade.exchange = "kraken"
|
|
||||||
assert trade.calc_profit_ratio() == round(-0.106619298245614, 8)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
|
Loading…
Reference in New Issue
Block a user