Merge branch 'develop' into pr/froggleston/7861
This commit is contained in:
+2
-10
@@ -3,7 +3,7 @@ import json
|
||||
import logging
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from unittest.mock import MagicMock, Mock, PropertyMock
|
||||
@@ -12,7 +12,6 @@ import arrow
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from telegram import Chat, Message, Update
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.commands import Arguments
|
||||
@@ -504,7 +503,7 @@ def get_default_conf(testdatadir):
|
||||
{"method": "StaticPairList"}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": True,
|
||||
"enabled": False,
|
||||
"token": "token",
|
||||
"chat_id": "0",
|
||||
"notification_settings": {},
|
||||
@@ -550,13 +549,6 @@ def get_default_conf_usdt(testdatadir):
|
||||
return configuration
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def update():
|
||||
_update = Update(0)
|
||||
_update.message = Message(0, datetime.utcnow(), Chat(0, 0))
|
||||
return _update
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fee():
|
||||
return MagicMock(return_value=0.0025)
|
||||
|
||||
@@ -98,7 +98,7 @@ def test_load_backtest_data_new_format(testdatadir):
|
||||
assert bt_data.equals(bt_data3)
|
||||
|
||||
with pytest.raises(ValueError, match=r"File .* does not exist\."):
|
||||
load_backtest_data(str("filename") + "nofile")
|
||||
load_backtest_data("filename" + "nofile")
|
||||
|
||||
with pytest.raises(ValueError, match=r"Unknown dataformat."):
|
||||
load_backtest_data(testdatadir / "backtest_results" / LAST_BT_RESULT_FN)
|
||||
|
||||
@@ -252,7 +252,7 @@ def test_datahandler__check_empty_df(testdatadir, caplog):
|
||||
assert log_has_re(expected_text, caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('datahandler', ['feather', 'parquet'])
|
||||
@pytest.mark.parametrize('datahandler', ['parquet'])
|
||||
def test_datahandler_trades_not_supported(datahandler, testdatadir, ):
|
||||
dh = get_datahandler(testdatadir, datahandler)
|
||||
with pytest.raises(NotImplementedError):
|
||||
@@ -496,6 +496,58 @@ def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir):
|
||||
assert unlinkmock.call_count == 2
|
||||
|
||||
|
||||
def test_featherdatahandler_trades_load(testdatadir):
|
||||
dh = get_datahandler(testdatadir, 'feather')
|
||||
trades = dh.trades_load('XRP/ETH')
|
||||
assert isinstance(trades, list)
|
||||
assert trades[0][0] == 1570752011620
|
||||
assert trades[-1][-1] == 0.1986231
|
||||
|
||||
trades1 = dh.trades_load('UNITTEST/NONEXIST')
|
||||
assert trades1 == []
|
||||
|
||||
|
||||
def test_featherdatahandler_trades_store(testdatadir, tmpdir):
|
||||
tmpdir1 = Path(tmpdir)
|
||||
dh = get_datahandler(testdatadir, 'feather')
|
||||
trades = dh.trades_load('XRP/ETH')
|
||||
|
||||
dh1 = get_datahandler(tmpdir1, 'feather')
|
||||
dh1.trades_store('XRP/NEW', trades)
|
||||
file = tmpdir1 / 'XRP_NEW-trades.feather'
|
||||
assert file.is_file()
|
||||
# Load trades back
|
||||
trades_new = dh1.trades_load('XRP/NEW')
|
||||
|
||||
assert len(trades_new) == len(trades)
|
||||
assert trades[0][0] == trades_new[0][0]
|
||||
assert trades[0][1] == trades_new[0][1]
|
||||
# assert trades[0][2] == trades_new[0][2] # This is nan - so comparison does not make sense
|
||||
assert trades[0][3] == trades_new[0][3]
|
||||
assert trades[0][4] == trades_new[0][4]
|
||||
assert trades[0][5] == trades_new[0][5]
|
||||
assert trades[0][6] == trades_new[0][6]
|
||||
assert trades[-1][0] == trades_new[-1][0]
|
||||
assert trades[-1][1] == trades_new[-1][1]
|
||||
# assert trades[-1][2] == trades_new[-1][2] # This is nan - so comparison does not make sense
|
||||
assert trades[-1][3] == trades_new[-1][3]
|
||||
assert trades[-1][4] == trades_new[-1][4]
|
||||
assert trades[-1][5] == trades_new[-1][5]
|
||||
assert trades[-1][6] == trades_new[-1][6]
|
||||
|
||||
|
||||
def test_featherdatahandler_trades_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
|
||||
dh = get_datahandler(testdatadir, 'feather')
|
||||
assert not dh.trades_purge('UNITTEST/NONEXIST')
|
||||
assert unlinkmock.call_count == 0
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
assert dh.trades_purge('UNITTEST/NONEXIST')
|
||||
assert unlinkmock.call_count == 1
|
||||
|
||||
|
||||
def test_gethandlerclass():
|
||||
cl = get_datahandlerclass('json')
|
||||
assert cl == JsonDataHandler
|
||||
|
||||
@@ -409,7 +409,7 @@ def test_init_with_refresh(default_conf, mocker) -> None:
|
||||
|
||||
|
||||
def test_file_dump_json_tofile(testdatadir) -> None:
|
||||
file = testdatadir / 'test_{id}.json'.format(id=str(uuid.uuid4()))
|
||||
file = testdatadir / f'test_{uuid.uuid4()}.json'
|
||||
data = {'bar': 'foo'}
|
||||
|
||||
# check the file we will create does not exist
|
||||
|
||||
@@ -11,6 +11,19 @@ from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has_re
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,type,time_in_force,expected', [
|
||||
('buy', 'limit', 'gtc', {'timeInForce': 'GTC'}),
|
||||
('buy', 'limit', 'IOC', {'timeInForce': 'IOC'}),
|
||||
('buy', 'market', 'IOC', {}),
|
||||
('buy', 'limit', 'PO', {'timeInForce': 'PO'}),
|
||||
('sell', 'limit', 'PO', {'timeInForce': 'PO'}),
|
||||
('sell', 'market', 'PO', {}),
|
||||
])
|
||||
def test__get_params_binance(default_conf, mocker, side, type, time_in_force, expected):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='binance')
|
||||
assert exchange._get_params(side, type, 1, False, time_in_force) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trademode', [TradingMode.FUTURES, TradingMode.SPOT])
|
||||
@pytest.mark.parametrize('limitratio,expected,side', [
|
||||
(None, 220 * 0.99, "sell"),
|
||||
@@ -35,11 +48,11 @@ def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expecte
|
||||
default_conf['margin_mode'] = MarginMode.ISOLATED
|
||||
default_conf['trading_mode'] = trademode
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
@@ -114,11 +127,11 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||
order_type = 'stop_loss_limit'
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
@@ -542,7 +555,6 @@ def test__set_leverage_binance(mocker, default_conf):
|
||||
"set_leverage",
|
||||
pair="XRP/USDT",
|
||||
leverage=5.0,
|
||||
trading_mode=TradingMode.FUTURES
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ EXCHANGES = {
|
||||
'stake_currency': 'USDT',
|
||||
'use_ci_proxy': True,
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': True,
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'hasQuoteVolumeFutures': True,
|
||||
@@ -66,7 +66,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': False,
|
||||
'sample_order': [{
|
||||
"symbol": "SOLUSDT",
|
||||
@@ -91,7 +91,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'leverage_tiers_public': False,
|
||||
'leverage_in_spot_market': True,
|
||||
},
|
||||
@@ -99,7 +99,7 @@ EXCHANGES = {
|
||||
'pair': 'XRP/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'leverage_tiers_public': False,
|
||||
'leverage_in_spot_market': True,
|
||||
'sample_order': [
|
||||
@@ -141,7 +141,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': True,
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'hasQuoteVolumeFutures': True,
|
||||
@@ -215,7 +215,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': True,
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'hasQuoteVolumeFutures': False,
|
||||
@@ -226,7 +226,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'futures': True,
|
||||
'leverage_tiers_public': True,
|
||||
@@ -253,14 +253,14 @@ EXCHANGES = {
|
||||
'pair': 'ETH/BTC',
|
||||
'stake_currency': 'BTC',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': False,
|
||||
},
|
||||
'bitvavo': {
|
||||
'pair': 'BTC/EUR',
|
||||
'stake_currency': 'EUR',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'leverage_tiers_public': False,
|
||||
'leverage_in_spot_market': False,
|
||||
},
|
||||
@@ -528,9 +528,11 @@ class TestCCXTExchange():
|
||||
assert res[1] == timeframe
|
||||
assert res[2] == candle_type
|
||||
candles = res[3]
|
||||
candle_count = exchange.ohlcv_candle_limit(timeframe, candle_type, since_ms) * 0.9
|
||||
candle_count1 = (now.timestamp() * 1000 - since_ms) // timeframe_ms
|
||||
assert len(candles) >= min(candle_count, candle_count1)
|
||||
factor = 0.9
|
||||
candle_count = exchange.ohlcv_candle_limit(timeframe, candle_type, since_ms) * factor
|
||||
candle_count1 = (now.timestamp() * 1000 - since_ms) // timeframe_ms * factor
|
||||
assert len(candles) >= min(candle_count, candle_count1), \
|
||||
f"{len(candles)} < {candle_count} in {timeframe}, Offset: {offset} {factor}"
|
||||
assert candles[0][0] == since_ms or (since_ms + timeframe_ms)
|
||||
|
||||
def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||
|
||||
+104
-75
@@ -8,6 +8,7 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
||||
import arrow
|
||||
import ccxt
|
||||
import pytest
|
||||
from ccxt import DECIMAL_PLACES, ROUND, ROUND_UP, TICK_SIZE, TRUNCATE
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
@@ -113,18 +114,21 @@ async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fu
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
await getattr(exchange, fun)(**kwargs)
|
||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||
exchange.close()
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
await getattr(exchange, fun)(**kwargs)
|
||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||
exchange.close()
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
await getattr(exchange, fun)(**kwargs)
|
||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
|
||||
exchange.close()
|
||||
|
||||
|
||||
def test_init(default_conf, mocker, caplog):
|
||||
@@ -312,35 +316,54 @@ def test_amount_to_precision(amount, precision_mode, precision, expected,):
|
||||
assert amount_to_precision(amount, precision, precision_mode) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
||||
(2.34559, 2, 4, 2.3456),
|
||||
(2.34559, 2, 5, 2.34559),
|
||||
(2.34559, 2, 3, 2.346),
|
||||
(2.9999, 2, 3, 3.000),
|
||||
(2.9909, 2, 3, 2.991),
|
||||
# Tests for Tick_size
|
||||
(2.34559, 4, 0.0001, 2.3456),
|
||||
(2.34559, 4, 0.00001, 2.34559),
|
||||
(2.34559, 4, 0.001, 2.346),
|
||||
(2.9999, 4, 0.001, 3.000),
|
||||
(2.9909, 4, 0.001, 2.991),
|
||||
(2.9909, 4, 0.005, 2.995),
|
||||
(2.9973, 4, 0.005, 3.0),
|
||||
(2.9977, 4, 0.005, 3.0),
|
||||
(234.43, 4, 0.5, 234.5),
|
||||
(234.53, 4, 0.5, 235.0),
|
||||
(0.891534, 4, 0.0001, 0.8916),
|
||||
(64968.89, 4, 0.01, 64968.89),
|
||||
(0.000000003483, 4, 1e-12, 0.000000003483),
|
||||
|
||||
@pytest.mark.parametrize("price,precision_mode,precision,expected,rounding_mode", [
|
||||
# Tests for DECIMAL_PLACES, ROUND_UP
|
||||
(2.34559, 2, 4, 2.3456, ROUND_UP),
|
||||
(2.34559, 2, 5, 2.34559, ROUND_UP),
|
||||
(2.34559, 2, 3, 2.346, ROUND_UP),
|
||||
(2.9999, 2, 3, 3.000, ROUND_UP),
|
||||
(2.9909, 2, 3, 2.991, ROUND_UP),
|
||||
# Tests for DECIMAL_PLACES, ROUND
|
||||
(2.345600000000001, DECIMAL_PLACES, 4, 2.3456, ROUND),
|
||||
(2.345551, DECIMAL_PLACES, 4, 2.3456, ROUND),
|
||||
(2.49, DECIMAL_PLACES, 0, 2., ROUND),
|
||||
(2.51, DECIMAL_PLACES, 0, 3., ROUND),
|
||||
(5.1, DECIMAL_PLACES, -1, 10., ROUND),
|
||||
(4.9, DECIMAL_PLACES, -1, 0., ROUND),
|
||||
# Tests for TICK_SIZE, ROUND_UP
|
||||
(2.34559, TICK_SIZE, 0.0001, 2.3456, ROUND_UP),
|
||||
(2.34559, TICK_SIZE, 0.00001, 2.34559, ROUND_UP),
|
||||
(2.34559, TICK_SIZE, 0.001, 2.346, ROUND_UP),
|
||||
(2.9999, TICK_SIZE, 0.001, 3.000, ROUND_UP),
|
||||
(2.9909, TICK_SIZE, 0.001, 2.991, ROUND_UP),
|
||||
(2.9909, TICK_SIZE, 0.005, 2.995, ROUND_UP),
|
||||
(2.9973, TICK_SIZE, 0.005, 3.0, ROUND_UP),
|
||||
(2.9977, TICK_SIZE, 0.005, 3.0, ROUND_UP),
|
||||
(234.43, TICK_SIZE, 0.5, 234.5, ROUND_UP),
|
||||
(234.53, TICK_SIZE, 0.5, 235.0, ROUND_UP),
|
||||
(0.891534, TICK_SIZE, 0.0001, 0.8916, ROUND_UP),
|
||||
(64968.89, TICK_SIZE, 0.01, 64968.89, ROUND_UP),
|
||||
(0.000000003483, TICK_SIZE, 1e-12, 0.000000003483, ROUND_UP),
|
||||
# Tests for TICK_SIZE, ROUND
|
||||
(2.49, TICK_SIZE, 1., 2., ROUND),
|
||||
(2.51, TICK_SIZE, 1., 3., ROUND),
|
||||
(2.000000051, TICK_SIZE, 0.0000001, 2.0000001, ROUND),
|
||||
(2.000000049, TICK_SIZE, 0.0000001, 2., ROUND),
|
||||
(2.9909, TICK_SIZE, 0.005, 2.990, ROUND),
|
||||
(2.9973, TICK_SIZE, 0.005, 2.995, ROUND),
|
||||
(2.9977, TICK_SIZE, 0.005, 3.0, ROUND),
|
||||
(234.24, TICK_SIZE, 0.5, 234., ROUND),
|
||||
(234.26, TICK_SIZE, 0.5, 234.5, ROUND),
|
||||
# Tests for TRUNCATTE
|
||||
(2.34559, 2, 4, 2.3455, TRUNCATE),
|
||||
(2.34559, 2, 5, 2.34559, TRUNCATE),
|
||||
(2.34559, 2, 3, 2.345, TRUNCATE),
|
||||
(2.9999, 2, 3, 2.999, TRUNCATE),
|
||||
(2.9909, 2, 3, 2.990, TRUNCATE),
|
||||
])
|
||||
def test_price_to_precision(price, precision_mode, precision, expected):
|
||||
# digits counting mode
|
||||
# DECIMAL_PLACES = 2
|
||||
# SIGNIFICANT_DIGITS = 3
|
||||
# TICK_SIZE = 4
|
||||
|
||||
assert price_to_precision(price, precision, precision_mode) == expected
|
||||
def test_price_to_precision(price, precision_mode, precision, expected, rounding_mode):
|
||||
assert price_to_precision(
|
||||
price, precision, precision_mode, rounding_mode=rounding_mode) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
||||
@@ -414,7 +437,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
||||
}
|
||||
mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets))
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
expected_result = 2 * 2 * (1 + 0.05) / (1 - abs(stoploss))
|
||||
expected_result = 2 * 2 * (1 + 0.05)
|
||||
assert pytest.approx(result) == expected_result
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
|
||||
@@ -423,14 +446,14 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||
assert result == 20000
|
||||
|
||||
# min amount and cost are set (cost is minimal)
|
||||
# min amount and cost are set (cost is minimal and therefore ignored)
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
'cost': {'min': 2, 'max': None},
|
||||
'amount': {'min': 2, 'max': None},
|
||||
}
|
||||
mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets))
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
expected_result = max(2, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss))
|
||||
expected_result = max(2, 2 * 2) * (1 + 0.05)
|
||||
assert pytest.approx(result) == expected_result
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
|
||||
@@ -473,6 +496,9 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||
assert result == 1000
|
||||
|
||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, 12.0)
|
||||
assert result == 1000 / 12
|
||||
|
||||
markets["ETH/BTC"]["contractSize"] = '0.01'
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
@@ -1039,9 +1065,9 @@ def test_validate_ordertypes(default_conf, mocker):
|
||||
('bybit', 'last', True),
|
||||
('bybit', 'mark', True),
|
||||
('bybit', 'index', True),
|
||||
# ('okx', 'last', True),
|
||||
# ('okx', 'mark', True),
|
||||
# ('okx', 'index', True),
|
||||
('okx', 'last', True),
|
||||
('okx', 'mark', True),
|
||||
('okx', 'index', True),
|
||||
('gate', 'last', True),
|
||||
('gate', 'mark', True),
|
||||
('gate', 'index', True),
|
||||
@@ -1229,9 +1255,10 @@ def test_create_dry_run_order_fees(
|
||||
("buy", 29.563, True, True),
|
||||
("sell", 21.563, True, True),
|
||||
])
|
||||
@pytest.mark.parametrize("leverage", [1, 2, 5])
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, filled, caplog,
|
||||
exchange_name, order_book_l2_usd, converted):
|
||||
exchange_name, order_book_l2_usd, converted, leverage):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
mocker.patch.multiple(EXMS,
|
||||
@@ -1245,7 +1272,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
|
||||
side=side,
|
||||
amount=1,
|
||||
rate=price,
|
||||
leverage=1.0
|
||||
leverage=leverage,
|
||||
)
|
||||
assert order_book_l2_usd.call_count == 1
|
||||
assert 'id' in order
|
||||
@@ -1269,6 +1296,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
|
||||
assert order_book_l2_usd.call_count == (1 if not filled else 0)
|
||||
assert order_closed['status'] == ('open' if not filled else 'closed')
|
||||
assert order_closed['filled'] == (0 if not filled else 1)
|
||||
assert order_closed['cost'] == 1 * order_closed['average']
|
||||
|
||||
order_book_l2_usd.reset_mock()
|
||||
|
||||
@@ -1291,9 +1319,10 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
|
||||
("sell", 25.564, 1000, 25.5555), # More than orderbook return
|
||||
("sell", 27, 10000, 25.65), # max-slippage 5%
|
||||
])
|
||||
@pytest.mark.parametrize("leverage", [1, 2, 5])
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amount, endprice,
|
||||
exchange_name, order_book_l2_usd):
|
||||
exchange_name, order_book_l2_usd, leverage):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
mocker.patch.multiple(EXMS,
|
||||
@@ -1307,7 +1336,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
|
||||
side=side,
|
||||
amount=amount,
|
||||
rate=rate,
|
||||
leverage=1.0
|
||||
leverage=leverage,
|
||||
)
|
||||
assert 'id' in order
|
||||
assert f'dry_run_{side}_' in order["id"]
|
||||
@@ -1316,6 +1345,8 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
|
||||
assert order["symbol"] == "LTC/USDT"
|
||||
assert order['status'] == 'closed'
|
||||
assert order['filled'] == amount
|
||||
assert order['amount'] == amount
|
||||
assert pytest.approx(order['cost']) == amount * order['average']
|
||||
assert round(order["average"], 4) == round(endprice, 4)
|
||||
|
||||
|
||||
@@ -1436,7 +1467,10 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
if exchange._order_needs_price(order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
@@ -1541,7 +1575,10 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
if exchange._order_needs_price(order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
# Market orders should not send timeInForce!!
|
||||
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
|
||||
|
||||
@@ -1585,7 +1622,10 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
if exchange._order_needs_price(order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
@@ -1679,7 +1719,10 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
if exchange._order_needs_price(order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
# Market orders should not send timeInForce!!
|
||||
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
|
||||
|
||||
@@ -2248,7 +2291,6 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
|
||||
assert res[pair2].at[0, 'open']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name):
|
||||
ohlcv = [
|
||||
@@ -2277,7 +2319,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
|
||||
assert res[3] == ohlcv
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == 1
|
||||
assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog)
|
||||
|
||||
exchange.close()
|
||||
# exchange = Exchange(default_conf)
|
||||
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
||||
"_async_get_candle_history", "fetch_ohlcv",
|
||||
@@ -2292,15 +2334,17 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
|
||||
await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT,
|
||||
(arrow.utcnow().int_timestamp - 2000) * 1000)
|
||||
|
||||
exchange.close()
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
|
||||
r'historical candle \(OHLCV\) data\..*'):
|
||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT,
|
||||
(arrow.utcnow().int_timestamp - 2000) * 1000)
|
||||
exchange.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
|
||||
from freqtrade.exchange.common import _reset_logging_mixin
|
||||
_reset_logging_mixin()
|
||||
@@ -2341,9 +2385,9 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
|
||||
# Expect the "returned exception" message 12 times (4 retries * 3 (loop))
|
||||
assert num_log_has_re(msg, caplog) == 12
|
||||
assert num_log_has_re(msg2, caplog) == 9
|
||||
exchange.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
|
||||
""" Test empty exchange result """
|
||||
ohlcv = []
|
||||
@@ -2363,6 +2407,7 @@ async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
|
||||
assert res[2] == CandleType.SPOT
|
||||
assert res[3] == ohlcv
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == 1
|
||||
exchange.close()
|
||||
|
||||
|
||||
def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
|
||||
@@ -2757,7 +2802,6 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
|
||||
assert res_ohlcv[9][5] == 2.31452783
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
fetch_trades_result):
|
||||
@@ -2785,8 +2829,8 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'}
|
||||
assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog)
|
||||
exchange.close()
|
||||
|
||||
exchange = Exchange(default_conf)
|
||||
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
||||
"_async_fetch_trades", "fetch_trades",
|
||||
pair='ABCD/BTC', since=None)
|
||||
@@ -2796,15 +2840,16 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
|
||||
exchange.close()
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
|
||||
r'historical trade data\..*'):
|
||||
api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
|
||||
exchange.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, exchange_name,
|
||||
fetch_trades_result):
|
||||
@@ -2839,6 +2884,7 @@ async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, e
|
||||
pair = 'ETH/USDT:USDT'
|
||||
res = await exchange._async_fetch_trades(pair, since=None, params=None)
|
||||
assert res[0][5] == 300
|
||||
exchange.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -3387,7 +3433,7 @@ def test_merge_ft_has_dict(default_conf, mocker):
|
||||
ex = Binance(default_conf)
|
||||
assert ex._ft_has != Exchange._ft_has_default
|
||||
assert ex.get_option('stoploss_on_exchange')
|
||||
assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC']
|
||||
assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC', 'PO']
|
||||
assert ex.get_option('trades_pagination') == 'id'
|
||||
assert ex.get_option('trades_pagination_arg') == 'fromId'
|
||||
|
||||
@@ -3868,29 +3914,6 @@ def test_get_stake_amount_considering_leverage(
|
||||
stake_amount, leverage) == min_stake_with_lev
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name,trading_mode", [
|
||||
("binance", TradingMode.FUTURES),
|
||||
])
|
||||
def test__set_leverage(mocker, default_conf, exchange_name, trading_mode):
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_leverage = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf,
|
||||
api_mock,
|
||||
exchange_name,
|
||||
"_set_leverage",
|
||||
"set_leverage",
|
||||
pair="XRP/USDT",
|
||||
leverage=5.0,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("margin_mode", [
|
||||
(MarginMode.CROSS),
|
||||
(MarginMode.ISOLATED)
|
||||
@@ -4830,7 +4853,6 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('exchange_name', EXCHANGES)
|
||||
async def test_get_market_leverage_tiers(mocker, default_conf, exchange_name):
|
||||
default_conf['exchange']['name'] = exchange_name
|
||||
@@ -5287,7 +5309,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.get_contract_size = MagicMock(return_value=contract_size)
|
||||
@@ -5307,3 +5329,10 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
|
||||
assert order['cost'] == 100
|
||||
assert order['filled'] == 100
|
||||
assert order['remaining'] == 100
|
||||
|
||||
|
||||
def test_price_to_precision_with_default_conf(default_conf, mocker):
|
||||
conf = copy.deepcopy(default_conf)
|
||||
patched_ex = get_patched_exchange(mocker, conf)
|
||||
prec_price = patched_ex.price_to_precision("XRP/USDT", 1.0000000101)
|
||||
assert prec_price == 1.00000001
|
||||
|
||||
@@ -4,42 +4,9 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import Gate
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
from tests.conftest import EXMS, get_patched_exchange
|
||||
|
||||
|
||||
def test_validate_order_types_gate(default_conf, mocker):
|
||||
default_conf['exchange']['name'] = 'gate'
|
||||
mocker.patch(f'{EXMS}._init_ccxt')
|
||||
mocker.patch(f'{EXMS}._load_markets', return_value={})
|
||||
mocker.patch(f'{EXMS}.validate_pairs')
|
||||
mocker.patch(f'{EXMS}.validate_timeframes')
|
||||
mocker.patch(f'{EXMS}.validate_stakecurrency')
|
||||
mocker.patch(f'{EXMS}.validate_pricing')
|
||||
mocker.patch(f'{EXMS}.name', 'Gate')
|
||||
exch = ExchangeResolver.load_exchange('gate', default_conf, True)
|
||||
assert isinstance(exch, Gate)
|
||||
|
||||
default_conf['order_types'] = {
|
||||
'entry': 'market',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Exchange .* does not support market orders.'):
|
||||
ExchangeResolver.load_exchange('gate', default_conf, True)
|
||||
|
||||
# market-orders supported on futures markets.
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
ex = ExchangeResolver.load_exchange('gate', default_conf, True)
|
||||
assert ex
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_fetch_stoploss_order_gate(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='gate')
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
||||
import ccxt
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException
|
||||
from tests.conftest import EXMS, get_patched_exchange
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
@@ -27,11 +27,11 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
||||
side=side,
|
||||
@@ -80,11 +80,11 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
|
||||
order_type = 'stop-limit'
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
||||
side='sell', leverage=1.0)
|
||||
|
||||
@@ -29,7 +29,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||
|
||||
order = exchange.create_order(
|
||||
@@ -192,7 +192,7 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||
|
||||
@@ -263,7 +263,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
|
||||
api_mock = MagicMock()
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
||||
import ccxt
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException
|
||||
from tests.conftest import EXMS, get_patched_exchange
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
@@ -27,11 +27,11 @@ def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
||||
if order_type == 'limit':
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={
|
||||
'stoploss': order_type,
|
||||
@@ -88,11 +88,11 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
|
||||
order_type = 'market'
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={'stoploss': 'limit',
|
||||
'stoploss_on_exchange_limit_ratio': 1.05},
|
||||
|
||||
+119
-3
@@ -1,12 +1,14 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock
|
||||
|
||||
import ccxt
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import RetryableOrderError, TemporaryError
|
||||
from freqtrade.exchange.exchange import timeframe_to_minutes
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has
|
||||
from tests.conftest import EXMS, get_patched_exchange, log_has
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
@@ -276,7 +278,7 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmpdir, caplog,
|
||||
'fetchLeverageTiers': False,
|
||||
'fetchMarketLeverageTiers': True,
|
||||
})
|
||||
api_mock.fetch_market_leverage_tiers = get_mock_coro(side_effect=[
|
||||
api_mock.fetch_market_leverage_tiers = AsyncMock(side_effect=[
|
||||
[
|
||||
{
|
||||
'tier': 1,
|
||||
@@ -339,6 +341,7 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmpdir, caplog,
|
||||
}
|
||||
},
|
||||
],
|
||||
TemporaryError("this Failed"),
|
||||
[
|
||||
{
|
||||
'tier': 1,
|
||||
@@ -476,3 +479,116 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmpdir, caplog,
|
||||
exchange.load_leverage_tiers()
|
||||
|
||||
assert log_has(logmsg, caplog)
|
||||
|
||||
|
||||
def test__set_leverage_okx(mocker, default_conf):
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_leverage = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
|
||||
default_conf['dry_run'] = False
|
||||
default_conf['trading_mode'] = TradingMode.FUTURES
|
||||
default_conf['margin_mode'] = MarginMode.ISOLATED
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx")
|
||||
exchange._lev_prep('BTC/USDT:USDT', 3.2, 'buy')
|
||||
assert api_mock.set_leverage.call_count == 1
|
||||
# Leverage is rounded to 3.
|
||||
assert api_mock.set_leverage.call_args_list[0][1]['leverage'] == 3.2
|
||||
assert api_mock.set_leverage.call_args_list[0][1]['symbol'] == 'BTC/USDT:USDT'
|
||||
assert api_mock.set_leverage.call_args_list[0][1]['params'] == {
|
||||
'mgnMode': 'isolated',
|
||||
'posSide': 'net'}
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf,
|
||||
api_mock,
|
||||
"okx",
|
||||
"_lev_prep",
|
||||
"set_leverage",
|
||||
pair="XRP/USDT:USDT",
|
||||
leverage=5.0,
|
||||
side='buy'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_fetch_stoploss_order_okx(default_conf, mocker):
|
||||
default_conf['dry_run'] = False
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_order = MagicMock()
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx')
|
||||
|
||||
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
assert api_mock.fetch_order.call_args_list[0][0][0] == '1234'
|
||||
assert api_mock.fetch_order.call_args_list[0][0][1] == 'ETH/BTC'
|
||||
assert api_mock.fetch_order.call_args_list[0][1]['params'] == {'stop': True}
|
||||
|
||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound)
|
||||
api_mock.fetch_open_orders = MagicMock(return_value=[])
|
||||
api_mock.fetch_closed_orders = MagicMock(return_value=[])
|
||||
api_mock.fetch_canceled_orders = MagicMock(creturn_value=[])
|
||||
|
||||
with pytest.raises(RetryableOrderError):
|
||||
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
assert api_mock.fetch_open_orders.call_count == 1
|
||||
assert api_mock.fetch_closed_orders.call_count == 1
|
||||
assert api_mock.fetch_canceled_orders.call_count == 1
|
||||
|
||||
api_mock.fetch_order.reset_mock()
|
||||
api_mock.fetch_open_orders.reset_mock()
|
||||
api_mock.fetch_closed_orders.reset_mock()
|
||||
api_mock.fetch_canceled_orders.reset_mock()
|
||||
|
||||
api_mock.fetch_closed_orders = MagicMock(return_value=[
|
||||
{
|
||||
'id': '1234',
|
||||
'status': 'closed',
|
||||
'info': {'ordId': '123455'}
|
||||
}
|
||||
])
|
||||
mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value={'id': '123455'}))
|
||||
resp = exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
assert api_mock.fetch_open_orders.call_count == 1
|
||||
assert api_mock.fetch_closed_orders.call_count == 1
|
||||
assert api_mock.fetch_canceled_orders.call_count == 0
|
||||
|
||||
assert resp['id'] == '1234'
|
||||
assert resp['id_stop'] == '123455'
|
||||
assert resp['type'] == 'stoploss'
|
||||
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx')
|
||||
dro_mock = mocker.patch(f"{EXMS}.fetch_dry_run_order", MagicMock(return_value={'id': '123455'}))
|
||||
|
||||
api_mock.fetch_order.reset_mock()
|
||||
api_mock.fetch_open_orders.reset_mock()
|
||||
api_mock.fetch_closed_orders.reset_mock()
|
||||
api_mock.fetch_canceled_orders.reset_mock()
|
||||
resp = exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
|
||||
assert api_mock.fetch_order.call_count == 0
|
||||
assert api_mock.fetch_open_orders.call_count == 0
|
||||
assert api_mock.fetch_closed_orders.call_count == 0
|
||||
assert api_mock.fetch_canceled_orders.call_count == 0
|
||||
assert dro_mock.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
|
||||
(1501, 1499, 1501, "sell"),
|
||||
(1499, 1501, 1499, "buy")
|
||||
])
|
||||
def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='okx')
|
||||
order = {
|
||||
'type': 'stoploss',
|
||||
'price': 1500,
|
||||
'stopLossPrice': 1500,
|
||||
}
|
||||
assert exchange.stoploss_adjust(sl1, order, side=side)
|
||||
assert not exchange.stoploss_adjust(sl2, order, side=side)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
@@ -85,6 +86,22 @@ def make_rl_config(conf):
|
||||
return conf
|
||||
|
||||
|
||||
def mock_pytorch_mlp_model_training_parameters() -> Dict[str, Any]:
|
||||
return {
|
||||
"learning_rate": 3e-4,
|
||||
"trainer_kwargs": {
|
||||
"max_iters": 1,
|
||||
"batch_size": 64,
|
||||
"max_n_eval_batches": 1,
|
||||
},
|
||||
"model_kwargs": {
|
||||
"hidden_dim": 32,
|
||||
"dropout_percent": 0.2,
|
||||
"n_layer": 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_patched_data_kitchen(mocker, freqaiconf):
|
||||
dk = FreqaiDataKitchen(freqaiconf)
|
||||
return dk
|
||||
@@ -119,6 +136,7 @@ def make_unfiltered_dataframe(mocker, freqai_conf):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
freqai.dk.pair = "ADA/BTC"
|
||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
||||
@@ -152,6 +170,7 @@ def make_data_dictionary(mocker, freqai_conf):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
freqai.dk.pair = "ADA/BTC"
|
||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
||||
|
||||
@@ -19,6 +19,7 @@ def test_update_historic_data(mocker, freqai_conf):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
@@ -41,6 +42,7 @@ def test_load_all_pairs_histories(mocker, freqai_conf):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
@@ -60,6 +62,7 @@ def test_get_base_and_corr_dataframes(mocker, freqai_conf):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
||||
@@ -87,6 +90,7 @@ def test_use_strategy_to_populate_indicators(mocker, freqai_conf):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
||||
@@ -103,8 +107,9 @@ def test_get_timerange_from_live_historic_predictions(mocker, freqai_conf):
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.live = False
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = False
|
||||
timerange = TimeRange.parse_timerange("20180126-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
sub_timerange = TimeRange.parse_timerange("20180128-20180130")
|
||||
|
||||
@@ -180,6 +180,7 @@ def test_get_full_model_path(mocker, freqai_conf, model):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||
from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_has_re
|
||||
from tests.freqai.conftest import get_patched_freqai_strategy, make_rl_config
|
||||
from tests.freqai.conftest import (get_patched_freqai_strategy, make_rl_config,
|
||||
mock_pytorch_mlp_model_training_parameters)
|
||||
|
||||
|
||||
def is_py11() -> bool:
|
||||
@@ -34,13 +35,14 @@ def is_mac() -> bool:
|
||||
|
||||
def can_run_model(model: str) -> None:
|
||||
if (is_arm() or is_py11()) and "Catboost" in model:
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
pytest.skip("CatBoost is not supported on ARM.")
|
||||
|
||||
if is_mac() and not is_arm() and 'Reinforcement' in model:
|
||||
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
|
||||
is_pytorch_model = 'Reinforcement' in model or 'PyTorch' in model
|
||||
if is_pytorch_model and is_mac() and not is_arm():
|
||||
pytest.skip("Reinforcement learning / PyTorch module not available on intel based Mac OS.")
|
||||
|
||||
if is_py11() and 'Reinforcement' in model:
|
||||
pytest.skip("Reinforcement learning currently not available on python 3.11.")
|
||||
if is_pytorch_model and is_py11():
|
||||
pytest.skip("Reinforcement learning / PyTorch currently not available on python 3.11.")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle, buffer', [
|
||||
@@ -48,11 +50,12 @@ def can_run_model(model: str) -> None:
|
||||
('XGBoostRegressor', False, True, False, True, False, 10),
|
||||
('XGBoostRFRegressor', False, False, False, True, False, 0),
|
||||
('CatboostRegressor', False, False, False, True, True, 0),
|
||||
('PyTorchMLPRegressor', False, False, False, True, False, 0),
|
||||
('ReinforcementLearner', False, True, False, True, False, 0),
|
||||
('ReinforcementLearner_multiproc', False, False, False, True, False, 0),
|
||||
('ReinforcementLearner_test_3ac', False, False, False, False, False, 0),
|
||||
('ReinforcementLearner_test_3ac', False, False, False, True, False, 0),
|
||||
('ReinforcementLearner_test_4ac', False, False, False, True, False, 0)
|
||||
('ReinforcementLearner_test_4ac', False, False, False, True, False, 0),
|
||||
])
|
||||
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
dbscan, float32, can_short, shuffle, buffer):
|
||||
@@ -79,6 +82,11 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
|
||||
freqai_conf["freqai"]["rl_config"]["drop_ohlc_from_features"] = True
|
||||
|
||||
if 'PyTorchMLPRegressor' in model:
|
||||
model_save_ext = 'zip'
|
||||
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
|
||||
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
|
||||
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||
@@ -87,6 +95,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
freqai.live = True
|
||||
freqai.can_short = can_short
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
freqai.dk.set_paths('ADA/BTC', 10000)
|
||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
@@ -122,8 +131,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
('CatboostClassifierMultiTarget', "freqai_test_multimodel_classifier_strat")
|
||||
])
|
||||
def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, strat):
|
||||
if (is_arm() or is_py11()) and 'Catboost' in model:
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
can_run_model(model)
|
||||
|
||||
freqai_conf.update({"timerange": "20180110-20180130"})
|
||||
freqai_conf.update({"strategy": strat})
|
||||
@@ -135,6 +143,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
@@ -162,10 +171,10 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
|
||||
'CatboostClassifier',
|
||||
'XGBoostClassifier',
|
||||
'XGBoostRFClassifier',
|
||||
'PyTorchMLPClassifier',
|
||||
])
|
||||
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
if (is_arm() or is_py11()) and model == 'CatboostClassifier':
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
can_run_model(model)
|
||||
|
||||
freqai_conf.update({"freqaimodel": model})
|
||||
freqai_conf.update({"strategy": "freqai_test_classifier"})
|
||||
@@ -178,6 +187,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
@@ -190,7 +200,20 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
freqai.extract_data_and_train_model(new_timerange, "ADA/BTC",
|
||||
strategy, freqai.dk, data_load_timerange)
|
||||
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").exists()
|
||||
if 'PyTorchMLPClassifier':
|
||||
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
|
||||
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
|
||||
|
||||
if freqai.dd.model_type == 'joblib':
|
||||
model_file_extension = ".joblib"
|
||||
elif freqai.dd.model_type == "pytorch":
|
||||
model_file_extension = ".zip"
|
||||
else:
|
||||
raise Exception(f"Unsupported model type: {freqai.dd.model_type},"
|
||||
f" can't assign model_file_extension")
|
||||
|
||||
assert Path(freqai.dk.data_path /
|
||||
f"{freqai.dk.model_filename}_model{model_file_extension}").exists()
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").exists()
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").exists()
|
||||
assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").exists()
|
||||
@@ -204,10 +227,12 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
("LightGBMRegressor", 2, "freqai_test_strat"),
|
||||
("XGBoostRegressor", 2, "freqai_test_strat"),
|
||||
("CatboostRegressor", 2, "freqai_test_strat"),
|
||||
("PyTorchMLPRegressor", 2, "freqai_test_strat"),
|
||||
("ReinforcementLearner", 3, "freqai_rl_test_strat"),
|
||||
("XGBoostClassifier", 2, "freqai_test_classifier"),
|
||||
("LightGBMClassifier", 2, "freqai_test_classifier"),
|
||||
("CatboostClassifier", 2, "freqai_test_classifier")
|
||||
("CatboostClassifier", 2, "freqai_test_classifier"),
|
||||
("PyTorchMLPClassifier", 2, "freqai_test_classifier")
|
||||
],
|
||||
)
|
||||
def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog):
|
||||
@@ -228,6 +253,10 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog)
|
||||
if 'test_4ac' in model:
|
||||
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
|
||||
|
||||
if 'PyTorchMLP' in model:
|
||||
pytorch_mlp_mtp = mock_pytorch_mlp_model_training_parameters()
|
||||
freqai_conf['freqai']['model_training_parameters'].update(pytorch_mlp_mtp)
|
||||
|
||||
freqai_conf.get("freqai", {}).get("feature_parameters", {}).update(
|
||||
{"indicator_periods_candles": [2]})
|
||||
|
||||
@@ -371,6 +400,9 @@ def test_backtesting_fit_live_predictions(mocker, freqai_conf, caplog):
|
||||
sub_timerange = TimeRange.parse_timerange("20180129-20180130")
|
||||
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC", freqai.dk)
|
||||
df = freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, "LTC/BTC")
|
||||
df = strategy.set_freqai_targets(df.copy(), metadata={"pair": "LTC/BTC"})
|
||||
df = freqai.dk.remove_special_chars_from_feature_names(df)
|
||||
freqai.dk.get_unique_classes_from_labels(df)
|
||||
freqai.dk.pair = "ADA/BTC"
|
||||
freqai.dk.full_df = df.fillna(0)
|
||||
freqai.dk.full_df
|
||||
@@ -394,6 +426,7 @@ def test_principal_component_analysis(mocker, freqai_conf):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
@@ -425,10 +458,12 @@ def test_plot_feature_importance(mocker, freqai_conf):
|
||||
freqai = strategy.freqai
|
||||
freqai.live = True
|
||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||
freqai.dk.live = True
|
||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||
|
||||
freqai.dd.pair_dict = MagicMock()
|
||||
freqai.dd.pair_dict = {"ADA/BTC": {"model_filename": "fake_name",
|
||||
"trained_timestamp": 1, "data_path": "", "extras": {}}}
|
||||
|
||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||
new_timerange = TimeRange.parse_timerange("20180120-20180130")
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import ExitType
|
||||
from freqtrade.enums import ExitType, TradingMode
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.persistence.trade_model import LocalTrade
|
||||
from tests.conftest import EXMS, patch_exchange
|
||||
@@ -925,12 +925,14 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
|
||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||
mocker.patch(f"{EXMS}.get_max_leverage", return_value=100)
|
||||
mocker.patch(f"{EXMS}.calculate_funding_fees", return_value=0)
|
||||
patch_exchange(mocker)
|
||||
frame = _build_backtest_dataframe(data.data)
|
||||
backtesting = Backtesting(default_conf)
|
||||
# TODO: Should we initialize this properly??
|
||||
backtesting._can_short = True
|
||||
backtesting.trading_mode = TradingMode.MARGIN
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting._can_short = True
|
||||
backtesting.required_startup = 0
|
||||
backtesting.strategy.advise_entry = lambda a, m: frame
|
||||
backtesting.strategy.advise_exit = lambda a, m: frame
|
||||
|
||||
@@ -344,7 +344,7 @@ def test_backtest_abort(default_conf, mocker, testdatadir) -> None:
|
||||
assert backtesting.progress.progress == 0
|
||||
|
||||
|
||||
def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
def get_timerange(input1):
|
||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||
|
||||
@@ -367,6 +367,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.bot_loop_start = MagicMock()
|
||||
backtesting.strategy.bot_start = MagicMock()
|
||||
backtesting.start()
|
||||
# check the logs, that will contain the backtest result
|
||||
exists = [
|
||||
@@ -376,7 +377,8 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
for line in exists:
|
||||
assert log_has(line, caplog)
|
||||
assert backtesting.strategy.dp._pairlists is not None
|
||||
assert backtesting.strategy.bot_loop_start.call_count == 1
|
||||
assert backtesting.strategy.bot_start.call_count == 1
|
||||
assert backtesting.strategy.bot_loop_start.call_count == 0
|
||||
assert sbs.call_count == 1
|
||||
assert sbc.call_count == 1
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from arrow import Arrow
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import ExitType
|
||||
from freqtrade.enums import ExitType, TradingMode
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from tests.conftest import EXMS, patch_exchange
|
||||
|
||||
@@ -108,9 +108,10 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
|
||||
default_conf.update({
|
||||
"stake_amount": 100.0,
|
||||
"dry_run_wallet": 1000.0,
|
||||
"strategy": "StrategyTestV3"
|
||||
"strategy": "StrategyTestV3",
|
||||
})
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.trading_mode = TradingMode.FUTURES
|
||||
backtesting._can_short = True
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
pair = 'XRP/USDT'
|
||||
|
||||
@@ -872,7 +872,8 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
@@ -922,7 +923,8 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir,
|
||||
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
@@ -959,7 +961,8 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmpdir, fee)
|
||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
|
||||
@@ -9,7 +9,7 @@ import pytest
|
||||
from arrow import Arrow
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
|
||||
from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data,
|
||||
load_backtest_stats)
|
||||
@@ -236,7 +236,7 @@ def test_store_backtest_candles(testdatadir, mocker):
|
||||
|
||||
assert dump_mock.call_count == 2
|
||||
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl'))
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
|
||||
|
||||
dump_mock.reset_mock()
|
||||
# mock file exporting
|
||||
@@ -245,7 +245,7 @@ def test_store_backtest_candles(testdatadir, mocker):
|
||||
assert dump_mock.call_count == 2
|
||||
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
|
||||
# result will be testdatadir / testresult-<timestamp>_signals.pkl
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl'))
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
|
||||
dump_mock.reset_mock()
|
||||
|
||||
|
||||
@@ -466,11 +466,14 @@ def test_generate_periodic_breakdown_stats(testdatadir):
|
||||
def test__get_resample_from_period():
|
||||
|
||||
assert _get_resample_from_period('day') == '1d'
|
||||
assert _get_resample_from_period('week') == '1w'
|
||||
assert _get_resample_from_period('week') == '1W-MON'
|
||||
assert _get_resample_from_period('month') == '1M'
|
||||
with pytest.raises(ValueError, match=r"Period noooo is not supported."):
|
||||
_get_resample_from_period('noooo')
|
||||
|
||||
for period in BACKTEST_BREAKDOWNS:
|
||||
assert isinstance(_get_resample_from_period(period), str)
|
||||
|
||||
|
||||
def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
|
||||
filename = testdatadir / "backtest_results/backtest-result.json"
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.persistence.key_value_store import KeyValueStore, set_startup_time
|
||||
from tests.conftest import create_mock_trades_usdt
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_key_value_store(time_machine):
|
||||
start = datetime(2023, 1, 1, 4, tzinfo=timezone.utc)
|
||||
time_machine.move_to(start, tick=False)
|
||||
|
||||
KeyValueStore.store_value("test", "testStringValue")
|
||||
KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
|
||||
KeyValueStore.store_value("test_float", 22.51)
|
||||
KeyValueStore.store_value("test_int", 15)
|
||||
|
||||
assert KeyValueStore.get_value("test") == "testStringValue"
|
||||
assert KeyValueStore.get_value("test") == "testStringValue"
|
||||
assert KeyValueStore.get_string_value("test") == "testStringValue"
|
||||
assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
|
||||
assert KeyValueStore.get_datetime_value("test_dt") == datetime.now(timezone.utc)
|
||||
assert KeyValueStore.get_string_value("test_dt") is None
|
||||
assert KeyValueStore.get_float_value("test_dt") is None
|
||||
assert KeyValueStore.get_int_value("test_dt") is None
|
||||
assert KeyValueStore.get_value("test_float") == 22.51
|
||||
assert KeyValueStore.get_float_value("test_float") == 22.51
|
||||
assert KeyValueStore.get_value("test_int") == 15
|
||||
assert KeyValueStore.get_int_value("test_int") == 15
|
||||
assert KeyValueStore.get_datetime_value("test_int") is None
|
||||
|
||||
time_machine.move_to(start + timedelta(days=20, hours=5), tick=False)
|
||||
assert KeyValueStore.get_value("test_dt") != datetime.now(timezone.utc)
|
||||
assert KeyValueStore.get_value("test_dt") == start
|
||||
# Test update works
|
||||
KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
|
||||
assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
|
||||
|
||||
KeyValueStore.store_value("test_float", 23.51)
|
||||
assert KeyValueStore.get_value("test_float") == 23.51
|
||||
# test deleting
|
||||
KeyValueStore.delete_value("test_float")
|
||||
assert KeyValueStore.get_value("test_float") is None
|
||||
# Delete same value again (should not fail)
|
||||
KeyValueStore.delete_value("test_float")
|
||||
|
||||
with pytest.raises(ValueError, match=r"Unknown value type"):
|
||||
KeyValueStore.store_value("test_float", {'some': 'dict'})
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_set_startup_time(fee, time_machine):
|
||||
create_mock_trades_usdt(fee)
|
||||
start = datetime.now(timezone.utc)
|
||||
time_machine.move_to(start, tick=False)
|
||||
set_startup_time()
|
||||
|
||||
assert KeyValueStore.get_value("startup_time") == start
|
||||
initial_time = KeyValueStore.get_value("bot_start_time")
|
||||
assert initial_time <= start
|
||||
|
||||
# Simulate bot restart
|
||||
new_start = start + timedelta(days=5)
|
||||
time_machine.move_to(new_start, tick=False)
|
||||
set_startup_time()
|
||||
|
||||
assert KeyValueStore.get_value("startup_time") == new_start
|
||||
assert KeyValueStore.get_value("bot_start_time") == initial_time
|
||||
@@ -1,15 +1,18 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import logging
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, select, text
|
||||
from sqlalchemy.schema import CreateTable
|
||||
|
||||
from freqtrade.constants import DEFAULT_DB_PROD_URL
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.persistence import Trade, init_db
|
||||
from freqtrade.persistence.base import ModelBase
|
||||
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
|
||||
from freqtrade.persistence.models import PairLock
|
||||
from tests.conftest import log_has
|
||||
@@ -411,3 +414,14 @@ def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
|
||||
assert len(pairlocks) == 1
|
||||
pairlocks[0].pair == 'ETH/BTC'
|
||||
pairlocks[0].side == '*'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dialect', [
|
||||
'sqlite', 'postgresql', 'mysql', 'oracle', 'mssql',
|
||||
])
|
||||
def test_create_table_compiles(dialect):
|
||||
|
||||
dialect_mod = import_module(f"sqlalchemy.dialects.{dialect}")
|
||||
for table in ModelBase.metadata.tables.values():
|
||||
create_sql = str(CreateTable(table).compile(dialect=dialect_mod.dialect()))
|
||||
assert 'CREATE TABLE' in create_sql
|
||||
|
||||
@@ -6,7 +6,7 @@ import arrow
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||
from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, DATETIME_PRINT_FORMAT
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import DependencyException
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
|
||||
@@ -1330,71 +1330,78 @@ def test_to_json(fee):
|
||||
open_rate=0.123,
|
||||
exchange='binance',
|
||||
enter_tag=None,
|
||||
open_order_id='dry_run_buy_12345'
|
||||
open_order_id='dry_run_buy_12345',
|
||||
precision_mode=1,
|
||||
amount_precision=8.0,
|
||||
price_precision=7.0,
|
||||
)
|
||||
result = trade.to_json()
|
||||
assert isinstance(result, dict)
|
||||
|
||||
assert result == {'trade_id': None,
|
||||
'pair': 'ADA/USDT',
|
||||
'base_currency': 'ADA',
|
||||
'quote_currency': 'USDT',
|
||||
'is_open': None,
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'open_order_id': 'dry_run_buy_12345',
|
||||
'close_date': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 0.123,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 15.1668225,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'close_rate': None,
|
||||
'close_rate_requested': None,
|
||||
'amount': 123.0,
|
||||
'amount_requested': 123.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': None,
|
||||
'trade_duration_s': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stop_loss_pct': None,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': None,
|
||||
'stoploss_last_update_timestamp': None,
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'min_rate': None,
|
||||
'max_rate': None,
|
||||
'strategy': None,
|
||||
'enter_tag': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
'interest_rate': None,
|
||||
'liquidation_price': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None,
|
||||
'orders': [],
|
||||
}
|
||||
assert result == {
|
||||
'trade_id': None,
|
||||
'pair': 'ADA/USDT',
|
||||
'base_currency': 'ADA',
|
||||
'quote_currency': 'USDT',
|
||||
'is_open': None,
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'open_order_id': 'dry_run_buy_12345',
|
||||
'close_date': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 0.123,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 15.1668225,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'close_rate': None,
|
||||
'close_rate_requested': None,
|
||||
'amount': 123.0,
|
||||
'amount_requested': 123.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': None,
|
||||
'trade_duration_s': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stop_loss_pct': None,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': None,
|
||||
'stoploss_last_update_timestamp': None,
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'min_rate': None,
|
||||
'max_rate': None,
|
||||
'strategy': None,
|
||||
'enter_tag': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
'interest_rate': None,
|
||||
'liquidation_price': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None,
|
||||
'amount_precision': 8.0,
|
||||
'price_precision': 7.0,
|
||||
'precision_mode': 1,
|
||||
'orders': [],
|
||||
}
|
||||
|
||||
# Simulate dry_run entries
|
||||
trade = Trade(
|
||||
@@ -1410,70 +1417,77 @@ def test_to_json(fee):
|
||||
close_rate=0.125,
|
||||
enter_tag='buys_signal_001',
|
||||
exchange='binance',
|
||||
precision_mode=2,
|
||||
amount_precision=7.0,
|
||||
price_precision=8.0,
|
||||
)
|
||||
result = trade.to_json()
|
||||
assert isinstance(result, dict)
|
||||
|
||||
assert result == {'trade_id': None,
|
||||
'pair': 'XRP/BTC',
|
||||
'base_currency': 'XRP',
|
||||
'quote_currency': 'BTC',
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'close_timestamp': int(trade.close_date.timestamp() * 1000),
|
||||
'open_rate': 0.123,
|
||||
'close_rate': 0.125,
|
||||
'amount': 100.0,
|
||||
'amount_requested': 101.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': 60,
|
||||
'trade_duration_s': 3600,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_pct': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': None,
|
||||
'stoploss_last_update_timestamp': None,
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'close_rate_requested': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'is_open': None,
|
||||
'max_rate': None,
|
||||
'min_rate': None,
|
||||
'open_order_id': None,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 12.33075,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'strategy': None,
|
||||
'enter_tag': 'buys_signal_001',
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
'interest_rate': None,
|
||||
'liquidation_price': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None,
|
||||
'orders': [],
|
||||
}
|
||||
assert result == {
|
||||
'trade_id': None,
|
||||
'pair': 'XRP/BTC',
|
||||
'base_currency': 'XRP',
|
||||
'quote_currency': 'BTC',
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'close_timestamp': int(trade.close_date.timestamp() * 1000),
|
||||
'open_rate': 0.123,
|
||||
'close_rate': 0.125,
|
||||
'amount': 100.0,
|
||||
'amount_requested': 101.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': 60,
|
||||
'trade_duration_s': 3600,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_pct': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': None,
|
||||
'stoploss_last_update_timestamp': None,
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'close_rate_requested': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'is_open': None,
|
||||
'max_rate': None,
|
||||
'min_rate': None,
|
||||
'open_order_id': None,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 12.33075,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'strategy': None,
|
||||
'enter_tag': 'buys_signal_001',
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
'interest_rate': None,
|
||||
'liquidation_price': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None,
|
||||
'amount_precision': 7.0,
|
||||
'price_precision': 8.0,
|
||||
'precision_mode': 2,
|
||||
'orders': [],
|
||||
}
|
||||
|
||||
|
||||
def test_stoploss_reinitialization(default_conf, fee):
|
||||
@@ -2023,6 +2037,7 @@ def test_Trade_object_idem():
|
||||
'get_mix_tag_performance',
|
||||
'get_trading_volume',
|
||||
'from_json',
|
||||
'validate_string_len',
|
||||
)
|
||||
EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count',
|
||||
'total_profit')
|
||||
@@ -2041,6 +2056,31 @@ def test_Trade_object_idem():
|
||||
assert item in trade
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_trade_truncates_string_fields():
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=20.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
open_date=datetime.utcnow() - timedelta(minutes=20),
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
exchange='binance',
|
||||
leverage=1.0,
|
||||
trading_mode='futures',
|
||||
enter_tag='a' * CUSTOM_TAG_MAX_LENGTH * 2,
|
||||
exit_reason='b' * CUSTOM_TAG_MAX_LENGTH * 2,
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
|
||||
trade1 = Trade.session.scalars(select(Trade)).first()
|
||||
|
||||
assert trade1.enter_tag == 'a' * CUSTOM_TAG_MAX_LENGTH
|
||||
assert trade1.exit_reason == 'b' * CUSTOM_TAG_MAX_LENGTH
|
||||
|
||||
|
||||
def test_recalc_trade_from_orders(fee):
|
||||
|
||||
o1_amount = 100
|
||||
@@ -2441,7 +2481,7 @@ def test_select_filled_orders(fee):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_order_to_ccxt(limit_buy_order_open):
|
||||
def test_order_to_ccxt(limit_buy_order_open, limit_sell_order_usdt_open):
|
||||
|
||||
order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy')
|
||||
order.ft_trade_id = 1
|
||||
@@ -2455,11 +2495,23 @@ def test_order_to_ccxt(limit_buy_order_open):
|
||||
del raw_order['fee']
|
||||
del raw_order['datetime']
|
||||
del raw_order['info']
|
||||
assert raw_order['stopPrice'] is None
|
||||
del raw_order['stopPrice']
|
||||
assert raw_order.get('stopPrice') is None
|
||||
raw_order.pop('stopPrice', None)
|
||||
del limit_buy_order_open['datetime']
|
||||
assert raw_order == limit_buy_order_open
|
||||
|
||||
order1 = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, 'mocked', 'sell')
|
||||
order1.ft_order_side = 'stoploss'
|
||||
order1.stop_price = order1.price * 0.9
|
||||
order1.ft_trade_id = 1
|
||||
order1.session.add(order1)
|
||||
Order.session.commit()
|
||||
|
||||
order_resp1 = Order.order_by_id(limit_sell_order_usdt_open['id'])
|
||||
raw_order1 = order_resp1.to_ccxt_object()
|
||||
|
||||
assert raw_order1.get('stopPrice') is not None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize('data', [
|
||||
|
||||
@@ -50,8 +50,8 @@ def test_trade_fromjson():
|
||||
"stop_loss_ratio": -0.216,
|
||||
"stop_loss_pct": -21.6,
|
||||
"stoploss_order_id": null,
|
||||
"stoploss_last_update": null,
|
||||
"stoploss_last_update_timestamp": null,
|
||||
"stoploss_last_update": "2022-10-18 09:13:42",
|
||||
"stoploss_last_update_timestamp": 1666077222000,
|
||||
"initial_stop_loss_abs": 0.1981,
|
||||
"initial_stop_loss_ratio": -0.216,
|
||||
"initial_stop_loss_pct": -21.6,
|
||||
|
||||
+20
-12
@@ -88,6 +88,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'is_short': False,
|
||||
'funding_fees': 0.0,
|
||||
'trading_mode': TradingMode.SPOT,
|
||||
'amount_precision': 8.0,
|
||||
'price_precision': 8.0,
|
||||
'precision_mode': 2,
|
||||
'orders': [{
|
||||
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
|
||||
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
|
||||
@@ -125,17 +128,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'profit_pct': 0.0,
|
||||
'profit_abs': 0.0,
|
||||
'total_profit_abs': 0.0,
|
||||
'stop_loss_abs': 0.0,
|
||||
'stop_loss_pct': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stoploss_current_dist': -1.099e-05,
|
||||
'stoploss_current_dist_ratio': -1.0,
|
||||
'stoploss_current_dist_pct': pytest.approx(-100.0),
|
||||
'stoploss_entry_dist': -0.0010025,
|
||||
'stoploss_entry_dist_ratio': -1.0,
|
||||
'initial_stop_loss_abs': 0.0,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'open_order': '(limit buy rem=91.07468123)',
|
||||
})
|
||||
response_unfilled['orders'][0].update({
|
||||
@@ -554,51 +546,67 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
'free': 10.0,
|
||||
'balance': 12.0,
|
||||
'used': 2.0,
|
||||
'bot_owned': 9.9, # available stake - reducing by reserved amount
|
||||
'est_stake': 10.0, # In futures mode, "free" is used here.
|
||||
'est_stake_bot': 9.9,
|
||||
'stake': 'BTC',
|
||||
'is_position': False,
|
||||
'leverage': 1.0,
|
||||
'position': 0.0,
|
||||
'side': 'long',
|
||||
'is_bot_managed': True,
|
||||
},
|
||||
{
|
||||
'free': 1.0,
|
||||
'balance': 5.0,
|
||||
'currency': 'ETH',
|
||||
'bot_owned': 0,
|
||||
'est_stake': 0.30794,
|
||||
'est_stake_bot': 0,
|
||||
'used': 4.0,
|
||||
'stake': 'BTC',
|
||||
'is_position': False,
|
||||
'leverage': 1.0,
|
||||
'position': 0.0,
|
||||
'side': 'long',
|
||||
|
||||
'is_bot_managed': False,
|
||||
},
|
||||
{
|
||||
'free': 5.0,
|
||||
'balance': 10.0,
|
||||
'currency': 'USDT',
|
||||
'bot_owned': 0,
|
||||
'est_stake': 0.0011562404610161968,
|
||||
'est_stake_bot': 0,
|
||||
'used': 5.0,
|
||||
'stake': 'BTC',
|
||||
'is_position': False,
|
||||
'leverage': 1.0,
|
||||
'position': 0.0,
|
||||
'side': 'long',
|
||||
'is_bot_managed': False,
|
||||
},
|
||||
{
|
||||
'free': 0.0,
|
||||
'balance': 0.0,
|
||||
'currency': 'ETH/USDT:USDT',
|
||||
'est_stake': 20,
|
||||
'est_stake_bot': 20,
|
||||
'used': 0,
|
||||
'stake': 'BTC',
|
||||
'is_position': True,
|
||||
'leverage': 5.0,
|
||||
'position': 1000.0,
|
||||
'side': 'short',
|
||||
'is_bot_managed': True,
|
||||
}
|
||||
]
|
||||
assert pytest.approx(result['total_bot']) == 29.9
|
||||
assert pytest.approx(result['total']) == 30.309096
|
||||
assert result['starting_capital'] == 10
|
||||
# Very high starting capital ratio, because the futures position really has the wrong unit.
|
||||
# TODO: improve this test (see comment above)
|
||||
assert result['starting_capital_ratio'] == pytest.approx(1.98999999)
|
||||
|
||||
|
||||
def test_rpc_start(mocker, default_conf) -> None:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Unit test file for rpc/api_server.py
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -282,7 +283,7 @@ def test_api__init__(default_conf, mocker):
|
||||
"username": "TestUser",
|
||||
"password": "testPass",
|
||||
}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||
mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock())
|
||||
apiserver = ApiServer(default_conf)
|
||||
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
||||
@@ -299,10 +300,6 @@ def test_api_UvicornServer(mocker):
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert thread_mock.call_count == 0
|
||||
|
||||
s.install_signal_handlers()
|
||||
# Original implementation starts a thread - make sure that's not the case
|
||||
assert thread_mock.call_count == 0
|
||||
|
||||
# Fake started to avoid sleeping forever
|
||||
s.started = True
|
||||
s.run_in_thread()
|
||||
@@ -318,10 +315,6 @@ def test_api_UvicornServer_run(mocker):
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
s.install_signal_handlers()
|
||||
# Original implementation starts a thread - make sure that's not the case
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
# Fake started to avoid sleeping forever
|
||||
s.started = True
|
||||
s.run()
|
||||
@@ -331,13 +324,10 @@ def test_api_UvicornServer_run(mocker):
|
||||
def test_api_UvicornServer_run_no_uvloop(mocker, import_fails):
|
||||
serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve',
|
||||
get_mock_coro(None))
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
s.install_signal_handlers()
|
||||
# Original implementation starts a thread - make sure that's not the case
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
# Fake started to avoid sleeping forever
|
||||
s.started = True
|
||||
s.run()
|
||||
@@ -351,7 +341,7 @@ def test_api_run(default_conf, mocker, caplog):
|
||||
"username": "TestUser",
|
||||
"password": "testPass",
|
||||
}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||
|
||||
server_inst_mock = MagicMock()
|
||||
server_inst_mock.run_in_thread = MagicMock()
|
||||
@@ -429,7 +419,7 @@ def test_api_cleanup(default_conf, mocker, caplog):
|
||||
"username": "TestUser",
|
||||
"password": "testPass",
|
||||
}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||
|
||||
server_mock = MagicMock()
|
||||
server_mock.cleanup = MagicMock()
|
||||
@@ -490,13 +480,18 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
|
||||
'free': 12.0,
|
||||
'balance': 12.0,
|
||||
'used': 0.0,
|
||||
'bot_owned': pytest.approx(11.879999),
|
||||
'est_stake': 12.0,
|
||||
'est_stake_bot': pytest.approx(11.879999),
|
||||
'stake': 'BTC',
|
||||
'is_position': False,
|
||||
'leverage': 1.0,
|
||||
'position': 0.0,
|
||||
'side': 'long',
|
||||
'is_bot_managed': True,
|
||||
}
|
||||
assert response['total'] == 12.159513094
|
||||
assert response['total_bot'] == pytest.approx(11.879999)
|
||||
assert 'starting_capital' in response
|
||||
assert 'starting_capital_fiat' in response
|
||||
assert 'starting_capital_pct' in response
|
||||
@@ -893,6 +888,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
|
||||
'max_drawdown': ANY,
|
||||
'max_drawdown_abs': ANY,
|
||||
'trading_volume': expected['trading_volume'],
|
||||
'bot_start_timestamp': 0,
|
||||
'bot_start_date': '',
|
||||
}
|
||||
|
||||
|
||||
@@ -1066,6 +1063,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
'liquidation_price': None,
|
||||
'funding_fees': None,
|
||||
'trading_mode': ANY,
|
||||
'amount_precision': None,
|
||||
'price_precision': None,
|
||||
'precision_mode': None,
|
||||
'orders': [ANY],
|
||||
}
|
||||
|
||||
@@ -1271,6 +1271,9 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
'liquidation_price': None,
|
||||
'funding_fees': None,
|
||||
'trading_mode': 'spot',
|
||||
'amount_precision': None,
|
||||
'price_precision': None,
|
||||
'precision_mode': None,
|
||||
'orders': [],
|
||||
}
|
||||
|
||||
@@ -1407,10 +1410,10 @@ def test_api_pair_candles(botclient, ohlcv_history):
|
||||
])
|
||||
|
||||
|
||||
def test_api_pair_history(botclient, ohlcv_history):
|
||||
def test_api_pair_history(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
timeframe = '5m'
|
||||
|
||||
lfm = mocker.patch('freqtrade.strategy.interface.IStrategy.load_freqAI_model')
|
||||
# No pair
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_history?timeframe={timeframe}"
|
||||
@@ -1444,6 +1447,7 @@ def test_api_pair_history(botclient, ohlcv_history):
|
||||
assert len(rc.json()['data']) == rc.json()['length']
|
||||
assert 'columns' in rc.json()
|
||||
assert 'data' in rc.json()
|
||||
assert lfm.call_count == 1
|
||||
assert rc.json()['pair'] == 'UNITTEST/BTC'
|
||||
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
|
||||
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
|
||||
@@ -1873,7 +1877,7 @@ def test_api_ws_send_msg(default_conf, mocker, caplog):
|
||||
"password": _TEST_PASS,
|
||||
"ws_token": _TEST_WS_TOKEN
|
||||
}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater')
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||
mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api')
|
||||
apiserver = ApiServer(default_conf)
|
||||
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
|
||||
|
||||
@@ -28,6 +28,7 @@ def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
|
||||
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['telegram']['enabled'] = True
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
@@ -52,6 +53,7 @@ def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
|
||||
def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['telegram']['enabled'] = True
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
|
||||
|
||||
@@ -85,7 +87,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', side_effect=ValueError())
|
||||
|
||||
default_conf['telegram']['enabled'] = True
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.send_msg({
|
||||
@@ -99,6 +101,7 @@ def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
|
||||
|
||||
def test_process_msg_queue(mocker, default_conf, caplog) -> None:
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
|
||||
default_conf['telegram']['enabled'] = True
|
||||
default_conf['telegram']['allow_custom_messages'] = True
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||
|
||||
@@ -115,9 +118,9 @@ def test_process_msg_queue(mocker, default_conf, caplog) -> None:
|
||||
|
||||
|
||||
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
default_conf['telegram']['enabled'] = True
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.send_msg({
|
||||
@@ -166,7 +169,8 @@ def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> Non
|
||||
caplog)
|
||||
|
||||
|
||||
def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
def test_startupmessages_telegram_enabled(mocker, default_conf) -> None:
|
||||
default_conf['telegram']['enabled'] = True
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
|
||||
|
||||
+313
-232
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,10 @@ def get_webhook_dict() -> dict:
|
||||
"enabled": True,
|
||||
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
|
||||
"webhookentry": {
|
||||
# Intentionally broken, as "entry" should have priority.
|
||||
"value1": "Buying {pair55555}",
|
||||
},
|
||||
"entry": {
|
||||
"value1": "Buying {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}",
|
||||
@@ -89,15 +93,15 @@ def test_send_msg_webhook(default_conf, mocker):
|
||||
webhook.send_msg(msg=msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value1"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value2"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value3"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value3"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value4"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value4"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value4"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value5"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value5"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value5"].format(**msg))
|
||||
# Test short
|
||||
msg_mock.reset_mock()
|
||||
|
||||
@@ -116,15 +120,15 @@ def test_send_msg_webhook(default_conf, mocker):
|
||||
webhook.send_msg(msg=msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value1"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value2"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value3"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value3"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value4"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value4"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value4"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value5"] ==
|
||||
default_conf["webhook"]["webhookentry"]["value5"].format(**msg))
|
||||
default_conf["webhook"]["entry"]["value5"].format(**msg))
|
||||
# Test buy cancel
|
||||
msg_mock.reset_mock()
|
||||
|
||||
@@ -328,6 +332,7 @@ def test_send_msg_webhook(default_conf, mocker):
|
||||
|
||||
def test_exception_send_msg(default_conf, mocker, caplog):
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
del default_conf["webhook"]["entry"]
|
||||
del default_conf["webhook"]["webhookentry"]
|
||||
|
||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
|
||||
@@ -82,7 +82,7 @@ class freqai_test_classifier(IStrategy):
|
||||
return dataframe
|
||||
|
||||
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
|
||||
|
||||
self.freqai.class_names = ["down", "up"]
|
||||
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
|
||||
dataframe["close"], 'up', 'down')
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
||||
return prot
|
||||
|
||||
bot_loop_started = False
|
||||
bot_started = False
|
||||
|
||||
def bot_loop_start(self):
|
||||
self.bot_loop_started = True
|
||||
@@ -58,6 +59,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
||||
"""
|
||||
Parameters can also be defined here ...
|
||||
"""
|
||||
self.bot_started = True
|
||||
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
|
||||
|
||||
def informative_pairs(self):
|
||||
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.enums import ExitCheckTuple, ExitType, HyperoptState, SignalDirection
|
||||
@@ -529,13 +530,13 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
|
||||
assert res[0].exit_reason == 'hello world'
|
||||
|
||||
caplog.clear()
|
||||
strategy.custom_exit = MagicMock(return_value='h' * 100)
|
||||
strategy.custom_exit = MagicMock(return_value='h' * CUSTOM_TAG_MAX_LENGTH * 2)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter=False, exit_=False,
|
||||
low=None, high=None)
|
||||
assert res[0].exit_type == ExitType.CUSTOM_EXIT
|
||||
assert res[0].exit_flag is True
|
||||
assert res[0].exit_reason == 'h' * 64
|
||||
assert res[0].exit_reason == 'h' * (CUSTOM_TAG_MAX_LENGTH)
|
||||
assert log_has_re('Custom exit reason returned from custom_exit is too long.*', caplog)
|
||||
|
||||
|
||||
@@ -986,7 +987,8 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
|
||||
}
|
||||
}
|
||||
}
|
||||
mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
|
||||
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||
return_value=expected_result)
|
||||
PairLocks.timeframe = default_conf['timeframe']
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
assert strategy.stoploss == -0.05
|
||||
@@ -1005,11 +1007,13 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
|
||||
}
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
|
||||
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||
return_value=expected_result)
|
||||
with pytest.raises(OperationalException, match="Invalid parameter file provided."):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
mocker.patch('freqtrade.strategy.hyper.json_load', MagicMock(side_effect=ValueError()))
|
||||
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||
MagicMock(side_effect=ValueError()))
|
||||
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
assert log_has("Invalid parameter file format.", caplog)
|
||||
|
||||
@@ -69,7 +69,7 @@ def test_load_strategy(default_conf, dataframe_1m):
|
||||
def test_load_strategy_base64(dataframe_1m, caplog, default_conf):
|
||||
filepath = Path(__file__).parents[2] / 'freqtrade/templates/sample_strategy.py'
|
||||
encoded_string = urlsafe_b64encode(filepath.read_bytes()).decode("utf-8")
|
||||
default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)})
|
||||
default_conf.update({'strategy': f'SampleStrategy:{encoded_string}'})
|
||||
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
assert 'rsi' in strategy.advise_indicators(dataframe_1m, {'pair': 'ETH/BTC'})
|
||||
|
||||
@@ -23,7 +23,8 @@ from freqtrade.configuration.load_config import (load_config_file, load_file, lo
|
||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.loggers import FTBufferingHandler, _set_loggers, setup_logging, setup_logging_pre
|
||||
from freqtrade.loggers import (FTBufferingHandler, FTStdErrStreamHandler, _set_loggers,
|
||||
setup_logging, setup_logging_pre)
|
||||
from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
@@ -658,7 +659,7 @@ def test_set_loggers_syslog():
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
|
||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
|
||||
# setting up logging again should NOT cause the loggers to be added a second time.
|
||||
setup_logging(config)
|
||||
@@ -681,7 +682,7 @@ def test_set_loggers_Filehandler(tmpdir):
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
|
||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
|
||||
# setting up logging again should NOT cause the loggers to be added a second time.
|
||||
setup_logging(config)
|
||||
@@ -706,7 +707,7 @@ def test_set_loggers_journald(mocker):
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 3
|
||||
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
|
||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||
# reset handlers to not break pytest
|
||||
logger.handlers = orig_handlers
|
||||
|
||||
|
||||
+333
-51
@@ -356,7 +356,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
|
||||
(5.0, True, True, 99),
|
||||
(0.049, True, False, 99), # Amount will be adjusted to min - which is 0.051
|
||||
(0.042, True, False, 99), # Amount will be adjusted to min - which is 0.051
|
||||
(0, False, True, 99),
|
||||
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
|
||||
])
|
||||
@@ -1060,9 +1060,19 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order,
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short) -> None:
|
||||
def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short, fee) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
'bid': 1.9,
|
||||
'ask': 2.2,
|
||||
'last': 1.9
|
||||
}),
|
||||
create_order=MagicMock(return_value=limit_order[entry_side(is_short)]),
|
||||
get_fee=fee,
|
||||
)
|
||||
order = limit_order[entry_side(is_short)]
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
|
||||
@@ -1074,8 +1084,10 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
@@ -1091,7 +1103,8 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short,
|
||||
limit_order) -> None:
|
||||
stoploss = MagicMock(return_value={'id': 13434334})
|
||||
stop_order_dict = {'id': "13434334"}
|
||||
stoploss = MagicMock(return_value=stop_order_dict)
|
||||
enter_order = limit_order[entry_side(is_short)]
|
||||
exit_order = limit_order[exit_side(is_short)]
|
||||
patch_RPCManager(mocker)
|
||||
@@ -1116,8 +1129,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
# First case: when stoploss is not yet set but the order is open
|
||||
# should get the stoploss order id immediately
|
||||
# and should return false as no trade actually happened
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
@@ -1129,44 +1143,62 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
|
||||
# Second case: when stoploss is set but it is not yet hit
|
||||
# should do nothing and return false
|
||||
stop_order_dict.update({'id': "102"})
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "100"
|
||||
trade.stoploss_order_id = "102"
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side='stoploss',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.stop_loss,
|
||||
order_id='102',
|
||||
status='open',
|
||||
)
|
||||
)
|
||||
|
||||
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', hanging_stoploss_order)
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert trade.stoploss_order_id == "100"
|
||||
assert trade.stoploss_order_id == "102"
|
||||
|
||||
# Third case: when stoploss was set but it was canceled for some reason
|
||||
# should set a stoploss immediately and return False
|
||||
caplog.clear()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "100"
|
||||
trade.stoploss_order_id = "102"
|
||||
|
||||
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
|
||||
canceled_stoploss_order = MagicMock(return_value={'id': '103_1', 'status': 'canceled'})
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order)
|
||||
stoploss.reset_mock()
|
||||
amount_before = trade.amount
|
||||
|
||||
stop_order_dict.update({'id': "103_1"})
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert stoploss.call_count == 1
|
||||
assert trade.stoploss_order_id == "13434334"
|
||||
assert trade.stoploss_order_id == "103_1"
|
||||
assert trade.amount == amount_before
|
||||
|
||||
# Fourth case: when stoploss is set and it is hit
|
||||
# should unset stoploss_order_id and return true
|
||||
# as a trade actually happened
|
||||
caplog.clear()
|
||||
freqtrade.enter_positions()
|
||||
stop_order_dict.update({'id': "104"})
|
||||
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "100"
|
||||
trade.stoploss_order_id = "104"
|
||||
trade.orders.append(Order(
|
||||
ft_order_side='stoploss',
|
||||
order_id='100',
|
||||
order_id='104',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
@@ -1175,7 +1207,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
assert trade
|
||||
|
||||
stoploss_order_hit = MagicMock(return_value={
|
||||
'id': "100",
|
||||
'id': "104",
|
||||
'status': 'closed',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
@@ -1197,7 +1229,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
|
||||
# Fifth case: fetch_order returns InvalidOrder
|
||||
# It should try to add stoploss order
|
||||
trade.stoploss_order_id = 100
|
||||
stop_order_dict.update({'id': "105"})
|
||||
trade.stoploss_order_id = "105"
|
||||
stoploss.reset_mock()
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException())
|
||||
mocker.patch(f'{EXMS}.create_stoploss', stoploss)
|
||||
@@ -1217,21 +1250,36 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
# Seventh case: emergency exit triggered
|
||||
# Trailing stop should not act anymore
|
||||
stoploss_order_cancelled = MagicMock(side_effect=[{
|
||||
'id': "100",
|
||||
'id': "107",
|
||||
'status': 'canceled',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
'average': 2,
|
||||
'amount': enter_order['amount'],
|
||||
'filled': 0,
|
||||
'remaining': enter_order['amount'],
|
||||
'info': {'stopPrice': 22},
|
||||
}])
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_order_id = "107"
|
||||
trade.is_open = True
|
||||
trade.stoploss_last_update = arrow.utcnow().shift(hours=-1).datetime
|
||||
trade.stop_loss = 24
|
||||
trade.exit_reason = None
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side='stoploss',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.stop_loss,
|
||||
order_id='107',
|
||||
status='open',
|
||||
)
|
||||
)
|
||||
freqtrade.config['trailing_stop'] = True
|
||||
stoploss = MagicMock(side_effect=InvalidOrderException())
|
||||
|
||||
Trade.commit()
|
||||
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result',
|
||||
side_effect=InvalidOrderException())
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled)
|
||||
@@ -1242,6 +1290,137 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_handle_stoploss_on_exchange_partial(
|
||||
mocker, default_conf_usdt, fee, is_short, limit_order) -> None:
|
||||
stop_order_dict = {'id': "101", "status": "open"}
|
||||
stoploss = MagicMock(return_value=stop_order_dict)
|
||||
enter_order = limit_order[entry_side(is_short)]
|
||||
exit_order = limit_order[exit_side(is_short)]
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
'bid': 1.9,
|
||||
'ask': 2.2,
|
||||
'last': 1.9
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
enter_order,
|
||||
exit_order,
|
||||
]),
|
||||
get_fee=fee,
|
||||
create_stoploss=stoploss
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert stoploss.call_count == 1
|
||||
assert trade.stoploss_order_id == "101"
|
||||
assert trade.amount == 30
|
||||
stop_order_dict.update({'id': "102"})
|
||||
# Stoploss on exchange is cancelled on exchange, but filled partially.
|
||||
# Must update trade amount to guarantee successful exit.
|
||||
stoploss_order_hit = MagicMock(return_value={
|
||||
'id': "101",
|
||||
'status': 'canceled',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
'average': 2,
|
||||
'filled': trade.amount / 2,
|
||||
'remaining': trade.amount / 2,
|
||||
'amount': enter_order['amount'],
|
||||
})
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
# Stoploss filled partially ...
|
||||
assert trade.amount == 15
|
||||
|
||||
assert trade.stoploss_order_id == "102"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_handle_stoploss_on_exchange_partial_cancel_here(
|
||||
mocker, default_conf_usdt, fee, is_short, limit_order, caplog) -> None:
|
||||
stop_order_dict = {'id': "101", "status": "open"}
|
||||
default_conf_usdt['trailing_stop'] = True
|
||||
stoploss = MagicMock(return_value=stop_order_dict)
|
||||
enter_order = limit_order[entry_side(is_short)]
|
||||
exit_order = limit_order[exit_side(is_short)]
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
'bid': 1.9,
|
||||
'ask': 2.2,
|
||||
'last': 1.9
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
enter_order,
|
||||
exit_order,
|
||||
]),
|
||||
get_fee=fee,
|
||||
create_stoploss=stoploss
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert stoploss.call_count == 1
|
||||
assert trade.stoploss_order_id == "101"
|
||||
assert trade.amount == 30
|
||||
stop_order_dict.update({'id': "102"})
|
||||
# Stoploss on exchange is open.
|
||||
# Freqtrade cancels the stop - but cancel returns a partial filled order.
|
||||
stoploss_order_hit = MagicMock(return_value={
|
||||
'id': "101",
|
||||
'status': 'open',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
'average': 2,
|
||||
'filled': 0,
|
||||
'remaining': trade.amount,
|
||||
'amount': enter_order['amount'],
|
||||
})
|
||||
stoploss_order_cancel = MagicMock(return_value={
|
||||
'id': "101",
|
||||
'status': 'canceled',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
'average': 2,
|
||||
'filled': trade.amount / 2,
|
||||
'remaining': trade.amount / 2,
|
||||
'amount': enter_order['amount'],
|
||||
})
|
||||
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
|
||||
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel)
|
||||
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-10).datetime
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
# Canceled Stoploss filled partially ...
|
||||
assert log_has_re('Cancelling current stoploss on exchange.*', caplog)
|
||||
|
||||
assert trade.stoploss_order_id == "102"
|
||||
assert trade.amount == 15
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short,
|
||||
limit_order) -> None:
|
||||
@@ -1273,10 +1452,21 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
|
||||
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
assert trade.is_short == is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_order_id = "100"
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side='stoploss',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.stop_loss,
|
||||
order_id='100',
|
||||
status='open',
|
||||
)
|
||||
)
|
||||
assert trade
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
@@ -1395,7 +1585,7 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
# When trailing stoploss is set
|
||||
enter_order = limit_order[entry_side(is_short)]
|
||||
exit_order = limit_order[exit_side(is_short)]
|
||||
stoploss = MagicMock(return_value={'id': 13434334})
|
||||
stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'})
|
||||
patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
@@ -1440,11 +1630,21 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_order_id = '100'
|
||||
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-20).datetime
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side='stoploss',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.stop_loss,
|
||||
order_id='100',
|
||||
)
|
||||
)
|
||||
|
||||
stoploss_order_hanging = MagicMock(return_value={
|
||||
'id': 100,
|
||||
'id': '100',
|
||||
'status': 'open',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': hang_price,
|
||||
@@ -1471,7 +1671,7 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
)
|
||||
|
||||
cancel_order_mock = MagicMock()
|
||||
stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
|
||||
stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'})
|
||||
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
|
||||
mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
|
||||
|
||||
@@ -1483,13 +1683,14 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert trade.stop_loss == stop_price[1]
|
||||
trade.stoploss_order_id = '100'
|
||||
|
||||
# setting stoploss_on_exchange_interval to 0 seconds
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
|
||||
cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
|
||||
cancel_order_mock.assert_called_once_with('100', 'ETH/USDT')
|
||||
stoploss_order_mock.assert_called_once_with(
|
||||
amount=pytest.approx(amt),
|
||||
pair='ETH/USDT',
|
||||
@@ -1519,7 +1720,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
|
||||
enter_order = limit_order[entry_side(is_short)]
|
||||
exit_order = limit_order[exit_side(is_short)]
|
||||
# When trailing stoploss is set
|
||||
stoploss = MagicMock(return_value={'id': 13434334})
|
||||
stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'})
|
||||
patch_exchange(mocker)
|
||||
|
||||
mocker.patch.multiple(
|
||||
@@ -1601,7 +1802,7 @@ def test_stoploss_on_exchange_price_rounding(
|
||||
EXMS,
|
||||
get_fee=fee,
|
||||
)
|
||||
price_mock = MagicMock(side_effect=lambda p, s: int(s))
|
||||
price_mock = MagicMock(side_effect=lambda p, s, **kwargs: int(s))
|
||||
stoploss_mock = MagicMock(return_value={'id': '13434334'})
|
||||
adjust_mock = MagicMock(return_value=False)
|
||||
mocker.patch.multiple(
|
||||
@@ -1628,7 +1829,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
enter_order = limit_order[entry_side(is_short)]
|
||||
exit_order = limit_order[exit_side(is_short)]
|
||||
# When trailing stoploss is set
|
||||
stoploss = MagicMock(return_value={'id': 13434334})
|
||||
stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'})
|
||||
patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
@@ -1673,11 +1874,21 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_order_id = '100'
|
||||
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side='stoploss',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.stop_loss,
|
||||
order_id='100',
|
||||
)
|
||||
)
|
||||
|
||||
stoploss_order_hanging = MagicMock(return_value={
|
||||
'id': 100,
|
||||
'id': '100',
|
||||
'status': 'open',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
@@ -1703,9 +1914,10 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
)
|
||||
|
||||
cancel_order_mock = MagicMock()
|
||||
stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
|
||||
stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'})
|
||||
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
|
||||
mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
|
||||
trade.stoploss_order_id = '100'
|
||||
|
||||
# stoploss should not be updated as the interval is 60 seconds
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
@@ -1722,7 +1934,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
|
||||
cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
|
||||
cancel_order_mock.assert_called_once_with('100', 'ETH/USDT')
|
||||
# Long uses modified ask - offset, short modified bid + offset
|
||||
stoploss_order_mock.assert_called_once_with(
|
||||
amount=pytest.approx(trade.amount),
|
||||
@@ -1751,7 +1963,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
|
||||
exit_order = limit_order['sell']
|
||||
|
||||
# When trailing stoploss is set
|
||||
stoploss = MagicMock(return_value={'id': 13434334})
|
||||
stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'})
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_edge(mocker)
|
||||
@@ -1800,11 +2012,21 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_last_update = arrow.utcnow()
|
||||
trade.stoploss_order_id = '100'
|
||||
trade.stoploss_last_update = arrow.utcnow().datetime
|
||||
trade.orders.append(
|
||||
Order(
|
||||
ft_order_side='stoploss',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.stop_loss,
|
||||
order_id='100',
|
||||
)
|
||||
)
|
||||
|
||||
stoploss_order_hanging = MagicMock(return_value={
|
||||
'id': 100,
|
||||
'id': '100',
|
||||
'status': 'open',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
@@ -1851,7 +2073,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
|
||||
|
||||
# stoploss should be set to 1% as trailing is on
|
||||
assert trade.stop_loss == 4.4 * 0.99
|
||||
cancel_order_mock.assert_called_once_with(100, 'NEO/BTC')
|
||||
cancel_order_mock.assert_called_once_with('100', 'NEO/BTC')
|
||||
stoploss_order_mock.assert_called_once_with(
|
||||
amount=pytest.approx(11.41438356),
|
||||
pair='NEO/BTC',
|
||||
@@ -1885,6 +2107,7 @@ def test_enter_positions(mocker, default_conf_usdt, return_value, side_effect,
|
||||
assert mock_ct.call_count == len(default_conf_usdt['exchange']['pair_whitelist'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
@@ -1893,12 +2116,33 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
|
||||
mocker.patch(f'{EXMS}.fetch_order', return_value=limit_order[entry_side(is_short)])
|
||||
mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[])
|
||||
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
trade.is_short = is_short
|
||||
trade.open_order_id = '123'
|
||||
trade.open_fee = 0.001
|
||||
order_id = '123'
|
||||
trade = Trade(
|
||||
open_order_id=order_id,
|
||||
pair='ETH/USDT',
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
open_rate=0.01,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
stake_amount=0.01,
|
||||
amount=11,
|
||||
exchange="binance",
|
||||
is_short=is_short,
|
||||
leverage=1,
|
||||
)
|
||||
trade.orders.append(Order(
|
||||
ft_order_side=entry_side(is_short),
|
||||
price=0.01,
|
||||
ft_pair=trade.pair,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.open_rate,
|
||||
order_id=order_id,
|
||||
|
||||
))
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
trades = [trade]
|
||||
freqtrade.wallets.update()
|
||||
n = freqtrade.exit_positions(trades)
|
||||
assert n == 0
|
||||
# Test amount not modified by fee-logic
|
||||
@@ -1911,17 +2155,40 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
|
||||
assert gra.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog, is_short) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
order = limit_order[entry_side(is_short)]
|
||||
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
|
||||
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
trade.is_short = is_short
|
||||
order_id = '123'
|
||||
trade = Trade(
|
||||
open_order_id=order_id,
|
||||
pair='ETH/USDT',
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
open_rate=0.01,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
stake_amount=0.01,
|
||||
amount=11,
|
||||
exchange="binance",
|
||||
is_short=is_short,
|
||||
leverage=1,
|
||||
)
|
||||
trade.orders.append(Order(
|
||||
ft_order_side=entry_side(is_short),
|
||||
price=0.01,
|
||||
ft_pair=trade.pair,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.open_rate,
|
||||
order_id=order_id,
|
||||
|
||||
))
|
||||
trade.open_order_id = None
|
||||
trade.pair = 'ETH/USDT'
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
freqtrade.wallets.update()
|
||||
trades = [trade]
|
||||
|
||||
# Test raise of DependencyException exception
|
||||
@@ -2733,6 +3000,9 @@ def test_manage_open_orders_exit_usercustom(
|
||||
assert rpc_mock.call_count == 2
|
||||
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
||||
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
# cancelling didn't succeed - order-id remains open.
|
||||
assert trade.open_order_id is not None
|
||||
|
||||
# 2nd canceled trade - Fail execute exit
|
||||
caplog.clear()
|
||||
@@ -3243,6 +3513,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
|
||||
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '125'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
order = {'remaining': 1,
|
||||
'id': '125',
|
||||
@@ -3250,6 +3521,10 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
|
||||
'status': "open"}
|
||||
assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
|
||||
# mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=order)
|
||||
# assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
# assert trade.open_order_id == '125'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short, open_rate, amt", [
|
||||
(False, 2.0, 30.0),
|
||||
@@ -3326,6 +3601,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
|
||||
'profit_ratio': 0.00493809 if is_short else 0.09451372,
|
||||
'stake_currency': 'USDT',
|
||||
'fiat_currency': 'USD',
|
||||
'base_currency': 'ETH',
|
||||
'sell_reason': ExitType.ROI.value,
|
||||
'exit_reason': ExitType.ROI.value,
|
||||
'open_date': ANY,
|
||||
@@ -3389,6 +3665,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
|
||||
'profit_amount': -5.65990099 if is_short else -0.00075,
|
||||
'profit_ratio': -0.0945681 if is_short else -1.247e-05,
|
||||
'stake_currency': 'USDT',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sell_reason': ExitType.STOP_LOSS.value,
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
@@ -3474,6 +3751,7 @@ def test_execute_trade_exit_custom_exit_price(
|
||||
'profit_amount': pytest.approx(profit_amount),
|
||||
'profit_ratio': profit_ratio,
|
||||
'stake_currency': 'USDT',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sell_reason': 'foo',
|
||||
'exit_reason': 'foo',
|
||||
@@ -3547,6 +3825,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
||||
'profit_ratio': -0.00501253 if is_short else -0.01493766,
|
||||
'stake_currency': 'USDT',
|
||||
'fiat_currency': 'USD',
|
||||
'base_currency': 'ETH',
|
||||
'sell_reason': ExitType.STOP_LOSS.value,
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
'open_date': ANY,
|
||||
@@ -3588,7 +3867,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=1234,
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
|
||||
assert create_order_mock.call_count == 2
|
||||
assert log_has('Could not cancel stoploss order abcd', caplog)
|
||||
assert log_has('Could not cancel stoploss order abcd for pair ETH/USDT', caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -3600,10 +3879,12 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
|
||||
patch_exchange(mocker)
|
||||
stoploss = MagicMock(return_value={
|
||||
'id': 123,
|
||||
'status': 'open',
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_order_fee')
|
||||
|
||||
cancel_order = MagicMock(return_value=True)
|
||||
mocker.patch.multiple(
|
||||
@@ -3701,12 +3982,12 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
|
||||
"lastTradeTimestamp": None,
|
||||
"symbol": "BTC/USDT",
|
||||
"type": "stop_loss_limit",
|
||||
"side": "sell",
|
||||
"side": "buy" if is_short else "sell",
|
||||
"price": 1.08801,
|
||||
"amount": 90.99181074,
|
||||
"cost": 99.0000000032274,
|
||||
"amount": trade.amount,
|
||||
"cost": 1.08801 * trade.amount,
|
||||
"average": 1.08801,
|
||||
"filled": 90.99181074,
|
||||
"filled": trade.amount,
|
||||
"remaining": 0.0,
|
||||
"status": "closed",
|
||||
"fee": None,
|
||||
@@ -3811,6 +4092,7 @@ def test_execute_trade_exit_market_order(
|
||||
'profit_amount': pytest.approx(profit_amount),
|
||||
'profit_ratio': profit_ratio,
|
||||
'stake_currency': 'USDT',
|
||||
'base_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sell_reason': ExitType.ROI.value,
|
||||
'exit_reason': ExitType.ROI.value,
|
||||
|
||||
+18
-16
@@ -35,7 +35,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
"type": "stop_loss_limit",
|
||||
"side": "sell",
|
||||
"price": 1.08801,
|
||||
"amount": 90.99181074,
|
||||
"amount": 91.07468123,
|
||||
"cost": 0.0,
|
||||
"average": 0.0,
|
||||
"filled": 0.0,
|
||||
@@ -49,8 +49,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
|
||||
|
||||
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
|
||||
stop_orders = [stoploss_order_closed, stoploss_order_open, stoploss_order_open]
|
||||
stoploss_order_mock = MagicMock(
|
||||
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
|
||||
side_effect=stop_orders)
|
||||
# Sell 3rd trade (not called for the first trade)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
[],
|
||||
@@ -93,13 +94,14 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
wallets_mock.reset_mock()
|
||||
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
# Make sure stoploss-order is open and trade is bought (since we mock update_trade_state)
|
||||
for trade in trades:
|
||||
stoploss_order_closed['id'] = '3'
|
||||
oobj = Order.parse_from_ccxt_object(stoploss_order_closed, trade.pair, 'stoploss')
|
||||
# Make sure stoploss-order is open and trade is bought
|
||||
for idx, trade in enumerate(trades):
|
||||
stop_order = stop_orders[idx]
|
||||
stop_order['id'] = f"stop{idx}"
|
||||
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
|
||||
|
||||
trade.orders.append(oobj)
|
||||
trade.stoploss_order_id = '3'
|
||||
trade.stoploss_order_id = f"stop{idx}"
|
||||
trade.open_order_id = None
|
||||
|
||||
n = freqtrade.exit_positions(trades)
|
||||
@@ -386,12 +388,12 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert trade.open_order_id is not None
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.open_rate == 1.96
|
||||
assert trade.stop_loss_pct is None
|
||||
assert trade.stop_loss == 0.0
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
assert trade.leverage == leverage
|
||||
assert trade.stake_amount == 60
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.initial_stop_loss_pct is None
|
||||
# No adjustment
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
@@ -407,11 +409,11 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert trade.open_order_id is not None
|
||||
# Open rate is not adjusted yet
|
||||
assert trade.open_rate == 1.96
|
||||
assert trade.stop_loss_pct is None
|
||||
assert trade.stop_loss == 0.0
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
|
||||
assert trade.stake_amount == 60
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.initial_stop_loss_pct is None
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
|
||||
# Fill order
|
||||
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True)
|
||||
@@ -424,7 +426,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert pytest.approx(trade.stop_loss) == 1.99 * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == 1.99 * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == 1.96 * (1 - 0.1 / leverage)
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
|
||||
# 2nd order - not filling
|
||||
|
||||
@@ -10,6 +10,8 @@ from freqtrade.exceptions import OperationalException
|
||||
|
||||
def test_parse_timerange_incorrect():
|
||||
|
||||
timerange = TimeRange.parse_timerange('')
|
||||
assert timerange == TimeRange(None, None, 0, 0)
|
||||
timerange = TimeRange.parse_timerange('20100522-')
|
||||
assert TimeRange('date', None, 1274486400, 0) == timerange
|
||||
assert timerange.timerange_str == '20100522-'
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user