Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Futures] update API and fix simulator #1126

Merged
merged 10 commits into from
Dec 4, 2024
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.4.128] - 2023-12-03
## [2.4.129] - 2023-12-03
### Updated
[Futures] fix simulation numbers and update api
### Fixed
[Exchanges] fix cancelled order status error

## [2.4.128] - 2023-`12-03
###` Updated
[OHLCVUpdater] prevent missing candles spam

## [2.4.127] - 2023-11-28
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# OctoBot-Trading [2.4.128](https://github.com/Drakkar-Software/OctoBot-Trading/blob/master/CHANGELOG.md)
# OctoBot-Trading [2.4.129](https://github.com/Drakkar-Software/OctoBot-Trading/blob/master/CHANGELOG.md)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/903b6b22bceb4661b608a86fea655f69)](https://app.codacy.com/gh/Drakkar-Software/OctoBot-Trading?utm_source=github.com&utm_medium=referral&utm_content=Drakkar-Software/OctoBot-Trading&utm_campaign=Badge_Grade_Dashboard)
[![PyPI](https://img.shields.io/pypi/v/OctoBot-Trading.svg)](https://pypi.python.org/pypi/OctoBot-Trading/)
[![Coverage Status](https://coveralls.io/repos/github/Drakkar-Software/OctoBot-Trading/badge.svg?branch=master)](https://coveralls.io/github/Drakkar-Software/OctoBot-Trading?branch=master)
Expand Down
2 changes: 1 addition & 1 deletion octobot_trading/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
# License along with this library.

PROJECT_NAME = "OctoBot-Trading"
VERSION = "2.4.128" # major.minor.revision
VERSION = "2.4.129" # major.minor.revision
10 changes: 10 additions & 0 deletions octobot_trading/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,17 @@
from octobot_trading.api.positions import (
get_positions,
close_position,
set_is_exclusively_using_exchange_position_details,
update_position_mark_price,
)
from octobot_trading.api.contracts import (
is_inverse_future_contract,
is_perpetual_future_contract,
get_pair_contracts,
is_handled_contract,
has_pair_future_contract,
load_pair_contract,
create_default_future_contract,
)
from octobot_trading.api.storage import (
clear_trades_storage_history,
Expand Down Expand Up @@ -380,6 +385,11 @@
"is_perpetual_future_contract",
"get_pair_contracts",
"is_handled_contract",
"has_pair_future_contract",
"load_pair_contract",
"create_default_future_contract",
"set_is_exclusively_using_exchange_position_details",
"update_position_mark_price",
"clear_trades_storage_history",
"clear_candles_storage_history",
"clear_database_storage_history",
Expand Down
18 changes: 17 additions & 1 deletion octobot_trading/api/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
import octobot_trading.exchange_data as exchange_data
import decimal

import octobot_trading.enums as enums
import octobot_trading.exchange_data as exchange_data

def is_inverse_future_contract(contract_type):
return exchange_data.FutureContract(None, None, contract_type).is_inverse_contract()
Expand All @@ -30,3 +32,17 @@ def get_pair_contracts(exchange_manager) -> dict:

def is_handled_contract(contract) -> bool:
return contract.is_handled_contract()


def has_pair_future_contract(exchange_manager, pair: str) -> bool:
return exchange_manager.exchange.has_pair_future_contract(pair)


def load_pair_contract(exchange_manager, contract_dict: dict):
exchange_data.update_future_contract_from_dict(exchange_manager, contract_dict)


def create_default_future_contract(
pair: str, leverage: decimal.Decimal, contract_type: enums.FutureContractType
) -> exchange_data.FutureContract:
return exchange_data.create_default_future_contract(pair, leverage, contract_type)
17 changes: 17 additions & 0 deletions octobot_trading/api/positions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,20 @@ async def close_position(exchange_manager, symbol: str, side: enums.PositionSide
emit_trading_signals=emit_trading_signals
) else 0
return 0


def set_is_exclusively_using_exchange_position_details(
exchange_manager, is_exclusively_using_exchange_position_details: bool
):
exchange_manager.exchange_personal_data.positions_manager.is_exclusively_using_exchange_position_details = (
is_exclusively_using_exchange_position_details
)


async def update_position_mark_price(
exchange_manager, symbol: str, side: enums.PositionSide, mark_price: decimal.Decimal
):
for position in exchange_manager.exchange_personal_data.positions_manager.get_symbol_positions(symbol):
if position.side is side:
await position.update(mark_price=mark_price)
return position
19 changes: 19 additions & 0 deletions octobot_trading/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ class TradeExtraConstants(enum.Enum):

class ExchangeConstantsPositionColumns(enum.Enum):
ID = "id"
LOCAL_ID = "local_id"
TIMESTAMP = "timestamp"
SYMBOL = "symbol"
ENTRY_PRICE = "entry_price"
Expand All @@ -355,6 +356,7 @@ class ExchangeConstantsPositionColumns(enum.Enum):
SIZE = "size"
NOTIONAL = "notional"
INITIAL_MARGIN = "initial_margin"
AUTO_DEPOSIT_MARGIN = "auto_deposit_margin"
COLLATERAL = "collateral"
LEVERAGE = "leverage"
MARGIN_TYPE = "margin_type"
Expand All @@ -366,6 +368,23 @@ class ExchangeConstantsPositionColumns(enum.Enum):
SIDE = "side"


class ExchangeConstantsMarginContractColumns(enum.Enum):
PAIR = "pair"
MARGIN_TYPE = "margin_type"
CONTRACT_SIZE = "contract_size"
MAXIMUM_LEVERAGE = "maximum_leverage"
CURRENT_LEVERAGE = "current_leverage"
RISK_LIMIT = "risk_limit"


class ExchangeConstantsFutureContractColumns(enum.Enum):
CONTRACT_TYPE = "contract_type"
MINIMUM_TICK_SIZE = "minimum_tick_size"
POSITION_MODE = "position_mode"
MAINTENANCE_MARGIN_RATE = "maintenance_margin_rate"
TAKE_PROFIT_STOP_LOSS_MODE = "take_profit_stop_loss_mode"


class ExchangeConstantsLiquidationColumns(enum.Enum):
ID = "id"
TIMESTAMP = "timestamp"
Expand Down
4 changes: 4 additions & 0 deletions octobot_trading/exchange_data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
MarginContract,
FutureContract,
update_contracts_from_positions,
update_future_contract_from_dict,
create_default_future_contract,
)
from octobot_trading.exchange_data import exchange_symbol_data
from octobot_trading.exchange_data.exchange_symbol_data import (
Expand Down Expand Up @@ -193,6 +195,8 @@
"MarginContract",
"FutureContract",
"update_contracts_from_positions",
"update_future_contract_from_dict",
"create_default_future_contract",
"ExchangeSymbolsData",
"ExchangeSymbolData",
"UNAUTHENTICATED_UPDATER_PRODUCERS",
Expand Down
4 changes: 4 additions & 0 deletions octobot_trading/exchange_data/contracts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@
from octobot_trading.exchange_data.contracts import contract_factory
from octobot_trading.exchange_data.contracts.contract_factory import (
update_contracts_from_positions,
update_future_contract_from_dict,
create_default_future_contract,
)

__all__ = [
"MarginContract",
"FutureContract",
"update_contracts_from_positions",
"update_future_contract_from_dict",
"create_default_future_contract",
]
42 changes: 42 additions & 0 deletions octobot_trading/exchange_data/contracts/contract_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
import decimal

import octobot_commons.logging as logging

import octobot_trading.enums as enums
import octobot_trading.constants as constants
import octobot_trading.exchange_data.contracts.future_contract as future_contract


def update_contracts_from_positions(exchange_manager, positions) -> bool:
Expand Down Expand Up @@ -51,5 +54,44 @@ def update_contracts_from_positions(exchange_manager, positions) -> bool:
return updated


def update_future_contract_from_dict(exchange_manager, contract: dict) -> bool:
return exchange_manager.exchange.create_pair_contract(
pair=contract[enums.ExchangeConstantsMarginContractColumns.PAIR.value],
current_leverage=decimal.Decimal(str(
contract[enums.ExchangeConstantsMarginContractColumns.CURRENT_LEVERAGE.value]
)),
contract_size=decimal.Decimal(str(
contract[enums.ExchangeConstantsMarginContractColumns.CONTRACT_SIZE.value]
)),
margin_type=enums.MarginType(contract[enums.ExchangeConstantsMarginContractColumns.MARGIN_TYPE.value]),
contract_type=enums.FutureContractType(contract[enums.ExchangeConstantsFutureContractColumns.CONTRACT_TYPE.value]),
position_mode=enums.PositionMode(contract[enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value])
if contract[enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value]
else contract[enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value],
maintenance_margin_rate=decimal.Decimal(str(
contract[enums.ExchangeConstantsFutureContractColumns.MAINTENANCE_MARGIN_RATE.value]
)),
maximum_leverage=None if contract[enums.ExchangeConstantsMarginContractColumns.MAXIMUM_LEVERAGE.value] is None
else decimal.Decimal(str(
contract[enums.ExchangeConstantsMarginContractColumns.MAXIMUM_LEVERAGE.value]
))
)


def create_default_future_contract(
pair: str, leverage: decimal.Decimal, contract_type: enums.FutureContractType
) -> future_contract.FutureContract:
return future_contract.FutureContract(
pair=pair,
contract_size=constants.DEFAULT_SYMBOL_CONTRACT_SIZE,
margin_type=constants.DEFAULT_SYMBOL_MARGIN_TYPE,
contract_type=contract_type,
maximum_leverage=constants.DEFAULT_SYMBOL_MAX_LEVERAGE,
current_leverage=leverage,
position_mode=constants.DEFAULT_SYMBOL_POSITION_MODE,
maintenance_margin_rate=constants.DEFAULT_SYMBOL_MAINTENANCE_MARGIN_RATE
)


def _get_logger():
return logging.get_logger("contract_factory")
16 changes: 16 additions & 0 deletions octobot_trading/exchange_data/contracts/future_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,19 @@ def update_from_position(self, raw_position) -> bool:
logging.get_logger(str(self)).debug(f"Changed position mode to {pos_mode}")
changed = True
return changed

def to_dict(self):
return {
**super().to_dict(),
**{
enums.ExchangeConstantsFutureContractColumns.CONTRACT_TYPE.value:
self.contract_type.value if self.contract_type else self.contract_type,
enums.ExchangeConstantsFutureContractColumns.MINIMUM_TICK_SIZE.value: self.minimum_tick_size,
enums.ExchangeConstantsFutureContractColumns.POSITION_MODE.value:
self.position_mode.value if self.position_mode else self.position_mode,
enums.ExchangeConstantsFutureContractColumns.MAINTENANCE_MARGIN_RATE.value:
self.maintenance_margin_rate,
enums.ExchangeConstantsFutureContractColumns.TAKE_PROFIT_STOP_LOSS_MODE.value:
self.take_profit_stop_loss_mode,
}
}
10 changes: 10 additions & 0 deletions octobot_trading/exchange_data/contracts/margin_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,13 @@ def update_from_position(self, raw_position) -> bool:
logging.get_logger(str(self)).debug(f"Changed margin type to {margin_type}")
changed = True
return changed

def to_dict(self):
return {
enums.ExchangeConstantsMarginContractColumns.PAIR.value: self.pair,
enums.ExchangeConstantsMarginContractColumns.MARGIN_TYPE.value: self.margin_type.value,
enums.ExchangeConstantsMarginContractColumns.CONTRACT_SIZE.value: self.contract_size,
enums.ExchangeConstantsMarginContractColumns.MAXIMUM_LEVERAGE.value: self.maximum_leverage,
enums.ExchangeConstantsMarginContractColumns.CURRENT_LEVERAGE.value: self.current_leverage,
enums.ExchangeConstantsMarginContractColumns.RISK_LIMIT.value: self.risk_limit,
}
34 changes: 24 additions & 10 deletions octobot_trading/exchanges/connectors/ccxt/ccxt_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,21 +273,36 @@ def parse_position(self, fixed, force_empty=False, **kwargs):
# CCXT standard position parsing logic
# if mode is enums.PositionMode.ONE_WAY:
original_side = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.SIDE.value)
position_side = enums.PositionSide.BOTH
# todo when handling cross positions
# side = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.SIDE.value, enums.PositionSide.UNKNOWN.value)
# position_side = enums.PositionSide.LONG \
# if side == enums.PositionSide.LONG.value else enums.PositionSide.
symbol = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.SYMBOL.value)
contract_size = decimal.Decimal(str(fixed.get(ccxt_enums.ExchangePositionCCXTColumns.CONTRACT_SIZE.value, 0)))
contracts = constants.ZERO if force_empty \
else decimal.Decimal(str(fixed.get(ccxt_enums.ExchangePositionCCXTColumns.CONTRACTS.value, 0)))
is_empty = contracts == constants.ZERO
position_mode = (
enums.PositionMode.HEDGE if fixed.get(ccxt_enums.ExchangePositionCCXTColumns.HEDGED.value, False)
else enums.PositionMode.ONE_WAY
)
if position_mode is enums.PositionMode.HEDGE:
# todo when handling hedge positions
side = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.SIDE.value, enums.PositionSide.UNKNOWN.value)
position_side = enums.PositionSide.LONG \
if side == enums.PositionSide.LONG.value else enums.PositionSide.SHORT
log_func = self.logger.debug
if is_empty:
log_func = self.logger.error
log_func(f"Unhandled {symbol} position mode ({position_mode.value}). This position can't be traded.")
else:
# One way position use BOTH side as there is always only one position per symbol.
# This position can turn long and short
position_side = enums.PositionSide.BOTH
liquidation_price = fixed.get(ccxt_enums.ExchangePositionCCXTColumns.LIQUIDATION_PRICE.value, 0)
if margin_type := fixed.get(ccxt_enums.ExchangePositionCCXTColumns.MARGIN_TYPE.value, None):
if margin_type := fixed.get(
ccxt_enums.ExchangePositionCCXTColumns.MARGIN_TYPE.value,
fixed.get(ccxt_enums.ExchangePositionCCXTColumns.MARGIN_MODE.value, None) # can also be contained in margin mode
):
margin_type = enums.MarginType(margin_type)
if force_empty or liquidation_price is None:
liquidation_price = constants.NaN
liquidation_price = constants.ZERO
else:
liquidation_price = decimal.Decimal(str(liquidation_price))
try:
Expand All @@ -306,9 +321,7 @@ def parse_position(self, fixed, force_empty=False, **kwargs):
enums.ExchangeConstantsPositionColumns.LEVERAGE.value:
self.safe_decimal(fixed, ccxt_enums.ExchangePositionCCXTColumns.LEVERAGE.value,
constants.DEFAULT_SYMBOL_LEVERAGE),
enums.ExchangeConstantsPositionColumns.POSITION_MODE.value: None if is_empty else
enums.PositionMode.HEDGE if fixed.get(ccxt_enums.ExchangePositionCCXTColumns.HEDGED.value, True)
else enums.PositionMode.ONE_WAY,
enums.ExchangeConstantsPositionColumns.POSITION_MODE.value: position_mode,
# next values are always 0 when the position empty (0 contracts)
enums.ExchangeConstantsPositionColumns.COLLATERAL.value: constants.ZERO if is_empty else
decimal.Decimal(
Expand All @@ -319,6 +332,7 @@ def parse_position(self, fixed, force_empty=False, **kwargs):
enums.ExchangeConstantsPositionColumns.INITIAL_MARGIN.value: constants.ZERO if is_empty else
decimal.Decimal(
f"{fixed.get(ccxt_enums.ExchangePositionCCXTColumns.INITIAL_MARGIN.value, 0) or 0}"),
enums.ExchangeConstantsPositionColumns.AUTO_DEPOSIT_MARGIN.value: False, # default value
enums.ExchangeConstantsPositionColumns.UNREALIZED_PNL.value: constants.ZERO if is_empty else
decimal.Decimal(
f"{fixed.get(ccxt_enums.ExchangePositionCCXTColumns.UNREALISED_PNL.value, 0) or 0}"),
Expand Down
6 changes: 5 additions & 1 deletion octobot_trading/exchanges/connectors/ccxt/ccxt_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ async def load_symbol_markets(
except KeyError:
load_markets = True
if load_markets:
self.logger.info(f"Loading {self.exchange_manager.exchange_name} exchange markets")
self.logger.info(
f"Loading {self.exchange_manager.exchange_name} "
f"{exchanges.get_exchange_type(self.exchange_manager).value}"
f"{' sandbox' if self.exchange_manager.is_sandboxed else ''} exchange markets"
)
try:
await self._load_markets(self.client, reload)
ccxt_client_util.set_markets_cache(self.client)
Expand Down
22 changes: 14 additions & 8 deletions octobot_trading/exchanges/implementations/exchange_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import octobot_trading.exchanges.util as exchange_util
import octobot_trading.exchanges.connectors.simulator.exchange_simulator_connector as exchange_simulator_connector
import octobot_trading.exchanges.types.rest_exchange as rest_exchange
import octobot_trading.exchange_data.contracts.contract_factory as contract_factory


class ExchangeSimulator(rest_exchange.RestExchange):
Expand Down Expand Up @@ -119,15 +120,20 @@ async def load_pair_future_contract(self, pair: str):
Create a new FutureContract for the pair
:param pair: the pair
"""
contract = contract_factory.create_default_future_contract(
pair,
constants.DEFAULT_SYMBOL_LEVERAGE,
self.exchange_manager.exchange_config.backtesting_exchange_config.future_contract_type
)
return self.create_pair_contract(
pair=pair,
current_leverage=constants.DEFAULT_SYMBOL_LEVERAGE,
contract_size=constants.DEFAULT_SYMBOL_CONTRACT_SIZE,
margin_type=constants.DEFAULT_SYMBOL_MARGIN_TYPE,
contract_type=self.exchange_manager.exchange_config.backtesting_exchange_config.future_contract_type,
position_mode=constants.DEFAULT_SYMBOL_POSITION_MODE,
maintenance_margin_rate=constants.DEFAULT_SYMBOL_MAINTENANCE_MARGIN_RATE,
maximum_leverage=constants.DEFAULT_SYMBOL_MAX_LEVERAGE
contract.pair,
contract.current_leverage,
contract.contract_size,
contract.margin_type,
contract.contract_type,
contract.position_mode,
contract.maintenance_margin_rate,
maximum_leverage = contract.maximum_leverage,
)

async def get_symbol_leverage(self, symbol: str, **kwargs: dict):
Expand Down
Loading
Loading