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:
parent
5667c1ef23
commit
13ec7610bf
@ -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).
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user