Started some pytests for short and leverage

1 short test passes
This commit is contained in:
Sam Germain 2021-06-22 22:26:10 -06:00
parent b80f8ca0af
commit c24ec89dc4
5 changed files with 809 additions and 138 deletions

View File

@ -133,6 +133,7 @@ class Order(_DECL_BASE):
order_update_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=1.0)
is_short = Column(Boolean, nullable=False, default=False)
def __repr__(self): def __repr__(self):
return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, ' return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, '
@ -447,14 +448,16 @@ class LocalTrade():
Determines if the trade is an opening (long buy or short sell) trade Determines if the trade is an opening (long buy or short sell) trade
:param side (string): the side (buy/sell) that order happens on :param side (string): the side (buy/sell) that order happens on
""" """
return (side == 'buy' and not self.is_short) or (side == 'sell' and self.is_short) is_short = self.is_short
return (side == 'buy' and not is_short) or (side == 'sell' and is_short)
def is_closing_trade(self, side) -> bool: def is_closing_trade(self, side) -> bool:
""" """
Determines if the trade is an closing (long sell or short buy) trade Determines if the trade is an closing (long sell or short buy) trade
:param side (string): the side (buy/sell) that order happens on :param side (string): the side (buy/sell) that order happens on
""" """
return (side == 'sell' and not self.is_short) or (side == 'buy' and self.is_short) is_short = self.is_short
return (side == 'sell' and not is_short) or (side == 'buy' and is_short)
def update(self, order: Dict) -> None: def update(self, order: Dict) -> None:
""" """
@ -463,6 +466,9 @@ class LocalTrade():
:return: None :return: None
""" """
order_type = order['type'] order_type = order['type']
# TODO: I don't like this, but it might be the only way
if 'is_short' in order and order['side'] == 'sell':
self.is_short = order['is_short']
# Ignore open and cancelled orders # Ignore open and cancelled orders
if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None: if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None:
return return
@ -579,11 +585,13 @@ class LocalTrade():
rate = Decimal(self.interest_rate) rate = Decimal(self.interest_rate)
borrowed = Decimal(self.borrowed) borrowed = Decimal(self.borrowed)
twenty4 = Decimal(24.0)
one = Decimal(1.0)
if self.exchange == 'binance': if self.exchange == 'binance':
# Rate is per day but accrued hourly or something # Rate is per day but accrued hourly or something
# binance: https://www.binance.com/en-AU/support/faq/360030157812 # binance: https://www.binance.com/en-AU/support/faq/360030157812
return borrowed * (rate/24) * max(hours, 1.0) # TODO-mg: Is hours rounded? return borrowed * (rate/twenty4) * max(hours, one) # TODO-mg: Is hours rounded?
elif self.exchange == 'kraken': elif self.exchange == 'kraken':
# https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading- # https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading-
opening_fee = borrowed * rate opening_fee = borrowed * rate
@ -591,10 +599,10 @@ class LocalTrade():
return opening_fee + roll_over_fee return opening_fee + roll_over_fee
elif self.exchange == 'binance_usdm_futures': elif self.exchange == 'binance_usdm_futures':
# ! TODO-mg: This is incorrect, I didn't look it up # ! TODO-mg: This is incorrect, I didn't look it up
return borrowed * (rate/24) * max(hours, 1.0) return borrowed * (rate/twenty4) * max(hours, one)
elif self.exchange == 'binance_coinm_futures': elif self.exchange == 'binance_coinm_futures':
# ! TODO-mg: This is incorrect, I didn't look it up # ! TODO-mg: This is incorrect, I didn't look it up
return borrowed * (rate/24) * max(hours, 1.0) return borrowed * (rate/twenty4) * max(hours, one)
else: else:
# TODO-mg: make sure this breaks and can't be squelched # TODO-mg: make sure this breaks and can't be squelched
raise OperationalException("Leverage not available on this exchange") raise OperationalException("Leverage not available on this exchange")
@ -612,14 +620,19 @@ class LocalTrade():
if rate is None and not self.close_rate: if rate is None and not self.close_rate:
return 0.0 return 0.0
close_trade = Decimal(self.amount) * Decimal(rate or self.close_rate) # type: ignore
fees = close_trade * Decimal(fee or self.fee_close)
interest = self.calculate_interest() interest = self.calculate_interest()
if self.is_short:
amount = Decimal(self.amount) + interest
else:
amount = Decimal(self.amount) - interest
close_trade = Decimal(amount) * Decimal(rate or self.close_rate) # type: ignore
fees = close_trade * Decimal(fee or self.fee_close)
if (self.is_short): if (self.is_short):
return float(close_trade + fees + interest) return float(close_trade + fees)
else: else:
return float(close_trade - fees - interest) return float(close_trade - fees)
def calc_profit(self, rate: Optional[float] = None, def calc_profit(self, rate: Optional[float] = None,
fee: Optional[float] = None) -> float: fee: Optional[float] = None) -> float:

View File

@ -7,12 +7,10 @@ from datetime import datetime, timedelta
from functools import reduce from functools import reduce
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock, Mock, PropertyMock from unittest.mock import MagicMock, Mock, PropertyMock
import arrow import arrow
import numpy as np import numpy as np
import pytest import pytest
from telegram import Chat, Message, Update from telegram import Chat, Message, Update
from freqtrade import constants from freqtrade import constants
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.data.converter import ohlcv_to_dataframe
@ -24,12 +22,8 @@ from freqtrade.persistence import LocalTrade, Trade, init_db
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver
from freqtrade.worker import Worker from freqtrade.worker import Worker
from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4, from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4,
mock_trade_5, mock_trade_6) mock_trade_5, mock_trade_6, short_trade, leverage_trade)
logging.getLogger('').setLevel(logging.INFO) logging.getLogger('').setLevel(logging.INFO)
# Do not mask numpy errors as warnings that no one read, raise the exсeption # Do not mask numpy errors as warnings that no one read, raise the exсeption
np.seterr(all='raise') np.seterr(all='raise')
@ -63,13 +57,12 @@ def log_has_re(line, logs):
def get_args(args): def get_args(args):
return Arguments(args).get_parsed_arg() return Arguments(args).get_parsed_arg()
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
def get_mock_coro(return_value): def get_mock_coro(return_value):
async def mock_coro(*args, **kwargs): async def mock_coro(*args, **kwargs):
return return_value return return_value
return Mock(wraps=mock_coro) return Mock(wraps=mock_coro)
@ -92,7 +85,6 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No
if mock_markets: if mock_markets:
mocker.patch('freqtrade.exchange.Exchange.markets', mocker.patch('freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=get_markets())) PropertyMock(return_value=get_markets()))
if api_mock: if api_mock:
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
else: else:
@ -126,7 +118,6 @@ def patch_edge(mocker) -> None:
# "LTC/BTC", # "LTC/BTC",
# "XRP/BTC", # "XRP/BTC",
# "NEO/BTC" # "NEO/BTC"
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={ return_value={
'NEO/BTC': PairInfo(-0.20, 0.66, 3.71, 0.50, 1.71, 10, 25), 'NEO/BTC': PairInfo(-0.20, 0.66, 3.71, 0.50, 1.71, 10, 25),
@ -140,7 +131,6 @@ def get_patched_edge(mocker, config) -> Edge:
patch_edge(mocker) patch_edge(mocker)
edge = Edge(config) edge = Edge(config)
return edge return edge
# Functions for recurrent object patching # Functions for recurrent object patching
@ -201,28 +191,24 @@ def create_mock_trades(fee, use_db: bool = True):
Trade.query.session.add(trade) Trade.query.session.add(trade)
else: else:
LocalTrade.add_bt_trade(trade) LocalTrade.add_bt_trade(trade)
# Simulate dry_run entries # Simulate dry_run entries
trade = mock_trade_1(fee) trade = mock_trade_1(fee)
add_trade(trade) add_trade(trade)
trade = mock_trade_2(fee) trade = mock_trade_2(fee)
add_trade(trade) add_trade(trade)
trade = mock_trade_3(fee) trade = mock_trade_3(fee)
add_trade(trade) add_trade(trade)
trade = mock_trade_4(fee) trade = mock_trade_4(fee)
add_trade(trade) add_trade(trade)
trade = mock_trade_5(fee) trade = mock_trade_5(fee)
add_trade(trade) add_trade(trade)
trade = mock_trade_6(fee) trade = mock_trade_6(fee)
add_trade(trade) add_trade(trade)
# TODO: margin trades
# TODO-mg: Add margin trades # trade = short_trade(fee)
# add_trade(trade)
# trade = leverage_trade(fee)
# add_trade(trade)
if use_db: if use_db:
Trade.query.session.flush() Trade.query.session.flush()
@ -234,7 +220,6 @@ def patch_coingekko(mocker) -> None:
:param mocker: mocker to patch coingekko class :param mocker: mocker to patch coingekko class
:return: None :return: None
""" """
tickermock = MagicMock(return_value={'bitcoin': {'usd': 12345.0}, 'ethereum': {'usd': 12345.0}}) tickermock = MagicMock(return_value={'bitcoin': {'usd': 12345.0}, 'ethereum': {'usd': 12345.0}})
listmock = MagicMock(return_value=[{'id': 'bitcoin', 'name': 'Bitcoin', 'symbol': 'btc', listmock = MagicMock(return_value=[{'id': 'bitcoin', 'name': 'Bitcoin', 'symbol': 'btc',
'website_slug': 'bitcoin'}, 'website_slug': 'bitcoin'},
@ -245,14 +230,13 @@ def patch_coingekko(mocker) -> None:
'freqtrade.rpc.fiat_convert.CoinGeckoAPI', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_price=tickermock, get_price=tickermock,
get_coins_list=listmock, get_coins_list=listmock,
) )
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def init_persistence(default_conf): def init_persistence(default_conf):
init_db(default_conf['db_url'], default_conf['dry_run']) init_db(default_conf['db_url'], default_conf['dry_run'])
# TODO-mg: margin with leverage and/or borrowed? # TODO-mg: trade with leverage and/or borrowed?
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
@ -943,7 +927,6 @@ def limit_buy_order_canceled_empty(request):
# Indirect fixture # Indirect fixture
# Documentation: # Documentation:
# https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments # https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments
exchange_name = request.param exchange_name = request.param
if exchange_name == 'ftx': if exchange_name == 'ftx':
return { return {
@ -1733,7 +1716,6 @@ def edge_conf(default_conf):
"max_trade_duration_minute": 1440, "max_trade_duration_minute": 1440,
"remove_pumps": False "remove_pumps": False
} }
return conf return conf
@ -1791,12 +1773,9 @@ def import_fails() -> None:
if name in ["filelock", 'systemd.journal', 'uvloop']: if name in ["filelock", 'systemd.journal', 'uvloop']:
raise ImportError(f"No module named '{name}'") raise ImportError(f"No module named '{name}'")
return realimport(name, *args, **kwargs) return realimport(name, *args, **kwargs)
builtins.__import__ = mockedimport builtins.__import__ = mockedimport
# Run test - then cleanup # Run test - then cleanup
yield yield
# restore previous importfunction # restore previous importfunction
builtins.__import__ = realimport builtins.__import__ = realimport
@ -2081,101 +2060,79 @@ def saved_hyperopt_results():
'is_best': False 'is_best': False
} }
] ]
for res in hyperopt_res: for res in hyperopt_res:
res['results_metrics']['holding_avg_s'] = res['results_metrics']['holding_avg' res['results_metrics']['holding_avg_s'] = res['results_metrics']['holding_avg'
].total_seconds() ].total_seconds()
return hyperopt_res return hyperopt_res
# * Margin Tests # * Margin Tests
@pytest.fixture @pytest.fixture
def leveraged_fee(): def ten_minutes_ago():
return return datetime.utcnow() - timedelta(hours=0, minutes=10)
@pytest.fixture @pytest.fixture
def short_fee(): def five_hours_ago():
return return datetime.utcnow() - timedelta(hours=1, minutes=0)
@pytest.fixture
def ticker_short():
return
@pytest.fixture
def ticker_exit_short_up():
return
@pytest.fixture
def ticker_exit_short_down():
return
@pytest.fixture
def leveraged_markets():
return
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def limit_short_order_open(): def limit_short_order_open():
return return {
'id': 'mocked_limit_short',
'type': 'limit',
@pytest.fixture(scope='function') 'side': 'sell',
def limit_short_order(limit_short_order_open): 'symbol': 'mocked',
return 'datetime': arrow.utcnow().isoformat(),
'timestamp': arrow.utcnow().int_timestamp,
'price': 0.00001173,
@pytest.fixture(scope='function') 'amount': 90.99181073,
def market_short_order(): 'borrowed': 90.99181073,
return 'filled': 0.0,
'cost': 0.00106733393,
'remaining': 90.99181073,
@pytest.fixture 'status': 'open',
def market_short_exit_order(): 'is_short': True
return }
@pytest.fixture
def limit_short_order_old():
return
@pytest.fixture
def limit_exit_short_order_old():
return
@pytest.fixture
def limit_short_order_old_partial():
return
@pytest.fixture
def limit_short_order_old_partial_canceled(limit_short_order_old_partial):
return
@pytest.fixture(scope='function')
def limit_short_order_canceled_empty(request):
return
@pytest.fixture @pytest.fixture
def limit_exit_short_order_open(): def limit_exit_short_order_open():
return return {
'id': 'mocked_limit_exit_short',
'type': 'limit',
'side': 'buy',
'pair': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'timestamp': arrow.utcnow().int_timestamp,
'price': 0.00001099,
'amount': 90.99181073,
'filled': 0.0,
'remaining': 90.99181073,
'status': 'open'
}
@pytest.fixture(scope='function')
def limit_short_order(limit_short_order_open):
order = deepcopy(limit_short_order_open)
order['status'] = 'closed'
order['filled'] = order['amount']
order['remaining'] = 0.0
return order
@pytest.fixture @pytest.fixture
def limit_exit_short_order(limit_sell_order_open): def limit_exit_short_order(limit_exit_short_order_open):
return order = deepcopy(limit_exit_short_order_open)
order['remaining'] = 0.0
order['filled'] = order['amount']
order['status'] = 'closed'
return order
@pytest.fixture @pytest.fixture
def short_order_fee(): def interest_rate():
return return MagicMock(return_value=0.0005)

View File

@ -304,4 +304,133 @@ def mock_trade_6(fee):
trade.orders.append(o) trade.orders.append(o)
return trade return trade
# TODO-mg: Mock orders for leveraged and short trades
#! TODO Currently the following short_trade test and leverage_trade test will fail
def short_order():
return {
'id': '1235',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'sell',
'type': 'limit',
'price': 0.123,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
'leverage': 5.0,
'isShort': True
}
def exit_short_order():
return {
'id': '12366',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.128,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
'leverage': 5.0,
'isShort': True
}
def short_trade(fee):
"""
Closed trade...
"""
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0, # TODO-mg: In BTC?
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',
is_open=False,
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),
# borrowed=
isShort=True
)
o = Order.parse_from_ccxt_object(short_order(), 'ETC/BTC', 'sell')
trade.orders.append(o)
o = Order.parse_from_ccxt_object(exit_short_order(), 'ETC/BTC', 'sell')
trade.orders.append(o)
return trade
def leverage_order():
return {
'id': '1235',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.123,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
'leverage': 5.0
}
def leverage_order_sell():
return {
'id': '12366',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'sell',
'type': 'limit',
'price': 0.128,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
'leverage': 5.0,
'isShort': True
}
def leverage_trade(fee):
"""
Closed trade...
"""
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=615.0,
amount_requested=615.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',
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),
# borrowed=
)
o = Order.parse_from_ccxt_object(leverage_order(), 'ETC/BTC', 'sell')
trade.orders.append(o)
o = Order.parse_from_ccxt_object(leverage_order_sell(), 'ETC/BTC', 'sell')
trade.orders.append(o)
return trade

View File

@ -129,9 +129,6 @@ def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog):
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).", r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).",
caplog) caplog)
# TODO-mg: create a short order
# TODO-mg: create a leveraged long order
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_update_market_order(market_buy_order, market_sell_order, fee, caplog): def test_update_market_order(market_buy_order, market_sell_order, fee, caplog):
@ -170,9 +167,6 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog):
r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).", r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).",
caplog) caplog)
# TODO-mg: market short
# TODO-mg: market leveraged long
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee): def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
@ -665,13 +659,11 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
order_date DATETIME, order_date DATETIME,
order_filled_date DATETIME, order_filled_date DATETIME,
order_update_date DATETIME, order_update_date DATETIME,
leverage FLOAT,
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id), CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id),
FOREIGN KEY(ft_trade_id) REFERENCES trades (id) FOREIGN KEY(ft_trade_id) REFERENCES trades (id)
) )
""")) """))
# TODO-mg: Had to add field leverage to this table, check that this is correct
connection.execute(text(""" connection.execute(text("""
insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
@ -920,14 +912,6 @@ def test_to_json(default_conf, fee):
'strategy': None, 'strategy': None,
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': None,
'borrowed': None,
'borrowed_currency': None,
'collateral_currency': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
} }
# Simulate dry_run entries # Simulate dry_run entries
@ -993,14 +977,6 @@ def test_to_json(default_conf, fee):
'strategy': None, 'strategy': None,
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': None,
'borrowed': None,
'borrowed_currency': None,
'collateral_currency': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
} }
@ -1339,7 +1315,7 @@ def test_Trade_object_idem():
'get_overall_performance', 'get_overall_performance',
'get_total_closed_profit', 'get_total_closed_profit',
'total_open_trades_stakes', 'total_open_trades_stakes',
'get_closed_trades_without_assigned_fees', 'get_sold_trades_without_assigned_fees',
'get_open_trades_without_assigned_fees', 'get_open_trades_without_assigned_fees',
'get_open_order_trades', 'get_open_order_trades',
'get_trades', 'get_trades',

View File

@ -0,0 +1,596 @@
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 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
# * Margin tests
@pytest.mark.usefixtures("init_persistence")
def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, interest_rate, ten_minutes_ago, caplog):
"""
On this test we will short and buy back(exit short) a crypto currency at 1x leverage
#*The actual program uses more precise numbers
Short
- 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))
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
(90.99181073 + 0.00189566272) * 0.00001099 = 0.00100002083 :(borrowed + interest * cost)
+ ((90.99181073 + 0.00189566272)*0.00001099)*0.0025 = 0.00000250005
= 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.061988453889463014104555743 With more precise numbers used
:param limit_short_order:
:param limit_exit_short_order:
:param fee
:param interest_rate
:param caplog
:return:
"""
trade = Trade(
id=2,
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.01,
amount=5,
is_open=True,
open_date=ten_minutes_ago,
fee_open=fee.return_value,
fee_close=fee.return_value,
interest_rate=interest_rate.return_value,
borrowed=90.99181073,
exchange='binance',
is_short=True
)
#assert trade.open_order_id is None
assert trade.close_profit is None
assert trade.close_date is None
#trade.open_order_id = 'something'
trade.update(limit_short_order)
#assert trade.open_order_id is None
assert trade.open_rate == 0.00001173
assert trade.close_profit is None
assert trade.close_date is None
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).",
caplog)
caplog.clear()
#trade.open_order_id = 'something'
trade.update(limit_exit_short_order)
#assert trade.open_order_id is None
assert trade.close_rate == 0.00001099
assert trade.close_profit == 0.06198845
assert trade.close_date is not None
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
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_buy_order, market_sell_order, fee, caplog):
# 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=arrow.utcnow().datetime,
# exchange='binance',
# )
# trade.open_order_id = 'something'
# trade.update(market_buy_order)
# 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 log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, "
# r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, 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.00004173
# 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: market short
# # TODO-mg: market leveraged long
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# open_rate=0.01,
# amount=5,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# )
# trade.open_order_id = 'something'
# trade.update(limit_buy_order)
# assert trade._calc_open_trade_value() == 0.0010024999999225068
# trade.update(limit_sell_order)
# assert trade.calc_close_trade_value() == 0.0010646656050132426
# # Profit in BTC
# assert trade.calc_profit() == 0.00006217
# # Profit in percent
# assert trade.calc_profit_ratio() == 0.06201058
# @pytest.mark.usefixtures("init_persistence")
# def test_trade_close(limit_buy_order, limit_sell_order, fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# open_rate=0.01,
# amount=5,
# is_open=True,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# open_date=arrow.Arrow(2020, 2, 1, 15, 5, 1).datetime,
# exchange='binance',
# )
# 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 == 0.99002494
# assert trade.close_date is not None
# 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_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',
# )
# trade.open_order_id = 'something'
# trade.update(limit_buy_order)
# assert trade.calc_close_trade_value() == 0.0
# @pytest.mark.usefixtures("init_persistence")
# def test_update_open_order(limit_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,
# exchange='binance',
# )
# assert trade.open_order_id is None
# assert trade.close_profit is None
# assert trade.close_date is None
# limit_buy_order['status'] = 'open'
# trade.update(limit_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(limit_buy_order, fee):
# 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,
# exchange='binance',
# )
# trade.open_order_id = 'open_trade'
# trade.update(limit_buy_order) # Buy @ 0.00001099
# # Get the open rate price with the standard fee rate
# assert trade._calc_open_trade_value() == 0.0010024999999225068
# trade.fee_open = 0.003
# # Get the open rate price with a custom fee rate
# assert trade._calc_open_trade_value() == 0.001002999999922468
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
# 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,
# exchange='binance',
# )
# trade.open_order_id = 'close_trade'
# trade.update(limit_buy_order) # Buy @ 0.00001099
# # Get the close rate price with a custom close rate and a regular fee rate
# assert trade.calc_close_trade_value(rate=0.00001234) == 0.0011200318470471794
# # Get the close rate price with a custom close rate and a custom fee rate
# assert trade.calc_close_trade_value(rate=0.00001234, fee=0.003) == 0.0011194704275749754
# # Test when we apply a Sell order, and ask price with a custom fee rate
# trade.update(limit_sell_order)
# assert trade.calc_close_trade_value(fee=0.005) == 0.0010619972701635854
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_profit(limit_buy_order, limit_sell_order, fee):
# 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,
# exchange='binance',
# )
# trade.open_order_id = 'something'
# trade.update(limit_buy_order) # Buy @ 0.00001099
# # Custom closing rate and regular fee rate
# # Higher than open rate
# assert trade.calc_profit(rate=0.00001234) == 0.00011753
# # Lower than open rate
# assert trade.calc_profit(rate=0.00000123) == -0.00089086
# # Custom closing rate and custom fee rate
# # Higher than open rate
# assert trade.calc_profit(rate=0.00001234, fee=0.003) == 0.00011697
# # Lower than open rate
# assert trade.calc_profit(rate=0.00000123, fee=0.003) == -0.00089092
# # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173
# trade.update(limit_sell_order)
# assert trade.calc_profit() == 0.00006217
# # Test with a custom fee rate on the close trade
# assert trade.calc_profit(fee=0.003) == 0.00006163
# @pytest.mark.usefixtures("init_persistence")
# def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee):
# 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,
# exchange='binance',
# )
# trade.open_order_id = 'something'
# trade.update(limit_buy_order) # Buy @ 0.00001099
# # Get percent of profit with a custom rate (Higher than open rate)
# assert trade.calc_profit_ratio(rate=0.00001234) == 0.11723875
# # Get percent of profit with a custom rate (Lower than open rate)
# assert trade.calc_profit_ratio(rate=0.00000123) == -0.88863828
# # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173
# trade.update(limit_sell_order)
# assert trade.calc_profit_ratio() == 0.06201058
# # Test with a custom fee rate on the close trade
# assert trade.calc_profit_ratio(fee=0.003) == 0.06147824
# trade.open_trade_value = 0.0
# assert trade.calc_profit_ratio(fee=0.003) == 0.0
# def test_adjust_stop_loss(fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# max_rate=1,
# )
# trade.adjust_stop_loss(trade.open_rate, 0.05, True)
# assert trade.stop_loss == 0.95
# assert trade.stop_loss_pct == -0.05
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # Get percent of profit with a lower rate
# trade.adjust_stop_loss(0.96, 0.05)
# assert trade.stop_loss == 0.95
# assert trade.stop_loss_pct == -0.05
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # Get percent of profit with a custom rate (Higher than open rate)
# trade.adjust_stop_loss(1.3, -0.1)
# assert round(trade.stop_loss, 8) == 1.17
# assert trade.stop_loss_pct == -0.1
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # current rate lower again ... should not change
# trade.adjust_stop_loss(1.2, 0.1)
# assert round(trade.stop_loss, 8) == 1.17
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # current rate higher... should raise stoploss
# trade.adjust_stop_loss(1.4, 0.1)
# assert round(trade.stop_loss, 8) == 1.26
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# # Initial is true but stop_loss set - so doesn't do anything
# trade.adjust_stop_loss(1.7, 0.1, True)
# assert round(trade.stop_loss, 8) == 1.26
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# assert trade.stop_loss_pct == -0.1
# def test_adjust_min_max_rates(fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# amount=5,
# fee_open=fee.return_value,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# )
# trade.adjust_min_max_rates(trade.open_rate)
# assert trade.max_rate == 1
# assert trade.min_rate == 1
# # check min adjusted, max remained
# trade.adjust_min_max_rates(0.96)
# assert trade.max_rate == 1
# assert trade.min_rate == 0.96
# # check max adjusted, min remains
# trade.adjust_min_max_rates(1.05)
# assert trade.max_rate == 1.05
# assert trade.min_rate == 0.96
# # current rate "in the middle" - no adjustment
# trade.adjust_min_max_rates(1.03)
# assert trade.max_rate == 1.05
# assert trade.min_rate == 0.96
# @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
# def test_stoploss_reinitialization(default_conf, fee):
# init_db(default_conf['db_url'])
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# fee_open=fee.return_value,
# open_date=arrow.utcnow().shift(hours=-2).datetime,
# amount=10,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# max_rate=1,
# )
# trade.adjust_stop_loss(trade.open_rate, 0.05, True)
# assert trade.stop_loss == 0.95
# assert trade.stop_loss_pct == -0.05
# assert trade.initial_stop_loss == 0.95
# assert trade.initial_stop_loss_pct == -0.05
# Trade.query.session.add(trade)
# # Lower stoploss
# Trade.stoploss_reinitialization(0.06)
# trades = Trade.get_open_trades()
# assert len(trades) == 1
# trade_adj = trades[0]
# assert trade_adj.stop_loss == 0.94
# assert trade_adj.stop_loss_pct == -0.06
# assert trade_adj.initial_stop_loss == 0.94
# assert trade_adj.initial_stop_loss_pct == -0.06
# # Raise stoploss
# Trade.stoploss_reinitialization(0.04)
# trades = Trade.get_open_trades()
# assert len(trades) == 1
# trade_adj = trades[0]
# assert trade_adj.stop_loss == 0.96
# assert trade_adj.stop_loss_pct == -0.04
# assert trade_adj.initial_stop_loss == 0.96
# assert trade_adj.initial_stop_loss_pct == -0.04
# # Trailing stoploss (move stoplos up a bit)
# trade.adjust_stop_loss(1.02, 0.04)
# assert trade_adj.stop_loss == 0.9792
# assert trade_adj.initial_stop_loss == 0.96
# Trade.stoploss_reinitialization(0.04)
# trades = Trade.get_open_trades()
# assert len(trades) == 1
# trade_adj = trades[0]
# # Stoploss should not change in this case.
# assert trade_adj.stop_loss == 0.9792
# assert trade_adj.stop_loss_pct == -0.04
# assert trade_adj.initial_stop_loss == 0.96
# assert trade_adj.initial_stop_loss_pct == -0.04
# def test_update_fee(fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# fee_open=fee.return_value,
# open_date=arrow.utcnow().shift(hours=-2).datetime,
# amount=10,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# max_rate=1,
# )
# fee_cost = 0.15
# fee_currency = 'BTC'
# fee_rate = 0.0075
# assert trade.fee_open_currency is None
# assert not trade.fee_updated('buy')
# assert not trade.fee_updated('sell')
# trade.update_fee(fee_cost, fee_currency, fee_rate, 'buy')
# assert trade.fee_updated('buy')
# assert not trade.fee_updated('sell')
# assert trade.fee_open_currency == fee_currency
# assert trade.fee_open_cost == fee_cost
# assert trade.fee_open == fee_rate
# # Setting buy rate should "guess" close rate
# assert trade.fee_close == fee_rate
# assert trade.fee_close_currency is None
# assert trade.fee_close_cost is None
# fee_rate = 0.0076
# trade.update_fee(fee_cost, fee_currency, fee_rate, 'sell')
# assert trade.fee_updated('buy')
# assert trade.fee_updated('sell')
# assert trade.fee_close == 0.0076
# assert trade.fee_close_cost == fee_cost
# assert trade.fee_close == fee_rate
# def test_fee_updated(fee):
# trade = Trade(
# pair='ETH/BTC',
# stake_amount=0.001,
# fee_open=fee.return_value,
# open_date=arrow.utcnow().shift(hours=-2).datetime,
# amount=10,
# fee_close=fee.return_value,
# exchange='binance',
# open_rate=1,
# max_rate=1,
# )
# assert trade.fee_open_currency is None
# assert not trade.fee_updated('buy')
# assert not trade.fee_updated('sell')
# assert not trade.fee_updated('asdf')
# trade.update_fee(0.15, 'BTC', 0.0075, 'buy')
# assert trade.fee_updated('buy')
# assert not trade.fee_updated('sell')
# assert trade.fee_open_currency is not None
# assert trade.fee_close_currency is None
# trade.update_fee(0.15, 'ABC', 0.0075, 'sell')
# assert trade.fee_updated('buy')
# assert trade.fee_updated('sell')
# assert not trade.fee_updated('asfd')
# @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")
# 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'})