Two margin tests pass now, although 3 persistance tests fail due the field in Trade being _leverage instead of leverage

This commit is contained in:
Sam Germain 2021-06-27 03:38:56 -06:00
parent 5667c1ef23
commit 13ec7610bf
6 changed files with 128 additions and 94 deletions

View File

@ -1,7 +1,7 @@
An instance of a `Trade`/`LocalTrade` object is given either a value for `leverage` or a value for `borrowed`, but not both, on instantiation/update with a short/long.
- 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).

View File

@ -48,7 +48,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
sell_reason = get_column_def(cols, 'sell_reason', 'null')
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_currency = get_column_def(cols, 'borrowed_currency', 'null')
collateral_currency = get_column_def(cols, 'collateral_currency', 'null')

View File

@ -132,7 +132,7 @@ class Order(_DECL_BASE):
order_filled_date = Column(DateTime, nullable=True)
order_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)
def __repr__(self):
@ -269,33 +269,40 @@ class LocalTrade():
interest_rate: float = 0.0
liquidation_price: float = None
is_short: bool = False
__leverage: float = 1.0 # * You probably want to use self.leverage instead |
__borrowed: float = 0.0 # * You probably want to use self.borrowed instead V
borrowed: float = 0.0
_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 leverage(self) -> float:
return self.__leverage or 1.0
@property
def borrowed(self) -> float:
return self.__borrowed or 0.0
return self._leverage
@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:
raise OperationalException(
'LocalTrade.amount and LocalTrade.is_short must be assigned before LocalTrade.leverage')
self.__leverage = lev
self.__borrowed = self.amount * (lev-1)
self.amount = self.amount * lev
'LocalTrade.amount and LocalTrade.is_short must be assigned before assigning leverage')
self._leverage = value
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)
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
@property
@ -501,11 +508,15 @@ 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']
if 'interest_rate' in order:
self.interest_rate = order['interest_rate']
self.recalc_open_trade_value()
if self.is_open:
payment = "SELL" if self.is_short else "BUY"
@ -514,6 +525,7 @@ 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
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'):
@ -853,18 +865,19 @@ class Trade(_DECL_BASE, LocalTrade):
# Lowest price reached
min_rate = Column(Float, nullable=True)
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)
timeframe = Column(Integer, nullable=True)
# 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_currency = Column(Float, nullable=True)
collateral_currency = Column(String(25), 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

@ -2102,9 +2102,9 @@ def limit_exit_short_order(limit_exit_short_order_open):
@pytest.fixture(scope='function')
def market_short_order():
return {
'id': 'mocked_market_buy',
'id': 'mocked_market_short',
'type': 'market',
'side': 'buy',
'side': 'sell',
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004173,
@ -2113,16 +2113,17 @@ def market_short_order():
'remaining': 0.0,
'status': 'closed',
'is_short': True,
'leverage': 3
'leverage': 3.0,
'interest_rate': 0.0005
}
@pytest.fixture
def market_exit_short_order():
return {
'id': 'mocked_limit_sell',
'id': 'mocked_limit_exit_short',
'type': 'market',
'side': 'sell',
'side': 'buy',
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004099,
@ -2130,8 +2131,7 @@ def market_exit_short_order():
'filled': 91.99181073,
'remaining': 0.0,
'status': 'closed',
'leverage': 3,
'interest_rate': 0.0005
'leverage': 3.0
}

View File

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

View File

@ -23,22 +23,22 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int
- 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))
((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
- 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
= 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.001064666/0.00100252088)-1 = 0.06198885353
#* ~0.061988453889463014104555743 With more precise numbers used
:param limit_short_order:
:param limit_exit_short_order:
@ -88,63 +88,84 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int
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_short_order,
market_exit_short_order,
fee,
interest_rate,
ten_minutes_ago,
caplog
):
"""
Test Kraken and leverage arguments as well as update market order
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,
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_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)
# TODO-mg: create a leveraged long order
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):