diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index ec5c15cee..a974691be 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -132,9 +132,6 @@ 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})') diff --git a/tests/conftest.py b/tests/conftest.py index 3c071f2f3..b17f9658e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -204,11 +204,34 @@ def create_mock_trades(fee, use_db: bool = True): add_trade(trade) trade = mock_trade_6(fee) add_trade(trade) - # TODO: margin trades - # trade = short_trade(fee) - # add_trade(trade) - # trade = leverage_trade(fee) - # add_trade(trade) + + +def create_mock_trades_with_leverage(fee, use_db: bool = True): + """ + Create some fake trades ... + """ + def add_trade(trade): + if use_db: + Trade.query.session.add(trade) + else: + LocalTrade.add_bt_trade(trade) + # Simulate dry_run entries + trade = mock_trade_1(fee) + add_trade(trade) + trade = mock_trade_2(fee) + add_trade(trade) + trade = mock_trade_3(fee) + add_trade(trade) + trade = mock_trade_4(fee) + add_trade(trade) + trade = mock_trade_5(fee) + add_trade(trade) + trade = mock_trade_6(fee) + add_trade(trade) + trade = short_trade(fee) + add_trade(trade) + trade = leverage_trade(fee) + add_trade(trade) if use_db: Trade.query.session.flush() @@ -2094,7 +2117,7 @@ def limit_short_order_open(): 'cost': 0.00106733393, 'remaining': 90.99181073, 'status': 'open', - 'is_short': True + 'exchange': 'binance' } @@ -2111,7 +2134,8 @@ def limit_exit_short_order_open(): 'amount': 90.99181073, 'filled': 0.0, 'remaining': 90.99181073, - 'status': 'open' + 'status': 'open', + 'exchange': 'binance' } @@ -2147,7 +2171,8 @@ def market_short_order(): 'remaining': 0.0, 'status': 'closed', 'is_short': True, - 'leverage': 3.0 + # 'leverage': 3.0, + 'exchange': 'kraken' } @@ -2164,5 +2189,96 @@ def market_exit_short_order(): 'filled': 91.99181073, 'remaining': 0.0, 'status': 'closed', - 'leverage': 3.0 + # 'leverage': 3.0, + 'exchange': 'kraken' + } + + +# leverage 3x +@pytest.fixture(scope='function') +def limit_leveraged_buy_order_open(): + return { + 'id': 'mocked_limit_buy', + 'type': 'limit', + 'side': 'buy', + 'symbol': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, + 'price': 0.00001099, + 'amount': 272.97543219, + 'filled': 0.0, + 'cost': 0.0029999999997681, + 'remaining': 272.97543219, + 'status': 'open', + 'exchange': 'binance' + } + + +@pytest.fixture(scope='function') +def limit_leveraged_buy_order(limit_leveraged_buy_order_open): + order = deepcopy(limit_leveraged_buy_order_open) + order['status'] = 'closed' + order['filled'] = order['amount'] + order['remaining'] = 0.0 + return order + + +@pytest.fixture +def limit_leveraged_sell_order_open(): + return { + 'id': 'mocked_limit_sell', + 'type': 'limit', + 'side': 'sell', + 'pair': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, + 'price': 0.00001173, + 'amount': 272.97543219, + 'filled': 0.0, + 'remaining': 272.97543219, + 'status': 'open', + 'exchange': 'binance' + } + + +@pytest.fixture +def limit_leveraged_sell_order(limit_leveraged_sell_order_open): + order = deepcopy(limit_leveraged_sell_order_open) + order['remaining'] = 0.0 + order['filled'] = order['amount'] + order['status'] = 'closed' + return order + + +@pytest.fixture(scope='function') +def market_leveraged_buy_order(): + return { + 'id': 'mocked_market_buy', + 'type': 'market', + 'side': 'buy', + 'symbol': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'price': 0.00004099, + 'amount': 275.97543219, + 'filled': 275.97543219, + 'remaining': 0.0, + 'status': 'closed', + 'exchange': 'kraken' + } + + +@pytest.fixture +def market_leveraged_sell_order(): + return { + 'id': 'mocked_limit_sell', + 'type': 'market', + 'side': 'sell', + 'symbol': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'price': 0.00004173, + 'amount': 275.97543219, + 'filled': 275.97543219, + 'remaining': 0.0, + 'status': 'closed', + 'exchange': 'kraken' } diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 41213732a..bc728dd44 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -310,7 +310,7 @@ def mock_trade_6(fee): def short_order(): return { - 'id': '1235', + 'id': '1236', 'symbol': 'ETC/BTC', 'status': 'closed', 'side': 'sell', @@ -319,14 +319,12 @@ def short_order(): 'amount': 123.0, 'filled': 123.0, 'remaining': 0.0, - 'leverage': 5.0, - 'isShort': True } def exit_short_order(): return { - 'id': '12366', + 'id': '12367', 'symbol': 'ETC/BTC', 'status': 'closed', 'side': 'buy', @@ -335,36 +333,60 @@ def exit_short_order(): 'amount': 123.0, 'filled': 123.0, 'remaining': 0.0, - 'leverage': 5.0, - 'isShort': True } def short_trade(fee): """ - Closed trade... + 10 minute short limit trade on binance + + Short trade + fee: 0.25% base + interest_rate: 0.05% per day + open_rate: 0.123 base + close_rate: 0.128 base + amount: 123.0 crypto + stake_amount: 15.129 base + borrowed: 123.0 crypto + time-periods: 10 minutes(rounds up to 1/24 time-period of 1 day) + interest: borrowed * interest_rate * time-periods + = 123.0 * 0.0005 * 1/24 = 0.0025625 crypto + open_value: (amount * open_rate) - (amount * open_rate * fee) + = (123 * 0.123) - (123 * 0.123 * 0.0025) + = 15.091177499999999 + amount_closed: amount + interest = 123 + 0.0025625 = 123.0025625 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (123.0025625 * 0.128) + (123.0025625 * 0.128 * 0.0025) + = 15.78368882 + total_profit = open_value - close_value + = 15.091177499999999 - 15.78368882 + = -0.6925113200000013 + total_profit_percentage = total_profit / stake_amount + = -0.6925113200000013 / 15.129 + = -0.04577376693766946 + """ trade = Trade( pair='ETC/BTC', - stake_amount=0.001, + stake_amount=15.129, amount=123.0, amount_requested=123.0, fee_open=fee.return_value, fee_close=fee.return_value, open_rate=0.123, - close_rate=0.128, - close_profit=0.025, - close_profit_abs=0.000584127, + # close_rate=0.128, + # close_profit=-0.04577376693766946, + # close_profit_abs=-0.6925113200000013, exchange='binance', - is_open=False, + is_open=True, open_order_id='dry_run_exit_short_12345', strategy='DefaultStrategy', timeframe=5, sell_reason='sell_signal', # TODO-mg: Update to exit/close reason open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), - close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), + # close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), # borrowed= - isShort=True + is_short=True ) o = Order.parse_from_ccxt_object(short_order(), 'ETC/BTC', 'sell') trade.orders.append(o) @@ -375,7 +397,7 @@ def short_trade(fee): def leverage_order(): return { - 'id': '1235', + 'id': '1237', 'symbol': 'ETC/BTC', 'status': 'closed', 'side': 'buy', @@ -390,7 +412,7 @@ def leverage_order(): def leverage_order_sell(): return { - 'id': '12366', + 'id': '12368', 'symbol': 'ETC/BTC', 'status': 'closed', 'side': 'sell', @@ -399,34 +421,60 @@ def leverage_order_sell(): 'amount': 123.0, 'filled': 123.0, 'remaining': 0.0, - 'leverage': 5.0, - 'isShort': True } def leverage_trade(fee): """ - Closed trade... + 5 hour short limit trade on kraken + + Short trade + fee: 0.25% base + 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 + stake_amount: 15.129 base + borrowed: 60.516 base + leverage: 5 + time-periods: 5 hrs( 5/4 time-period of 4 hours) + interest: borrowed * interest_rate * time-periods + = 60.516 * 0.0005 * 1/24 = 0.0378225 base + open_value: (amount * open_rate) - (amount * open_rate * fee) + = (615.0 * 0.123) - (615.0 * 0.123 * 0.0025) + = 75.4558875 + + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (615.0 * 0.128) + (615.0 * 0.128 * 0.0025) + = 78.9168 + total_profit = close_value - open_value - interest + = 78.9168 - 75.4558875 - 0.0378225 + = 3.423089999999992 + total_profit_percentage = total_profit / stake_amount + = 3.423089999999992 / 15.129 + = 0.22626016260162551 """ trade = Trade( pair='ETC/BTC', - stake_amount=0.001, - amount=615.0, - amount_requested=615.0, + stake_amount=15.129, + amount=123.0, + leverage=5, + amount_requested=123.0, fee_open=fee.return_value, fee_close=fee.return_value, open_rate=0.123, close_rate=0.128, - close_profit=0.005, # TODO-mg: Would this be -0.005 or -0.025 - close_profit_abs=0.000584127, - exchange='binance', + close_profit=0.22626016260162551, + close_profit_abs=3.423089999999992, + exchange='kraken', is_open=False, open_order_id='dry_run_leverage_sell_12345', strategy='DefaultStrategy', timeframe=5, sell_reason='sell_signal', # TODO-mg: Update to exit/close reason - open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), - close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), + open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300), + close_date=datetime.now(tz=timezone.utc), # borrowed= ) o = Order.parse_from_ccxt_object(leverage_order(), 'ETC/BTC', 'sell') diff --git a/tests/test_persistence_long.py b/tests/test_persistence_long.py new file mode 100644 index 000000000..cd0267cd1 --- /dev/null +++ b/tests/test_persistence_long.py @@ -0,0 +1,616 @@ +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_with_leverage, log_has, log_has_re + + +@pytest.mark.usefixtures("init_persistence") +def test_update_with_binance(limit_leveraged_buy_order, limit_leveraged_sell_order, fee, ten_minutes_ago, caplog): + """ + 10 minute leveraged limit trade on binance at 3x leverage + + Leveraged trade + fee: 0.25% base + interest_rate: 0.05% per day + open_rate: 0.00001099 base + close_rate: 0.00001173 base + amount: 272.97543219 crypto + stake_amount: 0.0009999999999226999 base + borrowed: 0.0019999999998453998 base + time-periods: 10 minutes(rounds up to 1/24 time-period of 1 day) + interest: borrowed * interest_rate * time-periods + = 0.0019999999998453998 * 0.0005 * 1/24 = 4.166666666344583e-08 base + open_value: (amount * open_rate) + (amount * open_rate * fee) + = (272.97543219 * 0.00001099) + (272.97543219 * 0.00001099 * 0.0025) + = 0.0030074999997675204 + close_value: (amount_closed * close_rate) - (amount_closed * close_rate * fee) + = (272.97543219 * 0.00001173) - (272.97543219 * 0.00001173 * 0.0025) + = 0.003193996815039728 + total_profit = close_value - open_value - interest + = 0.003193996815039728 - 0.0030074999997675204 - 4.166666666344583e-08 + = 0.00018645514860554435 + total_profit_percentage = total_profit / stake_amount + = 0.00018645514860554435 / 0.0009999999999226999 + = 0.18645514861995735 + + """ + trade = Trade( + id=2, + pair='ETH/BTC', + stake_amount=0.0009999999999226999, + 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_leveraged_buy_order) + # assert trade.open_order_id is None + assert trade.open_rate == 0.00001099 + assert trade.close_profit is None + assert trade.close_date is None + assert trade.borrowed == 0.0019999999998453998 + assert trade.is_short is True + assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, " + r"pair=ETH/BTC, amount=272.97543219, open_rate=0.00001099, open_since=.*\).", + caplog) + caplog.clear() + # trade.open_order_id = 'something' + trade.update(limit_leveraged_sell_order) + # assert trade.open_order_id is None + assert trade.close_rate == 0.00001173 + assert trade.close_profit == 0.18645514861995735 + assert trade.close_date is not None + assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, " + r"pair=ETH/BTC, amount=272.97543219, open_rate=0.00001099, open_since=.*\).", + caplog) + + +@pytest.mark.usefixtures("init_persistence") +def test_update_market_order(limit_leveraged_buy_order, limit_leveraged_sell_order, fee, ten_minutes_ago, caplog): + """ + 10 minute leveraged market trade on Kraken at 3x leverage + Short trade + fee: 0.25% base + interest_rate: 0.05% per 4 hrs + open_rate: 0.00004099 base + close_rate: 0.00004173 base + amount: 91.99181073 * leverage(3) = 275.97543219 crypto + stake_amount: 0.0037707443218227 + borrowed: 0.0075414886436454 base + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + interest: borrowed * interest_rate * time-periods + = 0.0075414886436454 * 0.0005 * 1 = 3.7707443218227e-06 crypto + open_value: (amount * open_rate) + (amount * open_rate * fee) + = (275.97543219 * 0.00004099) + (275.97543219 * 0.00004099 * 0.0025) + = 0.01134051354788177 + close_value: (amount_closed * close_rate) - (amount_closed * close_rate * fee) + = (275.97543219 * 0.00004173) - (275.97543219 * 0.00004173 * 0.0025) + = 0.011487663648325479 + total_profit = close_value - open_value - interest + = 0.011487663648325479 - 0.01134051354788177 - 3.7707443218227e-06 + = 0.0001433793561218866 + total_profit_percentage = total_profit / stake_amount + = 0.0001433793561218866 / 0.0037707443218227 + = 0.03802415223225211 + """ + trade = Trade( + id=1, + pair='ETH/BTC', + stake_amount=0.0037707443218227, + amount=5, + open_rate=0.01, + is_open=True, + leverage=3, + 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(limit_leveraged_buy_order) + assert trade.leverage == 3.0 + assert trade.is_short == True + assert trade.open_order_id is None + assert trade.open_rate == 0.00004099 + 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_BUY has been fulfilled for Trade\(id=1, " + r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004099, open_since=.*\).", + caplog) + caplog.clear() + trade.is_open = True + trade.open_order_id = 'something' + trade.update(limit_leveraged_sell_order) + assert trade.open_order_id is None + assert trade.close_rate == 0.00004173 + assert trade.close_profit == 0.03802415223225211 + 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_leveraged_buy_order, limit_leveraged_sell_order, five_hours_ago, fee): + """ + 5 hour leveraged trade on Binance + + fee: 0.25% base + interest_rate: 0.05% per day + open_rate: 0.00001099 base + close_rate: 0.00001173 base + amount: 272.97543219 crypto + stake_amount: 0.0009999999999226999 base + borrowed: 0.0019999999998453998 base + time-periods: 5 hours(rounds up to 5/24 time-period of 1 day) + interest: borrowed * interest_rate * time-periods + = 0.0019999999998453998 * 0.0005 * 5/24 = 2.0833333331722917e-07 base + open_value: (amount * open_rate) + (amount * open_rate * fee) + = (272.97543219 * 0.00001099) + (272.97543219 * 0.00001099 * 0.0025) + = 0.0030074999997675204 + close_value: (amount_closed * close_rate) - (amount_closed * close_rate * fee) + = (272.97543219 * 0.00001173) - (272.97543219 * 0.00001173 * 0.0025) + = 0.003193996815039728 + total_profit = close_value - open_value - interest + = 0.003193996815039728 - 0.0030074999997675204 - 2.0833333331722917e-07 + = 0.00018628848193889054 + total_profit_percentage = total_profit / stake_amount + = 0.00018628848193889054 / 0.0009999999999226999 + = 0.18628848195329067 + """ + trade = Trade( + pair='ETH/BTC', + stake_amount=0.0009999999999226999, + 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_leveraged_buy_order) + assert trade._calc_open_trade_value() == 0.0030074999997675204 + trade.update(limit_leveraged_sell_order) + + # Will be slightly different due to slight changes in compilation time, and the fact that interest depends on time + assert round(trade.calc_close_trade_value(), 11) == round(0.003193996815039728, 11) + # Profit in BTC + assert round(trade.calc_profit(), 8) == round(0.18628848195329067, 8) + # Profit in percent + # assert round(trade.calc_profit_ratio(), 11) == round(0.05822425142973869, 11) + + +@pytest.mark.usefixtures("init_persistence") +def test_trade_close(fee, five_hours_ago): + """ + 5 hour leveraged market trade on Kraken at 3x leverage + fee: 0.25% base + interest_rate: 0.05% per 4 hrs + open_rate: 0.1 base + close_rate: 0.2 base + amount: 5 * leverage(3) = 15 crypto + stake_amount: 0.5 + borrowed: 1 base + time-periods: 5/4 periods of 4hrs + interest: borrowed * interest_rate * time-periods + = 1 * 0.0005 * 5/4 = 0.000625 crypto + open_value: (amount * open_rate) + (amount * open_rate * fee) + = (15 * 0.1) + (15 * 0.1 * 0.0025) + = 1.50375 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (15 * 0.2) - (15 * 0.2 * 0.0025) + = 2.9925 + total_profit = close_value - open_value - interest + = 2.9925 - 1.50375 - 0.000625 + = 1.4881250000000001 + total_profit_percentage = total_profit / stake_amount + = 1.4881250000000001 / 0.5 + = 2.9762500000000003 + """ + trade = Trade( + pair='ETH/BTC', + stake_amount=0.1, + open_rate=0.01, + amount=5, + is_open=True, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_date=five_hours_ago, + exchange='kraken', + 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.02) + assert trade.is_open is False + assert trade.close_profit == round(2.9762500000000003, 8) + 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_leveraged_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', + interest_rate=0.0005, + borrowed=0.002 + ) + trade.open_order_id = 'something' + trade.update(limit_leveraged_buy_order) + assert trade.calc_close_trade_value() == 0.0 + + +@pytest.mark.usefixtures("init_persistence") +def test_update_open_order(limit_leveraged_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, + interest_rate=0.0005, + borrowed=2.00, + exchange='binance', + ) + assert trade.open_order_id is None + assert trade.close_profit is None + assert trade.close_date is None + limit_leveraged_buy_order['status'] = 'open' + trade.update(limit_leveraged_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(market_leveraged_buy_order, ten_minutes_ago, fee): + """ + 10 minute leveraged market trade on Kraken at 3x leverage + Short trade + fee: 0.25% base + interest_rate: 0.05% per 4 hrs + open_rate: 0.00004099 base + close_rate: 0.00004173 base + amount: 91.99181073 * leverage(3) = 275.97543219 crypto + stake_amount: 0.0037707443218227 + borrowed: 0.0075414886436454 base + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + interest: borrowed * interest_rate * time-periods + = 0.0075414886436454 * 0.0005 * 1 = 3.7707443218227e-06 crypto + open_value: (amount * open_rate) + (amount * open_rate * fee) + = (275.97543219 * 0.00004099) + (275.97543219 * 0.00004099 * 0.0025) + = 0.01134051354788177 + """ + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00004099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + interest_rate=0.0005, + exchange='kraken', + leverage=3 + ) + trade.open_order_id = 'open_trade' + trade.update(market_leveraged_buy_order) # Buy @ 0.00001099 + # Get the open rate price with the standard fee rate + assert trade._calc_open_trade_value() == 0.01134051354788177 + trade.fee_open = 0.003 + # Get the open rate price with a custom fee rate + assert trade._calc_open_trade_value() == 0.011346169664364504 + + +@pytest.mark.usefixtures("init_persistence") +def test_calc_close_trade_price(market_leveraged_buy_order, market_leveraged_sell_order, ten_minutes_ago, fee): + """ + 10 minute leveraged market trade on Kraken at 3x leverage + Short trade + fee: 0.25% base + interest_rate: 0.05% per 4 hrs + open_rate: 0.00004099 base + close_rate: 0.00004173 base + amount: 91.99181073 * leverage(3) = 275.97543219 crypto + stake_amount: 0.0037707443218227 + borrowed: 0.0075414886436454 base + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + interest: borrowed * interest_rate * time-periods + = 0.0075414886436454 * 0.0005 * 1 = 3.7707443218227e-06 crypto + open_value: (amount * open_rate) + (amount * open_rate * fee) + = (275.97543219 * 0.00004099) + (275.97543219 * 0.00004099 * 0.0025) + = 0.01134051354788177 + close_value: (amount_closed * close_rate) - (amount_closed * close_rate * fee) + = (275.97543219 * 0.00001234) - (275.97543219 * 0.00001234 * 0.0025) = 0.0033970229911415386 + = (275.97543219 * 0.00001234) - (275.97543219 * 0.00001234 * 0.003) = 0.0033953202227249265 + = (275.97543219 * 0.00004173) - (275.97543219 * 0.00004173 * 0.005) = 0.011458872511362258 + + """ + 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_leveraged_buy_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.0033970229911415386) + # 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.0033953202227249265) + # Test when we apply a Sell order, and ask price with a custom fee rate + trade.update(market_leveraged_sell_order) + assert isclose(trade.calc_close_trade_value(fee=0.005), 0.011458872511362258) + + +@pytest.mark.usefixtures("init_persistence") +def test_calc_profit(market_leveraged_buy_order, market_leveraged_sell_order, ten_minutes_ago, five_hours_ago, fee): + """ + # TODO: Update this one + Leveraged trade on Kraken at 3x leverage + fee: 0.25% base or 0.3% + interest_rate: 0.05%, 0.25% per 4 hrs + open_rate: 0.00004099 base + close_rate: 0.00004173 base + stake_amount: 0.0037707443218227 + amount: 91.99181073 * leverage(3) = 275.97543219 crypto + borrowed: 0.0075414886436454 base + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + 5 hours = 5/4 + + interest: borrowed * interest_rate * time-periods + = 0.0075414886436454 * 0.0005 * 1 = 3.7707443218227e-06 crypto + = 0.0075414886436454 * 0.00025 * 5/4 = 2.3567152011391876e-06 crypto + = 0.0075414886436454 * 0.0005 * 5/4 = 4.713430402278375e-06 crypto + = 0.0075414886436454 * 0.00025 * 1 = 1.88537216091135e-06 crypto + open_value: (amount * open_rate) + (amount * open_rate * fee) + = (275.97543219 * 0.00004099) + (275.97543219 * 0.00004099 * 0.0025) = 0.01134051354788177 + close_value: (amount_closed * close_rate) - (amount_closed * close_rate * fee) + (275.97543219 * 0.00005374) - (275.97543219 * 0.00005374 * 0.0025) = 0.014793842426575873 + (275.97543219 * 0.00000437) - (275.97543219 * 0.00000437 * 0.0025) = 0.0012029976070736241 + (275.97543219 * 0.00005374) - (275.97543219 * 0.00005374 * 0.003) = 0.014786426966712927 + (275.97543219 * 0.00000437) - (275.97543219 * 0.00000437 * 0.003) = 0.0012023946007542888 + total_profit = close_value - open_value + = 0.014793842426575873 - 0.01134051354788177 = 0.003453328878694104 + = 0.0012029976070736241 - 0.01134051354788177 = -0.010137515940808145 + = 0.014786426966712927 - 0.01134051354788177 = 0.0034459134188311574 + = 0.0012023946007542888 - 0.01134051354788177 = -0.01013811894712748 + total_profit_percentage = total_profit / stake_amount + 0.003453328878694104/0.0037707443218227 = 0.9158215418394733 + -0.010137515940808145/0.0037707443218227 = -2.6884654793852154 + 0.0034459134188311574/0.0037707443218227 = 0.9138549646255183 + -0.01013811894712748/0.0037707443218227 = -2.6886253964381557 + + """ + trade = Trade( + pair='ETH/BTC', + stake_amount=0.0038388182617629, + amount=5, + open_rate=0.00004099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='kraken', + leverage=3.0, + interest_rate=0.0005 + ) + trade.open_order_id = 'something' + trade.update(market_leveraged_buy_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) == round( + 0.003453328878694104, 8) + assert trade.calc_profit_ratio( + rate=0.00004374, interest_rate=0.0005) == round(0.9158215418394733, 8) + + # Lower than open rate + trade.open_date = five_hours_ago + assert trade.calc_profit( + rate=0.00000437, interest_rate=0.00025) == round(-0.010137515940808145, 8) + assert trade.calc_profit_ratio( + rate=0.00000437, interest_rate=0.00025) == round(-2.6884654793852154, 8) + + # 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) == round(0.0034459134188311574, 8) + assert trade.calc_profit_ratio(rate=0.00004374, fee=0.003, + interest_rate=0.0005) == round(0.9138549646255183, 8) + + # Lower than open rate + trade.open_date = ten_minutes_ago + assert trade.calc_profit(rate=0.00000437, fee=0.003, + interest_rate=0.00025) == round(-0.01013811894712748, 8) + assert trade.calc_profit_ratio(rate=0.00000437, fee=0.003, + interest_rate=0.00025) == round(-2.6886253964381557, 8) + + # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173 + trade.update(market_leveraged_sell_order) + assert trade.calc_profit() == round(0.0001433793561218866, 8) + assert trade.calc_profit_ratio() == round(0.03802415223225211, 8) + + # 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_leveraged_buy_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.00004099 base + close_rate: 0.00004173 base + stake_amount: 0.0037707443218227 + borrowed: 0.0075414886436454 + amount: + 91.99181073 * leverage(3) = 275.97543219 crypto + 91.99181073 * leverage(5) = 459.95905365 crypto + borrowed: + 0.0075414886436454 base + 0.0150829772872908 base + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + 5 hours = 5/4 + + interest: borrowed * interest_rate * time-periods + = 0.0075414886436454 * 0.0005 * 1 = 3.7707443218227e-06 base + = 0.0075414886436454 * 0.00025 * 5/4 = 2.3567152011391876e-06 base + = 0.0150829772872908 * 0.0005 * 5/4 = 9.42686080455675e-06 base + = 0.0150829772872908 * 0.00025 * 1 = 3.7707443218227e-06 base + """ + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.0037707443218227, + amount=91.99181073, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='kraken', + leverage=3.0, + interest_rate=0.0005 + ) + + assert float(round(trade.calculate_interest(), 8)) == 3.7707443218227e-06 + trade.open_date = five_hours_ago + assert float(round(trade.calculate_interest(interest_rate=0.00025), 8) + ) == 2.3567152011391876e-06 # TODO: Fails with 0.08624233 + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.0037707443218227, + amount=91.99181073, + open_rate=0.00001099, + open_date=five_hours_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='kraken', + is_short=True, + leverage=5.0, + interest_rate=0.0005 + ) + + assert float(round(trade.calculate_interest(), 8) + ) == 9.42686080455675e-06 # TODO: Fails with 0.28747445 + trade.open_date = ten_minutes_ago + assert float(round(trade.calculate_interest(interest_rate=0.00025), 8)) == 3.7707443218227e-06 + + +@pytest.mark.usefixtures("init_persistence") +def test_interest_binance(market_leveraged_buy_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.00004099 base + close_rate: 0.00004173 base + stake_amount: 0.0037707443218227 + borrowed: 0.0075414886436454 + amount: + 91.99181073 * leverage(3) = 275.97543219 crypto + 91.99181073 * leverage(5) = 459.95905365 crypto + borrowed: + 0.0075414886436454 base + 0.0150829772872908 base + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + 5 hours = 5/24 + + interest: borrowed * interest_rate * time-periods + = 0.0075414886436454 * 0.0005 * 1/24 = 1.571143467426125e-07 base + = 0.0075414886436454 * 0.00025 * 5/24 = 3.9278586685653125e-07 base + = 0.0150829772872908 * 0.0005 * 5/24 = 1.571143467426125e-06 base + = 0.0150829772872908 * 0.00025 * 1/24 = 1.571143467426125e-07 base + """ + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=275.97543219, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + is_short=True, + borrowed=275.97543219, + interest_rate=0.0005 + ) + + assert float(round(trade.calculate_interest(), 8)) == 1.571143467426125e-07 + trade.open_date = five_hours_ago + assert float(round(trade.calculate_interest(interest_rate=0.00025), 8) + ) == 3.9278586685653125e-07 + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=459.95905365, + open_rate=0.00001099, + open_date=five_hours_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + is_short=True, + borrowed=459.95905365, + interest_rate=0.0005 + ) + + assert float(round(trade.calculate_interest(), 8)) == 1.571143467426125e-06 + trade.open_date = ten_minutes_ago + assert float(round(trade.calculate_interest(interest_rate=0.00025), 8)) == 1.571143467426125e-07 diff --git a/tests/test_persistence_short.py b/tests/test_persistence_short.py index b240de006..759b25a1a 100644 --- a/tests/test_persistence_short.py +++ b/tests/test_persistence_short.py @@ -10,7 +10,7 @@ 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 +from tests.conftest import create_mock_trades_with_leverage, log_has, log_has_re @pytest.mark.usefixtures("init_persistence") @@ -43,9 +43,6 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, ten = (0.0010646656050132426 - 0.0010025208853391716) / 0.0010673339398629 = 0.05822425142973869 - #Old - = 1-(0.0010025208853391716/0.0010646656050132426) - = 0.05837017687191848 """ trade = Trade( id=2, @@ -295,7 +292,7 @@ def test_calc_close_trade_price_exception(limit_short_order, fee): exchange='binance', interest_rate=0.0005, is_short=True, - leverage=3.0 + borrowed=15 ) trade.open_order_id = 'something' trade.update(limit_short_order) @@ -636,40 +633,41 @@ def test_adjust_stop_loss(fee): 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 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 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 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 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 + +@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_with_leverage(fee, use_db) + assert len(Trade.get_open_trades()) == 5 + Trade.use_db = True def test_stoploss_reinitialization(default_conf, fee): + # TODO-mg: I don't understand this at all, I was just going in the opposite direction as the matching function form test_persistance.py init_db(default_conf['db_url']) trade = Trade( pair='ETH/BTC', @@ -721,83 +719,26 @@ def test_stoploss_reinitialization(default_conf, fee): 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") +@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_with_leverage(fee, use_db) + res = Trade.total_open_trades_stakes() + assert res == 15.133 + Trade.use_db = True -# @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'}) +@pytest.mark.usefixtures("init_persistence") +def test_get_best_pair(fee): + res = Trade.get_best_pair() + assert res is None + create_mock_trades_with_leverage(fee) + res = Trade.get_best_pair() + assert len(res) == 2 + assert res[0] == 'ETC/BTC' + assert res[1] == 0.22626016260162551