Updated tests to new persistence

This commit is contained in:
Sam Germain
2021-07-04 00:11:59 -06:00
parent 75b2c9ca1b
commit 9ddb6981dd
10 changed files with 874 additions and 973 deletions

View File

@@ -3,6 +3,7 @@ import logging
from typing import Dict
import ccxt
from decimal import Decimal
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
OperationalException, TemporaryError)
@@ -89,3 +90,12 @@ class Binance(Exchange):
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@staticmethod
def calculate_interest(borrowed: Decimal, hours: Decimal, interest_rate: Decimal) -> Decimal:
# Rate is per day but accrued hourly or something
# binance: https://www.binance.com/en-AU/support/faq/360030157812
one = Decimal(1)
twenty_four = Decimal(24)
# TODO-mg: Is hours rounded?
return borrowed * interest_rate * max(hours, one)/twenty_four

View File

@@ -3,6 +3,7 @@ import logging
from typing import Any, Dict
import ccxt
from decimal import Decimal
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
OperationalException, TemporaryError)
@@ -124,3 +125,11 @@ class Kraken(Exchange):
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@staticmethod
def calculate_interest(borrowed: Decimal, hours: Decimal, interest_rate: Decimal) -> Decimal:
four = Decimal(4.0)
# https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading-
opening_fee = borrowed * interest_rate
roll_over_fee = borrowed * interest_rate * max(0, (hours-four)/four)
return opening_fee + roll_over_fee

View File

@@ -49,9 +49,6 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
strategy = get_column_def(cols, 'strategy', 'null')
leverage = get_column_def(cols, 'leverage', 'null')
borrowed = get_column_def(cols, 'borrowed', '0.0')
borrowed_currency = get_column_def(cols, 'borrowed_currency', 'null')
collateral_currency = get_column_def(cols, 'collateral_currency', 'null')
interest_rate = get_column_def(cols, 'interest_rate', '0.0')
liquidation_price = get_column_def(cols, 'liquidation_price', 'null')
is_short = get_column_def(cols, 'is_short', 'False')
@@ -91,8 +88,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, sell_order_status, strategy,
timeframe, open_trade_value, close_profit_abs,
leverage, borrowed, borrowed_currency, collateral_currency, interest_rate,
liquidation_price, is_short
leverage, interest_rate, liquidation_price, is_short
)
select id, lower(exchange),
case
@@ -116,14 +112,11 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
{sell_order_status} sell_order_status,
{strategy} strategy, {timeframe} timeframe,
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
{leverage} leverage, {borrowed} borrowed, {borrowed_currency} borrowed_currency,
{collateral_currency} collateral_currency, {interest_rate} interest_rate,
{leverage} leverage, {interest_rate} interest_rate,
{liquidation_price} liquidation_price, {is_short} is_short
from {table_back_name}
"""))
# TODO: Does leverage go in here?
def migrate_open_orders_to_trades(engine):
with engine.begin() as connection:

View File

@@ -132,7 +132,11 @@ class Order(_DECL_BASE):
order_filled_date = Column(DateTime, nullable=True)
order_update_date = Column(DateTime, nullable=True)
leverage = Column(Float, nullable=True, default=None)
is_short = Column(Boolean, nullable=True, default=False)
def __repr__(self):
return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, '
f'side={self.side}, order_type={self.order_type}, status={self.status})')
@@ -226,7 +230,6 @@ class LocalTrade():
fee_close_currency: str = ''
open_rate: float = 0.0
open_rate_requested: Optional[float] = None
# open_trade_value - calculated via _calc_open_trade_value
open_trade_value: float = 0.0
close_rate: Optional[float] = None
@@ -261,61 +264,23 @@ class LocalTrade():
timeframe: Optional[int] = None
# Margin trading properties
borrowed_currency: str = None
collateral_currency: str = None
interest_rate: float = 0.0
liquidation_price: float = None
is_short: bool = False
borrowed: float = 0.0
leverage: float = None
# @property
# def base_currency(self) -> str:
# if not self.pair:
# raise OperationalException('LocalTrade.pair must be assigned')
# return self.pair.split("/")[1]
@property
def has_no_leverage(self) -> bool:
return (self.leverage == 1.0 and not self.is_short) or self.leverage is None
# TODO: @samgermain: Amount should be persisted "as is".
# I've partially reverted this (this killed most of your tests)
# but leave this here as i'm not sure where you intended to use this.
# @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
# def borrowed(self) -> float:
# if self._leverage is not None:
# if self.is_short:
# # If shorting the full amount must be borrowed
# return self._amount * self._leverage
# else:
# # If not shorting, then the trader already owns a bit
# return self._amount * (self._leverage-1)
# else:
# return self._borrowed
# @borrowed.setter
# def borrowed(self, value):
# self._borrowed = value
# self._leverage = None
# @property
# def leverage(self) -> float:
# return self._leverage
# @leverage.setter
# def leverage(self, value):
# self._leverage = value
# self._borrowed = None
# End of margin trading properties
@property
def borrowed(self) -> float:
if self.has_no_leverage:
return 0.0
elif not self.is_short:
return self.stake_amount * (self.leverage-1)
else:
return self.amount
@property
def open_date_utc(self):
@@ -326,13 +291,8 @@ class LocalTrade():
return self.close_date.replace(tzinfo=timezone.utc)
def __init__(self, **kwargs):
if kwargs.get('leverage') and kwargs.get('borrowed'):
# TODO-mg: should I raise an error?
raise OperationalException('Cannot pass both borrowed and leverage to Trade')
for key in kwargs:
setattr(self, key, kwargs[key])
if not self.is_short:
self.is_short = False
self.recalc_open_trade_value()
def __repr__(self):
@@ -404,9 +364,6 @@ class LocalTrade():
'max_rate': self.max_rate,
'leverage': self.leverage,
'borrowed': self.borrowed,
'borrowed_currency': self.borrowed_currency,
'collateral_currency': self.collateral_currency,
'interest_rate': self.interest_rate,
'liquidation_price': self.liquidation_price,
'is_short': self.is_short,
@@ -473,7 +430,7 @@ class LocalTrade():
# evaluate if the stop loss needs to be updated
else:
# stop losses only walk up, never down!, #TODO: But adding more to a margin account would create a lower liquidation price, decreasing the minimum stoploss
# stop losses only walk up, never down!, #But adding more to a margin account would create a lower liquidation price, decreasing the minimum stoploss
if (new_loss > self.stop_loss and not self.is_short) or (new_loss < self.stop_loss and self.is_short):
logger.debug(f"{self.pair} - Adjusting stoploss...")
self._set_new_stoploss(new_loss, stoploss)
@@ -510,13 +467,8 @@ class LocalTrade():
"""
order_type = order['type']
if ('leverage' in order and 'borrowed' in order):
raise OperationalException(
'Pass only one of Leverage or Borrowed to the order in update trade')
if 'is_short' in order and order['side'] == 'sell':
# Only set's is_short on opening trades, ignores non-shorts
# TODO-mg: I don't like this, but it might be the only way
self.is_short = order['is_short']
# Ignore open and cancelled orders
@@ -527,15 +479,10 @@ class LocalTrade():
if order_type in ('market', 'limit') and self.is_opening_trade(order['side']):
# Update open rate and actual amount
self.open_rate = float(safe_value_fallback(order, 'average', 'price'))
self.amount = float(safe_value_fallback(order, 'filled', 'amount'))
if 'borrowed' in order:
self.borrowed = order['borrowed']
elif 'leverage' in order:
if 'leverage' in order:
self.leverage = order['leverage']
self.recalc_open_trade_value()
if self.is_open:
payment = "SELL" if self.is_short else "BUY"
@@ -544,7 +491,8 @@ class LocalTrade():
elif order_type in ('market', 'limit') and self.is_closing_trade(order['side']):
if self.is_open:
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
# TODO-mg: On Shorts technically your buying a little bit more than the amount because it's the ammount plus the interest
# But this wll only print the original
logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.')
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'):
@@ -632,17 +580,16 @@ class LocalTrade():
: 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
zero = Decimal(0.0)
if not (self.borrowed):
# If nothing was borrowed
if (self.leverage == 1.0 and not self.is_short) or not self.leverage:
return zero
open_date = self.open_date.replace(tzinfo=None)
now = datetime.utcnow()
# sec_per_day = Decimal(86400)
now = (self.close_date or datetime.utcnow()).replace(tzinfo=None)
sec_per_hour = Decimal(3600)
total_seconds = Decimal((now - open_date).total_seconds())
# days = total_seconds/sec_per_day or zero
hours = total_seconds/sec_per_hour or zero
rate = Decimal(interest_rate or self.interest_rate)
@@ -654,7 +601,7 @@ class LocalTrade():
if self.exchange == 'binance':
# Rate is per day but accrued hourly or something
# binance: https://www.binance.com/en-AU/support/faq/360030157812
return borrowed * rate * max(hours, one)/twenty_four # TODO-mg: Is hours rounded?
return borrowed * rate * max(hours, one)/twenty_four
elif self.exchange == 'kraken':
# https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading-
opening_fee = borrowed * rate
@@ -746,16 +693,15 @@ class LocalTrade():
if (self.is_short and close_trade_value == 0.0) or (not self.is_short and self.open_trade_value == 0.0):
return 0.0
else:
if self.borrowed: # TODO: This is only needed so that previous tests that included dummy stake_amounts don't fail. Undate those tests and get rid of this else
if self.has_no_leverage:
# TODO: This is only needed so that previous tests that included dummy stake_amounts don't fail. Undate those tests and get rid of this else
profit_ratio = (close_trade_value/self.open_trade_value) - 1
else:
if self.is_short:
profit_ratio = ((self.open_trade_value - close_trade_value) / self.stake_amount)
else:
profit_ratio = ((close_trade_value - self.open_trade_value) / self.stake_amount)
else: # TODO: This is only needed so that previous tests that included dummy stake_amounts don't fail. Undate those tests and get rid of this else
if self.is_short:
profit_ratio = 1 - (close_trade_value/self.open_trade_value)
else:
profit_ratio = (close_trade_value/self.open_trade_value) - 1
return float(f"{profit_ratio:.8f}")
def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]:
@@ -907,14 +853,10 @@ class Trade(_DECL_BASE, LocalTrade):
timeframe = Column(Integer, nullable=True)
# Margin trading properties
leverage = Column(Float, nullable=True) # TODO: can this be nullable, or should it default to 1? (must also be changed in migrations eventually)
borrowed = Column(Float, nullable=False, default=0.0)
leverage = Column(Float, nullable=True)
interest_rate = Column(Float, nullable=False, default=0.0)
liquidation_price = Column(Float, nullable=True)
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
def __init__(self, **kwargs):