Skip to content

Commit 3878080

Browse files
committed
[SmartDCA] fix futures secondary orders
1 parent d64906b commit 3878080

File tree

2 files changed

+121
-4
lines changed

2 files changed

+121
-4
lines changed

Trading/Mode/dca_trading_mode/dca_trading.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,13 @@ async def create_new_orders(self, symbol, _, state, **kwargs):
137137
if self.exchange_manager.is_future:
138138
self.trading_mode.ensure_supported(symbol)
139139
# on futures, current_symbol_holding = current_market_holding = market_quantity
140-
initial_available_funds, _ = trading_personal_data.get_futures_max_order_size(
140+
initial_available_base_funds, _ = trading_personal_data.get_futures_max_order_size(
141141
self.exchange_manager, symbol, side,
142142
price, False, current_symbol_holding, market_quantity
143143
)
144+
initial_available_quote_funds = initial_available_base_funds * price
144145
else:
145-
initial_available_funds = current_market_holding \
146+
initial_available_quote_funds = current_market_holding \
146147
if side is trading_enums.TradeOrderSide.BUY else current_symbol_holding
147148
if side is trading_enums.TradeOrderSide.BUY:
148149
initial_entry_order_type = trading_enums.TraderOrderType.BUY_MARKET \
@@ -152,7 +153,7 @@ async def create_new_orders(self, symbol, _, state, **kwargs):
152153
if self.trading_mode.use_market_entry_orders else trading_enums.TraderOrderType.SELL_LIMIT
153154
adapted_entry_quantity = trading_personal_data.decimal_adapt_order_quantity_because_fees(
154155
self.exchange_manager, symbol, initial_entry_order_type, quantity, initial_entry_price,
155-
trading_enums.ExchangeConstantsMarketPropertyColumns.TAKER, side, initial_available_funds
156+
trading_enums.ExchangeConstantsMarketPropertyColumns.TAKER, side, initial_available_quote_funds
156157
)
157158

158159
# initial entry
@@ -172,7 +173,7 @@ async def create_new_orders(self, symbol, _, state, **kwargs):
172173
)
173174
else:
174175
for i in range(self.trading_mode.secondary_entry_orders_count):
175-
remaining_funds = initial_available_funds - sum(
176+
remaining_funds = initial_available_quote_funds - sum(
176177
trading_personal_data.get_locked_funds(order)
177178
for order in created_orders
178179
)

Trading/Mode/dca_trading_mode/tests/test_dca_trading_mode.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import octobot_trading.api as trading_api
3636
import octobot_trading.exchange_channel as exchanges_channel
3737
import octobot_trading.exchanges as exchanges
38+
import octobot_trading.exchange_data as trading_exchange_data
3839
import octobot_trading.personal_data as trading_personal_data
3940
import octobot_trading.enums as trading_enums
4041
import octobot_trading.constants as trading_constants
@@ -66,6 +67,18 @@ async def tools():
6667
await _stop(trader.exchange_manager)
6768

6869

70+
@pytest_asyncio.fixture
71+
async def futures_tools():
72+
trader = None
73+
try:
74+
tentacles_manager_api.reload_tentacle_info()
75+
mode, trader = await _get_futures_tools()
76+
yield mode, trader
77+
finally:
78+
if trader:
79+
await _stop(trader.exchange_manager)
80+
81+
6982
async def test_run_independent_backtestings_with_memory_check():
7083
"""
7184
Should always be called first here to avoid other tests' related memory check issues
@@ -1002,6 +1015,60 @@ async def _create_order(order, **kwargs):
10021015
assert total_cost <= decimal.Decimal("225.98463886") # took fees into account
10031016

10041017

1018+
async def test_create_new_buy_orders_futures_trading(futures_tools):
1019+
update = {}
1020+
mode, producer, consumer, trader = await _init_mode(futures_tools, _get_config(futures_tools, update))
1021+
mode.use_secondary_entry_orders = True
1022+
mode.secondary_entry_orders_count = 3
1023+
mode.secondary_entry_orders_amount = "8%t"
1024+
mode.use_market_entry_orders = False
1025+
mode.cancel_open_orders_at_each_entry = False
1026+
mode.trading_config[trading_constants.CONFIG_BUY_ORDER_AMOUNT] = "8%t"
1027+
1028+
mode.exchange_manager.exchange_config.traded_symbols = [
1029+
commons_symbols.parse_symbol("BTC/USDT:USDT")
1030+
]
1031+
portfolio = trader.exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio
1032+
portfolio["USDT"].available = decimal.Decimal("200")
1033+
portfolio["USDT"].total = decimal.Decimal("200")
1034+
portfolio.pop("USD", None)
1035+
portfolio.pop("BTC", None)
1036+
1037+
trading_api.force_set_mark_price(trader.exchange_manager, "BTC/USDT:USDT", 11.0096)
1038+
converter = trader.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.value_converter
1039+
converter.update_last_price("BTC/USDT:USDT", decimal.Decimal("11.0096"))
1040+
1041+
1042+
def _get_fees_currency(base, quote, order_type):
1043+
# force quote fees
1044+
return quote
1045+
1046+
def _read_fees_from_config(fees):
1047+
# use 0.2% fees
1048+
fees[trading_enums.ExchangeConstantsMarketPropertyColumns.MAKER.value] = 0.002
1049+
fees[trading_enums.ExchangeConstantsMarketPropertyColumns.TAKER.value] = 0.002
1050+
fees[trading_enums.ExchangeConstantsMarketPropertyColumns.FEE.value] = 0.002
1051+
1052+
async def _create_order(order, **kwargs):
1053+
await order.initialize(is_from_exchange_data=True, enable_associated_orders_creation=False)
1054+
return order
1055+
1056+
with mock.patch.object(
1057+
mode, "create_order", mock.AsyncMock(side_effect=_create_order)
1058+
) as create_order_mock, mock.patch.object(
1059+
trader.exchange_manager.exchange.connector, "_get_fees_currency",
1060+
mock.Mock(side_effect=_get_fees_currency)
1061+
) as _get_fees_currency_mock, mock.patch.object(
1062+
trader.exchange_manager.exchange.connector, "_read_fees_from_config",
1063+
mock.Mock(side_effect=_read_fees_from_config)
1064+
) as _get_fees_currency_mock:
1065+
orders = await consumer.create_new_orders("BTC/USDT:USDT", None, trading_enums.EvaluatorStates.LONG.value)
1066+
assert orders
1067+
assert len(orders) == 4
1068+
total_cost = sum(order.total_cost for order in orders)
1069+
assert round(total_cost) == decimal.Decimal("56")
1070+
1071+
10051072
async def test_single_exchange_process_optimize_initial_portfolio(tools):
10061073
update = {}
10071074
mode, producer, consumer, trader = await _init_mode(tools, _get_config(tools, update))
@@ -1203,6 +1270,55 @@ async def _get_tools(symbol="BTC/USDT"):
12031270
return mode, trader
12041271

12051272

1273+
async def _get_futures_tools(symbol="BTC/USDT:USDT"):
1274+
config = test_config.load_test_config()
1275+
config[commons_constants.CONFIG_SIMULATOR][commons_constants.CONFIG_STARTING_PORTFOLIO]["USDT"] = 2000
1276+
exchange_manager = test_exchanges.get_test_exchange_manager(config, "binance")
1277+
exchange_manager.tentacles_setup_config = test_utils_config.get_tentacles_setup_config()
1278+
1279+
# use backtesting not to spam exchanges apis
1280+
exchange_manager.is_spot_only = False
1281+
exchange_manager.is_future = True
1282+
exchange_manager.is_simulated = True
1283+
exchange_manager.is_backtesting = True
1284+
exchange_manager.use_cached_markets = False
1285+
backtesting = await backtesting_api.initialize_backtesting(
1286+
config,
1287+
exchange_ids=[exchange_manager.id],
1288+
matrix_id=None,
1289+
data_files=[os.path.join(test_config.TEST_CONFIG_FOLDER,
1290+
"AbstractExchangeHistoryCollector_1586017993.616272.data")])
1291+
exchange_manager.exchange = exchanges.ExchangeSimulator(
1292+
exchange_manager.config, exchange_manager, backtesting
1293+
)
1294+
await exchange_manager.exchange.initialize()
1295+
for exchange_channel_class_type in [exchanges_channel.ExchangeChannel, exchanges_channel.TimeFrameExchangeChannel]:
1296+
await channel_util.create_all_subclasses_channel(exchange_channel_class_type, exchanges_channel.set_chan,
1297+
exchange_manager=exchange_manager)
1298+
contract = trading_exchange_data.FutureContract(
1299+
pair=symbol,
1300+
margin_type=trading_enums.MarginType.ISOLATED,
1301+
contract_type=trading_enums.FutureContractType.LINEAR_PERPETUAL,
1302+
current_leverage=trading_constants.ONE,
1303+
maximum_leverage=trading_constants.ONE_HUNDRED
1304+
)
1305+
exchange_manager.exchange.set_pair_future_contract(symbol, contract)
1306+
trader = exchanges.TraderSimulator(config, exchange_manager)
1307+
await trader.initialize()
1308+
1309+
mode = Mode.DCATradingMode(config, exchange_manager)
1310+
mode.symbol = None if mode.get_is_symbol_wildcard() else symbol
1311+
# trading mode is not initialized: to be initialized with the required config in tests
1312+
1313+
# add mode to exchange manager so that it can be stopped and freed from memory
1314+
exchange_manager.trading_modes.append(mode)
1315+
1316+
# set BTC/USDT price at 1000 USDT
1317+
trading_api.force_set_mark_price(exchange_manager, symbol, 1000)
1318+
1319+
return mode, trader
1320+
1321+
12061322
async def _init_mode(tools, config):
12071323
mode, trader = tools
12081324
await mode.initialize(trading_config=config)

0 commit comments

Comments
 (0)