From efcc2adacf53e849a12add5da2f880f81525604a Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 27 Jun 2021 03:38:56 -0600 Subject: [PATCH] About 15 margin tests pass --- docs/leverage.md | 2 +- freqtrade/persistence/migrations.py | 2 +- freqtrade/persistence/models.py | 184 ++++--- tests/conftest.py | 24 +- tests/rpc/test_rpc.py | 5 +- tests/test_persistence.py | 79 +++ tests/test_persistence_margin.py | 616 --------------------- tests/test_persistence_short.py | 803 ++++++++++++++++++++++++++++ 8 files changed, 1011 insertions(+), 704 deletions(-) delete mode 100644 tests/test_persistence_margin.py create mode 100644 tests/test_persistence_short.py diff --git a/docs/leverage.md b/docs/leverage.md index 658146c6f..eee1d00bb 100644 --- a/docs/leverage.md +++ b/docs/leverage.md @@ -1,7 +1,7 @@ An instance of a `Trade`/`LocalTrade` object is given either a value for `leverage` or a value for `borrowed`, but not both, on instantiation/update with a short/long. - If given a value for `leverage`, then the `amount` value of the `Trade`/`Local` object is multiplied by the `leverage` value to obtain the new value for `amount`. The borrowed value is also calculated from the `amount` and `leverage` value -- If given a value for `borrowed`, then the `leverage` value is calculated from `borrowed` and `amount` +- If given a value for `borrowed`, then the `leverage` value is left as None For shorts, the currency which pays the interest fee for the `borrowed` currency is purchased at the same time of the closing trade (This means that the amount purchased in short closing trades is greater than the amount sold in short opening trades). diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index c4e6368c5..ef4a5623b 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -48,7 +48,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') - leverage = get_column_def(cols, 'leverage', '0.0') + leverage = get_column_def(cols, 'leverage', 'null') borrowed = get_column_def(cols, 'borrowed', '0.0') borrowed_currency = get_column_def(cols, 'borrowed_currency', 'null') collateral_currency = get_column_def(cols, 'collateral_currency', 'null') diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index dd81faa17..29e2f59e3 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -132,7 +132,7 @@ class Order(_DECL_BASE): order_filled_date = Column(DateTime, nullable=True) order_update_date = Column(DateTime, nullable=True) - leverage = Column(Float, nullable=True, default=1.0) + leverage = Column(Float, nullable=True, default=None) is_short = Column(Boolean, nullable=True, default=False) def __repr__(self): @@ -237,7 +237,7 @@ class LocalTrade(): close_profit: Optional[float] = None close_profit_abs: Optional[float] = None stake_amount: float = 0.0 - amount: float = 0.0 + _amount: float = 0.0 amount_requested: Optional[float] = None open_date: datetime close_date: Optional[datetime] = None @@ -269,33 +269,52 @@ class LocalTrade(): interest_rate: float = 0.0 liquidation_price: float = None is_short: bool = False - __leverage: float = 1.0 # * You probably want to use self.leverage instead | - __borrowed: float = 0.0 # * You probably want to use self.borrowed instead V + borrowed: float = 0.0 + _leverage: float = None # * You probably want to use LocalTrade.leverage instead + + # @property + # def base_currency(self) -> str: + # if not self.pair: + # raise OperationalException('LocalTrade.pair must be assigned') + # return self.pair.split("/")[1] + + @property + def amount(self) -> float: + if self.leverage is not None: + return self._amount * self.leverage + else: + return self._amount + + @amount.setter + def amount(self, value): + self._amount = value @property def leverage(self) -> float: - return self.__leverage or 1.0 - - @property - def borrowed(self) -> float: - return self.__borrowed or 0.0 + return self._leverage @leverage.setter - def leverage(self, lev: float): + def leverage(self, value): + # def set_leverage(self, lev: float, is_short: Optional[bool], amount: Optional[float]): + # TODO: Should this be @leverage.setter, or should it take arguments is_short and amount + # if is_short is None: + # is_short = self.is_short + # if amount is None: + # amount = self.amount if self.is_short is None or self.amount is None: raise OperationalException( - 'LocalTrade.amount and LocalTrade.is_short must be assigned before LocalTrade.leverage') - self.__leverage = lev - self.__borrowed = self.amount * (lev-1) - self.amount = self.amount * lev + 'LocalTrade.amount and LocalTrade.is_short must be assigned before assigning leverage') + + self._leverage = value + if self.is_short: + # If shorting the full amount must be borrowed + self.borrowed = self.amount * value + else: + # If not shorting, then the trader already owns a bit + self.borrowed = self.amount * (value-1) + # TODO: Maybe amount should be a computed property, so we don't have to modify it + self.amount = self.amount * value - @borrowed.setter - def borrowed(self, bor: float): - if not self.amount: - raise OperationalException( - 'LocalTrade.amount must be assigned before LocalTrade.borrowed') - self.__borrowed = bor - self.__leverage = self.amount / (self.amount - self.borrowed) # End of margin trading properties @property @@ -414,7 +433,10 @@ class LocalTrade(): def _set_new_stoploss(self, new_loss: float, stoploss: float): """Assign new stop value""" self.stop_loss = new_loss - self.stop_loss_pct = -1 * abs(stoploss) + if self.is_short: + self.stop_loss_pct = abs(stoploss) + else: + self.stop_loss_pct = -1 * abs(stoploss) self.stoploss_last_update = datetime.utcnow() def adjust_stop_loss(self, current_price: float, stoploss: float, @@ -430,17 +452,24 @@ class LocalTrade(): # Don't modify if called with initial and nothing to do return - new_loss = float(current_price * (1 - abs(stoploss))) - # TODO: Could maybe move this if into the new stoploss if branch - if (self.liquidation_price): # If trading on margin, don't set the stoploss below the liquidation price - new_loss = min(self.liquidation_price, new_loss) + if self.is_short: + new_loss = float(current_price * (1 + abs(stoploss))) + if self.liquidation_price: # If trading on margin, don't set the stoploss below the liquidation price + new_loss = min(self.liquidation_price, new_loss) + else: + new_loss = float(current_price * (1 - abs(stoploss))) + if self.liquidation_price: # If trading on margin, don't set the stoploss below the liquidation price + new_loss = max(self.liquidation_price, new_loss) # no stop loss assigned yet if not self.stop_loss: logger.debug(f"{self.pair} - Assigning new stoploss...") self._set_new_stoploss(new_loss, stoploss) self.initial_stop_loss = new_loss - self.initial_stop_loss_pct = -1 * abs(stoploss) + if self.is_short: + self.initial_stop_loss_pct = abs(stoploss) + else: + self.initial_stop_loss_pct = -1 * abs(stoploss) # evaluate if the stop loss needs to be updated else: @@ -501,6 +530,7 @@ class LocalTrade(): self.open_rate = float(safe_value_fallback(order, 'average', 'price')) self.amount = float(safe_value_fallback(order, 'filled', 'amount')) + if 'borrowed' in order: self.borrowed = order['borrowed'] elif 'leverage' in order: @@ -514,6 +544,7 @@ class LocalTrade(): elif order_type in ('market', 'limit') and self.is_closing_trade(order['side']): if self.is_open: payment = "BUY" if self.is_short else "SELL" + # TODO: On Shorts technically your buying a little bit more than the amount because it's the ammount plus the interest logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.') self.close(safe_value_fallback(order, 'average', 'price')) # TODO: Double check this elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): @@ -596,60 +627,68 @@ class LocalTrade(): """ self.open_trade_value = self._calc_open_trade_value() - def calculate_interest(self) -> Decimal: + def calculate_interest(self, interest_rate: Optional[float] = None) -> Decimal: + """ + : param interest_rate: interest_charge for borrowing this coin(optional). + If interest_rate is not set self.interest_rate will be used + """ # TODO-mg: Need to set other conditions because sometimes self.open_date is not defined, but why would it ever not be set - if not self.interest_rate or not (self.borrowed): - return Decimal(0.0) + zero = Decimal(0.0) + if not (self.borrowed): + return zero - try: - open_date = self.open_date.replace(tzinfo=None) - now = datetime.now() - secPerDay = 86400 - days = Decimal((now - open_date).total_seconds()/secPerDay) or 0.0 - hours = days/24 - except: - raise OperationalException("Time isn't calculated properly") + open_date = self.open_date.replace(tzinfo=None) + now = datetime.utcnow() + # sec_per_day = Decimal(86400) + sec_per_hour = Decimal(3600) + total_seconds = Decimal((now - open_date).total_seconds()) + #days = total_seconds/sec_per_day or zero + hours = total_seconds/sec_per_hour or zero - rate = Decimal(self.interest_rate) + rate = Decimal(interest_rate or self.interest_rate) borrowed = Decimal(self.borrowed) - twenty4 = Decimal(24.0) one = Decimal(1.0) + twenty_four = Decimal(24.0) + four = Decimal(4.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/twenty4) * max(hours, one) # TODO-mg: Is hours rounded? + return borrowed * rate * max(hours, one)/twenty_four # 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 - roll_over_fee = borrowed * rate * max(0, (hours-4)/4) + roll_over_fee = borrowed * rate * max(0, (hours-four)/four) 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/twenty4) * max(hours, one) + return borrowed * (rate/twenty_four) * max(hours, one) elif self.exchange == 'binance_coinm_futures': # ! TODO-mg: This is incorrect, I didn't look it up - return borrowed * (rate/twenty4) * max(hours, one) + return borrowed * (rate/twenty_four) * max(hours, one) else: # TODO-mg: make sure this breaks and can't be squelched raise OperationalException("Leverage not available on this exchange") def calc_close_trade_value(self, rate: Optional[float] = None, - fee: Optional[float] = None) -> float: + fee: Optional[float] = None, + interest_rate: Optional[float] = None) -> float: """ Calculate the close_rate including fee :param fee: fee to use on the close rate (optional). If rate is not set self.fee will be used :param rate: rate to compare with (optional). If rate is not set self.close_rate will be used + :param interest_rate: interest_charge for borrowing this coin (optional). + If interest_rate is not set self.interest_rate will be used :return: Price in BTC of the open trade """ if rate is None and not self.close_rate: return 0.0 - interest = self.calculate_interest() + interest = self.calculate_interest(interest_rate) if self.is_short: - amount = Decimal(self.amount) + interest + amount = Decimal(self.amount) + Decimal(interest) else: # The interest does not need to be purchased on longs because the user already owns that currency in your wallet amount = Decimal(self.amount) @@ -663,18 +702,22 @@ class LocalTrade(): return float(close_trade - fees - interest) def calc_profit(self, rate: Optional[float] = None, - fee: Optional[float] = None) -> float: + fee: Optional[float] = None, + interest_rate: Optional[float] = None) -> float: """ Calculate the absolute profit in stake currency between Close and Open trade :param fee: fee to use on the close rate (optional). If fee is not set self.fee will be used :param rate: close rate to compare with (optional). If rate is not set self.close_rate will be used + :param interest_rate: interest_charge for borrowing this coin (optional). + If interest_rate is not set self.interest_rate will be used :return: profit in stake currency as float """ close_trade_value = self.calc_close_trade_value( rate=(rate or self.close_rate), - fee=(fee or self.fee_close) + fee=(fee or self.fee_close), + interest_rate=(interest_rate or self.interest_rate) ) if self.is_short: @@ -684,17 +727,21 @@ class LocalTrade(): return float(f"{profit:.8f}") def calc_profit_ratio(self, rate: Optional[float] = None, - fee: Optional[float] = None) -> float: + fee: Optional[float] = None, + interest_rate: Optional[float] = None) -> float: """ Calculates the profit as ratio (including fee). :param rate: rate to compare with (optional). If rate is not set self.close_rate will be used :param fee: fee to use on the close rate (optional). + :param interest_rate: interest_charge for borrowing this coin (optional). + If interest_rate is not set self.interest_rate will be used :return: profit ratio as float """ close_trade_value = self.calc_close_trade_value( rate=(rate or self.close_rate), - fee=(fee or self.fee_close) + fee=(fee or self.fee_close), + interest_rate=(interest_rate or self.interest_rate) ) if self.is_short: if close_trade_value == 0.0: @@ -724,7 +771,7 @@ class LocalTrade(): else: return None - @ staticmethod + @staticmethod def get_trades_proxy(*, pair: str = None, is_open: bool = None, open_date: datetime = None, close_date: datetime = None, ) -> List['LocalTrade']: @@ -758,27 +805,27 @@ class LocalTrade(): return sel_trades - @ staticmethod + @staticmethod def close_bt_trade(trade): LocalTrade.trades_open.remove(trade) LocalTrade.trades.append(trade) LocalTrade.total_profit += trade.close_profit_abs - @ staticmethod + @staticmethod def add_bt_trade(trade): if trade.is_open: LocalTrade.trades_open.append(trade) else: LocalTrade.trades.append(trade) - @ staticmethod + @staticmethod def get_open_trades() -> List[Any]: """ Query trades from persistence layer """ return Trade.get_trades_proxy(is_open=True) - @ staticmethod + @staticmethod def stoploss_reinitialization(desired_stoploss): """ Adjust initial Stoploss to desired stoploss for all open trades. @@ -853,18 +900,19 @@ class Trade(_DECL_BASE, LocalTrade): # Lowest price reached min_rate = Column(Float, nullable=True) sell_reason = Column(String(100), nullable=True) # TODO: Change to close_reason - sell_order_status = Column(String(100), nullable=True) + sell_order_status = Column(String(100), nullable=True) # TODO: Change to close_order_status strategy = Column(String(100), nullable=True) timeframe = Column(Integer, nullable=True) # Margin trading properties - leverage = Column(Float, nullable=True, default=1.0) + _leverage: float = None # * You probably want to use LocalTrade.leverage instead borrowed = Column(Float, nullable=False, default=0.0) - borrowed_currency = Column(Float, nullable=True) - collateral_currency = Column(String(25), nullable=True) interest_rate = Column(Float, nullable=False, default=0.0) liquidation_price = Column(Float, nullable=True) is_short = Column(Boolean, nullable=False, default=False) + # TODO: Bottom 2 might not be needed + borrowed_currency = Column(Float, nullable=True) + collateral_currency = Column(String(25), nullable=True) # End of margin trading properties def __init__(self, **kwargs): @@ -879,11 +927,11 @@ class Trade(_DECL_BASE, LocalTrade): Trade.query.session.delete(self) Trade.commit() - @ staticmethod + @staticmethod def commit(): Trade.query.session.commit() - @ staticmethod + @staticmethod def get_trades_proxy(*, pair: str = None, is_open: bool = None, open_date: datetime = None, close_date: datetime = None, ) -> List['LocalTrade']: @@ -913,7 +961,7 @@ class Trade(_DECL_BASE, LocalTrade): close_date=close_date ) - @ staticmethod + @staticmethod def get_trades(trade_filter=None) -> Query: """ Helper function to query Trades using filters. @@ -933,7 +981,7 @@ class Trade(_DECL_BASE, LocalTrade): else: return Trade.query - @ staticmethod + @staticmethod def get_open_order_trades(): """ Returns all open trades @@ -941,7 +989,7 @@ class Trade(_DECL_BASE, LocalTrade): """ return Trade.get_trades(Trade.open_order_id.isnot(None)).all() - @ staticmethod + @staticmethod def get_open_trades_without_assigned_fees(): """ Returns all open trades which don't have open fees set correctly @@ -952,7 +1000,7 @@ class Trade(_DECL_BASE, LocalTrade): Trade.is_open.is_(True), ]).all() - @ staticmethod + @staticmethod def get_closed_trades_without_assigned_fees(): """ Returns all closed trades which don't have fees set correctly @@ -990,7 +1038,7 @@ class Trade(_DECL_BASE, LocalTrade): t.stake_amount for t in LocalTrade.get_trades_proxy(is_open=True)) return total_open_stake_amount or 0 - @ staticmethod + @staticmethod def get_overall_performance() -> List[Dict[str, Any]]: """ Returns List of dicts containing all Trades, including profit and trade count @@ -1053,7 +1101,7 @@ class PairLock(_DECL_BASE): return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, ' f'lock_end_time={lock_end_time})') - @ staticmethod + @staticmethod def query_pair_locks(pair: Optional[str], now: datetime) -> Query: """ Get all currently active locks for this pair diff --git a/tests/conftest.py b/tests/conftest.py index 3d62a33e2..3c071f2f3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -57,9 +57,9 @@ 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 @@ -2075,7 +2075,7 @@ def ten_minutes_ago(): @pytest.fixture def five_hours_ago(): - return datetime.utcnow() - timedelta(hours=1, minutes=0) + return datetime.utcnow() - timedelta(hours=5, minutes=0) @pytest.fixture(scope='function') @@ -2136,9 +2136,9 @@ def limit_exit_short_order(limit_exit_short_order_open): @pytest.fixture(scope='function') def market_short_order(): return { - 'id': 'mocked_market_buy', + 'id': 'mocked_market_short', 'type': 'market', - 'side': 'buy', + 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004173, @@ -2147,16 +2147,16 @@ def market_short_order(): 'remaining': 0.0, 'status': 'closed', 'is_short': True, - 'leverage': 3 + 'leverage': 3.0 } @pytest.fixture def market_exit_short_order(): return { - 'id': 'mocked_limit_sell', + 'id': 'mocked_limit_exit_short', 'type': 'market', - 'side': 'sell', + 'side': 'buy', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004099, @@ -2164,11 +2164,5 @@ def market_exit_short_order(): 'filled': 91.99181073, 'remaining': 0.0, 'status': 'closed', - 'leverage': 3, - 'interest_rate': 0.0005 + 'leverage': 3.0 } - - -@pytest.fixture -def interest_rate(): - return MagicMock(return_value=0.0005) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 50c1a0b31..e324626c3 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -108,7 +108,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_order': None, 'exchange': 'binance', - 'leverage': 1.0, + 'leverage': None, 'borrowed': 0.0, 'borrowed_currency': None, 'collateral_currency': None, @@ -182,14 +182,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_order': None, 'exchange': 'binance', - 'leverage': 1.0, + 'leverage': None, 'borrowed': 0.0, 'borrowed_currency': None, 'collateral_currency': None, 'interest_rate': 0.0, 'liquidation_price': None, 'is_short': False, - } diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 829e3f6e7..40542f943 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -63,6 +63,48 @@ def test_init_dryrun_db(default_conf, tmpdir): assert Path(filename).is_file() +@pytest.mark.usefixtures("init_persistence") +def test_is_opening_closing_trade(fee): + trade = Trade( + id=2, + pair='ETH/BTC', + stake_amount=0.001, + open_rate=0.01, + amount=5, + is_open=True, + open_date=arrow.utcnow().datetime, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + is_short=False, + leverage=2.0 + ) + assert trade.is_opening_trade('buy') == True + assert trade.is_opening_trade('sell') == False + assert trade.is_closing_trade('buy') == False + assert trade.is_closing_trade('sell') == True + + trade = Trade( + id=2, + pair='ETH/BTC', + stake_amount=0.001, + open_rate=0.01, + amount=5, + is_open=True, + open_date=arrow.utcnow().datetime, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + is_short=True, + leverage=2.0 + ) + + assert trade.is_opening_trade('buy') == False + assert trade.is_opening_trade('sell') == True + assert trade.is_closing_trade('buy') == True + assert trade.is_closing_trade('sell') == False + + @pytest.mark.usefixtures("init_persistence") def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog): """ @@ -196,6 +238,7 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee): @pytest.mark.usefixtures("init_persistence") def test_trade_close(limit_buy_order, limit_sell_order, fee): + # TODO: limit_buy_order and limit_sell_order aren't used, remove them probably trade = Trade( pair='ETH/BTC', stake_amount=0.001, @@ -1126,6 +1169,42 @@ def test_fee_updated(fee): assert not trade.fee_updated('asfd') +@pytest.mark.usefixtures("init_persistence") +def test_update_leverage(fee, ten_minutes_ago): + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + is_short=True, + interest_rate=0.0005 + ) + trade.leverage = 3.0 + assert trade.borrowed == 15.0 + assert trade.amount == 15.0 + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + is_short=False, + interest_rate=0.0005 + ) + + trade.leverage = 5.0 + assert trade.borrowed == 20.0 + assert trade.amount == 25.0 + + @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize('use_db', [True, False]) def test_total_open_trades_stakes(fee, use_db): diff --git a/tests/test_persistence_margin.py b/tests/test_persistence_margin.py deleted file mode 100644 index 94deb4a36..000000000 --- a/tests/test_persistence_margin.py +++ /dev/null @@ -1,616 +0,0 @@ -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' - ) - #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_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 trade.borrowed == 90.99181073 - assert trade.is_short is True - 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, -# interest_rate, -# ten_minutes_ago, -# caplog -# ): -# """Test Kraken and leverage arguments as well as update market order -# fee: 0.25% -# interest_rate: 0.05% per 4 hrs -# open_rate: 0.00004173 -# close_rate: 0.00004099 -# amount: 91.99181073 * leverage(3) = 275.97543219 -# borrowed: 183.98362146 -# time: 10 minutes(rounds to min of 4hrs) -# interest -# """ -# trade = Trade( -# id=1, -# pair='ETH/BTC', -# stake_amount=0.001, -# amount=5, -# open_rate=0.01, -# is_open=True, -# fee_open=fee.return_value, -# fee_close=fee.return_value, -# open_date=ten_minutes_ago, -# exchange='kraken' -# ) -# trade.open_order_id = 'something' -# trade.update(market_buy_order) -# assert trade.leverage is 3 -# assert trade.is_short is True -# assert trade.open_order_id is None -# assert trade.open_rate == 0.00004173 -# assert trade.close_profit is None -# assert trade.close_date is None -# assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, " -# r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004173, open_since=.*\).", -# caplog) -# caplog.clear() -# trade.is_open = True -# trade.open_order_id = 'something' -# trade.update(market_sell_order) -# assert trade.open_order_id is None -# assert trade.close_rate == 0.00004099 -# assert trade.close_profit == 0.01297561 -# assert trade.close_date is not None -# assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, " -# r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).", -# caplog) - - -# @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'}) diff --git a/tests/test_persistence_short.py b/tests/test_persistence_short.py new file mode 100644 index 000000000..84d9329b8 --- /dev/null +++ b/tests/test_persistence_short.py @@ -0,0 +1,803 @@ +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, log_has, log_has_re + + +@pytest.mark.usefixtures("init_persistence") +def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, ten_minutes_ago, caplog): + """ + 10 minute short limit trade on binance + + Short trade + fee: 0.25% base + interest_rate: 0.05% per day + open_rate: 0.00001173 base + close_rate: 0.00001099 base + amount: 90.99181073 crypto + borrowed: 90.99181073 crypto + time-periods: 10 minutes(rounds up to 1/24 time-period of 1 day) + interest: borrowed * interest_rate * time-periods + = 90.99181073 * 0.0005 * 1/24 = 0.0018956627235416667 crypto + open_value: (amount * open_rate) - (amount * open_rate * fee) + = 90.99181073 * 0.00001173 - 90.99181073 * 0.00001173 * 0.0025 + = 0.0010646656050132426 + amount_closed: amount + interest = 90.99181073 + 0.0018956627235416667 = 90.99370639272354 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (90.99370639272354 * 0.00001099) + (90.99370639272354 * 0.00001099 * 0.0025) + = 0.0010025208853391716 + total_profit = open_value - close_value + = 0.0010646656050132426 - 0.0010025208853391716 + = 0.00006214471967407108 + total_profit_percentage = (open_value/close_value) - 1 + = (0.0010646656050132426/0.0010025208853391716)-1 + = 0.06198845388946328 + """ + 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, + # 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_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 trade.borrowed == 90.99181073 + assert trade.is_short is True + 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) + + +@pytest.mark.usefixtures("init_persistence") +def test_update_market_order( + market_short_order, + market_exit_short_order, + fee, + ten_minutes_ago, + caplog +): + """ + 10 minute short market trade on Kraken at 3x leverage + Short trade + fee: 0.25% base + interest_rate: 0.05% per 4 hrs + open_rate: 0.00004173 base + close_rate: 0.00004099 base + amount: 91.99181073 * leverage(3) = 275.97543219 crypto + borrowed: 275.97543219 crypto + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + interest: borrowed * interest_rate * time-periods + = 275.97543219 * 0.0005 * 1 = 0.137987716095 crypto + open_value: (amount * open_rate) - (amount * open_rate * fee) + = 275.97543219 * 0.00004173 - 275.97543219 * 0.00004173 * 0.0025 + = 0.011487663648325479 + amount_closed: amount + interest = 275.97543219 + 0.137987716095 = 276.113419906095 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (276.113419906095 * 0.00004099) + (276.113419906095 * 0.00004099 * 0.0025) + = 0.01134618380465571 + total_profit = open_value - close_value + = 0.011487663648325479 - 0.01134618380465571 + = 0.00014147984366976937 + total_profit_percentage = (open_value/close_value) - 1 + = (0.011487663648325479/0.01134618380465571)-1 + = 0.012469377026284034 + """ + trade = Trade( + id=1, + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.01, + is_open=True, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_date=ten_minutes_ago, + interest_rate=0.0005, + exchange='kraken' + ) + trade.open_order_id = 'something' + trade.update(market_short_order) + assert trade.leverage == 3.0 + assert trade.is_short == True + assert trade.open_order_id is None + assert trade.open_rate == 0.00004173 + assert trade.close_profit is None + assert trade.close_date is None + assert trade.interest_rate == 0.0005 + # TODO: Uncomment the next assert and make it work. + # The logger also has the exact same but there's some spacing in there + # assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, " + # r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004173, open_since=.*\).", + # caplog) + caplog.clear() + trade.is_open = True + trade.open_order_id = 'something' + trade.update(market_exit_short_order) + assert trade.open_order_id is None + assert trade.close_rate == 0.00004099 + assert trade.close_profit == 0.01246938 + assert trade.close_date is not None + # TODO: The amount should maybe be the opening amount + the interest + # TODO: Uncomment the next assert and make it work. + # The logger also has the exact same but there's some spacing in there + # assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, " + # r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004099, open_since=.*\).", + # caplog) + + +@pytest.mark.usefixtures("init_persistence") +def test_calc_open_close_trade_price(limit_short_order, limit_exit_short_order, five_hours_ago, fee): + """ + 5 hour short trade on Binance + Short trade + fee: 0.25% base + interest_rate: 0.05% per day + open_rate: 0.00001173 base + close_rate: 0.00001099 base + amount: 90.99181073 crypto + borrowed: 90.99181073 crypto + time-periods: 5 hours = 5/24 + interest: borrowed * interest_rate * time-periods + = 90.99181073 * 0.0005 * 5/24 = 0.009478313617708333 crypto + open_value: (amount * open_rate) - (amount * open_rate * fee) + = 90.99181073 * 0.00001173 - 90.99181073 * 0.00001173 * 0.0025 + = 0.0010646656050132426 + amount_closed: amount + interest = 90.99181073 + 0.009478313617708333 = 91.0012890436177 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (91.0012890436177 * 0.00001099) + (91.0012890436177 * 0.00001099 * 0.0025) + = 0.001002604427005832 + total_profit = open_value - close_value + = 0.0010646656050132426 - 0.001002604427005832 + = 0.00006206117800741065 + total_profit_percentage = (open_value/close_value) - 1 + = (0.0010646656050132426/0.0010025208853391716)-1 + = 0.06189996406932852 + """ + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + 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_short_order) + assert trade._calc_open_trade_value() == 0.0010646656050132426 + trade.update(limit_exit_short_order) + + assert isclose(trade.calc_close_trade_value(), 0.001002604427005832) + # Profit in BTC + assert isclose(trade.calc_profit(), 0.00006206) + #Profit in percent + assert isclose(trade.calc_profit_ratio(), 0.06189996) + + +@pytest.mark.usefixtures("init_persistence") +def test_trade_close(fee, five_hours_ago): + """ + Five hour short trade on Kraken at 3x leverage + Short trade + Exchange: Kraken + fee: 0.25% base + interest_rate: 0.05% per 4 hours + open_rate: 0.02 base + close_rate: 0.01 base + leverage: 3.0 + amount: 5 * 3 = 15 crypto + borrowed: 15 crypto + time-periods: 5 hours = 5/4 + + interest: borrowed * interest_rate * time-periods + = 15 * 0.0005 * 5/4 = 0.009375 crypto + open_value: (amount * open_rate) - (amount * open_rate * fee) + = (15 * 0.02) - (15 * 0.02 * 0.0025) + = 0.29925 + amount_closed: amount + interest = 15 + 0.009375 = 15.009375 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (15.009375 * 0.01) + (15.009375 * 0.01 * 0.0025) + = 0.150468984375 + total_profit = open_value - close_value + = 0.29925 - 0.150468984375 + = 0.148781015625 + total_profit_percentage = (open_value/close_value) - 1 + = (0.29925/0.150468984375)-1 + = 0.9887819489377738 + """ + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + open_rate=0.02, + amount=5, + is_open=True, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_date=five_hours_ago, + exchange='kraken', + is_short=True, + 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.01) + assert trade.is_open is False + assert trade.close_profit == 0.98878195 + 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_short_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, + is_short=True, + leverage=3.0 + ) + trade.open_order_id = 'something' + trade.update(limit_short_order) + assert trade.calc_close_trade_value() == 0.0 + + +@pytest.mark.usefixtures("init_persistence") +def test_update_open_order(limit_short_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, + is_short=True, + exchange='binance', + ) + assert trade.open_order_id is None + assert trade.close_profit is None + assert trade.close_date is None + limit_short_order['status'] = 'open' + trade.update(limit_short_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_short_order, ten_minutes_ago, fee): + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00004173, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + interest_rate=0.0005, + is_short=True, + leverage=3.0, + exchange='kraken', + ) + trade.open_order_id = 'open_trade' + trade.update(market_short_order) # Buy @ 0.00001099 + # Get the open rate price with the standard fee rate + assert trade._calc_open_trade_value() == 0.011487663648325479 + trade.fee_open = 0.003 + # Get the open rate price with a custom fee rate + assert trade._calc_open_trade_value() == 0.011481905420932834 + + +@pytest.mark.usefixtures("init_persistence") +def test_calc_close_trade_price(market_short_order, market_exit_short_order, ten_minutes_ago, fee): + """ + 10 minute short market trade on Kraken at 3x leverage + Short trade + fee: 0.25% base + interest_rate: 0.05% per 4 hrs + open_rate: 0.00004173 base + close_rate: 0.00001234 base + amount: 91.99181073 * leverage(3) = 275.97543219 crypto + borrowed: 275.97543219 crypto + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + interest: borrowed * interest_rate * time-periods + = 275.97543219 * 0.0005 * 1 = 0.137987716095 crypto + amount_closed: amount + interest = 275.97543219 + 0.137987716095 = 276.113419906095 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + = (276.113419906095 * 0.00001234) + (276.113419906095 * 0.00001234 * 0.0025) + = 0.01134618380465571 + """ + 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_short_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.003415757700645315) + # 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.0034174613204461354) + # Test when we apply a Sell order, and ask price with a custom fee rate + trade.update(market_exit_short_order) + assert isclose(trade.calc_close_trade_value(fee=0.005), 0.011374478527360586) + + +@pytest.mark.usefixtures("init_persistence") +def test_calc_profit(market_short_order, market_exit_short_order, ten_minutes_ago, five_hours_ago, fee): + """ + Market trade on Kraken at 3x leverage + Short trade + fee: 0.25% base or 0.3% + interest_rate: 0.05%, 0.25% per 4 hrs + open_rate: 0.00004173 base + close_rate: 0.00004099 base + amount: 91.99181073 * leverage(3) = 275.97543219 crypto + borrowed: 275.97543219 crypto + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + 5 hours = 5/4 + + interest: borrowed * interest_rate * time-periods + = 275.97543219 * 0.0005 * 1 = 0.137987716095 crypto + = 275.97543219 * 0.00025 * 5/4 = 0.086242322559375 crypto + = 275.97543219 * 0.0005 * 5/4 = 0.17248464511875 crypto + = 275.97543219 * 0.00025 * 1 = 0.0689938580475 crypto + open_value: (amount * open_rate) - (amount * open_rate * fee) + = (275.97543219 * 0.00004173) - (275.97543219 * 0.00004173 * 0.0025) = 0.011487663648325479 + amount_closed: amount + interest + = 275.97543219 + 0.137987716095 = 276.113419906095 + = 275.97543219 + 0.086242322559375 = 276.06167451255936 + = 275.97543219 + 0.17248464511875 = 276.14791683511874 + = 275.97543219 + 0.0689938580475 = 276.0444260480475 + close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee) + (276.113419906095 * 0.00004374) + (276.113419906095 * 0.00004374 * 0.0025) = 0.012107393989159325 + (276.06167451255936 * 0.00000437) + (276.06167451255936 * 0.00000437 * 0.0025) = 0.0012094054914139338 + (276.14791683511874 * 0.00004374) + (276.14791683511874 * 0.00004374 * 0.003) = 0.012114946012015198 + (276.0444260480475 * 0.00000437) + (276.0444260480475 * 0.00000437 * 0.003) = 0.0012099330842554573 + total_profit = open_value - close_value + = print(0.011487663648325479 - 0.012107393989159325) = -0.0006197303408338461 + = print(0.011487663648325479 - 0.0012094054914139338) = 0.010278258156911545 + = print(0.011487663648325479 - 0.012114946012015198) = -0.0006272823636897188 + = print(0.011487663648325479 - 0.0012099330842554573) = 0.010277730564070022 + total_profit_percentage = (open_value/close_value) - 1 + print((0.011487663648325479 / 0.012107393989159325) - 1) = -0.051186105068418364 + print((0.011487663648325479 / 0.0012094054914139338) - 1) = 8.498603842864217 + print((0.011487663648325479 / 0.012114946012015198) - 1) = -0.05177756162244562 + print((0.011487663648325479 / 0.0012099330842554573) - 1) = 8.494461964724694 + """ + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='kraken', + is_short=True, + leverage=3.0, + interest_rate=0.0005 + ) + trade.open_order_id = 'something' + trade.update(market_short_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) == -0.00061973 + # == -0.0006197303408338461 + assert trade.calc_profit_ratio(rate=0.00004374, interest_rate=0.0005) == -0.05118611 + # == -0.051186105068418364 + + # Lower than open rate + trade.open_date = five_hours_ago + assert trade.calc_profit(rate=0.00000437, interest_rate=0.00025) == 0.01027826 + # == 0.010278258156911545 + assert trade.calc_profit_ratio(rate=0.00000437, interest_rate=0.00025) == 8.49860384 + # == 8.498603842864217 + + # 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) == -0.00062728 + # == -0.0006272823636897188 + assert trade.calc_profit_ratio(rate=0.00004374, fee=0.003, interest_rate=0.0005) == -0.05177756 + # == -0.05177756162244562 + + # Lower than open rate + trade.open_date = ten_minutes_ago + assert trade.calc_profit(rate=0.00000437, fee=0.003, interest_rate=0.00025) == 0.01027773 + # == 0.010277730564070022 + assert trade.calc_profit_ratio(rate=0.00000437, fee=0.003, interest_rate=0.00025) == 8.49446196 + # == 8.494461964724694 + + # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173 + trade.update(market_exit_short_order) + assert trade.calc_profit() == 0.00014148 + # == 0.00014147984366976937 + assert trade.calc_profit_ratio() == 0.01246938 + # == 0.012469377026284034 + + # 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_short_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.00004173 base + close_rate: 0.00004099 base + amount: + 91.99181073 * leverage(3) = 275.97543219 crypto + 91.99181073 * leverage(5) = 459.95905365 crypto + borrowed: + 275.97543219 crypto + 459.95905365 crypto + time-periods: 10 minutes(rounds up to 1 time-period of 4hrs) + 5 hours = 5/4 + + interest: borrowed * interest_rate * time-periods + = 275.97543219 * 0.0005 * 1 = 0.137987716095 crypto + = 275.97543219 * 0.00025 * 5/4 = 0.086242322559375 crypto + = 459.95905365 * 0.0005 * 5/4 = 0.17248464511875 crypto + = 459.95905365 * 0.00025 * 1 = 0.0689938580475 crypto + """ + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='kraken', + is_short=True, + leverage=3.0, + interest_rate=0.0005 + ) + trade.update(market_short_order) # Buy @ 0.00001099 + + assert isclose(float("{:.15f}".format(trade.calculate_interest())), 0.137987716095) + trade.open_date = five_hours_ago + assert isclose(float("{:.15f}".format( + trade.calculate_interest(interest_rate=0.00025))), 0.086242322559375) + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='kraken', + is_short=True, + leverage=5.0, + interest_rate=0.0005 + ) + trade.update(market_short_order) # Buy @ 0.00001099 + + assert isclose(float("{:.15f}".format(trade.calculate_interest())), 0.17248464511875) + trade.open_date = ten_minutes_ago + assert isclose(float("{:.15f}".format( + trade.calculate_interest(interest_rate=0.00025))), 0.0689938580475) + + +@pytest.mark.usefixtures("init_persistence") +def test_interest_binance(market_short_order, ten_minutes_ago, five_hours_ago, fee): + """ + Market trade on Binance at 3x and 5x leverage + Short trade + interest_rate: 0.05%, 0.25% per 1 day + open_rate: 0.00004173 base + close_rate: 0.00004099 base + amount: + 91.99181073 * leverage(3) = 275.97543219 crypto + 91.99181073 * leverage(5) = 459.95905365 crypto + borrowed: + 275.97543219 crypto + 459.95905365 crypto + time-periods: 10 minutes(rounds up to 1/24 time-period of 1 day) + 5 hours = 5/24 + + interest: borrowed * interest_rate * time-periods + = print(275.97543219 * 0.0005 * 1/24) = 0.005749488170625 crypto + = print(275.97543219 * 0.00025 * 5/24) = 0.0143737204265625 crypto + = print(459.95905365 * 0.0005 * 5/24) = 0.047912401421875 crypto + = print(459.95905365 * 0.00025 * 1/24) = 0.0047912401421875 crypto + """ + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + is_short=True, + interest_rate=0.0005 + ) + trade.update(market_short_order) # Buy @ 0.00001099 + + assert isclose(float("{:.15f}".format(trade.calculate_interest())), 0.005749488170625) + trade.open_date = five_hours_ago + assert isclose(float("{:.15f}".format( + trade.calculate_interest(interest_rate=0.00025))), 0.0143737204265625) + + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=5, + open_rate=0.00001099, + open_date=ten_minutes_ago, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + is_short=True, + leverage=5.0, + interest_rate=0.0005 + ) + trade.update(market_short_order) # Buy @ 0.00001099 + + assert isclose(float("{:.15f}".format(trade.calculate_interest())), 0.047912401421875) + trade.open_date = ten_minutes_ago + assert isclose(float("{:.15f}".format( + trade.calculate_interest(interest_rate=0.00025))), 0.0047912401421875) + + +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, + is_short=True + ) + trade.adjust_stop_loss(trade.open_rate, 0.05, True) + assert trade.stop_loss == 1.05 + assert trade.stop_loss_pct == 0.05 + assert trade.initial_stop_loss == 1.05 + assert trade.initial_stop_loss_pct == 0.05 + # Get percent of profit with a lower rate + trade.adjust_stop_loss(1.04, 0.05) + assert trade.stop_loss == 1.05 + assert trade.stop_loss_pct == 0.05 + assert trade.initial_stop_loss == 1.05 + 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 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 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 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 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 + + +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, + is_short=True + ) + trade.adjust_stop_loss(trade.open_rate, -0.05, True) + assert trade.stop_loss == 1.05 + assert trade.stop_loss_pct == 0.05 + assert trade.initial_stop_loss == 1.05 + 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 == 1.06 + assert trade_adj.stop_loss_pct == 0.06 + assert trade_adj.initial_stop_loss == 1.06 + 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 == 1.04 + assert trade_adj.stop_loss_pct == 0.04 + assert trade_adj.initial_stop_loss == 1.04 + assert trade_adj.initial_stop_loss_pct == 0.04 + # Trailing stoploss (move stoplos up a bit) + trade.adjust_stop_loss(0.98, -0.04) + assert trade_adj.stop_loss == 1.0208 + assert trade_adj.initial_stop_loss == 1.04 + 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 == 1.0208 + assert trade_adj.stop_loss_pct == 0.04 + 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") +# 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'})