Updated tests to new persistence

This commit is contained in:
Sam Germain 2021-07-04 00:11:59 -06:00
parent 243173d2be
commit 6b20a315e3
8 changed files with 77 additions and 115 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

@ -8,6 +8,7 @@ import inspect
import logging
from copy import deepcopy
from datetime import datetime, timezone
from decimal import Decimal
from math import ceil
from typing import Any, Dict, List, Optional, Tuple
@ -551,7 +552,7 @@ class Exchange:
amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent',
DEFAULT_AMOUNT_RESERVE_PERCENT)
amount_reserve_percent = (
amount_reserve_percent / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
amount_reserve_percent / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
)
# it should not be more than 50%
amount_reserve_percent = max(min(amount_reserve_percent, 1.5), 1)
@ -965,7 +966,7 @@ class Exchange:
logger.warning(
"Buy Price from orderbook could not be determined."
f"Orderbook: {order_book}"
)
)
raise PricingError from e
logger.info(f"Buy price from orderbook {bid_strategy['price_side'].capitalize()} side "
f"- top {order_book_top} order book buy rate {rate_from_l2:.8f}")
@ -1252,8 +1253,8 @@ class Exchange:
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
ohlcv_df = ohlcv_to_dataframe(
ticks, timeframe, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle)
ticks, timeframe, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle)
results_df[(pair, timeframe)] = ohlcv_df
if cache:
self._klines[(pair, timeframe)] = ohlcv_df
@ -1473,6 +1474,18 @@ class Exchange:
self._async_get_trade_history(pair=pair, since=since,
until=until, from_id=from_id))
@staticmethod
def calculate_interest(borrowed: Decimal, hours: Decimal, interest_rate: Decimal) -> Decimal:
"""Generate the interest owed for borrowing an amount of currency for a certain amount of time
:param borrowed: The amount of currency borrowed
:param hours: The length of time in hours that the currency has been borrowed for
:param interest_rate: The rate of interest for this trade
#TODO: May update this just to the currency of the borrowed amount
:raises ValueError: Throws value error if not implemented for the exchange
:returns The amount of interest owed for the borrowed currency
"""
raise ValueError('Margin trading is not available on this exchange with freqtrade')
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:
return exchange_name in ccxt_exchanges(ccxt_module)

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,7 +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,
leverage, interest_rate,
liquidation_price, is_short
)
select id, lower(exchange),
@ -116,8 +113,7 @@ 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}
"""))

View File

@ -132,6 +132,9 @@ 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})')
@ -261,61 +264,17 @@ 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]
# 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):
if not self.is_short:
return self.amount * (self.leverage-1)/self.leverage
else:
return self.amount
@property
def open_date_utc(self):
@ -326,13 +285,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 +358,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 +424,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,15 +461,6 @@ 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
if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None:
return
@ -531,11 +473,6 @@ class LocalTrade():
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:
self.leverage = order['leverage']
self.recalc_open_trade_value()
if self.is_open:
payment = "SELL" if self.is_short else "BUY"
@ -632,17 +569,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)
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)
@ -746,7 +682,8 @@ 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
# 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.leverage == 1.0 and not self.is_short) or not self.leverage:
if self.is_short:
profit_ratio = ((self.open_trade_value - close_trade_value) / self.stake_amount)
else:
@ -907,14 +844,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):

View File

@ -2132,8 +2132,8 @@ def market_short_order():
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004173,
'amount': 91.99181073,
'filled': 91.99181073,
'amount': 275.97543219,
'filled': 275.97543219,
'remaining': 0.0,
'status': 'closed',
'is_short': True,
@ -2151,8 +2151,8 @@ def market_exit_short_order():
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004099,
'amount': 91.99181073,
'filled': 91.99181073,
'amount': 275.97543219,
'filled': 275.97543219,
'remaining': 0.0,
'status': 'closed',
# 'leverage': 3.0,

View File

@ -433,8 +433,7 @@ def leverage_trade(fee):
interest_rate: 0.05% per day
open_rate: 0.123 base
close_rate: 0.128 base
amount: 123.0 crypto
amount_with_leverage: 615.0
amount: 615 crypto
stake_amount: 15.129 base
borrowed: 60.516 base
leverage: 5

View File

@ -54,14 +54,13 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, ten
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
# borrowed=90.99181073,
leverage=3.0,
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)
@ -101,7 +100,7 @@ def test_update_market_order(
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
amount: = 275.97543219 crypto
stake_amount: 0.0038388182617629
borrowed: 275.97543219 crypto
time-periods: 10 minutes(rounds up to 1 time-period of 4hrs)
@ -131,6 +130,7 @@ def test_update_market_order(
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=ten_minutes_ago,
leverage=3.0,
interest_rate=0.0005,
exchange='kraken'
)
@ -228,7 +228,7 @@ def test_trade_close(fee, five_hours_ago):
open_rate: 0.02 base
close_rate: 0.01 base
leverage: 3.0
amount: 5 * 3 = 15 crypto
amount: 15 crypto
borrowed: 15 crypto
time-periods: 5 hours = 5/4
@ -286,13 +286,13 @@ def test_calc_close_trade_price_exception(limit_short_order, fee):
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.1,
amount=5,
amount=15.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
interest_rate=0.0005,
is_short=True,
borrowed=15
leverage=3.0,
is_short=True
)
trade.open_order_id = 'something'
trade.update(limit_short_order)
@ -306,6 +306,7 @@ def test_update_open_order(limit_short_order):
stake_amount=1.00,
open_rate=0.01,
amount=5,
leverage=3.0,
fee_open=0.1,
fee_close=0.1,
interest_rate=0.0005,
@ -355,7 +356,7 @@ def test_calc_close_trade_price(market_short_order, market_exit_short_order, ten
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
amount: = 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
@ -399,7 +400,7 @@ def test_calc_profit(market_short_order, market_exit_short_order, ten_minutes_ag
open_rate: 0.00004173 base
close_rate: 0.00004099 base
stake_amount: 0.0038388182617629
amount: 91.99181073 * leverage(3) = 275.97543219 crypto
amount: = 275.97543219 crypto
borrowed: 275.97543219 crypto
time-periods: 10 minutes(rounds up to 1 time-period of 4hrs)
5 hours = 5/4
@ -494,8 +495,8 @@ def test_interest_kraken(market_short_order, ten_minutes_ago, five_hours_ago, fe
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
275.97543219 crypto
459.95905365 crypto
borrowed:
275.97543219 crypto
459.95905365 crypto
@ -512,7 +513,7 @@ def test_interest_kraken(market_short_order, ten_minutes_ago, five_hours_ago, fe
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=91.99181073,
amount=275.97543219,
open_rate=0.00001099,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
@ -531,7 +532,7 @@ def test_interest_kraken(market_short_order, ten_minutes_ago, five_hours_ago, fe
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=91.99181073,
amount=459.95905365,
open_rate=0.00001099,
open_date=five_hours_ago,
fee_open=fee.return_value,
@ -581,7 +582,7 @@ def test_interest_binance(market_short_order, ten_minutes_ago, five_hours_ago, f
fee_close=fee.return_value,
exchange='binance',
is_short=True,
borrowed=275.97543219,
leverage=3.0,
interest_rate=0.0005
)
@ -599,7 +600,7 @@ def test_interest_binance(market_short_order, ten_minutes_ago, five_hours_ago, f
fee_close=fee.return_value,
exchange='binance',
is_short=True,
borrowed=459.95905365,
leverage=5.0,
interest_rate=0.0005
)
@ -679,7 +680,8 @@ def test_stoploss_reinitialization(default_conf, fee):
exchange='binance',
open_rate=1,
max_rate=1,
is_short=True
is_short=True,
leverage=3.0,
)
trade.adjust_stop_loss(trade.open_rate, -0.05, True)
assert trade.stop_loss == 1.05