mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-11-29 08:33:07 +00:00
Compare commits
1 Commits
feat/dry_s
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfe9aac16b |
@@ -104,7 +104,6 @@ from freqtrade.misc import (
|
||||
deep_merge_dicts,
|
||||
file_dump_json,
|
||||
file_load_json,
|
||||
safe_value_fallback,
|
||||
safe_value_fallback2,
|
||||
)
|
||||
from freqtrade.util import FtTTLCache, PeriodicCache, dt_from_ts, dt_now
|
||||
@@ -1120,7 +1119,6 @@ class Exchange:
|
||||
leverage: float,
|
||||
params: dict | None = None,
|
||||
stop_loss: bool = False,
|
||||
stop_price: float | None = None,
|
||||
) -> CcxtOrder:
|
||||
now = dt_now()
|
||||
order_id = f"dry_run_{side}_{pair}_{now.timestamp()}"
|
||||
@@ -1147,7 +1145,7 @@ class Exchange:
|
||||
}
|
||||
if stop_loss:
|
||||
dry_order["info"] = {"stopPrice": dry_order["price"]}
|
||||
dry_order[self._ft_has["stop_price_prop"]] = stop_price or dry_order["price"]
|
||||
dry_order[self._ft_has["stop_price_prop"]] = dry_order["price"]
|
||||
# Workaround to avoid filling stoploss orders immediately
|
||||
dry_order["ft_order_type"] = "stoploss"
|
||||
orderbook: OrderBook | None = None
|
||||
@@ -1165,11 +1163,7 @@ class Exchange:
|
||||
|
||||
if dry_order["type"] == "market" and not dry_order.get("ft_order_type"):
|
||||
# Update market order pricing
|
||||
slippage = 0.05
|
||||
worst_rate = rate * ((1 + slippage) if side == "buy" else (1 - slippage))
|
||||
average = self.get_dry_market_fill_price(
|
||||
pair, side, amount, rate, worst_rate, orderbook
|
||||
)
|
||||
average = self.get_dry_market_fill_price(pair, side, amount, rate, orderbook)
|
||||
dry_order.update(
|
||||
{
|
||||
"average": average,
|
||||
@@ -1209,13 +1203,7 @@ class Exchange:
|
||||
return dry_order
|
||||
|
||||
def get_dry_market_fill_price(
|
||||
self,
|
||||
pair: str,
|
||||
side: str,
|
||||
amount: float,
|
||||
rate: float,
|
||||
worst_rate: float,
|
||||
orderbook: OrderBook | None,
|
||||
self, pair: str, side: str, amount: float, rate: float, orderbook: OrderBook | None
|
||||
) -> float:
|
||||
"""
|
||||
Get the market order fill price based on orderbook interpolation
|
||||
@@ -1224,6 +1212,8 @@ class Exchange:
|
||||
if not orderbook:
|
||||
orderbook = self.fetch_l2_order_book(pair, 20)
|
||||
ob_type: OBLiteral = "asks" if side == "buy" else "bids"
|
||||
slippage = 0.05
|
||||
max_slippage_val = rate * ((1 + slippage) if side == "buy" else (1 - slippage))
|
||||
|
||||
remaining_amount = amount
|
||||
filled_value = 0.0
|
||||
@@ -1247,10 +1237,11 @@ class Exchange:
|
||||
forecast_avg_filled_price = max(filled_value, 0) / amount
|
||||
# Limit max. slippage to specified value
|
||||
if side == "buy":
|
||||
forecast_avg_filled_price = min(forecast_avg_filled_price, worst_rate)
|
||||
forecast_avg_filled_price = min(forecast_avg_filled_price, max_slippage_val)
|
||||
|
||||
else:
|
||||
forecast_avg_filled_price = max(forecast_avg_filled_price, worst_rate)
|
||||
forecast_avg_filled_price = max(forecast_avg_filled_price, max_slippage_val)
|
||||
|
||||
return self.price_to_precision(pair, forecast_avg_filled_price)
|
||||
|
||||
return rate
|
||||
@@ -1262,15 +1253,13 @@ class Exchange:
|
||||
limit: float,
|
||||
orderbook: OrderBook | None = None,
|
||||
offset: float = 0.0,
|
||||
is_stop: bool = False,
|
||||
) -> bool:
|
||||
if not self.exchange_has("fetchL2OrderBook"):
|
||||
# True unless checking a stoploss order
|
||||
return not is_stop
|
||||
return True
|
||||
if not orderbook:
|
||||
orderbook = self.fetch_l2_order_book(pair, 1)
|
||||
try:
|
||||
if (side == "buy" and not is_stop) or (side == "sell" and is_stop):
|
||||
if side == "buy":
|
||||
price = orderbook["asks"][0][0]
|
||||
if limit * (1 - offset) >= price:
|
||||
return True
|
||||
@@ -1289,38 +1278,6 @@ class Exchange:
|
||||
"""
|
||||
Check dry-run limit order fill and update fee (if it filled).
|
||||
"""
|
||||
if order["status"] != "closed" and order.get("ft_order_type") == "stoploss":
|
||||
pair = order["symbol"]
|
||||
if not orderbook and self.exchange_has("fetchL2OrderBook"):
|
||||
orderbook = self.fetch_l2_order_book(pair, 20)
|
||||
price = safe_value_fallback(order, self._ft_has["stop_price_prop"], "price")
|
||||
crossed = self._dry_is_price_crossed(
|
||||
pair, order["side"], price, orderbook, is_stop=True
|
||||
)
|
||||
if crossed:
|
||||
average = self.get_dry_market_fill_price(
|
||||
pair,
|
||||
order["side"],
|
||||
order["amount"],
|
||||
price,
|
||||
worst_rate=order["price"],
|
||||
orderbook=orderbook,
|
||||
)
|
||||
order.update(
|
||||
{
|
||||
"status": "closed",
|
||||
"filled": order["amount"],
|
||||
"remaining": 0,
|
||||
"average": average,
|
||||
"cost": order["amount"] * average,
|
||||
}
|
||||
)
|
||||
self.add_dry_order_fee(
|
||||
pair,
|
||||
order,
|
||||
"taker" if immediate else "maker",
|
||||
)
|
||||
return order
|
||||
if (
|
||||
order["status"] != "closed"
|
||||
and order["type"] in ["limit"]
|
||||
@@ -1560,9 +1517,8 @@ class Exchange:
|
||||
ordertype,
|
||||
side,
|
||||
amount,
|
||||
limit_rate or stop_price_norm,
|
||||
stop_price_norm,
|
||||
stop_loss=True,
|
||||
stop_price=stop_price_norm,
|
||||
leverage=leverage,
|
||||
)
|
||||
return dry_order
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from freqtrade.constants import BuySell
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
@@ -56,6 +57,13 @@ class Hyperliquid(Exchange):
|
||||
config.update(super()._ccxt_config)
|
||||
return config
|
||||
|
||||
def market_is_tradable(self, market: dict[str, Any]) -> bool:
|
||||
parent_check = super().market_is_tradable(market)
|
||||
|
||||
# Exclude hip3 markets for now - which have the format XYZ:GOOGL/USDT:USDT -
|
||||
# and XYZ:GOOGL as base
|
||||
return parent_check and ":" not in market["base"]
|
||||
|
||||
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
|
||||
# There are no leverage tiers
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
|
||||
@@ -157,8 +157,7 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||
assert "type" in order
|
||||
|
||||
assert order["type"] == order_type
|
||||
assert order["price"] == 217.8
|
||||
assert order["stopPrice"] == 220
|
||||
assert order["price"] == 220
|
||||
assert order["amount"] == 1
|
||||
|
||||
|
||||
|
||||
@@ -1111,29 +1111,21 @@ def test_create_dry_run_order_fees(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"side,limit,offset,is_stop,expected",
|
||||
"side,limit,offset,expected",
|
||||
[
|
||||
("buy", 46.0, 0.0, False, True),
|
||||
("buy", 46.0, 0.0, True, False),
|
||||
("buy", 26.0, 0.0, False, True),
|
||||
("buy", 26.0, 0.0, True, False), # Stop - didn't trigger
|
||||
("buy", 25.55, 0.0, False, False),
|
||||
("buy", 25.55, 0.0, True, True), # Stop - triggered
|
||||
("buy", 1, 0.0, False, False), # Very far away
|
||||
("buy", 1, 0.0, True, True), # Current price is above stop - triggered
|
||||
("sell", 25.5, 0.0, False, True),
|
||||
("sell", 50, 0.0, False, False), # Very far away
|
||||
("sell", 25.58, 0.0, False, False),
|
||||
("sell", 25.563, 0.01, False, False),
|
||||
("sell", 25.563, 0.0, True, False), # stop order - Not triggered, best bid
|
||||
("sell", 25.566, 0.0, True, True), # stop order - triggered
|
||||
("sell", 26, 0.01, True, True), # stop order - triggered
|
||||
("sell", 5.563, 0.01, False, True),
|
||||
("sell", 5.563, 0.0, True, False), # stop order - not triggered
|
||||
("buy", 46.0, 0.0, True),
|
||||
("buy", 26.0, 0.0, True),
|
||||
("buy", 25.55, 0.0, False),
|
||||
("buy", 1, 0.0, False), # Very far away
|
||||
("sell", 25.5, 0.0, True),
|
||||
("sell", 50, 0.0, False), # Very far away
|
||||
("sell", 25.58, 0.0, False),
|
||||
("sell", 25.563, 0.01, False),
|
||||
("sell", 5.563, 0.01, True),
|
||||
],
|
||||
)
|
||||
def test__dry_is_price_crossed_with_orderbook(
|
||||
default_conf, mocker, order_book_l2_usd, side, limit, offset, is_stop, expected
|
||||
default_conf, mocker, order_book_l2_usd, side, limit, offset, expected
|
||||
):
|
||||
# Best bid 25.563
|
||||
# Best ask 25.566
|
||||
@@ -1142,14 +1134,14 @@ def test__dry_is_price_crossed_with_orderbook(
|
||||
exchange.fetch_l2_order_book = order_book_l2_usd
|
||||
orderbook = order_book_l2_usd.return_value
|
||||
result = exchange._dry_is_price_crossed(
|
||||
"LTC/USDT", side, limit, orderbook=orderbook, offset=offset, is_stop=is_stop
|
||||
"LTC/USDT", side, limit, orderbook=orderbook, offset=offset
|
||||
)
|
||||
assert result is expected
|
||||
assert order_book_l2_usd.call_count == 0
|
||||
|
||||
# Test without passing orderbook
|
||||
order_book_l2_usd.reset_mock()
|
||||
result = exchange._dry_is_price_crossed("LTC/USDT", side, limit, offset=offset, is_stop=is_stop)
|
||||
result = exchange._dry_is_price_crossed("LTC/USDT", side, limit, offset=offset)
|
||||
assert result is expected
|
||||
|
||||
|
||||
@@ -1173,10 +1165,7 @@ def test__dry_is_price_crossed_without_orderbook_support(default_conf, mocker):
|
||||
exchange.fetch_l2_order_book = MagicMock()
|
||||
mocker.patch(f"{EXMS}.exchange_has", return_value=False)
|
||||
assert exchange._dry_is_price_crossed("LTC/USDT", "buy", 1.0)
|
||||
assert exchange._dry_is_price_crossed("LTC/USDT", "sell", 1.0)
|
||||
assert exchange.fetch_l2_order_book.call_count == 0
|
||||
assert not exchange._dry_is_price_crossed("LTC/USDT", "buy", 1.0, is_stop=True)
|
||||
assert not exchange._dry_is_price_crossed("LTC/USDT", "sell", 1.0, is_stop=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -123,8 +123,7 @@ def test_create_stoploss_order_dry_run_htx(default_conf, mocker):
|
||||
assert "type" in order
|
||||
|
||||
assert order["type"] == order_type
|
||||
assert order["price"] == 217.8
|
||||
assert order["stopPrice"] == 220
|
||||
assert order["price"] == 220
|
||||
assert order["amount"] == 1
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user