Merge pull request #2982 from freqtrade/rate_side_optional

Rate side configurable
This commit is contained in:
hroff-1902 2020-03-04 16:07:08 +03:00 committed by GitHub
commit dea4ef957e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 222 additions and 122 deletions

View File

@ -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,

View File

@ -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). <br>*Defaults to `false`.* <br> **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).<br> **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).<br> **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).<br> *Defaults to `bid`.* <br> **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). <br> **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). <br>*Defaults to `1`.* <br> **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). <br>*Defaults to `false`.* <br> **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) <br> *Defaults to `0`.* <br> **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).<br> *Defaults to `ask`.* <br> **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). <br> **Datatype:** Boolean
| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **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. <br>*Defaults to `1`.* <br> **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

View File

@ -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',

View File

@ -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

View File

@ -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,

View File

@ -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'):

View File

@ -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,

View File

@ -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,

View File

@ -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)