Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Trading/Exchange/binance/binance_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class Binance(exchanges.RestExchange):
# binance {"code":-4048,"msg":"Margin type cannot be changed if there exists position."}
# Set True when the "limit" param when fetching order books is taken into account
SUPPORTS_CUSTOM_LIMIT_ORDER_BOOK_FETCH = True
# set True when create_market_buy_order_with_cost should be used to create buy market orders
# (useful to predict the exact spent amount)
ENABLE_SPOT_BUY_MARKET_WITH_COST = True

# should be overridden locally to match exchange support
SUPPORTED_ELEMENTS = {
Expand Down Expand Up @@ -167,7 +170,7 @@ def get_supported_exchange_types(cls) -> list:
def get_additional_connector_config(self):
config = {
ccxt_constants.CCXT_OPTIONS: {
"quoteOrderQty": False, # disable quote conversion
"quoteOrderQty": True, # enable quote conversion for market orders
"recvWindow": 60000, # default is 10000, avoid time related issues
"fetchPositions": "account", # required to fetch empty positions as well
"filterClosed": False, # return empty positions as well
Expand Down Expand Up @@ -216,6 +219,14 @@ async def create_order(self, order_type: trading_enums.TraderOrderType, symbol:
side=side, current_price=current_price,
reduce_only=reduce_only, params=params)

async def _create_market_sell_order(
self, symbol, quantity, price=None, reduce_only: bool = False, params=None
) -> dict:
# force price to None to avoid selling using quote amount (force market sell quantity in base amount)
return await super()._create_market_sell_order(
symbol, quantity, price=None, reduce_only=reduce_only, params=params
)

async def set_symbol_partial_take_profit_stop_loss(self, symbol: str, inverse: bool,
tp_sl_mode: trading_enums.TakeProfitStopLossMode):
"""
Expand Down
23 changes: 3 additions & 20 deletions Trading/Exchange/bitget/bitget_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,19 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
import decimal
import typing

import octobot_trading.exchanges as exchanges
import octobot_trading.exchanges.connectors.ccxt.constants as ccxt_constants
import octobot_trading.enums as trading_enums
import octobot_trading.errors


class Bitget(exchanges.RestExchange):
DESCRIPTION = ""

FIX_MARKET_STATUS = True
REMOVE_MARKET_STATUS_PRICE_LIMITS = True
# set True when create_market_buy_order_with_cost should be used to create buy market orders
# (useful to predict the exact spent amount)
ENABLE_SPOT_BUY_MARKET_WITH_COST = True

@classmethod
def get_name(cls):
Expand All @@ -44,22 +43,6 @@ def get_additional_connector_config(self):
}
}

async def create_order(self, order_type: trading_enums.TraderOrderType, symbol: str, quantity: decimal.Decimal,
price: decimal.Decimal = None, stop_price: decimal.Decimal = None,
side: trading_enums.TradeOrderSide = None, current_price: decimal.Decimal = None,
reduce_only: bool = False, params: dict = None) -> typing.Optional[dict]:
if order_type is trading_enums.TraderOrderType.BUY_MARKET:
# on Bitget, market orders are in quote currency (YYY in XYZ/YYY)
used_price = price or current_price
if not used_price:
raise octobot_trading.errors.NotSupported(f"{self.get_name()} requires a price parameter to create "
f"market orders as quantity is in quote currency")
quantity = quantity * used_price
return await super().create_order(order_type, symbol, quantity,
price=price, stop_price=stop_price,
side=side, current_price=current_price,
reduce_only=reduce_only, params=params)


class BitgetCCXTAdapter(exchanges.CCXTAdapter):

Expand Down
28 changes: 9 additions & 19 deletions Trading/Exchange/bitmart/bitmart_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
import decimal
import typing

import octobot_trading.exchanges as exchanges
import octobot_trading.exchanges.connectors.ccxt.constants as ccxt_constants
import octobot_trading.enums as trading_enums
import octobot_trading.errors
import octobot_trading.constants as trading_constants


class BitMartConnector(exchanges.CCXTConnector):
Expand All @@ -36,6 +33,11 @@ class BitMart(exchanges.RestExchange):
FIX_MARKET_STATUS = True
DEFAULT_CONNECTOR_CLASS = BitMartConnector
REQUIRE_ORDER_FEES_FROM_TRADES = True # set True when get_order is not giving fees on closed orders and fees
# set True when create_market_buy_order_with_cost should be used to create buy market orders
# (useful to predict the exact spent amount)
ENABLE_SPOT_BUY_MARKET_WITH_COST = True
# broken: need v4 endpoint required, 13/03/25 ccxt still doesn't have it
SUPPORT_FETCHING_CANCELLED_ORDERS = False

@classmethod
def get_name(cls):
Expand All @@ -53,21 +55,9 @@ def get_additional_connector_config(self):
}
}

async def create_order(self, order_type: trading_enums.TraderOrderType, symbol: str, quantity: decimal.Decimal,
price: decimal.Decimal = None, stop_price: decimal.Decimal = None,
side: trading_enums.TradeOrderSide = None, current_price: decimal.Decimal = None,
reduce_only: bool = False, params: dict = None) -> typing.Optional[dict]:
if order_type is trading_enums.TraderOrderType.BUY_MARKET:
# on BitMart, market orders are in quote currency (YYY in XYZ/YYY)
used_price = price or current_price
if not used_price:
raise octobot_trading.errors.NotSupported(f"{self.get_name()} requires a price parameter to create "
f"market orders as quantity is in quote currency")
quantity = quantity * used_price
return await super().create_order(order_type, symbol, quantity,
price=price, stop_price=stop_price,
side=side, current_price=current_price,
reduce_only=reduce_only, params=params)
async def get_account_id(self, **kwargs: dict) -> str:
# not available on bitmart
return trading_constants.DEFAULT_ACCOUNT_ID


class BitMartCCXTAdapter(exchanges.CCXTAdapter):
Expand Down
1 change: 1 addition & 0 deletions Trading/Exchange/bybit/bybit_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ async def create_order(self, order_type: trading_enums.TraderOrderType, symbol:
side: trading_enums.TradeOrderSide = None, current_price: decimal.Decimal = None,
reduce_only: bool = False, params: dict = None) -> typing.Optional[dict]:
if not self.exchange_manager.is_future:
# should be replacable by ENABLE_SPOT_BUY_MARKET_WITH_COST = True => check when upgrading to unified
if order_type is trading_enums.TraderOrderType.BUY_MARKET:
# on Bybit, market orders are in quote currency (YYY in XYZ/YYY)
used_price = price or current_price
Expand Down
15 changes: 3 additions & 12 deletions Trading/Exchange/coinbase/coinbase_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ class Coinbase(exchanges.RestExchange):
INSTANT_RETRY_ERROR_CODE = "429"

FIX_MARKET_STATUS = True
# set True when create_market_buy_order_with_cost should be used to create buy market orders
# (useful to predict the exact spent amount)
ENABLE_SPOT_BUY_MARKET_WITH_COST = True

# text content of errors due to orders not found errors
EXCHANGE_ORDER_NOT_FOUND_ERRORS: typing.List[typing.Iterable[str]] = [
Expand Down Expand Up @@ -265,18 +268,6 @@ async def get_all_currencies_price_ticker(self, **kwargs: dict) -> typing.Option
# override for retrier
return await super().get_all_currencies_price_ticker(**kwargs)

async def create_order(self, order_type: trading_enums.TraderOrderType, symbol: str, quantity: decimal.Decimal,
price: decimal.Decimal = None, stop_price: decimal.Decimal = None,
side: trading_enums.TradeOrderSide = None, current_price: decimal.Decimal = None,
reduce_only: bool = False, params: dict = None) -> typing.Optional[dict]:
# ccxt is converting quantity using price, make sure it's available
if order_type is trading_enums.TraderOrderType.BUY_MARKET and not current_price:
raise octobot_trading.errors.NotSupported(f"current_price is required for {order_type} orders")
return await super().create_order(order_type, symbol, quantity,
price=price, stop_price=stop_price,
side=side, current_price=current_price,
reduce_only=reduce_only, params=params)

@_coinbase_retrier
async def cancel_order(
self, exchange_order_id: str, symbol: str, order_type: trading_enums.TraderOrderType, **kwargs: dict
Expand Down
20 changes: 3 additions & 17 deletions Trading/Exchange/coinex/coinex_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
import decimal
import typing

import octobot_trading.exchanges as exchanges
import octobot_trading.exchanges.connectors.ccxt.constants as ccxt_constants
import octobot_trading.enums as trading_enums
import octobot_trading.errors
import octobot_trading.constants as constants


Expand All @@ -36,6 +34,9 @@ class Coinex(exchanges.RestExchange):
("order not found", )
]
SUPPORT_FETCHING_CANCELLED_ORDERS = False
# set True when create_market_buy_order_with_cost should be used to create buy market orders
# (useful to predict the exact spent amount)
ENABLE_SPOT_BUY_MARKET_WITH_COST = True

@classmethod
def get_name(cls):
Expand Down Expand Up @@ -80,21 +81,6 @@ async def get_closed_orders(self, symbol=None, since=None, limit=None, **kwargs)
limit=self._fix_limit(limit),
**kwargs)

async def create_order(self, order_type: trading_enums.TraderOrderType, symbol: str, quantity: decimal.Decimal,
price: decimal.Decimal = None, stop_price: decimal.Decimal = None,
side: trading_enums.TradeOrderSide = None, current_price: decimal.Decimal = None,
reduce_only: bool = False, params: dict = None) -> typing.Optional[dict]:
if order_type is trading_enums.TraderOrderType.BUY_MARKET:
# on coinex, market orders are in quote currency (YYY in XYZ/YYY)
if price is None:
raise octobot_trading.errors.NotSupported(f"{self.get_name()} requires a price parameter to create "
f"market orders as quantity is in quote currency")
quantity = quantity * price
return await super().create_order(order_type, symbol, quantity,
price=price, stop_price=stop_price,
side=side, current_price=current_price,
reduce_only=reduce_only, params=params)

def _fix_limit(self, limit: int) -> int:
return min(self.MAX_PAGINATION_LIMIT, limit) if limit else limit

Expand Down
24 changes: 3 additions & 21 deletions Trading/Exchange/htx/htx_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
import decimal
import typing

import octobot_trading.exchanges as exchanges
import octobot_trading.exchanges.connectors.ccxt.constants as ccxt_constants
import octobot_trading.enums as trading_enums
import octobot_trading.errors


class Htx(exchanges.RestExchange):
FIX_MARKET_STATUS = True
REMOVE_MARKET_STATUS_PRICE_LIMITS = True
# set True when create_market_buy_order_with_cost should be used to create buy market orders
# (useful to predict the exact spent amount)
ENABLE_SPOT_BUY_MARKET_WITH_COST = True

@classmethod
def get_name(cls):
Expand All @@ -49,22 +47,6 @@ async def get_symbol_prices(self, symbol, time_frame, limit: int = 500, **kwargs
kwargs[history_param] = False
return await super().get_symbol_prices(symbol, time_frame, limit=limit, **kwargs)

async def create_order(self, order_type: trading_enums.TraderOrderType, symbol: str, quantity: decimal.Decimal,
price: decimal.Decimal = None, stop_price: decimal.Decimal = None,
side: trading_enums.TradeOrderSide = None, current_price: decimal.Decimal = None,
reduce_only: bool = False, params: dict = None) -> typing.Optional[dict]:
if order_type is trading_enums.TraderOrderType.BUY_MARKET:
# on HTX, market orders are in quote currency (YYY in XYZ/YYY)
used_price = price or current_price
if not used_price:
raise octobot_trading.errors.NotSupported(f"{self.get_name()} requires a price parameter to create "
f"market orders as quantity is in quote currency")
quantity = quantity * used_price
return await super().create_order(order_type, symbol, quantity,
price=price, stop_price=stop_price,
side=side, current_price=current_price,
reduce_only=reduce_only, params=params)


class HtxCCXTAdapter(exchanges.CCXTAdapter):

Expand Down
5 changes: 4 additions & 1 deletion Trading/Exchange/kucoin/kucoin_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import octobot_trading.exchanges.connectors.ccxt.ccxt_connector as ccxt_connector
import octobot_trading.exchanges.connectors.ccxt.enums as ccxt_enums
import octobot_trading.exchanges.connectors.ccxt.constants as ccxt_constants
import octobot_trading.exchanges.connectors.ccxt.ccxt_client_util as ccxt_client_util
import octobot_commons.constants as commons_constants
import octobot_trading.constants as constants
import octobot_trading.enums as trading_enums
Expand Down Expand Up @@ -88,6 +87,9 @@ class Kucoin(exchanges.RestExchange):
INSTANT_RETRY_ERROR_CODE = "429000"
FUTURES_CCXT_CLASS_NAME = "kucoinfutures"
MAX_INCREASED_POSITION_QUANTITY_MULTIPLIER = decimal.Decimal("0.95")
# set True when create_market_buy_order_with_cost should be used to create buy market orders
# (useful to predict the exact spent amount)
ENABLE_SPOT_BUY_MARKET_WITH_COST = True

# set True when get_positions() is not returning empty positions and should use get_position() instead
REQUIRES_SYMBOL_FOR_EMPTY_POSITION = True
Expand Down Expand Up @@ -273,6 +275,7 @@ async def get_recent_trades(self, symbol, limit=50, **kwargs):
# filtered by limit before reversing (or most recent trades are lost)
recent_trades = await super().get_recent_trades(symbol, limit=None, **kwargs)
return recent_trades[::-1][:limit] if recent_trades else []

@_kucoin_retrier
async def get_order_book(self, symbol, limit=20, **kwargs):
# override default limit to be kucoin complient
Expand Down
19 changes: 3 additions & 16 deletions Trading/Exchange/mexc/mexc_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class MEXC(exchanges.RestExchange):

REQUIRE_ORDER_FEES_FROM_TRADES = True # set True when get_order is not giving fees on closed orders and fees
# text content of errors due to unhandled authentication issues
# set True when create_market_buy_order_with_cost should be used to create buy market orders
# (useful to predict the exact spent amount)
ENABLE_SPOT_BUY_MARKET_WITH_COST = True

EXCHANGE_PERMISSION_ERRORS: typing.List[typing.Iterable[str]] = [
# 'mexc {"code":700007,"msg":"No permission to access the endpoint."}'
Expand Down Expand Up @@ -95,22 +98,6 @@ async def get_all_tradable_symbols(self, active_only=True) -> set[str]:
await CACHED_MEXC_API_HANDLED_SYMBOLS.update(self)
return CACHED_MEXC_API_HANDLED_SYMBOLS.symbols

async def create_order(self, order_type: trading_enums.TraderOrderType, symbol: str, quantity: decimal.Decimal,
price: decimal.Decimal = None, stop_price: decimal.Decimal = None,
side: trading_enums.TradeOrderSide = None, current_price: decimal.Decimal = None,
reduce_only: bool = False, params: dict = None) -> typing.Optional[dict]:
if order_type is trading_enums.TraderOrderType.BUY_MARKET:
# on MEXC, market orders are in quote currency (YYY in XYZ/YYY)
used_price = price or current_price
if not used_price:
raise octobot_trading.errors.NotSupported(f"{self.get_name()} requires a price parameter to create "
f"market orders as quantity is in quote currency")
quantity = quantity * used_price
return await super().create_order(order_type, symbol, quantity,
price=price, stop_price=stop_price,
side=side, current_price=current_price,
reduce_only=reduce_only, params=params)

async def _create_specific_order(self, order_type, symbol, quantity: decimal.Decimal, price: decimal.Decimal = None,
side: trading_enums.TradeOrderSide = None, current_price: decimal.Decimal = None,
stop_price: decimal.Decimal = None, reduce_only: bool = False,
Expand Down
1 change: 0 additions & 1 deletion Trading/Exchange/phemex/phemex_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import asyncio
import decimal
import typing
import ccxt

import octobot_commons.enums as commons_enums
import octobot_commons.constants as commons_constants
Expand Down
9 changes: 4 additions & 5 deletions Trading/Mode/index_trading_mode/index_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,20 @@ async def _ensure_enough_funds_to_buy_after_selling(self):

async def _split_reference_market_into_indexed_coins(self, details: dict):
orders = []
ref_market = self.exchange_manager.exchange_personal_data.portfolio_manager.reference_market
if details[RebalanceDetails.SWAP.value]:
# has to infer total reference market holdings
reference_market_to_split = self.exchange_manager.exchange_personal_data.portfolio_manager. \
portfolio_value_holder.get_traded_assets_holdings_value(
self.exchange_manager.exchange_personal_data.portfolio_manager.reference_market, None
)
portfolio_value_holder.get_traded_assets_holdings_value(ref_market, None)
coins_to_buy = list(details[RebalanceDetails.SWAP.value].values())
else:
# can use actual reference market holdings: everything has been sold
reference_market_to_split = \
self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio.get_currency_portfolio(
self.exchange_manager.exchange_personal_data.portfolio_manager.reference_market
ref_market
).available
coins_to_buy = self.trading_mode.indexed_coins

self.logger.info(f"Splitting {reference_market_to_split} {ref_market} to buy {coins_to_buy}")
amount_by_symbol = await self._get_symbols_and_amounts(coins_to_buy, reference_market_to_split)
for symbol, ideal_amount in amount_by_symbol.items():
orders.extend(await self._buy_coin(symbol, ideal_amount))
Expand Down