Skip to content

Commit

Permalink
use only order dict in channels
Browse files Browse the repository at this point in the history
  • Loading branch information
Guillaume De Saint Martin committed Feb 22, 2020
1 parent 21bd1f4 commit 6eadcb4
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 17 deletions.
24 changes: 23 additions & 1 deletion octobot_trading/api/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
from octobot_trading.data.order import Order
from octobot_trading.api import LOGGER_TAG
from octobot_commons.logging.logging_util import get_logger
from octobot_trading.enums import TraderOrderType
from octobot_trading.enums import TraderOrderType, OrderStatus
from octobot_trading.data.order import parse_order_type as order_parse_order_type, \
parse_order_status as order_parse_order_status

LOGGER = get_logger(LOGGER_TAG)

Expand Down Expand Up @@ -58,6 +60,26 @@ def get_order_exchange_name(order) -> str:
return order.exchange_manager.get_exchange_name()


def order_to_dict(order) -> dict:
return order.to_dict()


def parse_order_type(dict_order) -> TraderOrderType:
return order_parse_order_type(dict_order)[1]


def parse_order_status(dict_order) -> OrderStatus:
return order_parse_order_status(dict_order)


def get_order_profitability(exchange_manager, order_id) -> float:
try:
return exchange_manager.exchange_personal_data.orders_manager.get_order(order_id).get_profitability()
except KeyError:
# try in trades (order might be filled and stored in trades)
return exchange_manager.exchange_personal_data.trades_manager.get_trade(order_id).trade_profitability


async def subscribe_to_order_channels(callback):
order_channel_name = OrdersChannel.get_name()
for exchange_id in get_exchange_ids():
Expand Down
2 changes: 1 addition & 1 deletion octobot_trading/cli/cli_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async def trades_callback(exchange: str, exchange_id: str, symbol: str, trade, o
f"|| OLD_TRADE = {old_trade}")


async def orders_callback(exchange: str, exchange_id: str, symbol: str, order, is_closed, is_updated, is_from_bot):
async def orders_callback(exchange: str, exchange_id: str, symbol: str, order: dict, is_closed, is_updated, is_from_bot):
if get_should_display_callbacks_logs():
order_string = f"ORDERS : EXCHANGE = {exchange} || SYMBOL = {symbol} ||"
if is_closed:
Expand Down
3 changes: 3 additions & 0 deletions octobot_trading/data/order.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ cdef class Order:
cdef list last_prices
cdef public list linked_orders

cdef object exchange_order_type # raw exchange order type, used to create order dict

cpdef bint update(self,
str symbol,
str order_id=*,
Expand Down Expand Up @@ -95,6 +97,7 @@ cdef class Order:
cpdef double generate_executed_time(self)
cpdef bint is_self_managed(self)
cpdef bint update_from_raw(self, dict raw_order)
cpdef dict to_dict(self)

cpdef object parse_order_status(dict raw_order)

Expand Down
66 changes: 57 additions & 9 deletions octobot_trading/data/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def __init__(self, trader):
self.order_profitability = 0
self.total_cost = 0

# raw exchange order type, used to create order dict
self.exchange_order_type = None

@classmethod
def get_name(cls):
return cls.__name__
Expand Down Expand Up @@ -143,6 +146,8 @@ def update(self, symbol, order_id="", status=OrderStatus.OPEN,

if order_type:
self.order_type = order_type
if self.exchange_order_type is None:
self.exchange_order_type = _get_trade_order_type(order_type)

if not self.filled_price and self.filled_quantity and self.total_cost:
self.filled_price = self.total_cost / self.filled_quantity
Expand Down Expand Up @@ -300,6 +305,10 @@ def update_order_from_raw(self, raw_order):
raw_order[ExchangeConstantsOrderColumns.TIMESTAMP.value])

def __update_type_from_raw(self, raw_order):
try:
self.exchange_order_type = TradeOrderType(raw_order[ExchangeConstantsOrderColumns.TYPE.value])
except ValueError:
self.exchange_order_type = TradeOrderType.UNKNOWN
self.side, self.order_type = parse_order_type(raw_order)

def __update_taker_maker_from_raw(self):
Expand All @@ -311,6 +320,22 @@ def __update_taker_maker_from_raw(self):
# (should only be used for simulation anyway)
self.taker_or_maker = ExchangeConstantsMarketPropertyColumns.MAKER.value

def to_dict(self):
filled_price = self.filled_price if self.filled_price > 0 else self.origin_price
return {
ExchangeConstantsOrderColumns.ID.value: self.order_id,
ExchangeConstantsOrderColumns.SYMBOL.value: self.symbol,
ExchangeConstantsOrderColumns.PRICE.value: filled_price,
ExchangeConstantsOrderColumns.STATUS.value: self.status.value,
ExchangeConstantsOrderColumns.TIMESTAMP.value: self.timestamp,
ExchangeConstantsOrderColumns.TYPE.value: self.exchange_order_type.value,
ExchangeConstantsOrderColumns.SIDE.value: self.side.value,
ExchangeConstantsOrderColumns.AMOUNT.value: self.origin_quantity,
ExchangeConstantsOrderColumns.COST.value: self.total_cost,
ExchangeConstantsOrderColumns.FILLED.value: self.filled_quantity,
ExchangeConstantsOrderColumns.FEE.value: self.fee
}

def to_string(self):
return (f"{self.symbol} | "
f"{self.order_type.name if self.order_type is not None else 'Unknown'} | "
Expand All @@ -322,29 +347,52 @@ def to_string(self):
def parse_order_type(raw_order):
try:
side: TradeOrderSide = TradeOrderSide(raw_order[ExchangeConstantsOrderColumns.SIDE.value])
order_type: TradeOrderType = TradeOrderType(raw_order[ExchangeConstantsOrderColumns.TYPE.value])
order_type: TradeOrderType = TradeOrderType.UNKNOWN
parsed_order_type: TraderOrderType = TraderOrderType.UNKNOWN
try:
order_type = TradeOrderType(raw_order[ExchangeConstantsOrderColumns.TYPE.value])
except ValueError:
return side, parsed_order_type

if side == TradeOrderSide.BUY:
if order_type == TradeOrderType.LIMIT or order_type == TradeOrderType.LIMIT_MAKER:
order_type = TraderOrderType.BUY_LIMIT
parsed_order_type = TraderOrderType.BUY_LIMIT
elif order_type == TradeOrderType.MARKET:
order_type = TraderOrderType.BUY_MARKET
parsed_order_type = TraderOrderType.BUY_MARKET
else:
order_type = _get_sell_and_buy_types(order_type)
parsed_order_type = _get_sell_and_buy_types(order_type)
elif side == TradeOrderSide.SELL:
if order_type == TradeOrderType.LIMIT or order_type == TradeOrderType.LIMIT_MAKER:
order_type = TraderOrderType.SELL_LIMIT
parsed_order_type = TraderOrderType.SELL_LIMIT
elif order_type == TradeOrderType.MARKET:
order_type = TraderOrderType.SELL_MARKET
parsed_order_type = TraderOrderType.SELL_MARKET
else:
order_type = _get_sell_and_buy_types(order_type)
return side, order_type
parsed_order_type = _get_sell_and_buy_types(order_type)
return side, parsed_order_type
except KeyError:
get_logger(Order.__class__.__name__).error("Failed to parse order type")
return None, None


def _get_sell_and_buy_types(order_type):
def _get_trade_order_type(order_type: TraderOrderType) -> TradeOrderType:
if order_type == TraderOrderType.BUY_LIMIT \
or order_type == TraderOrderType.SELL_LIMIT:
return TradeOrderType.LIMIT
if order_type == TraderOrderType.BUY_MARKET \
or order_type == TraderOrderType.SELL_MARKET:
return TradeOrderType.MARKET
elif order_type == TraderOrderType.STOP_LOSS_LIMIT:
return TradeOrderType.STOP_LOSS_LIMIT
elif order_type == TraderOrderType.TAKE_PROFIT_LIMIT:
return TradeOrderType.TAKE_PROFIT_LIMIT
elif order_type == TraderOrderType.TAKE_PROFIT:
return TradeOrderType.TAKE_PROFIT
elif order_type == TraderOrderType.STOP_LOSS:
return TradeOrderType.STOP_LOSS
return None


def _get_sell_and_buy_types(order_type) -> TraderOrderType:
if order_type == TradeOrderType.STOP_LOSS:
return TraderOrderType.STOP_LOSS
elif order_type == TradeOrderType.STOP_LOSS_LIMIT:
Expand Down
1 change: 1 addition & 0 deletions octobot_trading/data_manager/trades_manager.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ cdef class TradesManager(Initializable):
cdef void _reset_trades(self)
cdef void _remove_oldest_trades(self, int nb_to_remove)

cpdef Trade get_trade(self, str trade_id)
cpdef bint upsert_trade(self, str trade_id, dict raw_trade)
cpdef void upsert_trade_instance(self, Trade trade)
cpdef dict get_total_paid_fees(self)
3 changes: 3 additions & 0 deletions octobot_trading/data_manager/trades_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def get_total_paid_fees(self):
self.logger.warning(f"Trade without any registered fee: {trade}")
return total_fees

def get_trade(self, trade_id):
return self.trades[trade_id]

# private
def _check_trades_size(self):
if len(self.trades) > self.MAX_TRADES_COUNT:
Expand Down
2 changes: 2 additions & 0 deletions octobot_trading/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class TradeOrderType(Enum):
TAKE_PROFIT = "take_profit"
TAKE_PROFIT_LIMIT = "take_profit_limit"
LIMIT_MAKER = "limit_maker" # LIMIT_MAKER is a limit order that is rejected if would be filled as taker
UNKNOWN = "unknown"


class EvaluatorStates(Enum):
Expand Down Expand Up @@ -57,6 +58,7 @@ class TraderOrderType(Enum):
TRAILING_STOP = "trailing_stop"
TAKE_PROFIT = "take_profit"
TAKE_PROFIT_LIMIT = "take_profit_limit"
UNKNOWN = "unknown"


class ExchangeConstantsTickersColumns(Enum):
Expand Down
6 changes: 3 additions & 3 deletions octobot_trading/exchanges/data/exchange_personal_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ async def handle_order_update(self, symbol, order_id, order, should_notify: bool
if should_notify:
await get_chan(ORDERS_CHANNEL, self.exchange_manager.id).get_internal_producer() \
.send(symbol=symbol,
order=order,
order=order.to_dict(),
is_from_bot=True,
is_closed=False,
is_updated=changed)
Expand All @@ -122,7 +122,7 @@ async def handle_order_instance_update(self, order, should_notify: bool = True):
if should_notify:
await get_chan(ORDERS_CHANNEL, self.exchange_manager.id).get_internal_producer() \
.send(symbol=order.symbol,
order=order,
order=order.to_dict(),
is_from_bot=True,
is_closed=False,
is_updated=changed)
Expand All @@ -137,7 +137,7 @@ async def handle_closed_order_update(self, symbol, order_id, order, should_notif
if should_notify:
await get_chan(ORDERS_CHANNEL, self.exchange_manager.id).get_internal_producer() \
.send(symbol=symbol,
order=order,
order=order.to_dict(),
is_from_bot=True,
is_closed=True,
is_updated=changed)
Expand Down
5 changes: 5 additions & 0 deletions octobot_trading/exchanges/exchange_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,11 @@ def get_rate_limit(self):
def need_to_uniformize_timestamp(timestamp):
return not is_valid_timestamp(timestamp)

def get_uniformized_timestamp(self, timestamp):
if ExchangeManager.need_to_uniformize_timestamp(timestamp):
return self.exchange.get_uniform_timestamp(timestamp)
return timestamp

def uniformize_candles_if_necessary(self, candle_or_candles):
if candle_or_candles: # TODO improve
if isinstance(candle_or_candles[0], list):
Expand Down
3 changes: 2 additions & 1 deletion octobot_trading/orders/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from octobot_trading.orders.types.stop_loss_limit_order import StopLossLimitOrder
from octobot_trading.orders.types.stop_loss_order import StopLossOrder
from octobot_trading.orders.types.trailing_stop_order import TrailingStopOrder

from octobot_trading.orders.types.unknown_order import UnknownOrder

TraderOrderTypeClasses = {
TraderOrderType.BUY_MARKET: BuyMarketOrder,
Expand All @@ -32,4 +32,5 @@
TraderOrderType.STOP_LOSS_LIMIT: StopLossLimitOrder,
TraderOrderType.SELL_MARKET: SellMarketOrder,
TraderOrderType.SELL_LIMIT: SellLimitOrder,
TraderOrderType.UNKNOWN: UnknownOrder,
}
22 changes: 22 additions & 0 deletions octobot_trading/orders/types/unknown_order.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# cython: language_level=3
# Drakkar-Software OctoBot-Trading
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.

from octobot_trading.data.order cimport Order


cdef class UnknownOrder(Order):
pass
30 changes: 30 additions & 0 deletions octobot_trading/orders/types/unknown_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Drakkar-Software OctoBot-Trading
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
from octobot_trading.enums import TradeOrderSide, OrderStatus, ExchangeConstantsMarketPropertyColumns
from octobot_trading.data.order import Order


class UnknownOrder(Order):
"""UnknownOrder is used when an exchange is giving an order without a type (ex: binance 2yo+ orders)"""
def __init__(self, trader):
super().__init__(trader)

async def update_order_status(self, last_prices: list):
if not self.trader.simulate:
await self.default_exchange_update_order_status()
else:
# SHOULD NEVER HAPPEN
raise RuntimeError(f"{self.get_name()} can't be updated and should not appear in simulation mode")
2 changes: 1 addition & 1 deletion octobot_trading/producers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ def __init__(self, order_id):
UNAUTHENTICATED_UPDATER_PRODUCERS = [OHLCVUpdater, OrderBookUpdater, RecentTradeUpdater, TickerUpdater, KlineUpdater,
MarkPriceUpdater]
AUTHENTICATED_UPDATER_PRODUCERS = [BalanceUpdater, CloseOrdersUpdater, OpenOrdersUpdater, TradesUpdater,
PositionsUpdater, BalanceProfitabilityUpdater]
BalanceProfitabilityUpdater]
3 changes: 3 additions & 0 deletions octobot_trading/producers/orders_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def _cleanup_open_orders_dict(self, open_orders):
for open_order in open_orders:
try:
open_order.pop(ExchangeConstantsOrderColumns.INFO.value)
exchange_timestamp = open_order[ExchangeConstantsOrderColumns.TIMESTAMP.value]
open_order[ExchangeConstantsOrderColumns.TIMESTAMP.value] = \
self.channel.exchange_manager.get_uniformized_timestamp(exchange_timestamp)
except KeyError as e:
self.logger.error(f"Fail to cleanup open order dict ({e})")
return open_orders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async def __update_orders_status(self,
if order_filled:
await get_chan(ORDERS_CHANNEL, self.channel.exchange_manager.id).get_internal_producer() \
.send(symbol=order.symbol,
order=order,
order=order.to_dict(),
is_from_bot=True,
is_closed=True,
is_updated=False)
Expand Down

0 comments on commit 6eadcb4

Please sign in to comment.