From d613d63fdcea629c42a5a5dead54aa365f08cdd2 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Sun, 17 Dec 2017 13:07:56 -0800 Subject: [PATCH] Fix the fee calculation --- freqtrade/exchange/bittrex.py | 2 +- freqtrade/main.py | 13 +-- freqtrade/optimize/backtesting.py | 4 +- freqtrade/persistence.py | 69 ++++++++++-- freqtrade/rpc/telegram.py | 42 ++++---- freqtrade/tests/conftest.py | 33 ++++-- freqtrade/tests/test_main.py | 27 ++--- freqtrade/tests/test_persistence.py | 155 +++++++++++++++++++++++++-- freqtrade/tests/test_rpc_telegram.py | 82 ++++++++++---- 9 files changed, 344 insertions(+), 83 deletions(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index 1a0a035a9..841cad57e 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -51,7 +51,7 @@ class Bittrex(Exchange): @property def fee(self) -> float: # See https://bittrex.com/fees - return 0.0025 + return 0.0025 #0.25% def buy(self, pair: str, rate: float, amount: float) -> str: data = _API.buy_limit(pair.replace('_', '-'), amount, rate) diff --git a/freqtrade/main.py b/freqtrade/main.py index 941d6870a..212e9f0e9 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -118,13 +118,14 @@ def execute_sell(trade: Trade, limit: float) -> None: order_id = exchange.sell(str(trade.pair), limit, trade.amount) trade.open_order_id = order_id - fmt_exp_profit = round(trade.calc_profit(limit) * 100, 2) - rpc.send_msg('*{}:* Selling [{}]({}) with limit `{:.8f} (profit: ~{:.2f}%)`'.format( + fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2) + rpc.send_msg('*{}:* Selling [{}]({}) with limit `{:.8f} (profit: ~{:.2f}%, {:.8f})`'.format( trade.exchange, trade.pair.replace('_', '/'), exchange.get_pair_detail_url(trade.pair), limit, - fmt_exp_profit + fmt_exp_profit, + trade.calc_profit(rate=limit), )) Trade.session.flush() @@ -134,7 +135,7 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) - Based an earlier trade and current price and ROI configuration, decides whether bot should sell :return True if bot should sell at current rate """ - current_profit = trade.calc_profit(current_rate) + current_profit = trade.calc_profit_percent(current_rate) if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']): logger.debug('Stop loss hit.') return True @@ -145,7 +146,7 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) - if time_diff > float(duration) and current_profit > threshold: return True - logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', current_profit * 100.0) + logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', float(current_profit) * 100.0) return False @@ -233,7 +234,7 @@ def create_trade(stake_amount: float) -> bool: pair=pair, stake_amount=stake_amount, amount=amount, - fee=exchange.get_fee() * 2, + fee=exchange.get_fee(), open_rate=buy_limit, open_date=datetime.utcnow(), exchange=exchange.get_name().upper(), diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index dd7bf160c..52fdce1cc 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -99,7 +99,7 @@ def backtest(config: Dict, processed: Dict[str, DataFrame], open_rate=row.close, open_date=row.date, amount=config['stake_amount'], - fee=exchange.get_fee() * 2 + fee=exchange.get_fee() ) # calculate win/lose forwards from buy point @@ -109,7 +109,7 @@ def backtest(config: Dict, processed: Dict[str, DataFrame], trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1 if min_roi_reached(trade, row2.close, row2.date) or row2.sell == 1: - current_profit = trade.calc_profit(row2.close) + current_profit = trade.calc_profit_percent(row2.close) lock_pair_until = row2.Index trades.append((pair, current_profit, row2.Index - row.Index)) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ba0ad1785..ed2cd2029 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -92,10 +92,12 @@ class Trade(_DECL_BASE): return logger.info('Updating trade (id=%d) ...', self.id) + + getcontext().prec = 8 # Bittrex do not go above 8 decimal if order['type'] == 'LIMIT_BUY': # Update open rate and actual amount - self.open_rate = order['rate'] - self.amount = order['amount'] + self.open_rate = Decimal(order['rate']) + self.amount = Decimal(order['amount']) logger.info('LIMIT_BUY has been fulfilled for %s.', self) self.open_order_id = None elif order['type'] == 'LIMIT_SELL': @@ -109,8 +111,8 @@ class Trade(_DECL_BASE): Sets close_rate to the given rate, calculates total profit and marks trade as closed """ - self.close_rate = rate - self.close_profit = self.calc_profit() + self.close_rate = Decimal(rate) + self.close_profit = self.calc_profit_percent() self.close_date = datetime.utcnow() self.is_open = False self.open_order_id = None @@ -119,7 +121,54 @@ class Trade(_DECL_BASE): self ) - def calc_profit(self, rate: Optional[float] = None) -> float: + def calc_open_trade_price(self, fee: Optional[float] = None) -> float: + """ + Calculate the open_rate in BTC + :param fee: fee to use on the open rate (optional). + If rate is not set self.fee will be used + :return: Price in BTC of the open trade + """ + getcontext().prec = 8 + + buy_trade = (Decimal(self.amount) * Decimal(self.open_rate)) + fees = buy_trade * Decimal(fee or self.fee) + return float(buy_trade + fees) + + def calc_close_trade_price(self, rate: Optional[float] = None, fee: Optional[float] = None) -> float: + """ + Calculate the close_rate in BTC + :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 + :return: Price in BTC of the open trade + """ + getcontext().prec = 8 + + if rate is None and not self.close_rate: + return 0.0 + + sell_trade = (Decimal(self.amount) * Decimal(rate or self.close_rate)) + fees = sell_trade * Decimal(fee or self.fee) + return float(sell_trade - fees) + + def calc_profit(self, rate: Optional[float] = None, fee: Optional[float] = None) -> float: + """ + Calculate the profit in BTC between Close and Open trade + :param fee: fee to use on the close rate (optional). + If rate 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 + :return: profit in BTC as float + """ + open_trade_price = self.calc_open_trade_price() + close_trade_price = self.calc_close_trade_price( + rate=Decimal(rate or self.close_rate), + fee=Decimal(fee or self.fee) + ) + return float("{0:.8f}".format(close_trade_price - open_trade_price)) + + def calc_profit_percent(self, rate: Optional[float] = None, fee: Optional[float] = None) -> float: """ Calculates the profit in percentage (including fee). :param rate: rate to compare with (optional). @@ -127,5 +176,11 @@ class Trade(_DECL_BASE): :return: profit in percentage as float """ getcontext().prec = 8 - return float((Decimal(rate or self.close_rate) - Decimal(self.open_rate)) - / Decimal(self.open_rate) - Decimal(self.fee)) + + open_trade_price = self.calc_open_trade_price() + close_trade_price = self.calc_close_trade_price( + rate=Decimal(rate or self.close_rate), + fee=Decimal(fee or self.fee) + ) + + return float("{0:.8f}".format((close_trade_price / open_trade_price) - 1)) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index a097e45f4..b2e2d85dc 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1,7 +1,7 @@ import logging import re -from datetime import timedelta, date from decimal import Decimal +from datetime import timedelta, date, datetime from typing import Callable, Any import arrow @@ -139,7 +139,7 @@ def _status(bot: Bot, update: Update) -> None: order = exchange.get_order(trade.open_order_id) # calculate profit and send message to user current_rate = exchange.get_ticker(trade.pair)['bid'] - current_profit = trade.calc_profit(current_rate) + current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = '{:.2f}%'.format( round(trade.close_profit * 100, 2) ) if trade.close_profit else None @@ -196,7 +196,7 @@ def _status_table(bot: Bot, update: Update) -> None: trade.id, trade.pair, shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), - '{:.2f}'.format(100 * trade.calc_profit(current_rate)) + '{:.2f}'.format(100 * trade.calc_profit_percent(current_rate)) ]) columns = ['ID', 'Pair', 'Since', 'Profit'] @@ -218,7 +218,7 @@ def _daily(bot: Bot, update: Update) -> None: :param update: message update :return: None """ - today = date.today().toordinal() + today = datetime.utcnow().toordinal() profit_days = {} try: @@ -234,9 +234,13 @@ def _daily(bot: Bot, update: Update) -> None: # need to query between day+1 and day-1 nextdate = date.fromordinal(today-day+1) prevdate = date.fromordinal(today-day-1) - trades = Trade.query.filter(between(Trade.close_date, prevdate, nextdate)).all() - curdayprofit = sum(trade.close_profit * trade.stake_amount for trade in trades) - profit_days[date.fromordinal(today-day)] = format(curdayprofit, '.8f') + trades = Trade.query \ + .filter(Trade.is_open.is_(False)) \ + .filter(between(Trade.close_date, prevdate, nextdate)) \ + .order_by(Trade.close_date)\ + .all() + curdayprofit = sum(trade.calc_profit() for trade in trades) + profit_days[date.fromordinal(today - day)] = format(curdayprofit, '.8f') stats = [[key, str(value) + ' BTC'] for key, value in profit_days.items()] stats = tabulate(stats, headers=['Day', 'Profit'], tablefmt='simple') @@ -257,9 +261,9 @@ def _profit(bot: Bot, update: Update) -> None: trades = Trade.query.order_by(Trade.id).all() profit_all_btc = [] - profit_all = [] + profit_all_percent = [] profit_btc_closed = [] - profit_closed = [] + profit_closed_percent = [] durations = [] for trade in trades: @@ -271,16 +275,16 @@ def _profit(bot: Bot, update: Update) -> None: durations.append((trade.close_date - trade.open_date).total_seconds()) if not trade.is_open: - profit = trade.close_profit - profit_btc_closed.append(Decimal(trade.close_rate) - Decimal(trade.open_rate)) - profit_closed.append(profit) + profit_percent = trade.calc_profit_percent() + profit_btc_closed.append(trade.calc_profit()) + profit_closed_percent.append(profit_percent) else: # Get current rate current_rate = exchange.get_ticker(trade.pair)['bid'] - profit = trade.calc_profit(current_rate) + profit_percent = trade.calc_profit_percent(rate=current_rate) - profit_all_btc.append(Decimal(trade.close_rate or current_rate) - Decimal(trade.open_rate)) - profit_all.append(profit) + profit_all_btc.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate))) + profit_all_percent.append(profit_percent) best_pair = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ .filter(Trade.is_open.is_(False)) \ @@ -294,8 +298,8 @@ def _profit(bot: Bot, update: Update) -> None: bp_pair, bp_rate = best_pair markdown_msg = """ -*ROI Trade closed:* `{profit_closed_btc:.8f} BTC ({profit_closed:.2f}%)` -*ROI All trades:* `{profit_all_btc:.8f} BTC ({profit_all:.2f}%)` +*ROI Trade closed:* `{profit_closed_btc:.8f} BTC ({profit_closed_percent:.2f}%)` +*ROI All trades:* `{profit_all_btc:.8f} BTC ({profit_all_percent:.2f}%)` *Total Trade Count:* `{trade_count}` *First Trade opened:* `{first_trade_date}` *Latest Trade opened:* `{latest_trade_date}` @@ -303,9 +307,9 @@ def _profit(bot: Bot, update: Update) -> None: *Best Performing:* `{best_pair}: {best_rate:.2f}%` """.format( profit_closed_btc=round(sum(profit_btc_closed), 8), - profit_closed=round(sum(profit_closed) * 100, 2), + profit_closed_percent=round(sum(profit_closed_percent) * 100, 2), profit_all_btc=round(sum(profit_all_btc), 8), - profit_all=round(sum(profit_all) * 100, 2), + profit_all_percent=round(sum(profit_all_percent) * 100, 2), trade_count=len(trades), first_trade_date=arrow.get(trades[0].open_date).humanize(), latest_trade_date=arrow.get(trades[-1].open_date).humanize(), diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index c711f3953..5b1d98ef9 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -15,7 +15,7 @@ def default_conf(): configuration = { "max_open_trades": 1, "stake_currency": "BTC", - "stake_amount": 0.05, + "stake_amount": 0.001, "dry_run": True, "minimal_roi": { "40": 0.0, @@ -61,11 +61,26 @@ def update(): @pytest.fixture def ticker(): return MagicMock(return_value={ - 'bid': 0.07256061, - 'ask': 0.072661, - 'last': 0.07256061, + 'bid': 0.00001098, + 'ask': 0.00001099, + 'last': 0.00001098, }) +@pytest.fixture +def ticker_sell_up(): + return MagicMock(return_value={ + 'bid': 0.00001172, + 'ask': 0.00001173, + 'last': 0.00001172, + }) + +@pytest.fixture +def ticker_sell_down(): + return MagicMock(return_value={ + 'bid': 0.00001044, + 'ask': 0.00001043, + 'last': 0.00001044, + }) @pytest.fixture def health(): @@ -104,8 +119,8 @@ def limit_buy_order(): 'type': 'LIMIT_BUY', 'pair': 'mocked', 'opened': datetime.utcnow(), - 'rate': 0.07256061, - 'amount': 206.43811673387373, + 'rate': 0.00001099, + 'amount': 90.99181073, 'remaining': 0.0, 'closed': datetime.utcnow(), } @@ -118,8 +133,8 @@ def limit_sell_order(): 'type': 'LIMIT_SELL', 'pair': 'mocked', 'opened': datetime.utcnow(), - 'rate': 0.0802134, - 'amount': 206.43811673387373, + 'rate': 0.00001173, + 'amount': 90.99181073, 'remaining': 0.0, 'closed': datetime.utcnow(), } @@ -155,4 +170,4 @@ def ticker_history(): "T": "2017-11-26T09:00:00", "BV": 0.7039405 } - ] \ No newline at end of file + ] diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index aeea5a1a1..d9b774a9d 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -40,8 +40,8 @@ def test_process_trade_creation(default_conf, ticker, health, mocker): assert trade.is_open assert trade.open_date is not None assert trade.exchange == Exchanges.BITTREX.name - assert trade.open_rate == 0.072661 - assert trade.amount == 0.6881270557795791 + assert trade.open_rate == 0.00001099 + assert trade.amount == 90.99181073703367 def test_process_exchange_failures(default_conf, ticker, health, mocker): @@ -115,11 +115,11 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker): whitelist = copy.deepcopy(default_conf['exchange']['pair_whitelist']) init(default_conf, create_engine('sqlite://')) - create_trade(15.0) + create_trade(0.001) trade = Trade.query.first() assert trade is not None - assert trade.stake_amount == 15.0 + assert trade.stake_amount == 0.001 assert trade.is_open assert trade.open_date is not None assert trade.exchange == Exchanges.BITTREX.name @@ -127,8 +127,8 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker): # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) - assert trade.open_rate == 0.07256061 - assert trade.amount == 206.43811673387373 + assert trade.open_rate == 0.00001099 + assert trade.amount == 90.99181073 assert whitelist == default_conf['exchange']['pair_whitelist'] @@ -186,14 +186,14 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=MagicMock(return_value={ - 'bid': 0.17256061, - 'ask': 0.172661, - 'last': 0.17256061 + 'bid': 0.00001172, + 'ask': 0.00001173, + 'last': 0.00001172 }), buy=MagicMock(return_value='mocked_limit_buy'), sell=MagicMock(return_value='mocked_limit_sell')) init(default_conf, create_engine('sqlite://')) - create_trade(15.0) + create_trade(0.001) trade = Trade.query.first() assert trade @@ -207,8 +207,9 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): # Simulate fulfilled LIMIT_SELL order for trade trade.update(limit_sell_order) - assert trade.close_rate == 0.0802134 - assert trade.close_profit == 0.10046755 + assert trade.close_rate == 0.00001173 + assert trade.close_profit == 0.06201057 + assert trade.calc_profit() == 0.00006217 assert trade.close_date is not None @@ -223,7 +224,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo # Create trade and sell it init(default_conf, create_engine('sqlite://')) - create_trade(15.0) + create_trade(0.001) trade = Trade.query.first() assert trade diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index eeb89d831..6315cfcf1 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -5,11 +5,32 @@ from freqtrade.exchange import Exchanges from freqtrade.persistence import Trade -def test_update(limit_buy_order, limit_sell_order): +def test_update_with_bittrex(limit_buy_order, limit_sell_order): + """ + On this test we will buy and sell a crypto currency. + + Buy + - Buy: 90.99181073 Crypto at 0.00001099 BTC (90.99181073*0.00001099 = 0.0009999 BTC) + - Buying fee: 0.25% + - Total cost of buy trade: 0.001002500 BTC ((90.99181073*0.00001099) + ((90.99181073*0.00001099)*0.0025)) + + Sell + - Sell: 90.99181073 Crypto at 0.00001173 BTC (90.99181073*0.00001173 = 0,00106733394 BTC) + - Selling fee: 0.25% + - Total cost of sell trade: 0.001064666 BTC ((90.99181073*0.00001173) - ((90.99181073*0.00001173)*0.0025)) + + Profit/Loss: +0.000062166 BTC (Sell:0.001064666 - Buy:0.001002500) + Profit/Loss percentage: 0.0620 ((0.001064666/0.001002500)-1 = 6.20%) + + :param limit_buy_order: + :param limit_sell_order: + :return: + """ + trade = Trade( pair='BTC_ETH', - stake_amount=1.00, - fee=0.1, + stake_amount=0.001, + fee=0.0025, exchange=Exchanges.BITTREX, ) assert trade.open_order_id is None @@ -20,18 +41,40 @@ def test_update(limit_buy_order, limit_sell_order): trade.open_order_id = 'something' trade.update(limit_buy_order) assert trade.open_order_id is None - assert trade.open_rate == 0.07256061 + assert trade.open_rate == 0.00001099 assert trade.close_profit is None assert trade.close_date is None trade.open_order_id = 'something' trade.update(limit_sell_order) assert trade.open_order_id is None - assert trade.open_rate == 0.07256061 - assert trade.close_profit == 0.00546755 + assert trade.close_rate == 0.00001173 + assert trade.close_profit == 0.06201057 assert trade.close_date is not None +def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order): + trade = Trade( + pair='BTC_ETH', + stake_amount=0.001, + fee=0.0025, + exchange=Exchanges.BITTREX, + ) + + trade.open_order_id = 'something' + trade.update(limit_buy_order) + assert trade.calc_open_trade_price() == 0.001002500 + + trade.update(limit_sell_order) + assert trade.calc_close_trade_price() == 0.0010646656 + + # Profit in BTC + assert trade.calc_profit() == 0.00006217 + + # Profit in percent + assert trade.calc_profit_percent() == 0.06201057 + + def test_update_open_order(limit_buy_order): trade = Trade( pair='BTC_ETH', @@ -64,3 +107,103 @@ def test_update_invalid_order(limit_buy_order): limit_buy_order['type'] = 'invalid' with pytest.raises(ValueError, match=r'Unknown order type'): trade.update(limit_buy_order) + + +def test_calc_open_trade_price(limit_buy_order): + trade = Trade( + pair='BTC_ETH', + stake_amount=0.001, + fee=0.0025, + exchange=Exchanges.BITTREX, + ) + 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_price() == 0.001002500 + + # Get the open rate price with a custom fee rate + assert trade.calc_open_trade_price(fee=0.003) == 0.001003000 + + +def test_calc_close_trade_price(limit_buy_order, limit_sell_order): + trade = Trade( + pair='BTC_ETH', + stake_amount=0.001, + fee=0.0025, + exchange=Exchanges.BITTREX, + ) + 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_price(rate=0.00001234) == 0.0011200318 + + # Get the close rate price with a custom close rate and a custom fee rate + assert trade.calc_close_trade_price(rate=0.00001234, fee=0.003) == 0.0011194704 + + # 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_price(fee=0.005) == 0.0010619972 + + +def test_calc_profit(limit_buy_order, limit_sell_order): + trade = Trade( + pair='BTC_ETH', + stake_amount=0.001, + fee=0.0025, + exchange=Exchanges.BITTREX, + ) + trade.open_order_id = 'profit_percent' + 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 + + # Only custom fee without sell order applied + with pytest.raises(TypeError): + trade.calc_profit(fee=0.003) + + # 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 + + +def test_calc_profit_percent(limit_buy_order, limit_sell_order): + trade = Trade( + pair='BTC_ETH', + stake_amount=0.001, + fee=0.0025, + exchange=Exchanges.BITTREX, + ) + trade.open_order_id = 'profit_percent' + trade.update(limit_buy_order) # Buy @ 0.00001099 + + # Get percent of profit with a custom rate (Higher than open rate) + assert trade.calc_profit_percent(rate=0.00001234) == 0.1172387 + + # Get percent of profit with a custom rate (Lower than open rate) + assert trade.calc_profit_percent(rate=0.00000123) == -0.88863827 + + # Only custom fee without sell order applied + with pytest.raises(TypeError): + trade.calc_profit_percent(fee=0.003) + + # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173 + trade.update(limit_sell_order) + assert trade.calc_profit_percent() == 0.06201057 + + # Test with a custom fee rate on the close trade + assert trade.calc_profit_percent(fee=0.003) == 0.0614782 diff --git a/freqtrade/tests/test_rpc_telegram.py b/freqtrade/tests/test_rpc_telegram.py index afdef2692..cf5b7051b 100644 --- a/freqtrade/tests/test_rpc_telegram.py +++ b/freqtrade/tests/test_rpc_telegram.py @@ -102,7 +102,7 @@ def test_status_handle(default_conf, update, ticker, mocker): msg_mock.reset_mock() # Create some test data - create_trade(15.0) + create_trade(0.001) # Trigger status while we have a fulfilled order for the open trade _status(bot=MagicMock(), update=update) @@ -151,7 +151,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker): assert msg_mock.call_count == 1 -def test_profit_handle(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker): +def test_profit_handle(default_conf, update, ticker, ticker_sell_up, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() @@ -171,7 +171,7 @@ def test_profit_handle(default_conf, update, ticker, limit_buy_order, limit_sell msg_mock.reset_mock() # Create some test data - create_trade(15.0) + create_trade(0.001) trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade @@ -182,7 +182,10 @@ def test_profit_handle(default_conf, update, ticker, limit_buy_order, limit_sell assert 'no closed trade' in msg_mock.call_args_list[-1][0][0] msg_mock.reset_mock() - # Simulate fulfilled LIMIT_SELL order for trade + # Update the ticker with a market going up + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker_sell_up) trade.update(limit_sell_order) trade.close_date = datetime.utcnow() @@ -190,11 +193,12 @@ def test_profit_handle(default_conf, update, ticker, limit_buy_order, limit_sell _profit(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 - assert '*ROI All trades:* `0.00765279 BTC (10.05%)`' in msg_mock.call_args_list[-1][0][0] - assert 'Best Performing:* `BTC_ETH: 10.05%`' in msg_mock.call_args_list[-1][0][0] + assert '*ROI Trade closed:* `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] + assert '*ROI All trades:* `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] + assert 'Best Performing:* `BTC_ETH: 6.20%`' in msg_mock.call_args_list[-1][0][0] -def test_forcesell_handle(default_conf, update, ticker, mocker): +def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) @@ -208,7 +212,44 @@ def test_forcesell_handle(default_conf, update, ticker, mocker): init(default_conf, create_engine('sqlite://')) # Create some test data - create_trade(15.0) + create_trade(0.001) + + trade = Trade.query.first() + assert trade + + # Increase the price and sell it + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker_sell_up) + + update.message.text = '/forcesell 1' + _forcesell(bot=MagicMock(), update=update) + + assert rpc_mock.call_count == 2 + assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '0.00001172 (profit: ~6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] + + +def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, mocker): + mocker.patch.dict('freqtrade.main._CONF', default_conf) + mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) + rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) + mocker.patch.multiple('freqtrade.rpc.telegram', + _CONF=default_conf, + init=MagicMock(), + send_msg=MagicMock()) + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker) + init(default_conf, create_engine('sqlite://')) + + # Create some test data + create_trade(0.001) + + ## Decrease the price and sell it + mocker.patch.multiple('freqtrade.main.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker_sell_down) trade = Trade.query.first() assert trade @@ -218,7 +259,7 @@ def test_forcesell_handle(default_conf, update, ticker, mocker): assert rpc_mock.call_count == 2 assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] - assert '0.07256061 (profit: ~-0.64%)' in rpc_mock.call_args_list[-1][0][0] + assert '0.00001044 (profit: ~-5.48%, -0.00005492)' in rpc_mock.call_args_list[-1][0][0] def test_exec_forcesell_open_orders(default_conf, ticker, mocker): @@ -260,7 +301,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker): # Create some test data for _ in range(4): - create_trade(15.0) + create_trade(0.001) rpc_mock.reset_mock() update.message.text = '/forcesell all' @@ -268,7 +309,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker): assert rpc_mock.call_count == 4 for args in rpc_mock.call_args_list: - assert '0.07256061 (profit: ~-0.64%)' in args[0][0] + assert '0.00001098 (profit: ~-0.59%, -0.00000591)' in args[0][0] def test_forcesell_handle_invalid(default_conf, update, mocker): @@ -323,7 +364,7 @@ def test_performance_handle( init(default_conf, create_engine('sqlite://')) # Create some test data - create_trade(15.0) + create_trade(0.001) trade = Trade.query.first() assert trade @@ -339,7 +380,8 @@ def test_performance_handle( _performance(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Performance' in msg_mock.call_args_list[0][0][0] - assert 'BTC_ETH\t10.05%' in msg_mock.call_args_list[0][0][0] + assert 'BTC_ETH\t6.20%' in msg_mock.call_args_list[0][0][0] + def test_daily_handle( @@ -358,7 +400,7 @@ def test_daily_handle( init(default_conf, create_engine('sqlite://')) # Create some test data - create_trade(15.0) + create_trade(0.001) trade = Trade.query.first() assert trade @@ -371,14 +413,14 @@ def test_daily_handle( trade.close_date = datetime.utcnow() trade.is_open = False - # try valid data - update.message.text = '/daily 7' + # Try valid data + update.message.text = '/daily 2' _daily(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Daily' in msg_mock.call_args_list[0][0][0] - assert str(date.today()) + ' 1.50701325 BTC' in msg_mock.call_args_list[0][0][0] - - # try invalid data + assert str(datetime.utcnow().date()) + ' 0.00006217 BTC' in msg_mock.call_args_list[0][0][0] + + # Try invalid data msg_mock.reset_mock() update_state(State.RUNNING) update.message.text = '/daily -2' @@ -409,7 +451,7 @@ def test_count_handle(default_conf, update, ticker, mocker): update_state(State.RUNNING) # Create some test data - create_trade(15.0) + create_trade(0.001) msg_mock.reset_mock() _count(bot=MagicMock(), update=update)