diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 26503f8c6..811b7d1f8 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -133,6 +133,7 @@ class Order(_DECL_BASE): order_update_date = Column(DateTime, nullable=True) leverage = Column(Float, nullable=True, default=1.0) + is_short = Column(Boolean, nullable=False, default=False) def __repr__(self): 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 :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: """ Determines if the trade is an closing (long sell or short buy) trade :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: """ @@ -463,6 +466,9 @@ class LocalTrade(): :return: None """ 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 if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None: return @@ -579,11 +585,13 @@ class LocalTrade(): rate = Decimal(self.interest_rate) borrowed = Decimal(self.borrowed) + twenty4 = Decimal(24.0) + one = Decimal(1.0) if self.exchange == 'binance': # Rate is per day but accrued hourly or something # 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': # https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading- opening_fee = borrowed * rate @@ -591,10 +599,10 @@ class LocalTrade(): return opening_fee + roll_over_fee elif self.exchange == 'binance_usdm_futures': # ! 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': # ! 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: # TODO-mg: make sure this breaks and can't be squelched raise OperationalException("Leverage not available on this exchange") @@ -612,14 +620,19 @@ class LocalTrade(): if rate is None and not self.close_rate: 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() + 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): - return float(close_trade + fees + interest) + return float(close_trade + fees) else: - return float(close_trade - fees - interest) + return float(close_trade - fees) def calc_profit(self, rate: Optional[float] = None, fee: Optional[float] = None) -> float: diff --git a/tests/conftest.py b/tests/conftest.py index 6fb6b6c0a..a78dd2bc2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,12 +7,10 @@ from datetime import datetime, timedelta from functools import reduce from pathlib import Path from unittest.mock import MagicMock, Mock, PropertyMock - import arrow import numpy as np import pytest from telegram import Chat, Message, Update - from freqtrade import constants from freqtrade.commands import Arguments 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.worker import Worker 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) - - # Do not mask numpy errors as warnings that no one read, raise the exсeption np.seterr(all='raise') @@ -63,13 +57,12 @@ def log_has_re(line, logs): def get_args(args): return Arguments(args).get_parsed_arg() - - # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines + + def get_mock_coro(return_value): async def mock_coro(*args, **kwargs): return return_value - 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: mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=get_markets())) - if api_mock: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) else: @@ -126,7 +118,6 @@ def patch_edge(mocker) -> None: # "LTC/BTC", # "XRP/BTC", # "NEO/BTC" - mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( return_value={ '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) edge = Edge(config) return edge - # Functions for recurrent object patching @@ -201,28 +191,24 @@ def create_mock_trades(fee, use_db: bool = True): 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) - - # TODO-mg: Add margin trades - + # TODO: margin trades + # trade = short_trade(fee) + # add_trade(trade) + # trade = leverage_trade(fee) + # add_trade(trade) if use_db: Trade.query.session.flush() @@ -234,7 +220,6 @@ def patch_coingekko(mocker) -> None: :param mocker: mocker to patch coingekko class :return: None """ - tickermock = MagicMock(return_value={'bitcoin': {'usd': 12345.0}, 'ethereum': {'usd': 12345.0}}) listmock = MagicMock(return_value=[{'id': 'bitcoin', 'name': 'Bitcoin', 'symbol': 'btc', 'website_slug': 'bitcoin'}, @@ -245,14 +230,13 @@ def patch_coingekko(mocker) -> None: 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', get_price=tickermock, get_coins_list=listmock, - ) @pytest.fixture(scope='function') def init_persistence(default_conf): 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") @@ -943,7 +927,6 @@ def limit_buy_order_canceled_empty(request): # Indirect fixture # Documentation: # https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments - exchange_name = request.param if exchange_name == 'ftx': return { @@ -1733,7 +1716,6 @@ def edge_conf(default_conf): "max_trade_duration_minute": 1440, "remove_pumps": False } - return conf @@ -1791,12 +1773,9 @@ def import_fails() -> None: if name in ["filelock", 'systemd.journal', 'uvloop']: raise ImportError(f"No module named '{name}'") return realimport(name, *args, **kwargs) - builtins.__import__ = mockedimport - # Run test - then cleanup yield - # restore previous importfunction builtins.__import__ = realimport @@ -2081,101 +2060,79 @@ def saved_hyperopt_results(): 'is_best': False } ] - for res in hyperopt_res: res['results_metrics']['holding_avg_s'] = res['results_metrics']['holding_avg' ].total_seconds() - return hyperopt_res - # * Margin Tests + @pytest.fixture -def leveraged_fee(): - return +def ten_minutes_ago(): + return datetime.utcnow() - timedelta(hours=0, minutes=10) @pytest.fixture -def short_fee(): - return - - -@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 +def five_hours_ago(): + return datetime.utcnow() - timedelta(hours=1, minutes=0) @pytest.fixture(scope='function') def limit_short_order_open(): - return - - -@pytest.fixture(scope='function') -def limit_short_order(limit_short_order_open): - return - - -@pytest.fixture(scope='function') -def market_short_order(): - return - - -@pytest.fixture -def market_short_exit_order(): - 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 + return { + 'id': 'mocked_limit_short', + 'type': 'limit', + 'side': 'sell', + 'symbol': 'mocked', + 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, + 'price': 0.00001173, + 'amount': 90.99181073, + 'borrowed': 90.99181073, + 'filled': 0.0, + 'cost': 0.00106733393, + 'remaining': 90.99181073, + 'status': 'open', + 'is_short': True + } @pytest.fixture 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 -def limit_exit_short_order(limit_sell_order_open): - return +def limit_exit_short_order(limit_exit_short_order_open): + order = deepcopy(limit_exit_short_order_open) + order['remaining'] = 0.0 + order['filled'] = order['amount'] + order['status'] = 'closed' + return order @pytest.fixture -def short_order_fee(): - return +def interest_rate(): + return MagicMock(return_value=0.0005) diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index de856a98d..2aa1d6b4c 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -304,4 +304,133 @@ def mock_trade_6(fee): trade.orders.append(o) 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 diff --git a/tests/test_persistence.py b/tests/test_persistence.py index e9441136b..f4494e967 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -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=.*\).", caplog) - # TODO-mg: create a short order - # 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): @@ -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=.*\).", 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): @@ -665,13 +659,11 @@ def test_migrate_new(mocker, default_conf, fee, caplog): order_date DATETIME, order_filled_date DATETIME, order_update_date DATETIME, - leverage FLOAT, PRIMARY KEY (id), CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_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(""" 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, 'timeframe': None, '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 @@ -993,14 +977,6 @@ def test_to_json(default_conf, fee): 'strategy': None, 'timeframe': None, '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_total_closed_profit', 'total_open_trades_stakes', - 'get_closed_trades_without_assigned_fees', + 'get_sold_trades_without_assigned_fees', 'get_open_trades_without_assigned_fees', 'get_open_order_trades', 'get_trades', diff --git a/tests/test_persistence_margin.py b/tests/test_persistence_margin.py new file mode 100644 index 000000000..d31bde590 --- /dev/null +++ b/tests/test_persistence_margin.py @@ -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'})