About 15 margin tests pass

This commit is contained in:
Sam Germain 2021-06-27 03:38:56 -06:00
parent f5d7deedf4
commit efcc2adacf
8 changed files with 1011 additions and 704 deletions

View File

@ -1,7 +1,7 @@
An instance of a `Trade`/`LocalTrade` object is given either a value for `leverage` or a value for `borrowed`, but not both, on instantiation/update with a short/long. An instance of a `Trade`/`LocalTrade` object is given either a value for `leverage` or a value for `borrowed`, but not both, on instantiation/update with a short/long.
- If given a value for `leverage`, then the `amount` value of the `Trade`/`Local` object is multiplied by the `leverage` value to obtain the new value for `amount`. The borrowed value is also calculated from the `amount` and `leverage` value - If given a value for `leverage`, then the `amount` value of the `Trade`/`Local` object is multiplied by the `leverage` value to obtain the new value for `amount`. The borrowed value is also calculated from the `amount` and `leverage` value
- If given a value for `borrowed`, then the `leverage` value is calculated from `borrowed` and `amount` - If given a value for `borrowed`, then the `leverage` value is left as None
For shorts, the currency which pays the interest fee for the `borrowed` currency is purchased at the same time of the closing trade (This means that the amount purchased in short closing trades is greater than the amount sold in short opening trades). For shorts, the currency which pays the interest fee for the `borrowed` currency is purchased at the same time of the closing trade (This means that the amount purchased in short closing trades is greater than the amount sold in short opening trades).

View File

@ -48,7 +48,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
sell_reason = get_column_def(cols, 'sell_reason', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null')
strategy = get_column_def(cols, 'strategy', 'null') strategy = get_column_def(cols, 'strategy', 'null')
leverage = get_column_def(cols, 'leverage', '0.0') leverage = get_column_def(cols, 'leverage', 'null')
borrowed = get_column_def(cols, 'borrowed', '0.0') borrowed = get_column_def(cols, 'borrowed', '0.0')
borrowed_currency = get_column_def(cols, 'borrowed_currency', 'null') borrowed_currency = get_column_def(cols, 'borrowed_currency', 'null')
collateral_currency = get_column_def(cols, 'collateral_currency', 'null') collateral_currency = get_column_def(cols, 'collateral_currency', 'null')

View File

@ -132,7 +132,7 @@ class Order(_DECL_BASE):
order_filled_date = Column(DateTime, nullable=True) order_filled_date = Column(DateTime, nullable=True)
order_update_date = Column(DateTime, nullable=True) order_update_date = Column(DateTime, nullable=True)
leverage = Column(Float, nullable=True, default=1.0) leverage = Column(Float, nullable=True, default=None)
is_short = Column(Boolean, nullable=True, default=False) is_short = Column(Boolean, nullable=True, default=False)
def __repr__(self): def __repr__(self):
@ -237,7 +237,7 @@ class LocalTrade():
close_profit: Optional[float] = None close_profit: Optional[float] = None
close_profit_abs: Optional[float] = None close_profit_abs: Optional[float] = None
stake_amount: float = 0.0 stake_amount: float = 0.0
amount: float = 0.0 _amount: float = 0.0
amount_requested: Optional[float] = None amount_requested: Optional[float] = None
open_date: datetime open_date: datetime
close_date: Optional[datetime] = None close_date: Optional[datetime] = None
@ -269,33 +269,52 @@ class LocalTrade():
interest_rate: float = 0.0 interest_rate: float = 0.0
liquidation_price: float = None liquidation_price: float = None
is_short: bool = False is_short: bool = False
__leverage: float = 1.0 # * You probably want to use self.leverage instead | borrowed: float = 0.0
__borrowed: float = 0.0 # * You probably want to use self.borrowed instead V _leverage: float = None # * You probably want to use LocalTrade.leverage instead
# @property
# def base_currency(self) -> str:
# if not self.pair:
# raise OperationalException('LocalTrade.pair must be assigned')
# return self.pair.split("/")[1]
@property
def amount(self) -> float:
if self.leverage is not None:
return self._amount * self.leverage
else:
return self._amount
@amount.setter
def amount(self, value):
self._amount = value
@property @property
def leverage(self) -> float: def leverage(self) -> float:
return self.__leverage or 1.0 return self._leverage
@property
def borrowed(self) -> float:
return self.__borrowed or 0.0
@leverage.setter @leverage.setter
def leverage(self, lev: float): def leverage(self, value):
# def set_leverage(self, lev: float, is_short: Optional[bool], amount: Optional[float]):
# TODO: Should this be @leverage.setter, or should it take arguments is_short and amount
# if is_short is None:
# is_short = self.is_short
# if amount is None:
# amount = self.amount
if self.is_short is None or self.amount is None: if self.is_short is None or self.amount is None:
raise OperationalException( raise OperationalException(
'LocalTrade.amount and LocalTrade.is_short must be assigned before LocalTrade.leverage') 'LocalTrade.amount and LocalTrade.is_short must be assigned before assigning leverage')
self.__leverage = lev
self.__borrowed = self.amount * (lev-1) self._leverage = value
self.amount = self.amount * lev if self.is_short:
# If shorting the full amount must be borrowed
self.borrowed = self.amount * value
else:
# If not shorting, then the trader already owns a bit
self.borrowed = self.amount * (value-1)
# TODO: Maybe amount should be a computed property, so we don't have to modify it
self.amount = self.amount * value
@borrowed.setter
def borrowed(self, bor: float):
if not self.amount:
raise OperationalException(
'LocalTrade.amount must be assigned before LocalTrade.borrowed')
self.__borrowed = bor
self.__leverage = self.amount / (self.amount - self.borrowed)
# End of margin trading properties # End of margin trading properties
@property @property
@ -414,6 +433,9 @@ class LocalTrade():
def _set_new_stoploss(self, new_loss: float, stoploss: float): def _set_new_stoploss(self, new_loss: float, stoploss: float):
"""Assign new stop value""" """Assign new stop value"""
self.stop_loss = new_loss self.stop_loss = new_loss
if self.is_short:
self.stop_loss_pct = abs(stoploss)
else:
self.stop_loss_pct = -1 * abs(stoploss) self.stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow() self.stoploss_last_update = datetime.utcnow()
@ -430,16 +452,23 @@ class LocalTrade():
# Don't modify if called with initial and nothing to do # Don't modify if called with initial and nothing to do
return return
new_loss = float(current_price * (1 - abs(stoploss))) if self.is_short:
# TODO: Could maybe move this if into the new stoploss if branch new_loss = float(current_price * (1 + abs(stoploss)))
if (self.liquidation_price): # If trading on margin, don't set the stoploss below the liquidation price if self.liquidation_price: # If trading on margin, don't set the stoploss below the liquidation price
new_loss = min(self.liquidation_price, new_loss) new_loss = min(self.liquidation_price, new_loss)
else:
new_loss = float(current_price * (1 - abs(stoploss)))
if self.liquidation_price: # If trading on margin, don't set the stoploss below the liquidation price
new_loss = max(self.liquidation_price, new_loss)
# no stop loss assigned yet # no stop loss assigned yet
if not self.stop_loss: if not self.stop_loss:
logger.debug(f"{self.pair} - Assigning new stoploss...") logger.debug(f"{self.pair} - Assigning new stoploss...")
self._set_new_stoploss(new_loss, stoploss) self._set_new_stoploss(new_loss, stoploss)
self.initial_stop_loss = new_loss self.initial_stop_loss = new_loss
if self.is_short:
self.initial_stop_loss_pct = abs(stoploss)
else:
self.initial_stop_loss_pct = -1 * abs(stoploss) self.initial_stop_loss_pct = -1 * abs(stoploss)
# evaluate if the stop loss needs to be updated # evaluate if the stop loss needs to be updated
@ -501,6 +530,7 @@ class LocalTrade():
self.open_rate = float(safe_value_fallback(order, 'average', 'price')) self.open_rate = float(safe_value_fallback(order, 'average', 'price'))
self.amount = float(safe_value_fallback(order, 'filled', 'amount')) self.amount = float(safe_value_fallback(order, 'filled', 'amount'))
if 'borrowed' in order: if 'borrowed' in order:
self.borrowed = order['borrowed'] self.borrowed = order['borrowed']
elif 'leverage' in order: elif 'leverage' in order:
@ -514,6 +544,7 @@ class LocalTrade():
elif order_type in ('market', 'limit') and self.is_closing_trade(order['side']): elif order_type in ('market', 'limit') and self.is_closing_trade(order['side']):
if self.is_open: if self.is_open:
payment = "BUY" if self.is_short else "SELL" payment = "BUY" if self.is_short else "SELL"
# TODO: On Shorts technically your buying a little bit more than the amount because it's the ammount plus the interest
logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.') logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.')
self.close(safe_value_fallback(order, 'average', 'price')) # TODO: Double check this self.close(safe_value_fallback(order, 'average', 'price')) # TODO: Double check this
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
@ -596,60 +627,68 @@ class LocalTrade():
""" """
self.open_trade_value = self._calc_open_trade_value() self.open_trade_value = self._calc_open_trade_value()
def calculate_interest(self) -> Decimal: def calculate_interest(self, interest_rate: Optional[float] = None) -> Decimal:
"""
: param interest_rate: interest_charge for borrowing this coin(optional).
If interest_rate is not set self.interest_rate will be used
"""
# TODO-mg: Need to set other conditions because sometimes self.open_date is not defined, but why would it ever not be set # TODO-mg: Need to set other conditions because sometimes self.open_date is not defined, but why would it ever not be set
if not self.interest_rate or not (self.borrowed): zero = Decimal(0.0)
return Decimal(0.0) if not (self.borrowed):
return zero
try:
open_date = self.open_date.replace(tzinfo=None) open_date = self.open_date.replace(tzinfo=None)
now = datetime.now() now = datetime.utcnow()
secPerDay = 86400 # sec_per_day = Decimal(86400)
days = Decimal((now - open_date).total_seconds()/secPerDay) or 0.0 sec_per_hour = Decimal(3600)
hours = days/24 total_seconds = Decimal((now - open_date).total_seconds())
except: #days = total_seconds/sec_per_day or zero
raise OperationalException("Time isn't calculated properly") hours = total_seconds/sec_per_hour or zero
rate = Decimal(self.interest_rate) rate = Decimal(interest_rate or self.interest_rate)
borrowed = Decimal(self.borrowed) borrowed = Decimal(self.borrowed)
twenty4 = Decimal(24.0)
one = Decimal(1.0) one = Decimal(1.0)
twenty_four = Decimal(24.0)
four = Decimal(4.0)
if self.exchange == 'binance': if self.exchange == 'binance':
# Rate is per day but accrued hourly or something # Rate is per day but accrued hourly or something
# binance: https://www.binance.com/en-AU/support/faq/360030157812 # binance: https://www.binance.com/en-AU/support/faq/360030157812
return borrowed * (rate/twenty4) * max(hours, one) # TODO-mg: Is hours rounded? return borrowed * rate * max(hours, one)/twenty_four # TODO-mg: Is hours rounded?
elif self.exchange == 'kraken': elif self.exchange == 'kraken':
# https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading- # https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading-
opening_fee = borrowed * rate opening_fee = borrowed * rate
roll_over_fee = borrowed * rate * max(0, (hours-4)/4) roll_over_fee = borrowed * rate * max(0, (hours-four)/four)
return opening_fee + roll_over_fee return opening_fee + roll_over_fee
elif self.exchange == 'binance_usdm_futures': elif self.exchange == 'binance_usdm_futures':
# ! TODO-mg: This is incorrect, I didn't look it up # ! TODO-mg: This is incorrect, I didn't look it up
return borrowed * (rate/twenty4) * max(hours, one) return borrowed * (rate/twenty_four) * max(hours, one)
elif self.exchange == 'binance_coinm_futures': elif self.exchange == 'binance_coinm_futures':
# ! TODO-mg: This is incorrect, I didn't look it up # ! TODO-mg: This is incorrect, I didn't look it up
return borrowed * (rate/twenty4) * max(hours, one) return borrowed * (rate/twenty_four) * max(hours, one)
else: else:
# TODO-mg: make sure this breaks and can't be squelched # TODO-mg: make sure this breaks and can't be squelched
raise OperationalException("Leverage not available on this exchange") raise OperationalException("Leverage not available on this exchange")
def calc_close_trade_value(self, rate: Optional[float] = None, def calc_close_trade_value(self, rate: Optional[float] = None,
fee: Optional[float] = None) -> float: fee: Optional[float] = None,
interest_rate: Optional[float] = None) -> float:
""" """
Calculate the close_rate including fee Calculate the close_rate including fee
:param fee: fee to use on the close rate (optional). :param fee: fee to use on the close rate (optional).
If rate is not set self.fee will be used If rate is not set self.fee will be used
:param rate: rate to compare with (optional). :param rate: rate to compare with (optional).
If rate is not set self.close_rate will be used If rate is not set self.close_rate will be used
:param interest_rate: interest_charge for borrowing this coin (optional).
If interest_rate is not set self.interest_rate will be used
:return: Price in BTC of the open trade :return: Price in BTC of the open trade
""" """
if rate is None and not self.close_rate: if rate is None and not self.close_rate:
return 0.0 return 0.0
interest = self.calculate_interest() interest = self.calculate_interest(interest_rate)
if self.is_short: if self.is_short:
amount = Decimal(self.amount) + interest amount = Decimal(self.amount) + Decimal(interest)
else: else:
# The interest does not need to be purchased on longs because the user already owns that currency in your wallet # The interest does not need to be purchased on longs because the user already owns that currency in your wallet
amount = Decimal(self.amount) amount = Decimal(self.amount)
@ -663,18 +702,22 @@ class LocalTrade():
return float(close_trade - fees - interest) return float(close_trade - fees - interest)
def calc_profit(self, rate: Optional[float] = None, def calc_profit(self, rate: Optional[float] = None,
fee: Optional[float] = None) -> float: fee: Optional[float] = None,
interest_rate: Optional[float] = None) -> float:
""" """
Calculate the absolute profit in stake currency between Close and Open trade Calculate the absolute profit in stake currency between Close and Open trade
:param fee: fee to use on the close rate (optional). :param fee: fee to use on the close rate (optional).
If fee is not set self.fee will be used If fee is not set self.fee will be used
:param rate: close rate to compare with (optional). :param rate: close rate to compare with (optional).
If rate is not set self.close_rate will be used If rate is not set self.close_rate will be used
:param interest_rate: interest_charge for borrowing this coin (optional).
If interest_rate is not set self.interest_rate will be used
:return: profit in stake currency as float :return: profit in stake currency as float
""" """
close_trade_value = self.calc_close_trade_value( close_trade_value = self.calc_close_trade_value(
rate=(rate or self.close_rate), rate=(rate or self.close_rate),
fee=(fee or self.fee_close) fee=(fee or self.fee_close),
interest_rate=(interest_rate or self.interest_rate)
) )
if self.is_short: if self.is_short:
@ -684,17 +727,21 @@ class LocalTrade():
return float(f"{profit:.8f}") return float(f"{profit:.8f}")
def calc_profit_ratio(self, rate: Optional[float] = None, def calc_profit_ratio(self, rate: Optional[float] = None,
fee: Optional[float] = None) -> float: fee: Optional[float] = None,
interest_rate: Optional[float] = None) -> float:
""" """
Calculates the profit as ratio (including fee). Calculates the profit as ratio (including fee).
:param rate: rate to compare with (optional). :param rate: rate to compare with (optional).
If rate is not set self.close_rate will be used If rate is not set self.close_rate will be used
:param fee: fee to use on the close rate (optional). :param fee: fee to use on the close rate (optional).
:param interest_rate: interest_charge for borrowing this coin (optional).
If interest_rate is not set self.interest_rate will be used
:return: profit ratio as float :return: profit ratio as float
""" """
close_trade_value = self.calc_close_trade_value( close_trade_value = self.calc_close_trade_value(
rate=(rate or self.close_rate), rate=(rate or self.close_rate),
fee=(fee or self.fee_close) fee=(fee or self.fee_close),
interest_rate=(interest_rate or self.interest_rate)
) )
if self.is_short: if self.is_short:
if close_trade_value == 0.0: if close_trade_value == 0.0:
@ -724,7 +771,7 @@ class LocalTrade():
else: else:
return None return None
@ staticmethod @staticmethod
def get_trades_proxy(*, pair: str = None, is_open: bool = None, def get_trades_proxy(*, pair: str = None, is_open: bool = None,
open_date: datetime = None, close_date: datetime = None, open_date: datetime = None, close_date: datetime = None,
) -> List['LocalTrade']: ) -> List['LocalTrade']:
@ -758,27 +805,27 @@ class LocalTrade():
return sel_trades return sel_trades
@ staticmethod @staticmethod
def close_bt_trade(trade): def close_bt_trade(trade):
LocalTrade.trades_open.remove(trade) LocalTrade.trades_open.remove(trade)
LocalTrade.trades.append(trade) LocalTrade.trades.append(trade)
LocalTrade.total_profit += trade.close_profit_abs LocalTrade.total_profit += trade.close_profit_abs
@ staticmethod @staticmethod
def add_bt_trade(trade): def add_bt_trade(trade):
if trade.is_open: if trade.is_open:
LocalTrade.trades_open.append(trade) LocalTrade.trades_open.append(trade)
else: else:
LocalTrade.trades.append(trade) LocalTrade.trades.append(trade)
@ staticmethod @staticmethod
def get_open_trades() -> List[Any]: def get_open_trades() -> List[Any]:
""" """
Query trades from persistence layer Query trades from persistence layer
""" """
return Trade.get_trades_proxy(is_open=True) return Trade.get_trades_proxy(is_open=True)
@ staticmethod @staticmethod
def stoploss_reinitialization(desired_stoploss): def stoploss_reinitialization(desired_stoploss):
""" """
Adjust initial Stoploss to desired stoploss for all open trades. Adjust initial Stoploss to desired stoploss for all open trades.
@ -853,18 +900,19 @@ class Trade(_DECL_BASE, LocalTrade):
# Lowest price reached # Lowest price reached
min_rate = Column(Float, nullable=True) min_rate = Column(Float, nullable=True)
sell_reason = Column(String(100), nullable=True) # TODO: Change to close_reason sell_reason = Column(String(100), nullable=True) # TODO: Change to close_reason
sell_order_status = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True) # TODO: Change to close_order_status
strategy = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True) timeframe = Column(Integer, nullable=True)
# Margin trading properties # Margin trading properties
leverage = Column(Float, nullable=True, default=1.0) _leverage: float = None # * You probably want to use LocalTrade.leverage instead
borrowed = Column(Float, nullable=False, default=0.0) borrowed = Column(Float, nullable=False, default=0.0)
borrowed_currency = Column(Float, nullable=True)
collateral_currency = Column(String(25), nullable=True)
interest_rate = Column(Float, nullable=False, default=0.0) interest_rate = Column(Float, nullable=False, default=0.0)
liquidation_price = Column(Float, nullable=True) liquidation_price = Column(Float, nullable=True)
is_short = Column(Boolean, nullable=False, default=False) is_short = Column(Boolean, nullable=False, default=False)
# TODO: Bottom 2 might not be needed
borrowed_currency = Column(Float, nullable=True)
collateral_currency = Column(String(25), nullable=True)
# End of margin trading properties # End of margin trading properties
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -879,11 +927,11 @@ class Trade(_DECL_BASE, LocalTrade):
Trade.query.session.delete(self) Trade.query.session.delete(self)
Trade.commit() Trade.commit()
@ staticmethod @staticmethod
def commit(): def commit():
Trade.query.session.commit() Trade.query.session.commit()
@ staticmethod @staticmethod
def get_trades_proxy(*, pair: str = None, is_open: bool = None, def get_trades_proxy(*, pair: str = None, is_open: bool = None,
open_date: datetime = None, close_date: datetime = None, open_date: datetime = None, close_date: datetime = None,
) -> List['LocalTrade']: ) -> List['LocalTrade']:
@ -913,7 +961,7 @@ class Trade(_DECL_BASE, LocalTrade):
close_date=close_date close_date=close_date
) )
@ staticmethod @staticmethod
def get_trades(trade_filter=None) -> Query: def get_trades(trade_filter=None) -> Query:
""" """
Helper function to query Trades using filters. Helper function to query Trades using filters.
@ -933,7 +981,7 @@ class Trade(_DECL_BASE, LocalTrade):
else: else:
return Trade.query return Trade.query
@ staticmethod @staticmethod
def get_open_order_trades(): def get_open_order_trades():
""" """
Returns all open trades Returns all open trades
@ -941,7 +989,7 @@ class Trade(_DECL_BASE, LocalTrade):
""" """
return Trade.get_trades(Trade.open_order_id.isnot(None)).all() return Trade.get_trades(Trade.open_order_id.isnot(None)).all()
@ staticmethod @staticmethod
def get_open_trades_without_assigned_fees(): def get_open_trades_without_assigned_fees():
""" """
Returns all open trades which don't have open fees set correctly Returns all open trades which don't have open fees set correctly
@ -952,7 +1000,7 @@ class Trade(_DECL_BASE, LocalTrade):
Trade.is_open.is_(True), Trade.is_open.is_(True),
]).all() ]).all()
@ staticmethod @staticmethod
def get_closed_trades_without_assigned_fees(): def get_closed_trades_without_assigned_fees():
""" """
Returns all closed trades which don't have fees set correctly Returns all closed trades which don't have fees set correctly
@ -990,7 +1038,7 @@ class Trade(_DECL_BASE, LocalTrade):
t.stake_amount for t in LocalTrade.get_trades_proxy(is_open=True)) t.stake_amount for t in LocalTrade.get_trades_proxy(is_open=True))
return total_open_stake_amount or 0 return total_open_stake_amount or 0
@ staticmethod @staticmethod
def get_overall_performance() -> List[Dict[str, Any]]: def get_overall_performance() -> List[Dict[str, Any]]:
""" """
Returns List of dicts containing all Trades, including profit and trade count Returns List of dicts containing all Trades, including profit and trade count
@ -1053,7 +1101,7 @@ class PairLock(_DECL_BASE):
return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, ' return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, '
f'lock_end_time={lock_end_time})') f'lock_end_time={lock_end_time})')
@ staticmethod @staticmethod
def query_pair_locks(pair: Optional[str], now: datetime) -> Query: def query_pair_locks(pair: Optional[str], now: datetime) -> Query:
""" """
Get all currently active locks for this pair Get all currently active locks for this pair

View File

@ -57,9 +57,9 @@ def log_has_re(line, logs):
def get_args(args): def get_args(args):
return Arguments(args).get_parsed_arg() return Arguments(args).get_parsed_arg()
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
def get_mock_coro(return_value): def get_mock_coro(return_value):
async def mock_coro(*args, **kwargs): async def mock_coro(*args, **kwargs):
return return_value return return_value
@ -2075,7 +2075,7 @@ def ten_minutes_ago():
@pytest.fixture @pytest.fixture
def five_hours_ago(): def five_hours_ago():
return datetime.utcnow() - timedelta(hours=1, minutes=0) return datetime.utcnow() - timedelta(hours=5, minutes=0)
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
@ -2136,9 +2136,9 @@ def limit_exit_short_order(limit_exit_short_order_open):
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def market_short_order(): def market_short_order():
return { return {
'id': 'mocked_market_buy', 'id': 'mocked_market_short',
'type': 'market', 'type': 'market',
'side': 'buy', 'side': 'sell',
'symbol': 'mocked', 'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(), 'datetime': arrow.utcnow().isoformat(),
'price': 0.00004173, 'price': 0.00004173,
@ -2147,16 +2147,16 @@ def market_short_order():
'remaining': 0.0, 'remaining': 0.0,
'status': 'closed', 'status': 'closed',
'is_short': True, 'is_short': True,
'leverage': 3 'leverage': 3.0
} }
@pytest.fixture @pytest.fixture
def market_exit_short_order(): def market_exit_short_order():
return { return {
'id': 'mocked_limit_sell', 'id': 'mocked_limit_exit_short',
'type': 'market', 'type': 'market',
'side': 'sell', 'side': 'buy',
'symbol': 'mocked', 'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(), 'datetime': arrow.utcnow().isoformat(),
'price': 0.00004099, 'price': 0.00004099,
@ -2164,11 +2164,5 @@ def market_exit_short_order():
'filled': 91.99181073, 'filled': 91.99181073,
'remaining': 0.0, 'remaining': 0.0,
'status': 'closed', 'status': 'closed',
'leverage': 3, 'leverage': 3.0
'interest_rate': 0.0005
} }
@pytest.fixture
def interest_rate():
return MagicMock(return_value=0.0005)

View File

@ -108,7 +108,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_order': None, 'open_order': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': 1.0, 'leverage': None,
'borrowed': 0.0, 'borrowed': 0.0,
'borrowed_currency': None, 'borrowed_currency': None,
'collateral_currency': None, 'collateral_currency': None,
@ -182,14 +182,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_order': None, 'open_order': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': 1.0, 'leverage': None,
'borrowed': 0.0, 'borrowed': 0.0,
'borrowed_currency': None, 'borrowed_currency': None,
'collateral_currency': None, 'collateral_currency': None,
'interest_rate': 0.0, 'interest_rate': 0.0,
'liquidation_price': None, 'liquidation_price': None,
'is_short': False, 'is_short': False,
} }

View File

@ -63,6 +63,48 @@ def test_init_dryrun_db(default_conf, tmpdir):
assert Path(filename).is_file() assert Path(filename).is_file()
@pytest.mark.usefixtures("init_persistence")
def test_is_opening_closing_trade(fee):
trade = Trade(
id=2,
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.01,
amount=5,
is_open=True,
open_date=arrow.utcnow().datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
is_short=False,
leverage=2.0
)
assert trade.is_opening_trade('buy') == True
assert trade.is_opening_trade('sell') == False
assert trade.is_closing_trade('buy') == False
assert trade.is_closing_trade('sell') == True
trade = Trade(
id=2,
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.01,
amount=5,
is_open=True,
open_date=arrow.utcnow().datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
is_short=True,
leverage=2.0
)
assert trade.is_opening_trade('buy') == False
assert trade.is_opening_trade('sell') == True
assert trade.is_closing_trade('buy') == True
assert trade.is_closing_trade('sell') == False
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog): def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog):
""" """
@ -196,6 +238,7 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_trade_close(limit_buy_order, limit_sell_order, fee): def test_trade_close(limit_buy_order, limit_sell_order, fee):
# TODO: limit_buy_order and limit_sell_order aren't used, remove them probably
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
@ -1126,6 +1169,42 @@ def test_fee_updated(fee):
assert not trade.fee_updated('asfd') assert not trade.fee_updated('asfd')
@pytest.mark.usefixtures("init_persistence")
def test_update_leverage(fee, ten_minutes_ago):
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00001099,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
is_short=True,
interest_rate=0.0005
)
trade.leverage = 3.0
assert trade.borrowed == 15.0
assert trade.amount == 15.0
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00001099,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
is_short=False,
interest_rate=0.0005
)
trade.leverage = 5.0
assert trade.borrowed == 20.0
assert trade.amount == 25.0
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize('use_db', [True, False]) @pytest.mark.parametrize('use_db', [True, False])
def test_total_open_trades_stakes(fee, use_db): def test_total_open_trades_stakes(fee, use_db):

View File

@ -1,616 +0,0 @@
import logging
from datetime import datetime, timedelta, timezone
from pathlib import Path
from types import FunctionType
from unittest.mock import MagicMock
import arrow
import pytest
from sqlalchemy import create_engine, inspect, text
from freqtrade import constants
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
from tests.conftest import create_mock_trades, log_has, log_has_re
# * Margin tests
@pytest.mark.usefixtures("init_persistence")
def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, interest_rate, ten_minutes_ago, caplog):
"""
On this test we will short and buy back(exit short) a crypto currency at 1x leverage
#*The actual program uses more precise numbers
Short
- Sell: 90.99181073 Crypto at 0.00001173 BTC
- Selling fee: 0.25%
- Total value of sell trade: 0.001064666 BTC
((90.99181073*0.00001173) - ((90.99181073*0.00001173)*0.0025))
Exit Short
- Buy: 90.99181073 Crypto at 0.00001099 BTC
- Buying fee: 0.25%
- Interest fee: 0.05%
- Total interest
(90.99181073 * 0.0005)/24 = 0.00189566272
- Total cost of buy trade: 0.00100252088
(90.99181073 + 0.00189566272) * 0.00001099 = 0.00100002083 :(borrowed + interest * cost)
+ ((90.99181073 + 0.00189566272)*0.00001099)*0.0025 = 0.00000250005
= 0.00100252088
Profit/Loss: +0.00006214512 BTC
Sell:0.001064666 - Buy:0.00100252088
Profit/Loss percentage: 0.06198885353
(0.001064666/0.00100252088)-1 = 0.06198885353
#* ~0.061988453889463014104555743 With more precise numbers used
:param limit_short_order:
:param limit_exit_short_order:
:param fee
:param interest_rate
:param caplog
:return:
"""
trade = Trade(
id=2,
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.01,
amount=5,
is_open=True,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
interest_rate=interest_rate.return_value,
# borrowed=90.99181073,
exchange='binance'
)
#assert trade.open_order_id is None
assert trade.close_profit is None
assert trade.close_date is None
assert trade.borrowed is None
assert trade.is_short is None
#trade.open_order_id = 'something'
trade.update(limit_short_order)
#assert trade.open_order_id is None
assert trade.open_rate == 0.00001173
assert trade.close_profit is None
assert trade.close_date is None
assert trade.borrowed == 90.99181073
assert trade.is_short is True
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).",
caplog)
caplog.clear()
#trade.open_order_id = 'something'
trade.update(limit_exit_short_order)
#assert trade.open_order_id is None
assert trade.close_rate == 0.00001099
assert trade.close_profit == 0.06198845
assert trade.close_date is not None
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).",
caplog)
# TODO-mg: create a leveraged long order
# @pytest.mark.usefixtures("init_persistence")
# def test_update_market_order(
# market_buy_order,
# market_sell_order,
# fee,
# interest_rate,
# ten_minutes_ago,
# caplog
# ):
# """Test Kraken and leverage arguments as well as update market order
# fee: 0.25%
# interest_rate: 0.05% per 4 hrs
# open_rate: 0.00004173
# close_rate: 0.00004099
# amount: 91.99181073 * leverage(3) = 275.97543219
# borrowed: 183.98362146
# time: 10 minutes(rounds to min of 4hrs)
# interest
# """
# trade = Trade(
# id=1,
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# open_rate=0.01,
# is_open=True,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# open_date=ten_minutes_ago,
# exchange='kraken'
# )
# trade.open_order_id = 'something'
# trade.update(market_buy_order)
# assert trade.leverage is 3
# assert trade.is_short is True
# assert trade.open_order_id is None
# assert trade.open_rate == 0.00004173
# assert trade.close_profit is None
# assert trade.close_date is None
# assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, "
# r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004173, open_since=.*\).",
# caplog)
# caplog.clear()
# trade.is_open = True
# trade.open_order_id = 'something'
# trade.update(market_sell_order)
# assert trade.open_order_id is None
# assert trade.close_rate == 0.00004099
# assert trade.close_profit == 0.01297561
# assert trade.close_date is not None
# assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
# r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).",
# caplog)
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# open_rate=0.01,
# amount=5,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# )
# trade.open_order_id = 'something'
# trade.update(limit_buy_order)
# assert trade._calc_open_trade_value() == 0.0010024999999225068
# trade.update(limit_sell_order)
# assert trade.calc_close_trade_value() == 0.0010646656050132426
# # Profit in BTC
# assert trade.calc_profit() == 0.00006217
# # Profit in percent
# assert trade.calc_profit_ratio() == 0.06201058
# @pytest.mark.usefixtures("init_persistence")
# def test_trade_close(limit_buy_order, limit_sell_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# open_rate=0.01,
# amount=5,
# is_open=True,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# open_date=arrow.Arrow(2020, 2, 1, 15, 5, 1).datetime,
# exchange='binance',
# )
# assert trade.close_profit is None
# assert trade.close_date is None
# assert trade.is_open is True
# trade.close(0.02)
# assert trade.is_open is False
# assert trade.close_profit == 0.99002494
# assert trade.close_date is not None
# new_date = arrow.Arrow(2020, 2, 2, 15, 6, 1).datetime,
# assert trade.close_date != new_date
# # Close should NOT update close_date if the trade has been closed already
# assert trade.is_open is False
# trade.close_date = new_date
# trade.close(0.02)
# assert trade.close_date == new_date
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_close_trade_price_exception(limit_buy_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# open_rate=0.1,
# amount=5,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# )
# trade.open_order_id = 'something'
# trade.update(limit_buy_order)
# assert trade.calc_close_trade_value() == 0.0
# @pytest.mark.usefixtures("init_persistence")
# def test_update_open_order(limit_buy_order):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=1.00,
# open_rate=0.01,
# amount=5,
# fee_open=0.1,
# fee_close=0.1,
# exchange='binance',
# )
# assert trade.open_order_id is None
# assert trade.close_profit is None
# assert trade.close_date is None
# limit_buy_order['status'] = 'open'
# trade.update(limit_buy_order)
# assert trade.open_order_id is None
# assert trade.close_profit is None
# assert trade.close_date is None
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_open_trade_value(limit_buy_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# open_rate=0.00001099,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# )
# trade.open_order_id = 'open_trade'
# trade.update(limit_buy_order) # Buy @ 0.00001099
# # Get the open rate price with the standard fee rate
# assert trade._calc_open_trade_value() == 0.0010024999999225068
# trade.fee_open = 0.003
# # Get the open rate price with a custom fee rate
# assert trade._calc_open_trade_value() == 0.001002999999922468
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# open_rate=0.00001099,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# )
# trade.open_order_id = 'close_trade'
# trade.update(limit_buy_order) # Buy @ 0.00001099
# # Get the close rate price with a custom close rate and a regular fee rate
# assert trade.calc_close_trade_value(rate=0.00001234) == 0.0011200318470471794
# # Get the close rate price with a custom close rate and a custom fee rate
# assert trade.calc_close_trade_value(rate=0.00001234, fee=0.003) == 0.0011194704275749754
# # Test when we apply a Sell order, and ask price with a custom fee rate
# trade.update(limit_sell_order)
# assert trade.calc_close_trade_value(fee=0.005) == 0.0010619972701635854
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_profit(limit_buy_order, limit_sell_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# open_rate=0.00001099,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# )
# trade.open_order_id = 'something'
# trade.update(limit_buy_order) # Buy @ 0.00001099
# # Custom closing rate and regular fee rate
# # Higher than open rate
# assert trade.calc_profit(rate=0.00001234) == 0.00011753
# # Lower than open rate
# assert trade.calc_profit(rate=0.00000123) == -0.00089086
# # Custom closing rate and custom fee rate
# # Higher than open rate
# assert trade.calc_profit(rate=0.00001234, fee=0.003) == 0.00011697
# # Lower than open rate
# assert trade.calc_profit(rate=0.00000123, fee=0.003) == -0.00089092
# # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173
# trade.update(limit_sell_order)
# assert trade.calc_profit() == 0.00006217
# # Test with a custom fee rate on the close trade
# assert trade.calc_profit(fee=0.003) == 0.00006163
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# open_rate=0.00001099,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# )
# trade.open_order_id = 'something'
# trade.update(limit_buy_order) # Buy @ 0.00001099
# # Get percent of profit with a custom rate (Higher than open rate)
# assert trade.calc_profit_ratio(rate=0.00001234) == 0.11723875
# # Get percent of profit with a custom rate (Lower than open rate)
# assert trade.calc_profit_ratio(rate=0.00000123) == -0.88863828
# # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173
# trade.update(limit_sell_order)
# assert trade.calc_profit_ratio() == 0.06201058
# # Test with a custom fee rate on the close trade
# assert trade.calc_profit_ratio(fee=0.003) == 0.06147824
# trade.open_trade_value = 0.0
# assert trade.calc_profit_ratio(fee=0.003) == 0.0
# def test_adjust_stop_loss(fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# max_rate=1,
# )
# trade.adjust_stop_loss(trade.open_rate, 0.05, True)
# assert trade.stop_loss == 0.95
# assert trade.stop_loss_pct == -0.05
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # Get percent of profit with a lower rate
# trade.adjust_stop_loss(0.96, 0.05)
# assert trade.stop_loss == 0.95
# assert trade.stop_loss_pct == -0.05
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # Get percent of profit with a custom rate (Higher than open rate)
# trade.adjust_stop_loss(1.3, -0.1)
# assert round(trade.stop_loss, 8) == 1.17
# assert trade.stop_loss_pct == -0.1
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # current rate lower again ... should not change
# trade.adjust_stop_loss(1.2, 0.1)
# assert round(trade.stop_loss, 8) == 1.17
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # current rate higher... should raise stoploss
# trade.adjust_stop_loss(1.4, 0.1)
# assert round(trade.stop_loss, 8) == 1.26
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # Initial is true but stop_loss set - so doesn't do anything
# trade.adjust_stop_loss(1.7, 0.1, True)
# assert round(trade.stop_loss, 8) == 1.26
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# assert trade.stop_loss_pct == -0.1
# def test_adjust_min_max_rates(fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# )
# trade.adjust_min_max_rates(trade.open_rate)
# assert trade.max_rate == 1
# assert trade.min_rate == 1
# # check min adjusted, max remained
# trade.adjust_min_max_rates(0.96)
# assert trade.max_rate == 1
# assert trade.min_rate == 0.96
# # check max adjusted, min remains
# trade.adjust_min_max_rates(1.05)
# assert trade.max_rate == 1.05
# assert trade.min_rate == 0.96
# # current rate "in the middle" - no adjustment
# trade.adjust_min_max_rates(1.03)
# assert trade.max_rate == 1.05
# assert trade.min_rate == 0.96
# @pytest.mark.usefixtures("init_persistence")
# @pytest.mark.parametrize('use_db', [True, False])
# def test_get_open(fee, use_db):
# Trade.use_db = use_db
# Trade.reset_trades()
# create_mock_trades(fee, use_db)
# assert len(Trade.get_open_trades()) == 4
# Trade.use_db = True
# def test_stoploss_reinitialization(default_conf, fee):
# init_db(default_conf['db_url'])
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# fee_open=fee.return_value,
# open_date=arrow.utcnow().shift(hours=-2).datetime,
# amount=10,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# max_rate=1,
# )
# trade.adjust_stop_loss(trade.open_rate, 0.05, True)
# assert trade.stop_loss == 0.95
# assert trade.stop_loss_pct == -0.05
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# Trade.query.session.add(trade)
# # Lower stoploss
# Trade.stoploss_reinitialization(0.06)
# trades = Trade.get_open_trades()
# assert len(trades) == 1
# trade_adj = trades[0]
# assert trade_adj.stop_loss == 0.94
# assert trade_adj.stop_loss_pct == -0.06
# assert trade_adj.initial_stop_loss == 0.94
# assert trade_adj.initial_stop_loss_pct == -0.06
# # Raise stoploss
# Trade.stoploss_reinitialization(0.04)
# trades = Trade.get_open_trades()
# assert len(trades) == 1
# trade_adj = trades[0]
# assert trade_adj.stop_loss == 0.96
# assert trade_adj.stop_loss_pct == -0.04
# assert trade_adj.initial_stop_loss == 0.96
# assert trade_adj.initial_stop_loss_pct == -0.04
# # Trailing stoploss (move stoplos up a bit)
# trade.adjust_stop_loss(1.02, 0.04)
# assert trade_adj.stop_loss == 0.9792
# assert trade_adj.initial_stop_loss == 0.96
# Trade.stoploss_reinitialization(0.04)
# trades = Trade.get_open_trades()
# assert len(trades) == 1
# trade_adj = trades[0]
# # Stoploss should not change in this case.
# assert trade_adj.stop_loss == 0.9792
# assert trade_adj.stop_loss_pct == -0.04
# assert trade_adj.initial_stop_loss == 0.96
# assert trade_adj.initial_stop_loss_pct == -0.04
# def test_update_fee(fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# fee_open=fee.return_value,
# open_date=arrow.utcnow().shift(hours=-2).datetime,
# amount=10,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# max_rate=1,
# )
# fee_cost = 0.15
# fee_currency = 'BTC'
# fee_rate = 0.0075
# assert trade.fee_open_currency is None
# assert not trade.fee_updated('buy')
# assert not trade.fee_updated('sell')
# trade.update_fee(fee_cost, fee_currency, fee_rate, 'buy')
# assert trade.fee_updated('buy')
# assert not trade.fee_updated('sell')
# assert trade.fee_open_currency == fee_currency
# assert trade.fee_open_cost == fee_cost
# assert trade.fee_open == fee_rate
# # Setting buy rate should "guess" close rate
# assert trade.fee_close == fee_rate
# assert trade.fee_close_currency is None
# assert trade.fee_close_cost is None
# fee_rate = 0.0076
# trade.update_fee(fee_cost, fee_currency, fee_rate, 'sell')
# assert trade.fee_updated('buy')
# assert trade.fee_updated('sell')
# assert trade.fee_close == 0.0076
# assert trade.fee_close_cost == fee_cost
# assert trade.fee_close == fee_rate
# def test_fee_updated(fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# fee_open=fee.return_value,
# open_date=arrow.utcnow().shift(hours=-2).datetime,
# amount=10,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# max_rate=1,
# )
# assert trade.fee_open_currency is None
# assert not trade.fee_updated('buy')
# assert not trade.fee_updated('sell')
# assert not trade.fee_updated('asdf')
# trade.update_fee(0.15, 'BTC', 0.0075, 'buy')
# assert trade.fee_updated('buy')
# assert not trade.fee_updated('sell')
# assert trade.fee_open_currency is not None
# assert trade.fee_close_currency is None
# trade.update_fee(0.15, 'ABC', 0.0075, 'sell')
# assert trade.fee_updated('buy')
# assert trade.fee_updated('sell')
# assert not trade.fee_updated('asfd')
# @pytest.mark.usefixtures("init_persistence")
# @pytest.mark.parametrize('use_db', [True, False])
# def test_total_open_trades_stakes(fee, use_db):
# Trade.use_db = use_db
# Trade.reset_trades()
# res = Trade.total_open_trades_stakes()
# assert res == 0
# create_mock_trades(fee, use_db)
# res = Trade.total_open_trades_stakes()
# assert res == 0.004
# Trade.use_db = True
# @pytest.mark.usefixtures("init_persistence")
# def test_get_overall_performance(fee):
# create_mock_trades(fee)
# res = Trade.get_overall_performance()
# assert len(res) == 2
# assert 'pair' in res[0]
# assert 'profit' in res[0]
# assert 'count' in res[0]
# @pytest.mark.usefixtures("init_persistence")
# def test_get_best_pair(fee):
# res = Trade.get_best_pair()
# assert res is None
# create_mock_trades(fee)
# res = Trade.get_best_pair()
# assert len(res) == 2
# assert res[0] == 'XRP/BTC'
# assert res[1] == 0.01
# @pytest.mark.usefixtures("init_persistence")
# def test_update_order_from_ccxt(caplog):
# # Most basic order return (only has orderid)
# o = Order.parse_from_ccxt_object({'id': '1234'}, 'ETH/BTC', 'buy')
# assert isinstance(o, Order)
# assert o.ft_pair == 'ETH/BTC'
# assert o.ft_order_side == 'buy'
# assert o.order_id == '1234'
# assert o.ft_is_open
# ccxt_order = {
# 'id': '1234',
# 'side': 'buy',
# 'symbol': 'ETH/BTC',
# 'type': 'limit',
# 'price': 1234.5,
# 'amount': 20.0,
# 'filled': 9,
# 'remaining': 11,
# 'status': 'open',
# 'timestamp': 1599394315123
# }
# o = Order.parse_from_ccxt_object(ccxt_order, 'ETH/BTC', 'buy')
# assert isinstance(o, Order)
# assert o.ft_pair == 'ETH/BTC'
# assert o.ft_order_side == 'buy'
# assert o.order_id == '1234'
# assert o.order_type == 'limit'
# assert o.price == 1234.5
# assert o.filled == 9
# assert o.remaining == 11
# assert o.order_date is not None
# assert o.ft_is_open
# assert o.order_filled_date is None
# # Order has been closed
# ccxt_order.update({'filled': 20.0, 'remaining': 0.0, 'status': 'closed'})
# o.update_from_ccxt_object(ccxt_order)
# assert o.filled == 20.0
# assert o.remaining == 0.0
# assert not o.ft_is_open
# assert o.order_filled_date is not None
# ccxt_order.update({'id': 'somethingelse'})
# with pytest.raises(DependencyException, match=r"Order-id's don't match"):
# o.update_from_ccxt_object(ccxt_order)
# message = "aaaa is not a valid response object."
# assert not log_has(message, caplog)
# Order.update_orders([o], 'aaaa')
# assert log_has(message, caplog)
# # Call regular update - shouldn't fail.
# Order.update_orders([o], {'id': '1234'})

View File

@ -0,0 +1,803 @@
import logging
from datetime import datetime, timedelta, timezone
from pathlib import Path
from types import FunctionType
from unittest.mock import MagicMock
import arrow
import pytest
from math import isclose
from sqlalchemy import create_engine, inspect, text
from freqtrade import constants
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
from tests.conftest import create_mock_trades, log_has, log_has_re
@pytest.mark.usefixtures("init_persistence")
def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, ten_minutes_ago, caplog):
"""
10 minute short limit trade on binance
Short trade
fee: 0.25% base
interest_rate: 0.05% per day
open_rate: 0.00001173 base
close_rate: 0.00001099 base
amount: 90.99181073 crypto
borrowed: 90.99181073 crypto
time-periods: 10 minutes(rounds up to 1/24 time-period of 1 day)
interest: borrowed * interest_rate * time-periods
= 90.99181073 * 0.0005 * 1/24 = 0.0018956627235416667 crypto
open_value: (amount * open_rate) - (amount * open_rate * fee)
= 90.99181073 * 0.00001173 - 90.99181073 * 0.00001173 * 0.0025
= 0.0010646656050132426
amount_closed: amount + interest = 90.99181073 + 0.0018956627235416667 = 90.99370639272354
close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
= (90.99370639272354 * 0.00001099) + (90.99370639272354 * 0.00001099 * 0.0025)
= 0.0010025208853391716
total_profit = open_value - close_value
= 0.0010646656050132426 - 0.0010025208853391716
= 0.00006214471967407108
total_profit_percentage = (open_value/close_value) - 1
= (0.0010646656050132426/0.0010025208853391716)-1
= 0.06198845388946328
"""
trade = Trade(
id=2,
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.01,
amount=5,
is_open=True,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
# borrowed=90.99181073,
interest_rate=0.0005,
exchange='binance'
)
#assert trade.open_order_id is None
assert trade.close_profit is None
assert trade.close_date is None
assert trade.borrowed is None
assert trade.is_short is None
#trade.open_order_id = 'something'
trade.update(limit_short_order)
#assert trade.open_order_id is None
assert trade.open_rate == 0.00001173
assert trade.close_profit is None
assert trade.close_date is None
assert trade.borrowed == 90.99181073
assert trade.is_short is True
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).",
caplog)
caplog.clear()
#trade.open_order_id = 'something'
trade.update(limit_exit_short_order)
#assert trade.open_order_id is None
assert trade.close_rate == 0.00001099
assert trade.close_profit == 0.06198845
assert trade.close_date is not None
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).",
caplog)
@pytest.mark.usefixtures("init_persistence")
def test_update_market_order(
market_short_order,
market_exit_short_order,
fee,
ten_minutes_ago,
caplog
):
"""
10 minute short market trade on Kraken at 3x leverage
Short trade
fee: 0.25% base
interest_rate: 0.05% per 4 hrs
open_rate: 0.00004173 base
close_rate: 0.00004099 base
amount: 91.99181073 * leverage(3) = 275.97543219 crypto
borrowed: 275.97543219 crypto
time-periods: 10 minutes(rounds up to 1 time-period of 4hrs)
interest: borrowed * interest_rate * time-periods
= 275.97543219 * 0.0005 * 1 = 0.137987716095 crypto
open_value: (amount * open_rate) - (amount * open_rate * fee)
= 275.97543219 * 0.00004173 - 275.97543219 * 0.00004173 * 0.0025
= 0.011487663648325479
amount_closed: amount + interest = 275.97543219 + 0.137987716095 = 276.113419906095
close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
= (276.113419906095 * 0.00004099) + (276.113419906095 * 0.00004099 * 0.0025)
= 0.01134618380465571
total_profit = open_value - close_value
= 0.011487663648325479 - 0.01134618380465571
= 0.00014147984366976937
total_profit_percentage = (open_value/close_value) - 1
= (0.011487663648325479/0.01134618380465571)-1
= 0.012469377026284034
"""
trade = Trade(
id=1,
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.01,
is_open=True,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=ten_minutes_ago,
interest_rate=0.0005,
exchange='kraken'
)
trade.open_order_id = 'something'
trade.update(market_short_order)
assert trade.leverage == 3.0
assert trade.is_short == True
assert trade.open_order_id is None
assert trade.open_rate == 0.00004173
assert trade.close_profit is None
assert trade.close_date is None
assert trade.interest_rate == 0.0005
# TODO: Uncomment the next assert and make it work.
# The logger also has the exact same but there's some spacing in there
# assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
# r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004173, open_since=.*\).",
# caplog)
caplog.clear()
trade.is_open = True
trade.open_order_id = 'something'
trade.update(market_exit_short_order)
assert trade.open_order_id is None
assert trade.close_rate == 0.00004099
assert trade.close_profit == 0.01246938
assert trade.close_date is not None
# TODO: The amount should maybe be the opening amount + the interest
# TODO: Uncomment the next assert and make it work.
# The logger also has the exact same but there's some spacing in there
# assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
# r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004099, open_since=.*\).",
# caplog)
@pytest.mark.usefixtures("init_persistence")
def test_calc_open_close_trade_price(limit_short_order, limit_exit_short_order, five_hours_ago, fee):
"""
5 hour short trade on Binance
Short trade
fee: 0.25% base
interest_rate: 0.05% per day
open_rate: 0.00001173 base
close_rate: 0.00001099 base
amount: 90.99181073 crypto
borrowed: 90.99181073 crypto
time-periods: 5 hours = 5/24
interest: borrowed * interest_rate * time-periods
= 90.99181073 * 0.0005 * 5/24 = 0.009478313617708333 crypto
open_value: (amount * open_rate) - (amount * open_rate * fee)
= 90.99181073 * 0.00001173 - 90.99181073 * 0.00001173 * 0.0025
= 0.0010646656050132426
amount_closed: amount + interest = 90.99181073 + 0.009478313617708333 = 91.0012890436177
close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
= (91.0012890436177 * 0.00001099) + (91.0012890436177 * 0.00001099 * 0.0025)
= 0.001002604427005832
total_profit = open_value - close_value
= 0.0010646656050132426 - 0.001002604427005832
= 0.00006206117800741065
total_profit_percentage = (open_value/close_value) - 1
= (0.0010646656050132426/0.0010025208853391716)-1
= 0.06189996406932852
"""
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.01,
amount=5,
open_date=five_hours_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
interest_rate=0.0005
)
trade.open_order_id = 'something'
trade.update(limit_short_order)
assert trade._calc_open_trade_value() == 0.0010646656050132426
trade.update(limit_exit_short_order)
assert isclose(trade.calc_close_trade_value(), 0.001002604427005832)
# Profit in BTC
assert isclose(trade.calc_profit(), 0.00006206)
#Profit in percent
assert isclose(trade.calc_profit_ratio(), 0.06189996)
@pytest.mark.usefixtures("init_persistence")
def test_trade_close(fee, five_hours_ago):
"""
Five hour short trade on Kraken at 3x leverage
Short trade
Exchange: Kraken
fee: 0.25% base
interest_rate: 0.05% per 4 hours
open_rate: 0.02 base
close_rate: 0.01 base
leverage: 3.0
amount: 5 * 3 = 15 crypto
borrowed: 15 crypto
time-periods: 5 hours = 5/4
interest: borrowed * interest_rate * time-periods
= 15 * 0.0005 * 5/4 = 0.009375 crypto
open_value: (amount * open_rate) - (amount * open_rate * fee)
= (15 * 0.02) - (15 * 0.02 * 0.0025)
= 0.29925
amount_closed: amount + interest = 15 + 0.009375 = 15.009375
close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
= (15.009375 * 0.01) + (15.009375 * 0.01 * 0.0025)
= 0.150468984375
total_profit = open_value - close_value
= 0.29925 - 0.150468984375
= 0.148781015625
total_profit_percentage = (open_value/close_value) - 1
= (0.29925/0.150468984375)-1
= 0.9887819489377738
"""
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.02,
amount=5,
is_open=True,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=five_hours_ago,
exchange='kraken',
is_short=True,
leverage=3.0,
interest_rate=0.0005
)
assert trade.close_profit is None
assert trade.close_date is None
assert trade.is_open is True
trade.close(0.01)
assert trade.is_open is False
assert trade.close_profit == 0.98878195
assert trade.close_date is not None
# TODO-mg: Remove these comments probably
#new_date = arrow.Arrow(2020, 2, 2, 15, 6, 1).datetime,
# assert trade.close_date != new_date
# # Close should NOT update close_date if the trade has been closed already
# assert trade.is_open is False
# trade.close_date = new_date
# trade.close(0.02)
# assert trade.close_date == new_date
@pytest.mark.usefixtures("init_persistence")
def test_calc_close_trade_price_exception(limit_short_order, fee):
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.1,
amount=5,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
interest_rate=0.0005,
is_short=True,
leverage=3.0
)
trade.open_order_id = 'something'
trade.update(limit_short_order)
assert trade.calc_close_trade_value() == 0.0
@pytest.mark.usefixtures("init_persistence")
def test_update_open_order(limit_short_order):
trade = Trade(
pair='ETH/BTC',
stake_amount=1.00,
open_rate=0.01,
amount=5,
fee_open=0.1,
fee_close=0.1,
interest_rate=0.0005,
is_short=True,
exchange='binance',
)
assert trade.open_order_id is None
assert trade.close_profit is None
assert trade.close_date is None
limit_short_order['status'] = 'open'
trade.update(limit_short_order)
assert trade.open_order_id is None
assert trade.close_profit is None
assert trade.close_date is None
@pytest.mark.usefixtures("init_persistence")
def test_calc_open_trade_value(market_short_order, ten_minutes_ago, fee):
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00004173,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
interest_rate=0.0005,
is_short=True,
leverage=3.0,
exchange='kraken',
)
trade.open_order_id = 'open_trade'
trade.update(market_short_order) # Buy @ 0.00001099
# Get the open rate price with the standard fee rate
assert trade._calc_open_trade_value() == 0.011487663648325479
trade.fee_open = 0.003
# Get the open rate price with a custom fee rate
assert trade._calc_open_trade_value() == 0.011481905420932834
@pytest.mark.usefixtures("init_persistence")
def test_calc_close_trade_price(market_short_order, market_exit_short_order, ten_minutes_ago, fee):
"""
10 minute short market trade on Kraken at 3x leverage
Short trade
fee: 0.25% base
interest_rate: 0.05% per 4 hrs
open_rate: 0.00004173 base
close_rate: 0.00001234 base
amount: 91.99181073 * leverage(3) = 275.97543219 crypto
borrowed: 275.97543219 crypto
time-periods: 10 minutes(rounds up to 1 time-period of 4hrs)
interest: borrowed * interest_rate * time-periods
= 275.97543219 * 0.0005 * 1 = 0.137987716095 crypto
amount_closed: amount + interest = 275.97543219 + 0.137987716095 = 276.113419906095
close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
= (276.113419906095 * 0.00001234) + (276.113419906095 * 0.00001234 * 0.0025)
= 0.01134618380465571
"""
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00001099,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=ten_minutes_ago,
interest_rate=0.0005,
is_short=True,
leverage=3.0,
exchange='kraken',
)
trade.open_order_id = 'close_trade'
trade.update(market_short_order) # Buy @ 0.00001099
# Get the close rate price with a custom close rate and a regular fee rate
assert isclose(trade.calc_close_trade_value(rate=0.00001234), 0.003415757700645315)
# Get the close rate price with a custom close rate and a custom fee rate
assert isclose(trade.calc_close_trade_value(rate=0.00001234, fee=0.003), 0.0034174613204461354)
# Test when we apply a Sell order, and ask price with a custom fee rate
trade.update(market_exit_short_order)
assert isclose(trade.calc_close_trade_value(fee=0.005), 0.011374478527360586)
@pytest.mark.usefixtures("init_persistence")
def test_calc_profit(market_short_order, market_exit_short_order, ten_minutes_ago, five_hours_ago, fee):
"""
Market trade on Kraken at 3x leverage
Short trade
fee: 0.25% base or 0.3%
interest_rate: 0.05%, 0.25% per 4 hrs
open_rate: 0.00004173 base
close_rate: 0.00004099 base
amount: 91.99181073 * leverage(3) = 275.97543219 crypto
borrowed: 275.97543219 crypto
time-periods: 10 minutes(rounds up to 1 time-period of 4hrs)
5 hours = 5/4
interest: borrowed * interest_rate * time-periods
= 275.97543219 * 0.0005 * 1 = 0.137987716095 crypto
= 275.97543219 * 0.00025 * 5/4 = 0.086242322559375 crypto
= 275.97543219 * 0.0005 * 5/4 = 0.17248464511875 crypto
= 275.97543219 * 0.00025 * 1 = 0.0689938580475 crypto
open_value: (amount * open_rate) - (amount * open_rate * fee)
= (275.97543219 * 0.00004173) - (275.97543219 * 0.00004173 * 0.0025) = 0.011487663648325479
amount_closed: amount + interest
= 275.97543219 + 0.137987716095 = 276.113419906095
= 275.97543219 + 0.086242322559375 = 276.06167451255936
= 275.97543219 + 0.17248464511875 = 276.14791683511874
= 275.97543219 + 0.0689938580475 = 276.0444260480475
close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
(276.113419906095 * 0.00004374) + (276.113419906095 * 0.00004374 * 0.0025) = 0.012107393989159325
(276.06167451255936 * 0.00000437) + (276.06167451255936 * 0.00000437 * 0.0025) = 0.0012094054914139338
(276.14791683511874 * 0.00004374) + (276.14791683511874 * 0.00004374 * 0.003) = 0.012114946012015198
(276.0444260480475 * 0.00000437) + (276.0444260480475 * 0.00000437 * 0.003) = 0.0012099330842554573
total_profit = open_value - close_value
= print(0.011487663648325479 - 0.012107393989159325) = -0.0006197303408338461
= print(0.011487663648325479 - 0.0012094054914139338) = 0.010278258156911545
= print(0.011487663648325479 - 0.012114946012015198) = -0.0006272823636897188
= print(0.011487663648325479 - 0.0012099330842554573) = 0.010277730564070022
total_profit_percentage = (open_value/close_value) - 1
print((0.011487663648325479 / 0.012107393989159325) - 1) = -0.051186105068418364
print((0.011487663648325479 / 0.0012094054914139338) - 1) = 8.498603842864217
print((0.011487663648325479 / 0.012114946012015198) - 1) = -0.05177756162244562
print((0.011487663648325479 / 0.0012099330842554573) - 1) = 8.494461964724694
"""
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00001099,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='kraken',
is_short=True,
leverage=3.0,
interest_rate=0.0005
)
trade.open_order_id = 'something'
trade.update(market_short_order) # Buy @ 0.00001099
# Custom closing rate and regular fee rate
# Higher than open rate
assert trade.calc_profit(rate=0.00004374, interest_rate=0.0005) == -0.00061973
# == -0.0006197303408338461
assert trade.calc_profit_ratio(rate=0.00004374, interest_rate=0.0005) == -0.05118611
# == -0.051186105068418364
# Lower than open rate
trade.open_date = five_hours_ago
assert trade.calc_profit(rate=0.00000437, interest_rate=0.00025) == 0.01027826
# == 0.010278258156911545
assert trade.calc_profit_ratio(rate=0.00000437, interest_rate=0.00025) == 8.49860384
# == 8.498603842864217
# Custom closing rate and custom fee rate
# Higher than open rate
assert trade.calc_profit(rate=0.00004374, fee=0.003, interest_rate=0.0005) == -0.00062728
# == -0.0006272823636897188
assert trade.calc_profit_ratio(rate=0.00004374, fee=0.003, interest_rate=0.0005) == -0.05177756
# == -0.05177756162244562
# Lower than open rate
trade.open_date = ten_minutes_ago
assert trade.calc_profit(rate=0.00000437, fee=0.003, interest_rate=0.00025) == 0.01027773
# == 0.010277730564070022
assert trade.calc_profit_ratio(rate=0.00000437, fee=0.003, interest_rate=0.00025) == 8.49446196
# == 8.494461964724694
# Test when we apply a Sell order. Sell higher than open rate @ 0.00001173
trade.update(market_exit_short_order)
assert trade.calc_profit() == 0.00014148
# == 0.00014147984366976937
assert trade.calc_profit_ratio() == 0.01246938
# == 0.012469377026284034
# Test with a custom fee rate on the close trade
# assert trade.calc_profit(fee=0.003) == 0.00006163
# assert trade.calc_profit_ratio(fee=0.003) == 0.06147824
@pytest.mark.usefixtures("init_persistence")
def test_interest_kraken(market_short_order, ten_minutes_ago, five_hours_ago, fee):
"""
Market trade on Kraken at 3x and 8x leverage
Short trade
interest_rate: 0.05%, 0.25% per 4 hrs
open_rate: 0.00004173 base
close_rate: 0.00004099 base
amount:
91.99181073 * leverage(3) = 275.97543219 crypto
91.99181073 * leverage(5) = 459.95905365 crypto
borrowed:
275.97543219 crypto
459.95905365 crypto
time-periods: 10 minutes(rounds up to 1 time-period of 4hrs)
5 hours = 5/4
interest: borrowed * interest_rate * time-periods
= 275.97543219 * 0.0005 * 1 = 0.137987716095 crypto
= 275.97543219 * 0.00025 * 5/4 = 0.086242322559375 crypto
= 459.95905365 * 0.0005 * 5/4 = 0.17248464511875 crypto
= 459.95905365 * 0.00025 * 1 = 0.0689938580475 crypto
"""
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00001099,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='kraken',
is_short=True,
leverage=3.0,
interest_rate=0.0005
)
trade.update(market_short_order) # Buy @ 0.00001099
assert isclose(float("{:.15f}".format(trade.calculate_interest())), 0.137987716095)
trade.open_date = five_hours_ago
assert isclose(float("{:.15f}".format(
trade.calculate_interest(interest_rate=0.00025))), 0.086242322559375)
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00001099,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='kraken',
is_short=True,
leverage=5.0,
interest_rate=0.0005
)
trade.update(market_short_order) # Buy @ 0.00001099
assert isclose(float("{:.15f}".format(trade.calculate_interest())), 0.17248464511875)
trade.open_date = ten_minutes_ago
assert isclose(float("{:.15f}".format(
trade.calculate_interest(interest_rate=0.00025))), 0.0689938580475)
@pytest.mark.usefixtures("init_persistence")
def test_interest_binance(market_short_order, ten_minutes_ago, five_hours_ago, fee):
"""
Market trade on Binance at 3x and 5x leverage
Short trade
interest_rate: 0.05%, 0.25% per 1 day
open_rate: 0.00004173 base
close_rate: 0.00004099 base
amount:
91.99181073 * leverage(3) = 275.97543219 crypto
91.99181073 * leverage(5) = 459.95905365 crypto
borrowed:
275.97543219 crypto
459.95905365 crypto
time-periods: 10 minutes(rounds up to 1/24 time-period of 1 day)
5 hours = 5/24
interest: borrowed * interest_rate * time-periods
= print(275.97543219 * 0.0005 * 1/24) = 0.005749488170625 crypto
= print(275.97543219 * 0.00025 * 5/24) = 0.0143737204265625 crypto
= print(459.95905365 * 0.0005 * 5/24) = 0.047912401421875 crypto
= print(459.95905365 * 0.00025 * 1/24) = 0.0047912401421875 crypto
"""
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00001099,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
is_short=True,
interest_rate=0.0005
)
trade.update(market_short_order) # Buy @ 0.00001099
assert isclose(float("{:.15f}".format(trade.calculate_interest())), 0.005749488170625)
trade.open_date = five_hours_ago
assert isclose(float("{:.15f}".format(
trade.calculate_interest(interest_rate=0.00025))), 0.0143737204265625)
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
open_rate=0.00001099,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
is_short=True,
leverage=5.0,
interest_rate=0.0005
)
trade.update(market_short_order) # Buy @ 0.00001099
assert isclose(float("{:.15f}".format(trade.calculate_interest())), 0.047912401421875)
trade.open_date = ten_minutes_ago
assert isclose(float("{:.15f}".format(
trade.calculate_interest(interest_rate=0.00025))), 0.0047912401421875)
def test_adjust_stop_loss(fee):
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=5,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
open_rate=1,
max_rate=1,
is_short=True
)
trade.adjust_stop_loss(trade.open_rate, 0.05, True)
assert trade.stop_loss == 1.05
assert trade.stop_loss_pct == 0.05
assert trade.initial_stop_loss == 1.05
assert trade.initial_stop_loss_pct == 0.05
# Get percent of profit with a lower rate
trade.adjust_stop_loss(1.04, 0.05)
assert trade.stop_loss == 1.05
assert trade.stop_loss_pct == 0.05
assert trade.initial_stop_loss == 1.05
assert trade.initial_stop_loss_pct == 0.05
# Get percent of profit with a custom rate (Higher than open rate)
trade.adjust_stop_loss(0.7, 0.1)
# assert round(trade.stop_loss, 8) == 1.17 #TODO-mg: What is this test?
assert trade.stop_loss_pct == 0.1
assert trade.initial_stop_loss == 1.05
assert trade.initial_stop_loss_pct == 0.05
# current rate lower again ... should not change
trade.adjust_stop_loss(0.8, -0.1)
# assert round(trade.stop_loss, 8) == 1.17 #TODO-mg: What is this test?
assert trade.initial_stop_loss == 1.05
assert trade.initial_stop_loss_pct == 0.05
# current rate higher... should raise stoploss
trade.adjust_stop_loss(0.6, -0.1)
# assert round(trade.stop_loss, 8) == 1.26 #TODO-mg: What is this test?
assert trade.initial_stop_loss == 1.05
assert trade.initial_stop_loss_pct == 0.05
# Initial is true but stop_loss set - so doesn't do anything
trade.adjust_stop_loss(0.3, -0.1, True)
# assert round(trade.stop_loss, 8) == 1.26 #TODO-mg: What is this test?
assert trade.initial_stop_loss == 1.05
assert trade.initial_stop_loss_pct == 0.05
assert trade.stop_loss_pct == 0.1
# TODO-mg: Do a test with a trade that has a liquidation price
# TODO: I don't know how to do this test, but it should be tested for shorts
# @pytest.mark.usefixtures("init_persistence")
# @pytest.mark.parametrize('use_db', [True, False])
# def test_get_open(fee, use_db):
# Trade.use_db = use_db
# Trade.reset_trades()
# create_mock_trades(fee, use_db)
# assert len(Trade.get_open_trades()) == 4
# Trade.use_db = True
def test_stoploss_reinitialization(default_conf, fee):
init_db(default_conf['db_url'])
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
fee_open=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime,
amount=10,
fee_close=fee.return_value,
exchange='binance',
open_rate=1,
max_rate=1,
is_short=True
)
trade.adjust_stop_loss(trade.open_rate, -0.05, True)
assert trade.stop_loss == 1.05
assert trade.stop_loss_pct == 0.05
assert trade.initial_stop_loss == 1.05
assert trade.initial_stop_loss_pct == 0.05
Trade.query.session.add(trade)
# Lower stoploss
Trade.stoploss_reinitialization(-0.06)
trades = Trade.get_open_trades()
assert len(trades) == 1
trade_adj = trades[0]
assert trade_adj.stop_loss == 1.06
assert trade_adj.stop_loss_pct == 0.06
assert trade_adj.initial_stop_loss == 1.06
assert trade_adj.initial_stop_loss_pct == 0.06
# Raise stoploss
Trade.stoploss_reinitialization(-0.04)
trades = Trade.get_open_trades()
assert len(trades) == 1
trade_adj = trades[0]
assert trade_adj.stop_loss == 1.04
assert trade_adj.stop_loss_pct == 0.04
assert trade_adj.initial_stop_loss == 1.04
assert trade_adj.initial_stop_loss_pct == 0.04
# Trailing stoploss (move stoplos up a bit)
trade.adjust_stop_loss(0.98, -0.04)
assert trade_adj.stop_loss == 1.0208
assert trade_adj.initial_stop_loss == 1.04
Trade.stoploss_reinitialization(-0.04)
trades = Trade.get_open_trades()
assert len(trades) == 1
trade_adj = trades[0]
# Stoploss should not change in this case.
assert trade_adj.stop_loss == 1.0208
assert trade_adj.stop_loss_pct == 0.04
assert trade_adj.initial_stop_loss == 1.04
assert trade_adj.initial_stop_loss_pct == 0.04
# @pytest.mark.usefixtures("init_persistence")
# @pytest.mark.parametrize('use_db', [True, False])
# def test_total_open_trades_stakes(fee, use_db):
# Trade.use_db = use_db
# Trade.reset_trades()
# res = Trade.total_open_trades_stakes()
# assert res == 0
# create_mock_trades(fee, use_db)
# res = Trade.total_open_trades_stakes()
# assert res == 0.004
# Trade.use_db = True
# @pytest.mark.usefixtures("init_persistence")
# def test_get_overall_performance(fee):
# create_mock_trades(fee)
# res = Trade.get_overall_performance()
# assert len(res) == 2
# assert 'pair' in res[0]
# assert 'profit' in res[0]
# assert 'count' in res[0]
# @pytest.mark.usefixtures("init_persistence")
# def test_get_best_pair(fee):
# res = Trade.get_best_pair()
# assert res is None
# create_mock_trades(fee)
# res = Trade.get_best_pair()
# assert len(res) == 2
# assert res[0] == 'XRP/BTC'
# assert res[1] == 0.01
# @pytest.mark.usefixtures("init_persistence")
# def test_update_order_from_ccxt(caplog):
# # Most basic order return (only has orderid)
# o = Order.parse_from_ccxt_object({'id': '1234'}, 'ETH/BTC', 'buy')
# assert isinstance(o, Order)
# assert o.ft_pair == 'ETH/BTC'
# assert o.ft_order_side == 'buy'
# assert o.order_id == '1234'
# assert o.ft_is_open
# ccxt_order = {
# 'id': '1234',
# 'side': 'buy',
# 'symbol': 'ETH/BTC',
# 'type': 'limit',
# 'price': 1234.5,
# 'amount': 20.0,
# 'filled': 9,
# 'remaining': 11,
# 'status': 'open',
# 'timestamp': 1599394315123
# }
# o = Order.parse_from_ccxt_object(ccxt_order, 'ETH/BTC', 'buy')
# assert isinstance(o, Order)
# assert o.ft_pair == 'ETH/BTC'
# assert o.ft_order_side == 'buy'
# assert o.order_id == '1234'
# assert o.order_type == 'limit'
# assert o.price == 1234.5
# assert o.filled == 9
# assert o.remaining == 11
# assert o.order_date is not None
# assert o.ft_is_open
# assert o.order_filled_date is None
# # Order has been closed
# ccxt_order.update({'filled': 20.0, 'remaining': 0.0, 'status': 'closed'})
# o.update_from_ccxt_object(ccxt_order)
# assert o.filled == 20.0
# assert o.remaining == 0.0
# assert not o.ft_is_open
# assert o.order_filled_date is not None
# ccxt_order.update({'id': 'somethingelse'})
# with pytest.raises(DependencyException, match=r"Order-id's don't match"):
# o.update_from_ccxt_object(ccxt_order)
# message = "aaaa is not a valid response object."
# assert not log_has(message, caplog)
# Order.update_orders([o], 'aaaa')
# assert log_has(message, caplog)
# # Call regular update - shouldn't fail.
# Order.update_orders([o], {'id': '1234'})