Merge pull request #2982 from freqtrade/rate_side_optional
Rate side configurable
This commit is contained in:
commit
dea4ef957e
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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'):
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user