diff --git a/config_full.json.example b/config_full.json.example
index cdb7e841e..f0414bd0d 100644
--- a/config_full.json.example
+++ b/config_full.json.example
@@ -25,6 +25,7 @@
"sell": 30
},
"bid_strategy": {
+ "price_side": "bid",
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
@@ -34,6 +35,7 @@
}
},
"ask_strategy":{
+ "price_side": "ask",
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9,
diff --git a/docs/configuration.md b/docs/configuration.md
index b3f032bc6..5580b9c68 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -60,11 +60,13 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean
| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer
| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer
-| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook).
+| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).
*Defaults to `bid`.*
**Datatype:** String (either `ask` or `bid`).
+| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook-enabled).
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled).
**Datatype:** Boolean
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled).
*Defaults to `1`.*
**Datatype:** Positive Integer
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market).
*Defaults to `false`.*
**Datatype:** Boolean
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market)
*Defaults to `0`.*
**Datatype:** Float (as ratio)
+| `ask_strategy.price_side` | Select the side of the spread the bot should look at to get the sell rate. [More information below](#sell-price-side).
*Defaults to `ask`.*
**Datatype:** String (either `ask` or `bid`).
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled).
**Datatype:** Boolean
| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
**Datatype:** Positive Integer
| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
**Datatype:** Positive Integer
@@ -463,23 +465,72 @@ Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) s
!!! Note
A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side).
+#### Buy price side
+
+The configuration setting `bid_strategy.price_side` defines the side of the spread the bot looks for when buying.
+
+The following displays an orderbook.
+
+``` explanation
+...
+103
+102
+101 # ask
+-------------Current spread
+99 # bid
+98
+97
+...
+```
+
+If `bid_strategy.price_side` is set to `"bid"`, then the bot will use 99 as buying price.
+In line with that, if `bid_strategy.price_side` is set to `"ask"`, then the bot will use 101 as buying price.
+
+Using `ask` price often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary.
+Taker fees instead of maker fees will most likely apply even when using limit buy orders.
+Also, prices at the "ask" side of the spread are higher than prices at the "bid" side in the orderbook, so the order behaves similar to a market order (however with a maximum price).
+
#### Buy price with Orderbook enabled
-When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and then uses the entry specified as `bid_strategy.order_book_top` on the `bid` (buy) side of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
+When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and then uses the entry specified as `bid_strategy.order_book_top` on the configured side (`bid_strategy.price_side`) of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
#### Buy price without Orderbook enabled
-When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `ask` (sell) price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `ask` price is not below the `last` price), it calculates a rate between `ask` and `last` price.
+The following section uses `side` as the configured `bid_strategy.price_side`.
-The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `ask` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
+When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price.
-Using `ask` price often guarantees quicker success in the bid, but the bot can also end up paying more than what would have been necessary.
+The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
### Sell price
+#### Sell price side
+
+The configuration setting `ask_strategy.price_side` defines the side of the spread the bot looks for when selling.
+
+The following displays an orderbook:
+
+``` explanation
+...
+103
+102
+101 # ask
+-------------Current spread
+99 # bid
+98
+97
+...
+```
+
+If `ask_strategy.price_side` is set to `"ask"`, then the bot will use 101 as selling price.
+In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot will use 99 as selling price.
+
#### Sell price with Orderbook enabled
-When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the `ask` orderbook side are validated for a profitable sell-possibility based on the strategy configuration and the sell order is placed at the first profitable spot.
+When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the configured orderbook side are validated for a profitable sell-possibility based on the strategy configuration (`minimal_roi` conditions) and the sell order is placed at the first profitable spot.
+
+!!! Note
+ Using `order_book_max` higher than `order_book_min` only makes sense when ask_strategy.price_side is set to `"ask"`.
The idea here is to place the sell order early, to be ahead in the queue.
@@ -490,7 +541,7 @@ A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting
#### Sell price without Orderbook enabled
-When not using orderbook (`ask_strategy.use_order_book=False`), the `bid` price from the ticker will be used as the sell price.
+When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price.
## Pairlists
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index 1504d1f1c..ac1a8a6a9 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -15,6 +15,7 @@ UNLIMITED_STAKE_AMOUNT = 'unlimited'
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
REQUIRED_ORDERTIF = ['buy', 'sell']
REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
+ORDERBOOK_SIDES = ['ask', 'bid']
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
@@ -113,15 +114,16 @@ CONF_SCHEMA = {
'minimum': 0,
'maximum': 1,
'exclusiveMaximum': False,
- 'use_order_book': {'type': 'boolean'},
- 'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1},
- 'check_depth_of_market': {
- 'type': 'object',
- 'properties': {
- 'enabled': {'type': 'boolean'},
- 'bids_to_ask_delta': {'type': 'number', 'minimum': 0},
- }
- },
+ },
+ 'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'},
+ 'use_order_book': {'type': 'boolean'},
+ 'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1},
+ 'check_depth_of_market': {
+ 'type': 'object',
+ 'properties': {
+ 'enabled': {'type': 'boolean'},
+ 'bids_to_ask_delta': {'type': 'number', 'minimum': 0},
+ }
},
},
'required': ['ask_last_balance']
@@ -129,6 +131,7 @@ CONF_SCHEMA = {
'ask_strategy': {
'type': 'object',
'properties': {
+ 'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'ask'},
'use_order_book': {'type': 'boolean'},
'order_book_min': {'type': 'integer', 'minimum': 1},
'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50},
@@ -299,6 +302,7 @@ SCHEMA_TRADE_REQUIRED = [
'last_stake_amount_min_ratio',
'dry_run',
'dry_run_wallet',
+ 'ask_strategy',
'bid_strategy',
'unfilledtimeout',
'stoploss',
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index dffec940c..920c9203e 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -242,25 +242,25 @@ class FreqtradeBot:
logger.info(f"Using cached buy rate for {pair}.")
return rate
- config_bid_strategy = self.config.get('bid_strategy', {})
- if 'use_order_book' in config_bid_strategy and\
- config_bid_strategy.get('use_order_book', False):
- logger.info('Getting price from order book')
- order_book_top = config_bid_strategy.get('order_book_top', 1)
+ bid_strategy = self.config.get('bid_strategy', {})
+ if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False):
+ logger.info(
+ f"Getting price from order book {bid_strategy['price_side'].capitalize()} side."
+ )
+ order_book_top = bid_strategy.get('order_book_top', 1)
order_book = self.exchange.get_order_book(pair, order_book_top)
logger.debug('order_book %s', order_book)
# top 1 = index 0
- order_book_rate = order_book['bids'][order_book_top - 1][0]
- logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate)
+ order_book_rate = order_book[f"{bid_strategy['price_side']}s"][order_book_top - 1][0]
+ logger.info(f'...top {order_book_top} order book buy rate {order_book_rate:.8f}')
used_rate = order_book_rate
else:
- logger.info('Using Last Ask / Last Price')
+ logger.info(f"Using Last {bid_strategy['price_side'].capitalize()} / Last Price")
ticker = self.exchange.fetch_ticker(pair)
- if ticker['ask'] < ticker['last']:
- ticker_rate = ticker['ask']
- else:
+ ticker_rate = ticker[bid_strategy['price_side']]
+ if ticker['last'] and ticker_rate > ticker['last']:
balance = self.config['bid_strategy']['ask_last_balance']
- ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
+ ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
used_rate = ticker_rate
self._buy_rate_cache[pair] = used_rate
@@ -617,6 +617,15 @@ class FreqtradeBot:
return trades_closed
+ def _order_book_gen(self, pair: str, side: str, order_book_max: int = 1,
+ order_book_min: int = 1):
+ """
+ Helper generator to query orderbook in loop (used for early sell-order placing)
+ """
+ order_book = self.exchange.get_order_book(pair, order_book_max)
+ for i in range(order_book_min, order_book_max + 1):
+ yield order_book[side][i - 1][0]
+
def get_sell_rate(self, pair: str, refresh: bool) -> float:
"""
Get sell rate - either using get-ticker bid or first bid based on orderbook
@@ -636,13 +645,12 @@ class FreqtradeBot:
config_ask_strategy = self.config.get('ask_strategy', {})
if config_ask_strategy.get('use_order_book', False):
+ # This code is only used for notifications, selling uses the generator directly
logger.debug('Using order book to get sell rate')
-
- order_book = self.exchange.get_order_book(pair, 1)
- rate = order_book['bids'][0][0]
+ rate = next(self._order_book_gen(pair, f"{config_ask_strategy['price_side']}s"))
else:
- rate = self.exchange.fetch_ticker(pair)['bid']
+ rate = self.exchange.fetch_ticker(pair)[config_ask_strategy['price_side']]
self._sell_rate_cache[pair] = rate
return rate
@@ -672,12 +680,13 @@ class FreqtradeBot:
order_book_min = config_ask_strategy.get('order_book_min', 1)
order_book_max = config_ask_strategy.get('order_book_max', 1)
- order_book = self.exchange.get_order_book(trade.pair, order_book_max)
-
+ order_book = self._order_book_gen(trade.pair, f"{config_ask_strategy['price_side']}s",
+ order_book_min=order_book_min,
+ order_book_max=order_book_max)
for i in range(order_book_min, order_book_max + 1):
- order_book_rate = order_book['asks'][i - 1][0]
- logger.debug(' order book asks top %s: %0.8f', i, order_book_rate)
- sell_rate = order_book_rate
+ sell_rate = next(order_book)
+ logger.debug(f" order book {config_ask_strategy['price_side']} top {i}: "
+ f"{sell_rate:0.8f}")
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
return True
diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2
index 88edeb1e8..0049d59a0 100644
--- a/freqtrade/templates/base_config.json.j2
+++ b/freqtrade/templates/base_config.json.j2
@@ -11,6 +11,7 @@
"sell": 30
},
"bid_strategy": {
+ "price_side": "bid",
"ask_last_balance": 0.0,
"use_order_book": false,
"order_book_top": 1,
@@ -20,6 +21,7 @@
}
},
"ask_strategy": {
+ "price_side": "ask",
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9,
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index 93b6f6058..6319ab9e6 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -51,13 +51,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_date_hum': ANY,
'close_date': None,
'close_date_hum': None,
- 'open_rate': 1.099e-05,
+ 'open_rate': 1.098e-05,
'close_rate': None,
- 'current_rate': 1.098e-05,
- 'amount': 90.99181074,
+ 'current_rate': 1.099e-05,
+ 'amount': 91.07468124,
'stake_amount': 0.001,
'close_profit': None,
- 'current_profit': -0.59,
+ 'current_profit': -0.41,
'stop_loss': 0.0,
'initial_stop_loss': 0.0,
'initial_stop_loss_pct': None,
@@ -78,10 +78,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_date_hum': ANY,
'close_date': None,
'close_date_hum': None,
- 'open_rate': 1.099e-05,
+ 'open_rate': 1.098e-05,
'close_rate': None,
'current_rate': ANY,
- 'amount': 90.99181074,
+ 'amount': 91.07468124,
'stake_amount': 0.001,
'close_profit': None,
'current_profit': ANY,
@@ -121,7 +121,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert "Pair" in headers
assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1]
- assert '-0.59%' == result[0][3]
+ assert '-0.41%' == result[0][3]
# Test with fiatconvert
rpc._fiat_converter = CryptoToFiatConverter()
@@ -130,7 +130,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert "Pair" in headers
assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1]
- assert '-0.59% (-0.09)' == result[0][3]
+ assert '-0.41% (-0.06)' == result[0][3]
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
@@ -245,9 +245,9 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05)
assert prec_satoshi(stats['profit_closed_percent'], 6.2)
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255)
- assert prec_satoshi(stats['profit_all_coin'], 5.632e-05)
- assert prec_satoshi(stats['profit_all_percent'], 2.81)
- assert prec_satoshi(stats['profit_all_fiat'], 0.8448)
+ assert prec_satoshi(stats['profit_all_coin'], 5.802e-05)
+ assert prec_satoshi(stats['profit_all_percent'], 2.89)
+ assert prec_satoshi(stats['profit_all_fiat'], 0.8703)
assert stats['trade_count'] == 2
assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now'
@@ -668,7 +668,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None
trade = rpc._rpc_forcebuy(pair, None)
assert isinstance(trade, Trade)
assert trade.pair == pair
- assert trade.open_rate == ticker()['ask']
+ assert trade.open_rate == ticker()['bid']
# Test buy duplicate
with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'):
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 25c971bf7..e0abd886d 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -426,20 +426,20 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc)
assert len(rc.json) == 1
- assert rc.json == [{'amount': 90.99181074,
+ assert rc.json == [{'amount': 91.07468124,
'base_currency': 'BTC',
'close_date': None,
'close_date_hum': None,
'close_profit': None,
'close_rate': None,
- 'current_profit': -0.59,
- 'current_rate': 1.098e-05,
+ 'current_profit': -0.41,
+ 'current_rate': 1.099e-05,
'initial_stop_loss': 0.0,
'initial_stop_loss_pct': None,
'open_date': ANY,
'open_date_hum': 'just now',
'open_order': '(limit buy rem=0.00000000)',
- 'open_rate': 1.099e-05,
+ 'open_rate': 1.098e-05,
'pair': 'ETH/BTC',
'stake_amount': 0.001,
'stop_loss': 0.0,
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index a8b8e0c5a..fd3e4039a 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -720,13 +720,13 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'profit',
- 'limit': 1.172e-05,
- 'amount': 90.99181073703367,
+ 'limit': 1.173e-05,
+ 'amount': 91.07468123861567,
'order_type': 'limit',
- 'open_rate': 1.099e-05,
- 'current_rate': 1.172e-05,
- 'profit_amount': 6.126e-05,
- 'profit_percent': 0.0611052,
+ 'open_rate': 1.098e-05,
+ 'current_rate': 1.173e-05,
+ 'profit_amount': 6.314e-05,
+ 'profit_percent': 0.0629778,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.FORCE_SELL.value,
@@ -779,13 +779,13 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'loss',
- 'limit': 1.044e-05,
- 'amount': 90.99181073703367,
+ 'limit': 1.043e-05,
+ 'amount': 91.07468123861567,
'order_type': 'limit',
- 'open_rate': 1.099e-05,
- 'current_rate': 1.044e-05,
- 'profit_amount': -5.492e-05,
- 'profit_percent': -0.05478342,
+ 'open_rate': 1.098e-05,
+ 'current_rate': 1.043e-05,
+ 'profit_amount': -5.497e-05,
+ 'profit_percent': -0.05482878,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.FORCE_SELL.value,
@@ -827,13 +827,13 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'loss',
- 'limit': 1.098e-05,
- 'amount': 90.99181073703367,
+ 'limit': 1.099e-05,
+ 'amount': 91.07468123861567,
'order_type': 'limit',
- 'open_rate': 1.099e-05,
- 'current_rate': 1.098e-05,
- 'profit_amount': -5.91e-06,
- 'profit_percent': -0.00589291,
+ 'open_rate': 1.098e-05,
+ 'current_rate': 1.099e-05,
+ 'profit_amount': -4.09e-06,
+ 'profit_percent': -0.00408133,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.FORCE_SELL.value,
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 852b6b990..a5506017c 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -761,8 +761,8 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
assert trade.is_open
assert trade.open_date is not None
assert trade.exchange == 'bittrex'
- assert trade.open_rate == 0.00001099
- assert trade.amount == 90.99181073703367
+ assert trade.open_rate == 0.00001098
+ assert trade.amount == 91.07468123861567
assert log_has(
'Buy signal found: about create a new trade with stake_amount: 0.001 ...', caplog
@@ -906,20 +906,37 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
assert ("ETH/BTC", default_conf["ticker_interval"]) in refresh_mock.call_args[0][0]
-@pytest.mark.parametrize("ask,last,last_ab,expected", [
- (20, 10, 0.0, 20), # Full ask side
- (20, 10, 1.0, 10), # Full last side
- (20, 10, 0.5, 15), # Between ask and last
- (20, 10, 0.7, 13), # Between ask and last
- (20, 10, 0.3, 17), # Between ask and last
- (5, 10, 1.0, 5), # last bigger than ask
- (5, 10, 0.5, 5), # last bigger than ask
+@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [
+ ('ask', 20, 19, 10, 0.0, 20), # Full ask side
+ ('ask', 20, 19, 10, 1.0, 10), # Full last side
+ ('ask', 20, 19, 10, 0.5, 15), # Between ask and last
+ ('ask', 20, 19, 10, 0.7, 13), # Between ask and last
+ ('ask', 20, 19, 10, 0.3, 17), # Between ask and last
+ ('ask', 5, 6, 10, 1.0, 5), # last bigger than ask
+ ('ask', 5, 6, 10, 0.5, 5), # last bigger than ask
+ ('ask', 10, 20, None, 0.5, 10), # last not available - uses ask
+ ('ask', 4, 5, None, 0.5, 4), # last not available - uses ask
+ ('ask', 4, 5, None, 1, 4), # last not available - uses ask
+ ('ask', 4, 5, None, 0, 4), # last not available - uses ask
+ ('bid', 10, 20, 10, 0.0, 20), # Full bid side
+ ('bid', 10, 20, 10, 1.0, 10), # Full last side
+ ('bid', 10, 20, 10, 0.5, 15), # Between bid and last
+ ('bid', 10, 20, 10, 0.7, 13), # Between bid and last
+ ('bid', 10, 20, 10, 0.3, 17), # Between bid and last
+ ('bid', 4, 5, 10, 1.0, 5), # last bigger than bid
+ ('bid', 4, 5, 10, 0.5, 5), # last bigger than bid
+ ('bid', 10, 20, None, 0.5, 20), # last not available - uses bid
+ ('bid', 4, 5, None, 0.5, 5), # last not available - uses bid
+ ('bid', 4, 5, None, 1, 5), # last not available - uses bid
+ ('bid', 4, 5, None, 0, 5), # last not available - uses bid
])
-def test_get_buy_rate(mocker, default_conf, caplog, ask, last, last_ab, expected) -> None:
+def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
+ last, last_ab, expected) -> None:
default_conf['bid_strategy']['ask_last_balance'] = last_ab
+ default_conf['bid_strategy']['price_side'] = side
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
- MagicMock(return_value={'ask': ask, 'last': last}))
+ MagicMock(return_value={'ask': ask, 'last': last, 'bid': bid}))
assert freqtrade.get_buy_rate('ETH/BTC', True) == expected
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
@@ -1317,7 +1334,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
stoploss_order_mock.assert_not_called()
assert freqtrade.handle_trade(trade) is False
- assert trade.stop_loss == 0.00002344 * 0.95
+ assert trade.stop_loss == 0.00002346 * 0.95
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
@@ -1325,10 +1342,10 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/BTC')
- stoploss_order_mock.assert_called_once_with(amount=85.25149190110828,
+ stoploss_order_mock.assert_called_once_with(amount=85.32423208191126,
pair='ETH/BTC',
order_types=freqtrade.strategy.order_types,
- stop_price=0.00002344 * 0.95)
+ stop_price=0.00002346 * 0.95)
# price fell below stoploss, so dry-run sells trade.
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
@@ -1510,12 +1527,12 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
assert freqtrade.handle_stoploss_on_exchange(trade) is False
# stoploss should be set to 1% as trailing is on
- assert trade.stop_loss == 0.00002344 * 0.99
+ assert trade.stop_loss == 0.00002346 * 0.99
cancel_order_mock.assert_called_once_with(100, 'NEO/BTC')
- stoploss_order_mock.assert_called_once_with(amount=2131074.168797954,
+ stoploss_order_mock.assert_called_once_with(amount=2132892.491467577,
pair='NEO/BTC',
order_types=freqtrade.strategy.order_types,
- stop_price=0.00002344 * 0.99)
+ stop_price=0.00002346 * 0.99)
def test_enter_positions(mocker, default_conf, caplog) -> None:
@@ -2292,12 +2309,12 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
'pair': 'ETH/BTC',
'gain': 'profit',
'limit': 1.172e-05,
- 'amount': 90.99181073703367,
+ 'amount': 91.07468123861567,
'order_type': 'limit',
- 'open_rate': 1.099e-05,
- 'current_rate': 1.172e-05,
- 'profit_amount': 6.126e-05,
- 'profit_percent': 0.0611052,
+ 'open_rate': 1.098e-05,
+ 'current_rate': 1.173e-05,
+ 'profit_amount': 6.223e-05,
+ 'profit_percent': 0.0620716,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value,
@@ -2341,12 +2358,12 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
'pair': 'ETH/BTC',
'gain': 'loss',
'limit': 1.044e-05,
- 'amount': 90.99181073703367,
+ 'amount': 91.07468123861567,
'order_type': 'limit',
- 'open_rate': 1.099e-05,
- 'current_rate': 1.044e-05,
- 'profit_amount': -5.492e-05,
- 'profit_percent': -0.05478342,
+ 'open_rate': 1.098e-05,
+ 'current_rate': 1.043e-05,
+ 'profit_amount': -5.406e-05,
+ 'profit_percent': -0.05392257,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value,
@@ -2397,12 +2414,12 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
'pair': 'ETH/BTC',
'gain': 'loss',
'limit': 1.08801e-05,
- 'amount': 90.99181073703367,
+ 'amount': 91.07468123861567,
'order_type': 'limit',
- 'open_rate': 1.099e-05,
- 'current_rate': 1.044e-05,
- 'profit_amount': -1.498e-05,
- 'profit_percent': -0.01493766,
+ 'open_rate': 1.098e-05,
+ 'current_rate': 1.043e-05,
+ 'profit_amount': -1.408e-05,
+ 'profit_percent': -0.01404051,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value,
@@ -2587,7 +2604,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI)
assert not trade.is_open
- assert trade.close_profit == 0.0611052
+ assert trade.close_profit == 0.0620716
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
@@ -2597,12 +2614,12 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
'pair': 'ETH/BTC',
'gain': 'profit',
'limit': 1.172e-05,
- 'amount': 90.99181073703367,
+ 'amount': 91.07468123861567,
'order_type': 'market',
- 'open_rate': 1.099e-05,
- 'current_rate': 1.172e-05,
- 'profit_amount': 6.126e-05,
- 'profit_percent': 0.0611052,
+ 'open_rate': 1.098e-05,
+ 'current_rate': 1.173e-05,
+ 'profit_amount': 6.223e-05,
+ 'profit_percent': 0.0620716,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value,
@@ -3624,13 +3641,20 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order
assert freqtrade.handle_trade(trade) is True
-def test_get_sell_rate(default_conf, mocker, caplog, ticker, order_book_l2) -> None:
-
- mocker.patch.multiple(
- 'freqtrade.exchange.Exchange',
- get_order_book=order_book_l2,
- fetch_ticker=ticker,
- )
+@pytest.mark.parametrize('side,ask,bid,expected', [
+ ('bid', 10.0, 11.0, 11.0),
+ ('bid', 10.0, 11.2, 11.2),
+ ('bid', 10.0, 11.0, 11.0),
+ ('bid', 9.8, 11.0, 11.0),
+ ('bid', 0.0001, 0.002, 0.002),
+ ('ask', 10.0, 11.0, 10.0),
+ ('ask', 10.11, 11.2, 10.11),
+ ('ask', 0.001, 0.002, 0.001),
+ ('ask', 0.006, 1.0, 0.006),
+])
+def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask, expected) -> None:
+ default_conf['ask_strategy']['price_side'] = side
+ mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': ask, 'bid': bid})
pair = "ETH/BTC"
# Test regular mode
@@ -3638,25 +3662,33 @@ def test_get_sell_rate(default_conf, mocker, caplog, ticker, order_book_l2) -> N
rate = ft.get_sell_rate(pair, True)
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
assert isinstance(rate, float)
- assert rate == 0.00001098
+ assert rate == expected
# Use caching
rate = ft.get_sell_rate(pair, False)
- assert rate == 0.00001098
+ assert rate == expected
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
- caplog.clear()
+@pytest.mark.parametrize('side,expected', [
+ ('bid', 0.043936), # Value from order_book_l2 fiture - bids side
+ ('ask', 0.043949), # Value from order_book_l2 fiture - asks side
+])
+def test_get_sell_rate_orderbook(default_conf, mocker, caplog, side, expected, order_book_l2):
# Test orderbook mode
+ default_conf['ask_strategy']['price_side'] = side
default_conf['ask_strategy']['use_order_book'] = True
default_conf['ask_strategy']['order_book_min'] = 1
default_conf['ask_strategy']['order_book_max'] = 2
+ # TODO: min/max is irrelevant for this test until refactoring
+ pair = "ETH/BTC"
+ mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
ft = get_patched_freqtradebot(mocker, default_conf)
rate = ft.get_sell_rate(pair, True)
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
assert isinstance(rate, float)
- assert rate == 0.043936
+ assert rate == expected
rate = ft.get_sell_rate(pair, False)
- assert rate == 0.043936
+ assert rate == expected
assert log_has("Using cached sell rate for ETH/BTC.", caplog)