Skip to content
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