About 15 margin tests pass
This commit is contained in:
parent
f5d7deedf4
commit
efcc2adacf
@ -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).
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
@ -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:
|
||||||
@ -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):
|
||||||
|
@ -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)
|
|
||||||
|
@ -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,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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'})
|
|
803
tests/test_persistence_short.py
Normal file
803
tests/test_persistence_short.py
Normal 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'})
|
Loading…
Reference in New Issue
Block a user