Merge remote-tracking branch 'origin/feat/binance_liq' into feat/binance_liq
This commit is contained in:
commit
bc835a1a00
@ -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
|
||||||
|
@ -16,10 +16,11 @@ from freqtrade.configuration import validate_config_consistency
|
|||||||
from freqtrade.data.converter import order_book_to_dataframe
|
from freqtrade.data.converter import order_book_to_dataframe
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.edge import Edge
|
from freqtrade.edge import Edge
|
||||||
from freqtrade.enums import RPCMessageType, SellType, State
|
from freqtrade.enums import Collateral, RPCMessageType, SellType, State, TradingMode
|
||||||
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, PricingError)
|
InvalidOrderException, PricingError)
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||||
|
from freqtrade.maintenance_margin import MaintenanceMargin
|
||||||
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
||||||
from freqtrade.mixins import LoggingMixin
|
from freqtrade.mixins import LoggingMixin
|
||||||
from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db
|
from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db
|
||||||
@ -41,6 +42,9 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
This is from here the bot start its logic.
|
This is from here the bot start its logic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
collateral: Optional[Collateral] = None
|
||||||
|
trading_mode: TradingMode = TradingMode.SPOT
|
||||||
|
|
||||||
def __init__(self, config: Dict[str, Any]) -> None:
|
def __init__(self, config: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Init all variables and objects the bot needs to work
|
Init all variables and objects the bot needs to work
|
||||||
@ -104,6 +108,22 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self._exit_lock = Lock()
|
self._exit_lock = Lock()
|
||||||
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
||||||
|
|
||||||
|
if self.config.get("trading_mode"):
|
||||||
|
self.trading_mode = TradingMode(self.config.get("trading_mode"))
|
||||||
|
|
||||||
|
if self.config.get('collateral'):
|
||||||
|
self.collateral = Collateral(self.config.get('collateral'))
|
||||||
|
|
||||||
|
# Start calculating maintenance margin if on cross margin
|
||||||
|
# TODO: Add margin_mode to freqtrade.configuration?
|
||||||
|
if self.collateral == Collateral.CROSS:
|
||||||
|
|
||||||
|
self.maintenance_margin = MaintenanceMargin(
|
||||||
|
exchange_name=self.exchange.name,
|
||||||
|
trading_mode=self.trading_mode)
|
||||||
|
|
||||||
|
self.maintenance_margin.run()
|
||||||
|
|
||||||
def notify_status(self, msg: str) -> None:
|
def notify_status(self, msg: str) -> None:
|
||||||
"""
|
"""
|
||||||
Public method for users of this class (worker, etc.) to send notifications
|
Public method for users of this class (worker, etc.) to send notifications
|
||||||
@ -588,6 +608,9 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if order_status == 'closed':
|
if order_status == 'closed':
|
||||||
self.update_trade_state(trade, order_id, order)
|
self.update_trade_state(trade, order_id, order)
|
||||||
|
|
||||||
|
if self.collateral == Collateral.CROSS:
|
||||||
|
self.maintenance_margin.add_new_trade(trade)
|
||||||
|
|
||||||
Trade.query.session.add(trade)
|
Trade.query.session.add(trade)
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
@ -1165,9 +1188,18 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
reason='Auto lock')
|
reason='Auto lock')
|
||||||
|
|
||||||
self._notify_exit(trade, order_type)
|
self._notify_exit(trade, order_type)
|
||||||
|
self._remove_maintenance_trade(trade)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _remove_maintenance_trade(self, trade: Trade):
|
||||||
|
"""
|
||||||
|
Removes a trade from the maintenance margin object
|
||||||
|
:param trade: The trade to remove from the maintenance margin
|
||||||
|
"""
|
||||||
|
if self.collateral == Collateral.CROSS:
|
||||||
|
self.maintenance_margin.remove_trade(trade)
|
||||||
|
|
||||||
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Sends rpc notification when a sell occurred.
|
Sends rpc notification when a sell occurred.
|
||||||
|
@ -19,6 +19,7 @@ def liquidation_price(
|
|||||||
entry_price_1: Optional[float],
|
entry_price_1: Optional[float],
|
||||||
maintenance_margin_rate: Optional[float]
|
maintenance_margin_rate: Optional[float]
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
|
|
||||||
if trading_mode == TradingMode.SPOT:
|
if trading_mode == TradingMode.SPOT:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
52
freqtrade/maintenance_margin.py
Normal file
52
freqtrade/maintenance_margin.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from freqtrade.enums import TradingMode
|
||||||
|
from freqtrade.leverage import liquidation_price
|
||||||
|
from freqtrade.persistence import Trade
|
||||||
|
|
||||||
|
|
||||||
|
class MaintenanceMargin:
|
||||||
|
|
||||||
|
trades: List[Trade]
|
||||||
|
exchange_name: str
|
||||||
|
trading_mode: TradingMode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def margin_level(self):
|
||||||
|
# This is the current value of all assets,
|
||||||
|
# and if you pass below liq_level, you are liquidated
|
||||||
|
# TODO-lev: Add args to formula
|
||||||
|
return liquidation_price(
|
||||||
|
trading_mode=self.trading_mode,
|
||||||
|
exchange_name=self.exchange_name
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def liq_level(self): # This may be a constant value and may not need a function
|
||||||
|
# TODO-lev: The is the value that you are liquidated at
|
||||||
|
return # If constant, would need to be recalculated after each new trade
|
||||||
|
|
||||||
|
def __init__(self, exchange_name: str, trading_mode: TradingMode):
|
||||||
|
self.exchange_name = exchange_name
|
||||||
|
self.trading_mode = trading_mode
|
||||||
|
return
|
||||||
|
|
||||||
|
def add_new_trade(self, trade):
|
||||||
|
self.trades.append(trade)
|
||||||
|
|
||||||
|
def remove_trade(self, trade):
|
||||||
|
self.trades.remove(trade)
|
||||||
|
|
||||||
|
# ? def update_trade_pric(self):
|
||||||
|
|
||||||
|
def sell_all(self):
|
||||||
|
# TODO-lev
|
||||||
|
return
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# TODO-lev: implement a thread that constantly updates with every price change,
|
||||||
|
# TODO-lev: must update at least every few seconds or so
|
||||||
|
# while true:
|
||||||
|
# if self.margin_level <= self.liq_level:
|
||||||
|
# self.sell_all()
|
||||||
|
return
|
@ -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.
|
||||||
|
@ -14,9 +14,9 @@ from sqlalchemy.pool import StaticPool
|
|||||||
from sqlalchemy.sql.schema import UniqueConstraint
|
from sqlalchemy.sql.schema import UniqueConstraint
|
||||||
|
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
|
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
|
||||||
from freqtrade.enums import SellType
|
from freqtrade.enums import Collateral, SellType, TradingMode
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.leverage import interest
|
from freqtrade.leverage import interest, liquidation_price
|
||||||
from freqtrade.misc import safe_value_fallback
|
from freqtrade.misc import safe_value_fallback
|
||||||
from freqtrade.persistence.migrations import check_migrate
|
from freqtrade.persistence.migrations import check_migrate
|
||||||
|
|
||||||
@ -265,6 +265,8 @@ class LocalTrade():
|
|||||||
buy_tag: Optional[str] = None
|
buy_tag: Optional[str] = None
|
||||||
timeframe: Optional[int] = None
|
timeframe: Optional[int] = None
|
||||||
|
|
||||||
|
trading_mode: TradingMode
|
||||||
|
|
||||||
# Leverage trading properties
|
# Leverage trading properties
|
||||||
is_short: bool = False
|
is_short: bool = False
|
||||||
isolated_liq: Optional[float] = None
|
isolated_liq: Optional[float] = None
|
||||||
@ -318,8 +320,9 @@ class LocalTrade():
|
|||||||
for key in kwargs:
|
for key in kwargs:
|
||||||
setattr(self, key, kwargs[key])
|
setattr(self, key, kwargs[key])
|
||||||
if self.isolated_liq:
|
if self.isolated_liq:
|
||||||
self.set_isolated_liq(self.isolated_liq)
|
self.set_isolated_liq(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):
|
||||||
"""
|
"""
|
||||||
@ -344,11 +347,25 @@ class LocalTrade():
|
|||||||
self.stop_loss_pct = -1 * abs(percent)
|
self.stop_loss_pct = -1 * abs(percent)
|
||||||
self.stoploss_last_update = datetime.utcnow()
|
self.stoploss_last_update = datetime.utcnow()
|
||||||
|
|
||||||
def set_isolated_liq(self, isolated_liq: float):
|
def set_isolated_liq(self, isolated_liq: Optional[float]):
|
||||||
"""
|
"""
|
||||||
Method you should use to set self.liquidation price.
|
Method you should use to set self.liquidation price.
|
||||||
Assures stop_loss is not passed the liquidation price
|
Assures stop_loss is not passed the liquidation price
|
||||||
"""
|
"""
|
||||||
|
if not isolated_liq:
|
||||||
|
isolated_liq = liquidation_price(
|
||||||
|
exchange_name=self.exchange,
|
||||||
|
open_rate=self.open_rate,
|
||||||
|
is_short=self.is_short,
|
||||||
|
leverage=self.leverage,
|
||||||
|
trading_mode=self.trading_mode,
|
||||||
|
collateral=Collateral.ISOLATED
|
||||||
|
)
|
||||||
|
if isolated_liq is None:
|
||||||
|
raise OperationalException(
|
||||||
|
"leverage/isolated_liq returned None. This exception should never happen"
|
||||||
|
)
|
||||||
|
|
||||||
if self.stop_loss is not None:
|
if self.stop_loss is not None:
|
||||||
if self.is_short:
|
if self.is_short:
|
||||||
self.stop_loss = min(self.stop_loss, isolated_liq)
|
self.stop_loss = min(self.stop_loss, isolated_liq)
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
@ -1,11 +1,92 @@
|
|||||||
|
import pytest
|
||||||
from math import isclose
|
from math import isclose
|
||||||
|
|
||||||
import pytest
|
from freqtrade.enums import Collateral, TradingMode
|
||||||
|
|
||||||
from freqtrade.enums import TradingMode, Collateral
|
|
||||||
from freqtrade.leverage import liquidation_price
|
from freqtrade.leverage import liquidation_price
|
||||||
|
|
||||||
|
|
||||||
|
# from freqtrade.exceptions import OperationalException
|
||||||
|
|
||||||
|
spot = TradingMode.SPOT
|
||||||
|
margin = TradingMode.MARGIN
|
||||||
|
futures = TradingMode.FUTURES
|
||||||
|
|
||||||
|
cross = Collateral.CROSS
|
||||||
|
isolated = Collateral.ISOLATED
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exchange_name,open_rate,is_short,leverage,trading_mode,collateral', [
|
||||||
|
# Bittrex
|
||||||
|
('bittrex', "2.0", False, "3.0", spot, None),
|
||||||
|
('bittrex', "2.0", False, "1.0", spot, cross),
|
||||||
|
('bittrex', "2.0", True, "3.0", spot, isolated),
|
||||||
|
# Binance
|
||||||
|
('binance', "2.0", False, "3.0", spot, None),
|
||||||
|
('binance', "2.0", False, "1.0", spot, cross),
|
||||||
|
('binance', "2.0", True, "3.0", spot, isolated),
|
||||||
|
# Kraken
|
||||||
|
('kraken', "2.0", False, "3.0", spot, None),
|
||||||
|
('kraken', "2.0", True, "3.0", spot, cross),
|
||||||
|
('kraken', "2.0", False, "1.0", spot, isolated),
|
||||||
|
# FTX
|
||||||
|
('ftx', "2.0", True, "3.0", spot, None),
|
||||||
|
('ftx', "2.0", False, "3.0", spot, cross),
|
||||||
|
('ftx', "2.0", False, "3.0", spot, isolated),
|
||||||
|
])
|
||||||
|
def test_liquidation_price_is_none(
|
||||||
|
exchange_name,
|
||||||
|
open_rate,
|
||||||
|
is_short,
|
||||||
|
leverage,
|
||||||
|
trading_mode,
|
||||||
|
collateral
|
||||||
|
):
|
||||||
|
assert liquidation_price(
|
||||||
|
exchange_name,
|
||||||
|
open_rate,
|
||||||
|
is_short,
|
||||||
|
leverage,
|
||||||
|
trading_mode,
|
||||||
|
collateral,
|
||||||
|
1535443.01,
|
||||||
|
71200.81144,
|
||||||
|
-56354.57,
|
||||||
|
135365.00,
|
||||||
|
3683.979,
|
||||||
|
1456.84,
|
||||||
|
0.10,
|
||||||
|
) is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exchange_name,open_rate,is_short,leverage,trading_mode,collateral', [
|
||||||
|
# Bittrex
|
||||||
|
('bittrex', "2.0", False, "3.0", margin, cross),
|
||||||
|
('bittrex', "2.0", False, "3.0", margin, isolated),
|
||||||
|
('bittrex', "2.0", False, "3.0", futures, cross),
|
||||||
|
('bittrex', "2.0", False, "3.0", futures, isolated),
|
||||||
|
# Binance
|
||||||
|
# Binance supports isolated margin, but freqtrade likely won't for a while on Binance
|
||||||
|
('binance', "2.0", True, "3.0", margin, isolated),
|
||||||
|
# Kraken
|
||||||
|
('kraken', "2.0", False, "1.0", margin, isolated),
|
||||||
|
('kraken', "2.0", False, "1.0", futures, isolated),
|
||||||
|
# FTX
|
||||||
|
('ftx', "2.0", False, "3.0", margin, isolated),
|
||||||
|
('ftx', "2.0", False, "3.0", futures, isolated),
|
||||||
|
])
|
||||||
|
def test_liquidation_price_exception_thrown(
|
||||||
|
exchange_name,
|
||||||
|
open_rate,
|
||||||
|
is_short,
|
||||||
|
leverage,
|
||||||
|
trading_mode,
|
||||||
|
collateral,
|
||||||
|
result
|
||||||
|
):
|
||||||
|
# TODO-lev assert exception is thrown
|
||||||
|
return # Here to avoid indent error, remove when implemented
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'exchange_name, open_rate, is_short, leverage, trading_mode, collateral, wallet_balance, '
|
'exchange_name, open_rate, is_short, leverage, trading_mode, collateral, wallet_balance, '
|
||||||
'maintenance_margin_ex_1, unrealized_pnl_ex_1, maintenance_amount, position_1, entry_price_1, '
|
'maintenance_margin_ex_1, unrealized_pnl_ex_1, maintenance_amount, position_1, entry_price_1, '
|
||||||
|
@ -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,32 +816,63 @@ 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):
|
||||||
ohlcv_data = {
|
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||||
('ETH/BTC', '1d'): ohlcv_history,
|
ohlcv_data = {
|
||||||
('TKN/BTC', '1d'): ohlcv_history,
|
('ETH/BTC', '1d'): ohlcv_history,
|
||||||
('LTC/BTC', '1d'): ohlcv_history,
|
('TKN/BTC', '1d'): ohlcv_history,
|
||||||
}
|
('LTC/BTC', '1d'): ohlcv_history,
|
||||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
}
|
||||||
markets=PropertyMock(return_value=markets),
|
mocker.patch.multiple(
|
||||||
exchange_has=MagicMock(return_value=True),
|
'freqtrade.exchange.Exchange',
|
||||||
get_tickers=tickers
|
markets=PropertyMock(return_value=markets),
|
||||||
)
|
exchange_has=MagicMock(return_value=True),
|
||||||
mocker.patch.multiple(
|
get_tickers=tickers,
|
||||||
'freqtrade.exchange.Exchange',
|
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
||||||
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter)
|
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter)
|
||||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 0
|
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 0
|
||||||
freqtrade.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
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
|
||||||
# Called once for XRP/BTC
|
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 2
|
||||||
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.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
|
||||||
|
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")
|
||||||
@ -105,7 +103,7 @@ def test_set_stop_loss_isolated_liq(fee):
|
|||||||
is_short=False,
|
is_short=False,
|
||||||
leverage=2.0
|
leverage=2.0
|
||||||
)
|
)
|
||||||
trade.set_isolated_liq(0.09)
|
trade.set_isolated_liq(isolated_liq=0.09)
|
||||||
assert trade.isolated_liq == 0.09
|
assert trade.isolated_liq == 0.09
|
||||||
assert trade.stop_loss == 0.09
|
assert trade.stop_loss == 0.09
|
||||||
assert trade.initial_stop_loss == 0.09
|
assert trade.initial_stop_loss == 0.09
|
||||||
@ -115,12 +113,12 @@ def test_set_stop_loss_isolated_liq(fee):
|
|||||||
assert trade.stop_loss == 0.1
|
assert trade.stop_loss == 0.1
|
||||||
assert trade.initial_stop_loss == 0.09
|
assert trade.initial_stop_loss == 0.09
|
||||||
|
|
||||||
trade.set_isolated_liq(0.08)
|
trade.set_isolated_liq(isolated_liq=0.08)
|
||||||
assert trade.isolated_liq == 0.08
|
assert trade.isolated_liq == 0.08
|
||||||
assert trade.stop_loss == 0.1
|
assert trade.stop_loss == 0.1
|
||||||
assert trade.initial_stop_loss == 0.09
|
assert trade.initial_stop_loss == 0.09
|
||||||
|
|
||||||
trade.set_isolated_liq(0.11)
|
trade.set_isolated_liq(isolated_liq=0.11)
|
||||||
assert trade.isolated_liq == 0.11
|
assert trade.isolated_liq == 0.11
|
||||||
assert trade.stop_loss == 0.11
|
assert trade.stop_loss == 0.11
|
||||||
assert trade.initial_stop_loss == 0.09
|
assert trade.initial_stop_loss == 0.09
|
||||||
@ -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")
|
||||||
@ -1570,7 +1302,7 @@ def test_adjust_stop_loss_short(fee):
|
|||||||
assert trade.initial_stop_loss == 1.05
|
assert trade.initial_stop_loss == 1.05
|
||||||
assert trade.initial_stop_loss_pct == 0.05
|
assert trade.initial_stop_loss_pct == 0.05
|
||||||
assert trade.stop_loss_pct == 0.1
|
assert trade.stop_loss_pct == 0.1
|
||||||
trade.set_isolated_liq(0.63)
|
trade.set_isolated_liq(isolated_liq=0.63)
|
||||||
trade.adjust_stop_loss(0.59, -0.1)
|
trade.adjust_stop_loss(0.59, -0.1)
|
||||||
assert trade.stop_loss == 0.63
|
assert trade.stop_loss == 0.63
|
||||||
assert trade.isolated_liq == 0.63
|
assert trade.isolated_liq == 0.63
|
||||||
@ -1894,7 +1626,7 @@ def test_stoploss_reinitialization_short(default_conf, fee):
|
|||||||
assert trade_adj.initial_stop_loss == 1.04
|
assert trade_adj.initial_stop_loss == 1.04
|
||||||
assert trade_adj.initial_stop_loss_pct == 0.04
|
assert trade_adj.initial_stop_loss_pct == 0.04
|
||||||
# Stoploss can't go above liquidation price
|
# Stoploss can't go above liquidation price
|
||||||
trade_adj.set_isolated_liq(1.0)
|
trade_adj.set_isolated_liq(isolated_liq=1.0)
|
||||||
trade.adjust_stop_loss(0.97, -0.04)
|
trade.adjust_stop_loss(0.97, -0.04)
|
||||||
assert trade_adj.stop_loss == 1.0
|
assert trade_adj.stop_loss == 1.0
|
||||||
assert trade_adj.stop_loss == 1.0
|
assert trade_adj.stop_loss == 1.0
|
||||||
|
Loading…
Reference in New Issue
Block a user