mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-03-01 15:52:43 +00:00
Merge branch 'develop' into feat/binance_trades_fast
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user