Skip to content

Commit 0accdb0

Browse files
committed
[Trades] add get_real_or_estimated_trade_fee
1 parent b1657bf commit 0accdb0

File tree

6 files changed

+454
-9
lines changed

6 files changed

+454
-9
lines changed

octobot_trading/personal_data/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@
223223
TradePnl,
224224
compute_win_rate,
225225
aggregate_trades_by_exchange_order_id,
226+
get_real_or_estimated_trade_fee,
226227
)
227228
from octobot_trading.personal_data import transactions
228229
from octobot_trading.personal_data.transactions import (
@@ -458,6 +459,7 @@
458459
"TradePnl",
459460
"compute_win_rate",
460461
"aggregate_trades_by_exchange_order_id",
462+
"get_real_or_estimated_trade_fee",
461463
"ExchangePersonalData",
462464
"get_asset_price_from_converter_or_tickers",
463465
"resolve_sub_portfolios",

octobot_trading/personal_data/trades/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from octobot_trading.personal_data.trades.trades_util import (
4343
compute_win_rate,
4444
aggregate_trades_by_exchange_order_id,
45+
get_real_or_estimated_trade_fee,
4546
)
4647

4748
__all__ = [
@@ -57,4 +58,5 @@
5758
"TradePnl",
5859
"compute_win_rate",
5960
"aggregate_trades_by_exchange_order_id",
61+
"get_real_or_estimated_trade_fee",
6062
]

octobot_trading/personal_data/trades/trade.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ def from_dict(cls, trader, trade_dict):
176176
trade.creation_time = trade_dict.get(enums.TradeExtraConstants.CREATION_TIME.value)
177177
return trade
178178

179+
def duplicate(self):
180+
return self.__class__.from_dict(self.trader, self.to_dict())
181+
179182
def clear(self):
180183
self.trader = None # type: ignore
181184
self.exchange_manager = None # type: ignore

octobot_trading/personal_data/trades/trade_factory.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,23 @@
1313
#
1414
# You should have received a copy of the GNU Lesser General Public
1515
# License along with this library.
16+
import typing
17+
1618
import octobot_commons.logging as logging
1719
import octobot_trading.personal_data.trades.trade as trade_class
20+
import octobot_trading.personal_data.orders.order as order_class
1821
import octobot_trading.personal_data.orders.order_factory as order_factory
1922
import octobot_trading.enums as enums
2023
import octobot_trading.constants as constants
2124

2225

23-
def create_trade_instance_from_raw(trader, raw_trade):
26+
def create_trade_instance_from_raw(trader, raw_trade: dict[str, typing.Any]) -> "trade_class.Trade":
2427
order = create_closed_order_instance_from_raw_trade(trader, raw_trade)
2528
exchange_trade_id = raw_trade.get(enums.ExchangeConstantsOrderColumns.EXCHANGE_TRADE_ID.value)
2629
return create_trade_from_order(order, exchange_trade_id=exchange_trade_id)
2730

2831

29-
def create_closed_order_instance_from_raw_trade(trader, raw_trade):
32+
def create_closed_order_instance_from_raw_trade(trader, raw_trade: dict[str, typing.Any]) -> "order_class.Order":
3033
order = order_factory.create_order_from_raw(trader, raw_trade)
3134
order.update_from_raw(raw_trade)
3235
if order.status is enums.OrderStatus.CANCELED:
@@ -38,12 +41,12 @@ def create_closed_order_instance_from_raw_trade(trader, raw_trade):
3841
return order
3942

4043

41-
def create_trade_from_order(order,
42-
close_status=None,
43-
creation_time=0,
44-
canceled_time=0,
45-
executed_time=0,
46-
exchange_trade_id=None):
44+
def create_trade_from_order(order: "order_class.Order",
45+
close_status: typing.Optional[enums.OrderStatus] = None,
46+
creation_time: int = 0,
47+
canceled_time: int = 0,
48+
executed_time: int = 0,
49+
exchange_trade_id: typing.Optional[str] = None) -> "trade_class.Trade":
4750
if close_status is not None:
4851
order.status = close_status
4952
trade = trade_class.Trade(order.trader)
@@ -58,5 +61,5 @@ def create_trade_from_order(order,
5861
return trade
5962

6063

61-
def create_trade_from_dict(trader, trade_dict):
64+
def create_trade_from_dict(trader, trade_dict: dict[str, typing.Any]) -> "trade_class.Trade":
6265
return trade_class.Trade.from_dict(trader, trade_dict)

octobot_trading/personal_data/trades/trades_util.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# License along with this library.
1616
import decimal
1717

18+
import octobot_commons.symbols as symbols_util
1819
import octobot_trading.enums as trading_enums
1920
import octobot_trading.constants as constants
2021
import octobot_trading.personal_data as personal_data
@@ -93,3 +94,67 @@ def aggregate_trades_by_exchange_order_id(trades: list) -> dict:
9394
+= trade.fee[trading_enums.FeePropertyColumns.COST.value]
9495
to_update.executed_time = max(to_update.executed_time, trade.executed_time)
9596
return aggregated_trades_by_exchange_order_id
97+
98+
99+
def get_real_or_estimated_trade_fee(trade: "personal_data.Trade") -> tuple[dict, bool]:
100+
order_type = trading_enums.TraderOrderType.BUY_LIMIT if trade.side is trading_enums.TradeOrderSide.BUY else trading_enums.TraderOrderType.SELL_LIMIT
101+
trading_order_fee_currency = None
102+
trading_order_fee_rate = None
103+
is_estimated_trading_fee = False
104+
if trade.fee and all(
105+
key.value in trade.fee for key in [
106+
trading_enums.FeePropertyColumns.CURRENCY, trading_enums.FeePropertyColumns.RATE
107+
]
108+
):
109+
trading_order_fee_currency = trade.fee[trading_enums.FeePropertyColumns.CURRENCY.value]
110+
trading_order_fee_rate = trade.fee[trading_enums.FeePropertyColumns.RATE.value]
111+
else:
112+
# try to get fees from somewhere else
113+
try:
114+
# check if order has fees
115+
order = trade.exchange_manager.exchange_personal_data.orders_manager.get_order(
116+
None, exchange_order_id=trade.exchange_order_id
117+
)
118+
if not order.fee or not all(
119+
key.value in order.fee for key in [
120+
trading_enums.FeePropertyColumns.CURRENCY, trading_enums.FeePropertyColumns.RATE
121+
]
122+
):
123+
raise KeyError("no fee")
124+
trading_order_fee_currency = order.fee[trading_enums.FeePropertyColumns.CURRENCY.value]
125+
trading_order_fee_rate = order.fee[trading_enums.FeePropertyColumns.RATE.value]
126+
except KeyError:
127+
# try with other trades from the same order
128+
trades = trade.exchange_manager.exchange_personal_data.trades_manager.get_trades(
129+
None, exchange_order_id=trade.exchange_order_id
130+
)
131+
for other_trade in trades:
132+
if not other_trade.fee or not all(
133+
key.value in other_trade.fee for key in [
134+
trading_enums.FeePropertyColumns.CURRENCY, trading_enums.FeePropertyColumns.RATE
135+
]
136+
):
137+
continue
138+
trading_order_fee_currency = other_trade.fee[trading_enums.FeePropertyColumns.CURRENCY.value]
139+
trading_order_fee_rate = other_trade.fee[trading_enums.FeePropertyColumns.RATE.value]
140+
141+
if trading_order_fee_currency and trading_order_fee_rate:
142+
base, quote = symbols_util.parse_symbol(trade.symbol).base_and_quote()
143+
if trading_order_fee_currency == base:
144+
trading_order_fee_cost = trading_order_fee_rate * trade.executed_quantity
145+
elif trading_order_fee_currency == quote:
146+
trading_order_fee_cost = trading_order_fee_rate * trade.executed_quantity * trade.executed_price
147+
else:
148+
# fee currency is not the base or quote of the symbol
149+
trading_order_fee_cost = constants.ZERO
150+
trading_fee = {
151+
trading_enums.FeePropertyColumns.CURRENCY.value: trading_order_fee_currency,
152+
trading_enums.FeePropertyColumns.COST.value: trading_order_fee_cost,
153+
}
154+
else:
155+
# estimate fees
156+
trading_fee = trade.exchange_manager.exchange.get_trade_fee(
157+
trade.symbol, order_type, trade.executed_quantity, trade.executed_price, trade.taker_or_maker
158+
)
159+
is_estimated_trading_fee = True
160+
return trading_fee, is_estimated_trading_fee

0 commit comments

Comments
 (0)