Merge branch 'develop' into feat/binance_trades_fast

This commit is contained in:
Matthias
2025-02-13 06:43:56 +01:00
51 changed files with 2149 additions and 857 deletions

View File

@@ -1779,15 +1779,6 @@ def limit_buy_order_open():
}
@pytest.fixture(scope="function")
def limit_buy_order(limit_buy_order_open):
order = deepcopy(limit_buy_order_open)
order["status"] = "closed"
order["filled"] = order["amount"]
order["remaining"] = 0.0
return order
@pytest.fixture
def limit_buy_order_old():
return {

View File

@@ -645,7 +645,7 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine):
# Tried once, failed
lam_spy.reset_mock()
# When forceing (bot startup), it should retry 3 times.
# When forcing (bot startup), it should retry 3 times.
exchange.reload_markets(force=True)
assert lam_spy.call_count == 4
assert exchange.markets == updated_markets
@@ -4439,7 +4439,7 @@ def test_ohlcv_candle_limit(default_conf, mocker, exchange_name):
pytest.skip("Tested separately for okx")
exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
timeframes = ("1m", "5m", "1h")
expected = exchange._ft_has["ohlcv_candle_limit"]
expected = exchange._ft_has.get("ohlcv_candle_limit", 500)
for timeframe in timeframes:
# if 'ohlcv_candle_limit_per_timeframe' in exchange._ft_has:
# expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe]
@@ -6262,3 +6262,26 @@ def test_price_to_precision_with_default_conf(default_conf, mocker):
prec_price = patched_ex.price_to_precision("XRP/USDT", 1.0000000101)
assert prec_price == 1.00000001
assert prec_price == 1.00000001
def test_exchange_features(default_conf, mocker):
conf = copy.deepcopy(default_conf)
exchange = get_patched_exchange(mocker, conf)
exchange._api_async.features = {
"spot": {
"fetchOHLCV": {
"limit": 995,
}
},
"swap": {
"linear": {
"fetchOHLCV": {
"limit": 997,
}
}
},
}
assert exchange.features("spot", "fetchOHLCV", "limit", 500) == 995
assert exchange.features("futures", "fetchOHLCV", "limit", 500) == 997
# Fall back to default
assert exchange.features("futures", "fetchOHLCV_else", "limit", 601) == 601

View File

@@ -1,9 +1,12 @@
import asyncio
import logging
import threading
from datetime import timedelta
from time import sleep
from unittest.mock import AsyncMock, MagicMock
from ccxt import NotSupported
from freqtrade.enums import CandleType
from freqtrade.exchange.exchange_ws import ExchangeWS
from ft_client.test_client.test_rest_client import log_has_re
@@ -61,15 +64,18 @@ def patch_eventloop_threading(exchange):
pass
async def test_exchangews_ohlcv(mocker, time_machine):
async def test_exchangews_ohlcv(mocker, time_machine, caplog):
config = MagicMock()
ccxt_object = MagicMock()
caplog.set_level(logging.DEBUG)
async def sleeper(*args, **kwargs):
# pass
await asyncio.sleep(0.12)
return MagicMock()
ccxt_object.un_watch_ohlcv_for_symbols = AsyncMock(side_effect=NotSupported)
ccxt_object.watch_ohlcv = AsyncMock(side_effect=sleeper)
ccxt_object.close = AsyncMock()
time_machine.move_to("2024-11-01 01:00:02 +00:00")
@@ -101,11 +107,14 @@ async def test_exchangews_ohlcv(mocker, time_machine):
time_machine.shift(timedelta(minutes=5))
exchange_ws.schedule_ohlcv("ETH/BTC", "1m", CandleType.SPOT)
await asyncio.sleep(1)
assert log_has_re("un_watch_ohlcv_for_symbols not supported: ", caplog)
# XRP/BTC should be cleaned up.
assert exchange_ws._klines_watching == {
("ETH/BTC", "1m", CandleType.SPOT),
}
# Cleanup happened.
ccxt_object.un_watch_ohlcv_for_symbols = AsyncMock(side_effect=ValueError)
exchange_ws.schedule_ohlcv("ETH/BTC", "1m", CandleType.SPOT)
assert exchange_ws._klines_watching == {
("ETH/BTC", "1m", CandleType.SPOT),
@@ -117,6 +126,7 @@ async def test_exchangews_ohlcv(mocker, time_machine):
finally:
# Cleanup
exchange_ws.cleanup()
assert log_has_re("Exception in _unwatch_ohlcv", caplog)
async def test_exchangews_get_ohlcv(mocker, caplog):

View File

@@ -21,6 +21,7 @@ EXCHANGES = {
"use_ci_proxy": True,
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 1000,
"futures": True,
"futures_pair": "BTC/USDT:USDT",
"hasQuoteVolumeFutures": True,
@@ -96,6 +97,7 @@ EXCHANGES = {
"stake_currency": "USDT",
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 1000,
"futures": False,
"skip_ws_tests": True,
"sample_order": [
@@ -136,6 +138,7 @@ EXCHANGES = {
"stake_currency": "USD",
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 720,
"leverage_tiers_public": False,
"leverage_in_spot_market": True,
"trades_lookback_hours": 12,
@@ -162,6 +165,7 @@ EXCHANGES = {
"stake_currency": "USDT",
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 1500,
"leverage_tiers_public": False,
"leverage_in_spot_market": True,
"sample_order": [
@@ -229,6 +233,7 @@ EXCHANGES = {
"stake_currency": "USDT",
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 1000,
"futures": True,
"futures_pair": "BTC/USDT:USDT",
"hasQuoteVolumeFutures": True,
@@ -345,6 +350,7 @@ EXCHANGES = {
"stake_currency": "USDT",
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 300,
"futures": True,
"futures_pair": "BTC/USDT:USDT",
"hasQuoteVolumeFutures": False,
@@ -358,6 +364,7 @@ EXCHANGES = {
"hasQuoteVolume": True,
"use_ci_proxy": True,
"timeframe": "1h",
"candle_count": 1000,
"futures_pair": "BTC/USDT:USDT",
"futures": True,
"orderbook_max_entries": 50,
@@ -398,6 +405,7 @@ EXCHANGES = {
"stake_currency": "USDT",
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 200,
"orderbook_max_entries": 50,
},
"htx": {
@@ -405,13 +413,14 @@ EXCHANGES = {
"stake_currency": "BTC",
"hasQuoteVolume": True,
"timeframe": "1h",
"futures": False,
"candle_count": 1000,
},
"bitvavo": {
"pair": "BTC/EUR",
"stake_currency": "EUR",
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 1440,
"leverage_tiers_public": False,
"leverage_in_spot_market": False,
},
@@ -420,6 +429,7 @@ EXCHANGES = {
"stake_currency": "USDT",
"hasQuoteVolume": True,
"timeframe": "1h",
"candle_count": 1000,
"futures": False,
"sample_order": [
{
@@ -482,6 +492,7 @@ EXCHANGES = {
"hasQuoteVolume": False,
"timeframe": "1h",
"futures": True,
"candle_count": 5000,
"orderbook_max_entries": 20,
"futures_pair": "BTC/USDC:USDC",
"hasQuoteVolumeFutures": True,

View File

@@ -48,6 +48,22 @@ class TestCCXTExchange:
}
)
def test_ohlcv_limit(self, exchange: EXCHANGE_FIXTURE_TYPE):
exch, exchangename = exchange
expected_count = EXCHANGES[exchangename].get("candle_count")
if not expected_count:
pytest.skip("No expected candle count for exchange")
assert exch.ohlcv_candle_limit("1m", CandleType.SPOT) == expected_count
def test_ohlcv_limit_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
exch, exchangename = exchange_futures
expected_count = EXCHANGES[exchangename].get("candle_count")
if not expected_count:
pytest.skip("No expected candle count for exchange")
assert exch.ohlcv_candle_limit("1m", CandleType.SPOT) == expected_count
def test_load_markets_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
exchange, exchangename = exchange_futures
pair = EXCHANGES[exchangename]["pair"]

View File

@@ -107,7 +107,7 @@ def test_volume_change_pair_list_init_wrong_lookback_period(mocker, rpl_config):
with pytest.raises(
OperationalException,
match=r"ChangeFilter requires lookback_period to not exceed"
r" exchange max request size \(1000\)",
r" exchange max request size \(\d+\)",
):
get_patched_freqtradebot(mocker, rpl_config)

View File

@@ -36,6 +36,7 @@ from tests.conftest import (
EXMS,
create_mock_trades,
create_mock_trades_usdt,
generate_test_data,
get_mock_coro,
get_patched_freqtradebot,
log_has,
@@ -220,16 +221,16 @@ def test_api_ws_auth(botclient):
bad_token = "bad-ws_token"
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect(url(bad_token)) as websocket:
websocket.receive()
with client.websocket_connect(url(bad_token)):
pass
good_token = _TEST_WS_TOKEN
with client.websocket_connect(url(good_token)) as websocket:
with client.websocket_connect(url(good_token)):
pass
jwt_secret = ftbot.config["api_server"].get("jwt_secret_key", "super-secret")
jwt_token = create_token({"identity": {"u": "Freqtrade"}}, jwt_secret)
with client.websocket_connect(url(jwt_token)) as websocket:
with client.websocket_connect(url(jwt_token)):
pass
@@ -1914,6 +1915,15 @@ def test_api_pair_history(botclient, tmp_path, mocker):
timeframe = "5m"
lfm = mocker.patch("freqtrade.strategy.interface.IStrategy.load_freqAI_model")
# Wrong mode
rc = client_get(
client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}",
)
assert_response(rc, 503)
_ftbot.config["runmode"] = RunMode.WEBSERVER
# No pair
rc = client_get(
client,
@@ -2025,6 +2035,87 @@ def test_api_pair_history(botclient, tmp_path, mocker):
assert_response(rc, 502)
assert rc.json()["detail"] == ("No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
# No strategy
rc = client_post(
client,
f"{BASE_URI}/pair_history",
data={
"pair": "UNITTEST/BTC",
"timeframe": timeframe,
"timerange": "20180111-20180112",
# "strategy": CURRENT_TEST_STRATEGY,
"columns": ["rsi", "fastd", "fastk"],
},
)
assert_response(rc, 200)
result = rc.json()
assert result["length"] == 289
assert len(result["data"]) == result["length"]
assert "columns" in result
assert "data" in result
# Result without strategy won't have enter_long assigned.
assert "enter_long" not in result["columns"]
assert result["columns"] == ["date", "open", "high", "low", "close", "volume", "__date_ts"]
def test_api_pair_history_live_mode(botclient, tmp_path, mocker):
_ftbot, client = botclient
_ftbot.config["user_data_dir"] = tmp_path
_ftbot.config["runmode"] = RunMode.WEBSERVER
mocker.patch("freqtrade.strategy.interface.IStrategy.load_freqAI_model")
# no strategy, live data
gho = mocker.patch(
"freqtrade.exchange.binance.Binance.get_historic_ohlcv",
return_value=generate_test_data("1h", 100),
)
rc = client_post(
client,
f"{BASE_URI}/pair_history",
data={
"pair": "UNITTEST/BTC",
"timeframe": "1h",
"timerange": "20240101-",
# "strategy": CURRENT_TEST_STRATEGY,
"columns": ["rsi", "fastd", "fastk"],
"live_mode": True,
},
)
assert_response(rc, 200)
result = rc.json()
# 100 candles - as in the generate_test_data call above
assert result["length"] == 100
assert len(result["data"]) == result["length"]
assert result["columns"] == ["date", "open", "high", "low", "close", "volume", "__date_ts"]
assert gho.call_count == 1
gho.reset_mock()
rc = client_post(
client,
f"{BASE_URI}/pair_history",
data={
"pair": "UNITTEST/BTC",
"timeframe": "1h",
"timerange": "20240101-",
"strategy": CURRENT_TEST_STRATEGY,
"columns": ["rsi", "fastd", "fastk"],
"live_mode": True,
},
)
assert_response(rc, 200)
result = rc.json()
# 80 candles - as in the generate_test_data call above - 20 startup candles
assert result["length"] == 100 - 20
assert len(result["data"]) == result["length"]
assert "rsi" in result["columns"]
assert "enter_long" in result["columns"]
assert "fastd" in result["columns"]
assert "date" in result["columns"]
assert gho.call_count == 1
def test_api_plot_config(botclient, mocker, tmp_path):
ftbot, client = botclient
@@ -2849,7 +2940,7 @@ def test_api_ws_send_msg(default_conf, mocker, caplog):
ApiServer.shutdown()
def test_api_download_data(botclient, mocker, tmp_path, caplog):
def test_api_download_data(botclient, mocker, tmp_path):
ftbot, client = botclient
rc = client_post(client, f"{BASE_URI}/download_data", data={})
@@ -2918,3 +3009,55 @@ def test_api_download_data(botclient, mocker, tmp_path, caplog):
assert response["job_category"] == "download_data"
assert response["status"] == "failed"
assert response["error"] == "Download error"
def test_api_markets_live(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/markets")
assert_response(rc, 200)
response = rc.json()
assert "markets" in response
assert len(response["markets"]) >= 0
assert response["markets"]["XRP/USDT"] == {
"base": "XRP",
"quote": "USDT",
"symbol": "XRP/USDT",
"spot": True,
"swap": False,
}
assert "BTC/USDT" in response["markets"]
assert "XRP/BTC" in response["markets"]
rc = client_get(
client,
f"{BASE_URI}/markets?base=XRP",
)
assert_response(rc, 200)
response = rc.json()
assert "XRP/USDT" in response["markets"]
assert "XRP/BTC" in response["markets"]
assert "BTC/USDT" not in response["markets"]
def test_api_markets_webserver(botclient):
# Ensure webserver exchanges are reset
ApiBG.exchanges = {}
ftbot, client = botclient
# Test in webserver mode
ftbot.config["runmode"] = RunMode.WEBSERVER
rc = client_get(client, f"{BASE_URI}/markets?exchange=binance")
assert_response(rc, 200)
response = rc.json()
assert "markets" in response
assert len(response["markets"]) >= 0
assert response["exchange_id"] == "binance"
rc = client_get(client, f"{BASE_URI}/markets?exchange=hyperliquid")
assert_response(rc, 200)
assert "hyperliquid_spot" in ApiBG.exchanges
assert "binance_spot" in ApiBG.exchanges