mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Compare commits
40 Commits
096202de77
...
a5a4006a38
Author | SHA1 | Date | |
---|---|---|---|
|
a5a4006a38 | ||
|
2fe67edab3 | ||
|
167e43cbef | ||
|
d8cb407c25 | ||
|
9452afe3f7 | ||
|
8dc6d9ce7d | ||
|
bf4b8a318d | ||
|
20f6022050 | ||
|
65e6c737cd | ||
|
6d2572e347 | ||
|
52a35197c7 | ||
|
2b3a41db3e | ||
|
09c1459411 | ||
|
98f18b89da | ||
|
4249db4330 | ||
|
a7f46500ed | ||
|
c73fa2b0eb | ||
|
db4c4b971a | ||
|
e9ccc98ada | ||
|
11d6ec33b3 | ||
|
c3b6f4ca85 | ||
|
d37405a307 | ||
|
d7a9841328 | ||
|
cf3af42477 | ||
|
ad8e6e7d67 | ||
|
6b8ca7217b | ||
|
2811a470aa | ||
|
1fccdd8cda | ||
|
33b421014d | ||
|
730bef2920 | ||
|
79ce1ddaef | ||
|
714822c93c | ||
|
fb3787173f | ||
|
e9ba0d2ce8 | ||
|
6a580176ea | ||
|
910b3ad536 | ||
|
05cf4cab8e | ||
|
6752c3e288 | ||
|
faeda2a166 | ||
|
3d67e0893a |
|
@ -16,7 +16,7 @@ repos:
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- types-cachetools==5.5.0.20240820
|
- types-cachetools==5.5.0.20240820
|
||||||
- types-filelock==3.2.7
|
- types-filelock==3.2.7
|
||||||
- types-requests==2.32.0.20240907
|
- types-requests==2.32.0.20240914
|
||||||
- types-tabulate==0.9.0.20240106
|
- types-tabulate==0.9.0.20240106
|
||||||
- types-python-dateutil==2.9.0.20240906
|
- types-python-dateutil==2.9.0.20240906
|
||||||
- SQLAlchemy==2.0.34
|
- SQLAlchemy==2.0.34
|
||||||
|
|
|
@ -3594,7 +3594,7 @@ class Exchange:
|
||||||
Wherein, "+" or "-" depends on whether the contract goes long or short:
|
Wherein, "+" or "-" depends on whether the contract goes long or short:
|
||||||
"-" for long, and "+" for short.
|
"-" for long, and "+" for short.
|
||||||
|
|
||||||
okex: https://www.okex.com/support/hc/en-us/articles/
|
okex: https://www.okx.com/support/hc/en-us/articles/
|
||||||
360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin
|
360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin
|
||||||
|
|
||||||
:param pair: Pair to calculate liquidation price for
|
:param pair: Pair to calculate liquidation price for
|
||||||
|
|
|
@ -715,8 +715,6 @@ class FreqtradeBot(LoggingMixin):
|
||||||
"""
|
"""
|
||||||
# Walk through each pair and check if it needs changes
|
# Walk through each pair and check if it needs changes
|
||||||
for trade in Trade.get_open_trades():
|
for trade in Trade.get_open_trades():
|
||||||
# If there is any open orders, wait for them to finish.
|
|
||||||
# TODO Remove to allow mul open orders
|
|
||||||
if not trade.has_open_orders:
|
if not trade.has_open_orders:
|
||||||
# Do a wallets update (will be ratelimited to once per hour)
|
# Do a wallets update (will be ratelimited to once per hour)
|
||||||
self.wallets.update(False)
|
self.wallets.update(False)
|
||||||
|
@ -724,8 +722,7 @@ class FreqtradeBot(LoggingMixin):
|
||||||
self.check_and_call_adjust_trade_position(trade)
|
self.check_and_call_adjust_trade_position(trade)
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Unable to adjust position of trade for {trade.pair}: {exception}"
|
f"Unable to adjust position of trade for {trade.pair}: {exception}")
|
||||||
)
|
|
||||||
|
|
||||||
def check_and_call_adjust_trade_position(self, trade: Trade):
|
def check_and_call_adjust_trade_position(self, trade: Trade):
|
||||||
"""
|
"""
|
||||||
|
@ -1251,8 +1248,7 @@ class FreqtradeBot(LoggingMixin):
|
||||||
trades_closed = 0
|
trades_closed = 0
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
if (
|
if (
|
||||||
not trade.has_open_orders
|
not trade.has_open_sl_orders
|
||||||
and not trade.has_open_sl_orders
|
|
||||||
and not self.wallets.check_exit_amount(trade)
|
and not self.wallets.check_exit_amount(trade)
|
||||||
):
|
):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
@ -1277,7 +1273,7 @@ class FreqtradeBot(LoggingMixin):
|
||||||
f"Unable to handle stoploss on exchange for {trade.pair}: {exception}"
|
f"Unable to handle stoploss on exchange for {trade.pair}: {exception}"
|
||||||
)
|
)
|
||||||
# Check if we can sell our current pair
|
# Check if we can sell our current pair
|
||||||
if not trade.has_open_orders and trade.is_open and self.handle_trade(trade):
|
if trade.is_open and self.handle_trade(trade):
|
||||||
trades_closed += 1
|
trades_closed += 1
|
||||||
|
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
|
@ -1421,7 +1417,7 @@ class FreqtradeBot(LoggingMixin):
|
||||||
self.handle_protections(trade.pair, trade.trade_direction)
|
self.handle_protections(trade.pair, trade.trade_direction)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if trade.has_open_orders or not trade.is_open:
|
if not trade.is_open:
|
||||||
# Trade has an open order, Stoploss-handling can't happen in this case
|
# Trade has an open order, Stoploss-handling can't happen in this case
|
||||||
# as the Amount on the exchange is tied up in another trade.
|
# as the Amount on the exchange is tied up in another trade.
|
||||||
# The trade can be closed already (sell-order fill confirmation came in this iteration)
|
# The trade can be closed already (sell-order fill confirmation came in this iteration)
|
||||||
|
@ -1689,6 +1685,34 @@ class FreqtradeBot(LoggingMixin):
|
||||||
logger.warning(f"Unable to replace order for {trade.pair}: {exception}")
|
logger.warning(f"Unable to replace order for {trade.pair}: {exception}")
|
||||||
self.replace_order_failed(trade, f"Could not replace order for {trade}.")
|
self.replace_order_failed(trade, f"Could not replace order for {trade}.")
|
||||||
|
|
||||||
|
def cancel_open_orders_of_trade(self, trade: Trade, reason: str, sides: List[str]) -> None:
|
||||||
|
"""
|
||||||
|
Cancel trade orders of specified sides that are currently open
|
||||||
|
:param trade: Trade object of the trade we're analyzing
|
||||||
|
:param reason: The reason for that cancelation
|
||||||
|
:param sides: The sides where cancellation should take place
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
for open_order in trade.open_orders:
|
||||||
|
try:
|
||||||
|
order = self.exchange.fetch_order(open_order.order_id, trade.pair)
|
||||||
|
except ExchangeError:
|
||||||
|
logger.info("Can't query order for %s due to %s", trade, traceback.format_exc())
|
||||||
|
continue
|
||||||
|
|
||||||
|
for side in sides:
|
||||||
|
if (order["side"] == side):
|
||||||
|
if order["side"] == trade.entry_side:
|
||||||
|
self.handle_cancel_enter(
|
||||||
|
trade, order, open_order, reason
|
||||||
|
)
|
||||||
|
|
||||||
|
elif order["side"] == trade.exit_side:
|
||||||
|
self.handle_cancel_exit(
|
||||||
|
trade, order, open_order, reason
|
||||||
|
)
|
||||||
|
|
||||||
def cancel_all_open_orders(self) -> None:
|
def cancel_all_open_orders(self) -> None:
|
||||||
"""
|
"""
|
||||||
Cancel all orders that are currently open
|
Cancel all orders that are currently open
|
||||||
|
@ -1696,22 +1720,11 @@ class FreqtradeBot(LoggingMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for trade in Trade.get_open_trades():
|
for trade in Trade.get_open_trades():
|
||||||
for open_order in trade.open_orders:
|
self.cancel_open_orders_of_trade(
|
||||||
try:
|
trade, constants.CANCEL_REASON["ALL_CANCELLED"],
|
||||||
order = self.exchange.fetch_order(open_order.order_id, trade.pair)
|
[trade.entry_side, trade.exit_side]
|
||||||
except ExchangeError:
|
)
|
||||||
logger.info("Can't query order for %s due to %s", trade, traceback.format_exc())
|
|
||||||
continue
|
|
||||||
|
|
||||||
if order["side"] == trade.entry_side:
|
|
||||||
self.handle_cancel_enter(
|
|
||||||
trade, order, open_order, constants.CANCEL_REASON["ALL_CANCELLED"]
|
|
||||||
)
|
|
||||||
|
|
||||||
elif order["side"] == trade.exit_side:
|
|
||||||
self.handle_cancel_exit(
|
|
||||||
trade, order, open_order, constants.CANCEL_REASON["ALL_CANCELLED"]
|
|
||||||
)
|
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
def handle_cancel_enter(
|
def handle_cancel_enter(
|
||||||
|
@ -1957,6 +1970,14 @@ class FreqtradeBot(LoggingMixin):
|
||||||
|
|
||||||
limit = self.get_valid_price(custom_exit_price, proposed_limit_rate)
|
limit = self.get_valid_price(custom_exit_price, proposed_limit_rate)
|
||||||
|
|
||||||
|
if trade.has_open_orders:
|
||||||
|
# cancel any open order of this trade
|
||||||
|
self.cancel_open_orders_of_trade(
|
||||||
|
trade, constants.CANCEL_REASON["REPLACE"],
|
||||||
|
[trade.exit_side]
|
||||||
|
)
|
||||||
|
Trade.commit()
|
||||||
|
|
||||||
# First cancelling stoploss on exchange ...
|
# First cancelling stoploss on exchange ...
|
||||||
trade = self.cancel_stoploss_on_exchange(trade)
|
trade = self.cancel_stoploss_on_exchange(trade)
|
||||||
|
|
||||||
|
|
|
@ -1376,8 +1376,7 @@ class Backtesting:
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
|
||||||
# 4. Create exit orders (if any)
|
# 4. Create exit orders (if any)
|
||||||
if not trade.has_open_orders:
|
self._check_trade_exit(trade, row, current_time) # Place exit order if necessary
|
||||||
self._check_trade_exit(trade, row, current_time) # Place exit order if necessary
|
|
||||||
|
|
||||||
# 5. Process exit orders.
|
# 5. Process exit orders.
|
||||||
order = trade.select_order(trade.exit_side, is_open=True)
|
order = trade.select_order(trade.exit_side, is_open=True)
|
||||||
|
|
|
@ -178,6 +178,7 @@ class Order(ModelBase):
|
||||||
return (
|
return (
|
||||||
f"Order(id={self.id}, trade={self.ft_trade_id}, order_id={self.order_id}, "
|
f"Order(id={self.id}, trade={self.ft_trade_id}, order_id={self.order_id}, "
|
||||||
f"side={self.side}, filled={self.safe_filled}, price={self.safe_price}, "
|
f"side={self.side}, filled={self.safe_filled}, price={self.safe_price}, "
|
||||||
|
f"amount={self.amount}, "
|
||||||
f"status={self.status}, date={self.order_date_utc:{DATETIME_PRINT_FORMAT}})"
|
f"status={self.status}, date={self.order_date_utc:{DATETIME_PRINT_FORMAT}})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -585,6 +586,67 @@ class LocalTrade:
|
||||||
]
|
]
|
||||||
return len(open_orders_wo_sl) > 0
|
return len(open_orders_wo_sl) > 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_open_entry_orders(self) -> bool:
|
||||||
|
"""
|
||||||
|
True if there are open entry orders for this trade
|
||||||
|
"""
|
||||||
|
open_entry_orders = [
|
||||||
|
o for o in self.orders
|
||||||
|
if o.ft_order_side == self.entry_side and o.ft_is_open
|
||||||
|
]
|
||||||
|
return len(open_entry_orders) > 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_open_position(self) -> bool:
|
||||||
|
"""
|
||||||
|
True if there is an open position for this trade
|
||||||
|
"""
|
||||||
|
entry_orders = [
|
||||||
|
o for o in self.orders
|
||||||
|
if o.ft_order_side == self.entry_side
|
||||||
|
]
|
||||||
|
entry_orders_filled_qty = sum(eno.safe_filled for eno in entry_orders)
|
||||||
|
|
||||||
|
exit_orders = [
|
||||||
|
o for o in self.orders
|
||||||
|
if o.ft_order_side == self.exit_side
|
||||||
|
]
|
||||||
|
exit_orders_filled_qty = sum(exo.safe_filled for exo in exit_orders)
|
||||||
|
|
||||||
|
return (entry_orders_filled_qty - exit_orders_filled_qty) > 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def untied_assets(self) -> float:
|
||||||
|
entry_orders = [
|
||||||
|
o for o in self.orders
|
||||||
|
if o.ft_order_side == self.entry_side
|
||||||
|
]
|
||||||
|
entry_orders_filled_qty = sum(eno.safe_filled for eno in entry_orders)
|
||||||
|
|
||||||
|
exit_orders = [
|
||||||
|
o for o in self.orders
|
||||||
|
if o.ft_order_side == self.exit_side
|
||||||
|
]
|
||||||
|
exit_orders_remaining_qty = sum(exo.safe_remaining for exo in exit_orders)
|
||||||
|
untied_remaining = entry_orders_filled_qty - exit_orders_remaining_qty
|
||||||
|
|
||||||
|
logger.info(f"entry_orders: {entry_orders}")
|
||||||
|
logger.info(f"exit_orders: {exit_orders}")
|
||||||
|
logger.info(f"entry_orders_filled_qty: {entry_orders_filled_qty}")
|
||||||
|
logger.info(f"exit_orders_remaining_qty: {exit_orders_remaining_qty}")
|
||||||
|
|
||||||
|
logger.info(f"untied_remaining: {untied_remaining}")
|
||||||
|
|
||||||
|
return untied_remaining
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_untied_assets(self) -> bool:
|
||||||
|
"""
|
||||||
|
True if there is still remaining position not yet tied up to exit order
|
||||||
|
"""
|
||||||
|
return self.untied_assets > 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def open_sl_orders(self) -> List[Order]:
|
def open_sl_orders(self) -> List[Order]:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
-r docs/requirements-docs.txt
|
-r docs/requirements-docs.txt
|
||||||
|
|
||||||
coveralls==4.0.1
|
coveralls==4.0.1
|
||||||
ruff==0.6.4
|
ruff==0.6.5
|
||||||
mypy==1.11.2
|
mypy==1.11.2
|
||||||
pre-commit==3.8.0
|
pre-commit==3.8.0
|
||||||
pytest==8.3.2
|
pytest==8.3.3
|
||||||
pytest-asyncio==0.24.0
|
pytest-asyncio==0.24.0
|
||||||
pytest-cov==5.0.0
|
pytest-cov==5.0.0
|
||||||
pytest-mock==3.14.0
|
pytest-mock==3.14.0
|
||||||
|
@ -27,6 +27,6 @@ nbconvert==7.16.4
|
||||||
# mypy types
|
# mypy types
|
||||||
types-cachetools==5.5.0.20240820
|
types-cachetools==5.5.0.20240820
|
||||||
types-filelock==3.2.7
|
types-filelock==3.2.7
|
||||||
types-requests==2.32.0.20240907
|
types-requests==2.32.0.20240914
|
||||||
types-tabulate==0.9.0.20240106
|
types-tabulate==0.9.0.20240106
|
||||||
types-python-dateutil==2.9.0.20240906
|
types-python-dateutil==2.9.0.20240906
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
-r requirements-plot.txt
|
-r requirements-plot.txt
|
||||||
|
|
||||||
# Required for freqai
|
# Required for freqai
|
||||||
scikit-learn==1.5.1
|
scikit-learn==1.5.2
|
||||||
joblib==1.4.2
|
joblib==1.4.2
|
||||||
catboost==1.2.7; 'arm' not in platform_machine
|
catboost==1.2.7; 'arm' not in platform_machine
|
||||||
# Pin Matplotlib - it's depended on by catboost
|
# Pin Matplotlib - it's depended on by catboost
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
# Required for hyperopt
|
# Required for hyperopt
|
||||||
scipy==1.14.1; python_version >= "3.10"
|
scipy==1.14.1; python_version >= "3.10"
|
||||||
scipy==1.13.1; python_version < "3.10"
|
scipy==1.13.1; python_version < "3.10"
|
||||||
scikit-learn==1.5.1
|
scikit-learn==1.5.2
|
||||||
ft-scikit-optimize==0.9.2
|
ft-scikit-optimize==0.9.2
|
||||||
filelock==3.16.0
|
filelock==3.16.0
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Include all requirements to run the bot.
|
# Include all requirements to run the bot.
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
plotly==5.24.0
|
plotly==5.24.1
|
||||||
|
|
|
@ -4,7 +4,7 @@ bottleneck==1.4.0
|
||||||
numexpr==2.10.1
|
numexpr==2.10.1
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==4.3.98
|
ccxt==4.4.3
|
||||||
cryptography==42.0.8; platform_machine == 'armv7l'
|
cryptography==42.0.8; platform_machine == 'armv7l'
|
||||||
cryptography==43.0.1; platform_machine != 'armv7l'
|
cryptography==43.0.1; platform_machine != 'armv7l'
|
||||||
aiohttp==3.10.5
|
aiohttp==3.10.5
|
||||||
|
@ -15,7 +15,7 @@ httpx>=0.24.1
|
||||||
humanize==4.10.0
|
humanize==4.10.0
|
||||||
cachetools==5.5.0
|
cachetools==5.5.0
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
urllib3==2.2.2
|
urllib3==2.2.3
|
||||||
jsonschema==4.23.0
|
jsonschema==4.23.0
|
||||||
TA-Lib==0.4.32
|
TA-Lib==0.4.32
|
||||||
technical==1.4.4
|
technical==1.4.4
|
||||||
|
@ -26,7 +26,7 @@ jinja2==3.1.4
|
||||||
tables==3.9.1; python_version < "3.10"
|
tables==3.9.1; python_version < "3.10"
|
||||||
tables==3.10.1; python_version >= "3.10"
|
tables==3.10.1; python_version >= "3.10"
|
||||||
joblib==1.4.2
|
joblib==1.4.2
|
||||||
rich==13.8.0
|
rich==13.8.1
|
||||||
pyarrow==17.0.0; platform_machine != 'armv7l'
|
pyarrow==17.0.0; platform_machine != 'armv7l'
|
||||||
|
|
||||||
# find first, C search in arrays
|
# find first, C search in arrays
|
||||||
|
@ -41,8 +41,8 @@ orjson==3.10.7
|
||||||
sdnotify==0.3.2
|
sdnotify==0.3.2
|
||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
fastapi==0.114.0
|
fastapi==0.114.2
|
||||||
pydantic==2.9.0
|
pydantic==2.9.1
|
||||||
uvicorn==0.30.6
|
uvicorn==0.30.6
|
||||||
pyjwt==2.9.0
|
pyjwt==2.9.0
|
||||||
aiofiles==24.1.0
|
aiofiles==24.1.0
|
||||||
|
@ -53,7 +53,7 @@ questionary==2.0.1
|
||||||
prompt-toolkit==3.0.36
|
prompt-toolkit==3.0.36
|
||||||
# Extensions to datetime library
|
# Extensions to datetime library
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
pytz==2024.1
|
pytz==2024.2
|
||||||
|
|
||||||
#Futures
|
#Futures
|
||||||
schedule==1.2.2
|
schedule==1.2.2
|
||||||
|
|
|
@ -964,6 +964,29 @@ def get_markets():
|
||||||
},
|
},
|
||||||
"info": {},
|
"info": {},
|
||||||
},
|
},
|
||||||
|
"ETC/BTC": {
|
||||||
|
"id": "ETCBTC",
|
||||||
|
"symbol": "ETC/BTC",
|
||||||
|
"base": "ETC",
|
||||||
|
"quote": "BTC",
|
||||||
|
"active": True,
|
||||||
|
"spot": True,
|
||||||
|
"swap": False,
|
||||||
|
"linear": None,
|
||||||
|
"type": "spot",
|
||||||
|
"contractSize": None,
|
||||||
|
"precision": {"base": 8, "quote": 8, "amount": 2, "price": 7},
|
||||||
|
"limits": {
|
||||||
|
"amount": {"min": 0.01, "max": 90000000.0},
|
||||||
|
"price": {"min": 1e-07, "max": 1000.0},
|
||||||
|
"cost": {"min": 0.0001, "max": 9000000.0},
|
||||||
|
"leverage": {
|
||||||
|
"min": None,
|
||||||
|
"max": None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"info": {},
|
||||||
|
},
|
||||||
"ETH/USDT": {
|
"ETH/USDT": {
|
||||||
"id": "USDT-ETH",
|
"id": "USDT-ETH",
|
||||||
"symbol": "ETH/USDT",
|
"symbol": "ETH/USDT",
|
||||||
|
|
|
@ -1265,14 +1265,14 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
|
||||||
trades = [trade]
|
trades = [trade]
|
||||||
freqtrade.wallets.update()
|
freqtrade.wallets.update()
|
||||||
n = freqtrade.exit_positions(trades)
|
n = freqtrade.exit_positions(trades)
|
||||||
assert n == 0
|
assert n == 1
|
||||||
# Test amount not modified by fee-logic
|
# Test amount not modified by fee-logic
|
||||||
assert not log_has_re(r"Applying fee to amount for Trade .*", caplog)
|
assert not log_has_re(r"Applying fee to amount for Trade .*", caplog)
|
||||||
|
|
||||||
gra = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", return_value=0.0)
|
gra = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", return_value=0.0)
|
||||||
# test amount modified by fee-logic
|
# test amount modified by fee-logic
|
||||||
n = freqtrade.exit_positions(trades)
|
n = freqtrade.exit_positions(trades)
|
||||||
assert n == 0
|
assert n == 1
|
||||||
assert gra.call_count == 0
|
assert gra.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -1305,6 +1305,7 @@ def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog
|
||||||
ft_price=trade.open_rate,
|
ft_price=trade.open_rate,
|
||||||
order_id=order_id,
|
order_id=order_id,
|
||||||
ft_is_open=False,
|
ft_is_open=False,
|
||||||
|
filled=11
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
import time
|
import time
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
@ -347,8 +348,8 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
||||||
assert trade.nr_of_successful_exits == 1
|
assert trade.nr_of_successful_exits == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("leverage", [1, 2])
|
@pytest.mark.parametrize("leverage", [1])
|
||||||
def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) -> None:
|
def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker, caplog) -> None:
|
||||||
default_conf_usdt["position_adjustment_enable"] = True
|
default_conf_usdt["position_adjustment_enable"] = True
|
||||||
default_conf_usdt["trading_mode"] = "futures"
|
default_conf_usdt["trading_mode"] = "futures"
|
||||||
default_conf_usdt["margin_mode"] = "isolated"
|
default_conf_usdt["margin_mode"] = "isolated"
|
||||||
|
@ -478,10 +479,16 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||||
assert pytest.approx(trade.amount) == 91.689215 * leverage
|
assert pytest.approx(trade.amount) == 91.689215 * leverage
|
||||||
assert pytest.approx(trade.orders[-1].amount) == 91.689215 * leverage
|
assert pytest.approx(trade.orders[-1].amount) == 91.689215 * leverage
|
||||||
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
||||||
|
|
||||||
|
caplog.clear()
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
# Process again, should not adjust entry price
|
# Process again, should not adjust entry price
|
||||||
freqtrade.process()
|
freqtrade.process()
|
||||||
trade = Trade.get_trades().first()
|
trade = Trade.get_trades().first()
|
||||||
assert len(trade.orders) == 5
|
|
||||||
|
assert trade.orders[-2].status == "canceled"
|
||||||
|
assert len(trade.orders) == 6
|
||||||
|
assert trade.orders[-1].side == trade.exit_side
|
||||||
assert trade.orders[-1].status == "open"
|
assert trade.orders[-1].status == "open"
|
||||||
assert trade.orders[-1].price == 2.02
|
assert trade.orders[-1].price == 2.02
|
||||||
# Adjust entry price cannot be called - this is an exit order
|
# Adjust entry price cannot be called - this is an exit order
|
||||||
|
|
Loading…
Reference in New Issue
Block a user