Skip to content

Commit d16d9b6

Browse files
committed
[Futures] add more flexibility in contracts and positions update
1 parent 751817d commit d16d9b6

File tree

22 files changed

+289
-59
lines changed

22 files changed

+289
-59
lines changed

octobot_trading/api/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,16 @@
201201
from octobot_trading.api.positions import (
202202
get_positions,
203203
close_position,
204+
set_is_exclusively_using_exchange_position_details,
204205
)
205206
from octobot_trading.api.contracts import (
206207
is_inverse_future_contract,
207208
is_perpetual_future_contract,
208209
get_pair_contracts,
209210
is_handled_contract,
211+
has_pair_future_contract,
210212
load_pair_contract,
213+
create_default_future_contract,
211214
)
212215
from octobot_trading.api.storage import (
213216
clear_trades_storage_history,
@@ -381,7 +384,10 @@
381384
"is_perpetual_future_contract",
382385
"get_pair_contracts",
383386
"is_handled_contract",
387+
"has_pair_future_contract",
384388
"load_pair_contract",
389+
"create_default_future_contract",
390+
"set_is_exclusively_using_exchange_position_details",
385391
"clear_trades_storage_history",
386392
"clear_candles_storage_history",
387393
"clear_database_storage_history",

octobot_trading/api/contracts.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
#
1414
# You should have received a copy of the GNU Lesser General Public
1515
# License along with this library.
16-
import octobot_trading.exchange_data as exchange_data
16+
import decimal
1717

18+
import octobot_trading.enums as enums
19+
import octobot_trading.exchange_data as exchange_data
1820

1921
def is_inverse_future_contract(contract_type):
2022
return exchange_data.FutureContract(None, None, contract_type).is_inverse_contract()
@@ -32,5 +34,15 @@ def is_handled_contract(contract) -> bool:
3234
return contract.is_handled_contract()
3335

3436

37+
def has_pair_future_contract(exchange_manager, pair: str) -> bool:
38+
return exchange_manager.exchange.has_pair_future_contract(pair)
39+
40+
3541
def load_pair_contract(exchange_manager, contract_dict: dict):
3642
exchange_data.update_future_contract_from_dict(exchange_manager, contract_dict)
43+
44+
45+
def create_default_future_contract(
46+
pair: str, leverage: decimal.Decimal, contract_type: enums.FutureContractType
47+
) -> exchange_data.FutureContract:
48+
return exchange_data.create_default_future_contract(pair, leverage, contract_type)

octobot_trading/api/positions.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,9 @@ async def close_position(exchange_manager, symbol: str, side: enums.PositionSide
3636
return 0
3737

3838

39-
def load_pair_contract(exchange_manager, position_dict: dict):
40-
exchange_data.update_future_contract_from_dict(exchange_manager, position_dict)
39+
def set_is_exclusively_using_exchange_position_details(
40+
exchange_manager, is_exclusively_using_exchange_position_details: bool
41+
):
42+
exchange_manager.exchange_personal_data.positions_manager.is_exclusively_using_exchange_position_details = (
43+
is_exclusively_using_exchange_position_details
44+
)

octobot_trading/enums.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ class TradeExtraConstants(enum.Enum):
342342

343343
class ExchangeConstantsPositionColumns(enum.Enum):
344344
ID = "id"
345+
LOCAL_ID = "local_id"
345346
TIMESTAMP = "timestamp"
346347
SYMBOL = "symbol"
347348
ENTRY_PRICE = "entry_price"
@@ -355,6 +356,7 @@ class ExchangeConstantsPositionColumns(enum.Enum):
355356
SIZE = "size"
356357
NOTIONAL = "notional"
357358
INITIAL_MARGIN = "initial_margin"
359+
AUTO_DEPOSIT_MARGIN = "auto_deposit_margin"
358360
COLLATERAL = "collateral"
359361
LEVERAGE = "leverage"
360362
MARGIN_TYPE = "margin_type"
@@ -364,7 +366,6 @@ class ExchangeConstantsPositionColumns(enum.Enum):
364366
MAINTENANCE_MARGIN_RATE = "maintenance_margin_rate"
365367
STATUS = "status"
366368
SIDE = "side"
367-
EXCHANGE_POSITION_ID = "exchange_position_id"
368369

369370

370371
class ExchangeConstantsMarginContractColumns(enum.Enum):

octobot_trading/exchange_data/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
FutureContract,
9393
update_contracts_from_positions,
9494
update_future_contract_from_dict,
95+
create_default_future_contract,
9596
)
9697
from octobot_trading.exchange_data import exchange_symbol_data
9798
from octobot_trading.exchange_data.exchange_symbol_data import (
@@ -195,6 +196,7 @@
195196
"FutureContract",
196197
"update_contracts_from_positions",
197198
"update_future_contract_from_dict",
199+
"create_default_future_contract",
198200
"ExchangeSymbolsData",
199201
"ExchangeSymbolData",
200202
"UNAUTHENTICATED_UPDATER_PRODUCERS",

octobot_trading/exchange_data/contracts/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
from octobot_trading.exchange_data.contracts.contract_factory import (
2929
update_contracts_from_positions,
3030
update_future_contract_from_dict,
31+
create_default_future_contract,
3132
)
3233

3334
__all__ = [
3435
"MarginContract",
3536
"FutureContract",
3637
"update_contracts_from_positions",
3738
"update_future_contract_from_dict",
39+
"create_default_future_contract",
3840
]

octobot_trading/exchange_data/contracts/contract_factory.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import octobot_trading.enums as enums
2121
import octobot_trading.constants as constants
22+
import octobot_trading.exchange_data.contracts.future_contract as future_contract
2223

2324

2425
def update_contracts_from_positions(exchange_manager, positions) -> bool:
@@ -64,7 +65,9 @@ def update_future_contract_from_dict(exchange_manager, contract: dict) -> bool:
6465
)),
6566
margin_type=enums.MarginType(contract[enums.ExchangeConstantsMarginContractColumns.MARGIN_TYPE.value]),
6667
contract_type=enums.FutureContractType(contract[enums.ExchangeConstantsFutureContractColumns.CONTRACT_TYPE.value]),
67-
position_mode=enums.PositionMode(contract[enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value]),
68+
position_mode=enums.PositionMode(contract[enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value])
69+
if contract[enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value]
70+
else contract[enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value],
6871
maintenance_margin_rate=decimal.Decimal(str(
6972
contract[enums.ExchangeConstantsFutureContractColumns.MAINTENANCE_MARGIN_RATE.value]
7073
)),
@@ -75,5 +78,20 @@ def update_future_contract_from_dict(exchange_manager, contract: dict) -> bool:
7578
)
7679

7780

81+
def create_default_future_contract(
82+
pair: str, leverage: decimal.Decimal, contract_type: enums.FutureContractType
83+
) -> future_contract.FutureContract:
84+
return future_contract.FutureContract(
85+
pair=pair,
86+
contract_size=constants.DEFAULT_SYMBOL_CONTRACT_SIZE,
87+
margin_type=constants.DEFAULT_SYMBOL_MARGIN_TYPE,
88+
contract_type=contract_type,
89+
maximum_leverage=constants.DEFAULT_SYMBOL_MAX_LEVERAGE,
90+
current_leverage=leverage,
91+
position_mode=constants.DEFAULT_SYMBOL_POSITION_MODE,
92+
maintenance_margin_rate=constants.DEFAULT_SYMBOL_MAINTENANCE_MARGIN_RATE
93+
)
94+
95+
7896
def _get_logger():
7997
return logging.get_logger("contract_factory")

octobot_trading/exchange_data/contracts/future_contract.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ def to_dict(self):
119119
enums.ExchangeConstantsFutureContractColumns.CONTRACT_TYPE.value:
120120
self.contract_type.value if self.contract_type else self.contract_type,
121121
enums.ExchangeConstantsFutureContractColumns.MINIMUM_TICK_SIZE.value: self.minimum_tick_size,
122-
enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value: self.position_mode.value,
122+
enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value:
123+
self.position_mode.value if self.position_mode else self.position_mode,
123124
enums.ExchangeConstantsFutureContractColumns.MAINTENANCE_MARGIN_RATE.value:
124125
self.maintenance_margin_rate,
125126
enums.ExchangeConstantsFutureContractColumns.TAKE_PROFIT_STOP_LOSS_MODE.value:

octobot_trading/exchanges/connectors/ccxt/ccxt_adapter.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -273,18 +273,33 @@ def parse_position(self, fixed, force_empty=False, **kwargs):
273273
# CCXT standard position parsing logic
274274
# if mode is enums.PositionMode.ONE_WAY:
275275
original_side = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.SIDE.value)
276-
position_side = enums.PositionSide.BOTH
277-
# todo when handling cross positions
278-
# side = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.SIDE.value, enums.PositionSide.UNKNOWN.value)
279-
# position_side = enums.PositionSide.LONG \
280-
# if side == enums.PositionSide.LONG.value else enums.PositionSide.
281276
symbol = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.SYMBOL.value)
282277
contract_size = decimal.Decimal(str(fixed.get(ccxt_enums.ExchangePositionCCXTColumns.CONTRACT_SIZE.value, 0)))
283278
contracts = constants.ZERO if force_empty \
284279
else decimal.Decimal(str(fixed.get(ccxt_enums.ExchangePositionCCXTColumns.CONTRACTS.value, 0)))
285280
is_empty = contracts == constants.ZERO
281+
position_mode = (
282+
enums.PositionMode.HEDGE if fixed.get(ccxt_enums.ExchangePositionCCXTColumns.HEDGED.value, False)
283+
else enums.PositionMode.ONE_WAY
284+
)
285+
if position_mode is enums.PositionMode.HEDGE:
286+
# todo when handling helge positions
287+
side = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.SIDE.value, enums.PositionSide.UNKNOWN.value)
288+
position_side = enums.PositionSide.LONG \
289+
if side == enums.PositionSide.LONG.value else enums.PositionSide.SHORT
290+
log_func = self.logger.debug
291+
if is_empty:
292+
log_func = self.logger.error
293+
log_func(f"Unhandled {symbol} position mode ({position_mode.value}). This position can't be traded.")
294+
else:
295+
# One way position use BOTH side as there is always only one position per symbol.
296+
# This position can turn long and short
297+
position_side = enums.PositionSide.BOTH
286298
liquidation_price = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.LIQUIDATION_PRICE.value, 0)
287-
if margin_type := fixed.get(ccxt_enums.ExchangePositionCCXTColumns.MARGIN_TYPE.value, None):
299+
if margin_type := fixed.get(
300+
ccxt_enums.ExchangePositionCCXTColumns.MARGIN_TYPE.value,
301+
fixed.get(ccxt_enums.ExchangePositionCCXTColumns.MARGIN_MODE.value, None) # can also be contained in margin mode
302+
):
288303
margin_type = enums.MarginType(margin_type)
289304
if force_empty or liquidation_price is None:
290305
liquidation_price = constants.NaN
@@ -306,9 +321,7 @@ def parse_position(self, fixed, force_empty=False, **kwargs):
306321
enums.ExchangeConstantsPositionColumns.LEVERAGE.value:
307322
self.safe_decimal(fixed, ccxt_enums.ExchangePositionCCXTColumns.LEVERAGE.value,
308323
constants.DEFAULT_SYMBOL_LEVERAGE),
309-
enums.ExchangeConstantsPositionColumns.POSITION_MODE.value: None if is_empty else
310-
enums.PositionMode.HEDGE if fixed.get(ccxt_enums.ExchangePositionCCXTColumns.HEDGED.value, True)
311-
else enums.PositionMode.ONE_WAY,
324+
enums.ExchangeConstantsPositionColumns.POSITION_MODE.value: position_mode,
312325
# next values are always 0 when the position empty (0 contracts)
313326
enums.ExchangeConstantsPositionColumns.COLLATERAL.value: constants.ZERO if is_empty else
314327
decimal.Decimal(
@@ -319,6 +332,7 @@ def parse_position(self, fixed, force_empty=False, **kwargs):
319332
enums.ExchangeConstantsPositionColumns.INITIAL_MARGIN.value: constants.ZERO if is_empty else
320333
decimal.Decimal(
321334
f"{fixed.get(ccxt_enums.ExchangePositionCCXTColumns.INITIAL_MARGIN.value, 0) or 0}"),
335+
enums.ExchangeConstantsPositionColumns.AUTO_DEPOSIT_MARGIN.value: False, # default value
322336
enums.ExchangeConstantsPositionColumns.UNREALIZED_PNL.value: constants.ZERO if is_empty else
323337
decimal.Decimal(
324338
f"{fixed.get(ccxt_enums.ExchangePositionCCXTColumns.UNREALISED_PNL.value, 0) or 0}"),

octobot_trading/exchanges/implementations/exchange_simulator.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import octobot_trading.exchanges.util as exchange_util
2020
import octobot_trading.exchanges.connectors.simulator.exchange_simulator_connector as exchange_simulator_connector
2121
import octobot_trading.exchanges.types.rest_exchange as rest_exchange
22+
import octobot_trading.exchange_data.contracts.contract_factory as contract_factory
2223

2324

2425
class ExchangeSimulator(rest_exchange.RestExchange):
@@ -119,15 +120,20 @@ async def load_pair_future_contract(self, pair: str):
119120
Create a new FutureContract for the pair
120121
:param pair: the pair
121122
"""
123+
contract = contract_factory.create_default_future_contract(
124+
pair,
125+
constants.DEFAULT_SYMBOL_LEVERAGE,
126+
self.exchange_manager.exchange_config.backtesting_exchange_config.future_contract_type
127+
)
122128
return self.create_pair_contract(
123-
pair=pair,
124-
current_leverage=constants.DEFAULT_SYMBOL_LEVERAGE,
125-
contract_size=constants.DEFAULT_SYMBOL_CONTRACT_SIZE,
126-
margin_type=constants.DEFAULT_SYMBOL_MARGIN_TYPE,
127-
contract_type=self.exchange_manager.exchange_config.backtesting_exchange_config.future_contract_type,
128-
position_mode=constants.DEFAULT_SYMBOL_POSITION_MODE,
129-
maintenance_margin_rate=constants.DEFAULT_SYMBOL_MAINTENANCE_MARGIN_RATE,
130-
maximum_leverage=constants.DEFAULT_SYMBOL_MAX_LEVERAGE
129+
contract.pair,
130+
contract.current_leverage,
131+
contract.contract_size,
132+
contract.margin_type,
133+
contract.contract_type,
134+
contract.position_mode,
135+
contract.maintenance_margin_rate,
136+
maximum_leverage = contract.maximum_leverage,
131137
)
132138

133139
async def get_symbol_leverage(self, symbol: str, **kwargs: dict):

0 commit comments

Comments
 (0)