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

[Staggered] use dynamic order fees when available #1108

Merged
merged 3 commits into from
Nov 18, 2023
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
21 changes: 12 additions & 9 deletions Trading/Mode/grid_trading_mode/grid_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ def init_user_inputs(self, inputs: dict) -> None:
"total order cost (price * volume).",
)
self.UI.user_input(
self.CONFIG_REINVEST_PROFITS, commons_enums.UserInputTypes.BOOLEAN,
default_config[self.CONFIG_REINVEST_PROFITS], inputs,
self.CONFIG_IGNORE_EXCHANGE_FEES, commons_enums.UserInputTypes.BOOLEAN,
default_config[self.CONFIG_IGNORE_EXCHANGE_FEES], inputs,
parent_input_name=self.CONFIG_PAIR_SETTINGS,
title="Reinvest profits: when checked, profits will be included in mirror orders resulting in maximum "
"size mirror orders. When unchecked, a part of the total volume will be reduced to take "
"exchange fees into account. WARNING: incompatible with fixed volume on mirror orders.",
title="Ignore exchange fees: when checked, exchange fees won't be considered when creating mirror orders. "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"When unchecked, a part of the total volume will be reduced to take exchange "
"fees into account.",
)
self.UI.user_input(
self.CONFIG_MIRROR_ORDER_DELAY, commons_enums.UserInputTypes.FLOAT,
Expand All @@ -157,7 +157,7 @@ def init_user_inputs(self, inputs: dict) -> None:
default_config[self.CONFIG_USE_FIXED_VOLUMES_FOR_MIRROR_ORDERS], inputs,
parent_input_name=self.CONFIG_PAIR_SETTINGS,
title="Fixed volume on mirror orders: when checked, sell and buy orders volume settings will be used for "
"mirror orders. WARNING: incompatible with profits reinvesting.",
"mirror orders. WARNING: incompatible with 'Ignore exchange fees'.",
)
self.UI.user_input(
self.CONFIG_USE_EXISTING_ORDERS_ONLY, commons_enums.UserInputTypes.BOOLEAN,
Expand Down Expand Up @@ -188,7 +188,7 @@ def get_default_pair_config(self, symbol, flat_spread, flat_increment) -> dict:
self.CONFIG_STARTING_PRICE: 0,
self.CONFIG_BUY_VOLUME_PER_ORDER: 0,
self.CONFIG_SELL_VOLUME_PER_ORDER: 0,
self.CONFIG_REINVEST_PROFITS: False,
self.CONFIG_IGNORE_EXCHANGE_FEES: False,
self.CONFIG_MIRROR_ORDER_DELAY: 0,
self.CONFIG_USE_FIXED_VOLUMES_FOR_MIRROR_ORDERS: False,
self.CONFIG_USE_EXISTING_ORDERS_ONLY: False,
Expand Down Expand Up @@ -278,8 +278,11 @@ def read_config(self):
self.buy_volume_per_order)))
self.limit_orders_count_if_necessary = \
self.symbol_trading_config.get(self.trading_mode.LIMIT_ORDERS_IF_NECESSARY, True)
self.reinvest_profits = self.symbol_trading_config.get(self.trading_mode.CONFIG_REINVEST_PROFITS,
self.reinvest_profits)
# tmp: ensure "reinvest_profits" legacy param still works
self.ignore_exchange_fees = self.symbol_trading_config.get("reinvest_profits", self.ignore_exchange_fees)
# end tmp
self.ignore_exchange_fees = self.symbol_trading_config.get(self.trading_mode.CONFIG_IGNORE_EXCHANGE_FEES,
self.ignore_exchange_fees)
self.use_fixed_volume_for_mirror_orders = self.symbol_trading_config.get(
self.trading_mode.CONFIG_USE_FIXED_VOLUMES_FOR_MIRROR_ORDERS,
self.use_fixed_volume_for_mirror_orders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"allow_instant_fill": true,
"operational_depth": 100,
"mirror_order_delay": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_existing_orders_only": false
},
{
Expand All @@ -24,7 +24,7 @@
"allow_instant_fill": true,
"operational_depth": 50,
"mirror_order_delay": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_existing_orders_only": false
},
{
Expand All @@ -37,7 +37,7 @@
"allow_instant_fill": true,
"operational_depth": 50,
"mirror_order_delay": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_existing_orders_only": false
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class StaggeredOrdersTradingMode(trading_modes.AbstractTradingMode):
CONFIG_SELL_FUNDS = "sell_funds"
CONFIG_SELL_VOLUME_PER_ORDER = "sell_volume_per_order"
CONFIG_BUY_VOLUME_PER_ORDER = "buy_volume_per_order"
CONFIG_REINVEST_PROFITS = "reinvest_profits"
CONFIG_IGNORE_EXCHANGE_FEES = "ignore_exchange_fees"
CONFIG_USE_FIXED_VOLUMES_FOR_MIRROR_ORDERS = "use_fixed_volume_for_mirror_orders"
CONFIG_DEFAULT_SPREAD_PERCENT = 1.5
CONFIG_DEFAULT_INCREMENT_PERCENT = 0.5
Expand Down Expand Up @@ -189,10 +189,10 @@ def init_user_inputs(self, inputs: dict) -> None:
"is filled. This can generate extra profits on quick market moves.",
)
self.UI.user_input(
self.CONFIG_REINVEST_PROFITS, commons_enums.UserInputTypes.BOOLEAN, False, inputs,
self.CONFIG_IGNORE_EXCHANGE_FEES, commons_enums.UserInputTypes.BOOLEAN, False, inputs,
parent_input_name=self.CONFIG_PAIR_SETTINGS,
title="Reinvest profits: when checked, profits will be included in mirror orders resulting in maximum "
"size mirror orders. When unchecked, a part of the total volume will be reduced to take exchange "
title="Ignore exchange fees: when checked, exchange fees won't be considered when creating mirror orders. "
"When unchecked, a part of the total volume will be reduced to take exchange "
"fees into account.",
)
self.UI.user_input(
Expand Down Expand Up @@ -509,7 +509,7 @@ def __init__(self, channel, config, trading_mode, exchange_manager):
self.symbol_trading_config = None

self.use_existing_orders_only = self.limit_orders_count_if_necessary = \
self.reinvest_profits = self.use_fixed_volume_for_mirror_orders = False
self.ignore_exchange_fees = self.use_fixed_volume_for_mirror_orders = False
self.mode = self.spread \
= self.increment = self.operational_depth \
= self.lowest_buy = self.highest_sell \
Expand Down Expand Up @@ -581,8 +581,11 @@ def read_config(self):
self.buy_funds)))
self.sell_funds = decimal.Decimal(str(self.symbol_trading_config.get(self.trading_mode.CONFIG_SELL_FUNDS,
self.sell_funds)))
self.reinvest_profits = self.symbol_trading_config.get(self.trading_mode.CONFIG_REINVEST_PROFITS,
self.reinvest_profits)
# tmp: ensure "reinvest_profits" legacy param still works
self.ignore_exchange_fees = self.symbol_trading_config.get("reinvest_profits", self.ignore_exchange_fees)
# end tmp
self.ignore_exchange_fees = self.symbol_trading_config.get(self.trading_mode.CONFIG_IGNORE_EXCHANGE_FEES,
self.ignore_exchange_fees)

async def start(self) -> None:
await super().start()
Expand Down Expand Up @@ -704,7 +707,8 @@ async def order_filled_callback(self, filled_order):
filled_price = decimal.Decimal(str(filled_order[trading_enums.ExchangeConstantsOrderColumns.PRICE.value]))
filled_volume = decimal.Decimal(str(filled_order[trading_enums.ExchangeConstantsOrderColumns.FILLED.value]))
price = filled_price + price_increment if now_selling else filled_price - price_increment
volume = self._compute_mirror_order_volume(now_selling, filled_price, price, filled_volume)
fee = filled_order[trading_enums.ExchangeConstantsOrderColumns.FEE.value]
volume = self._compute_mirror_order_volume(now_selling, filled_price, price, filled_volume, fee)
new_order = OrderData(new_side, volume, price, self.symbol, False, associated_entry_id)
self.logger.debug(f"Creating mirror order: {new_order} after filled order: {filled_order}")
if self.mirror_order_delay == 0 or trading_api.get_is_backtesting(self.exchange_manager):
Expand All @@ -717,7 +721,7 @@ async def order_filled_callback(self, filled_order):
self._lock_portfolio_and_create_order_when_possible(new_order, filled_price)
))

def _compute_mirror_order_volume(self, now_selling, filled_price, target_price, filled_volume):
def _compute_mirror_order_volume(self, now_selling, filled_price, target_price, filled_volume, paid_fees: dict):
# use target volumes if set
if self.sell_volume_per_order != trading_constants.ZERO and now_selling:
return self.sell_volume_per_order
Expand All @@ -729,12 +733,23 @@ def _compute_mirror_order_volume(self, now_selling, filled_price, target_price,
# buying => adapt order quantity
new_order_quantity = filled_price / target_price * filled_volume
# use max possible volume
if self.reinvest_profits:
if self.ignore_exchange_fees:
return new_order_quantity
# remove exchange fees
quantity_change = self.max_fees
quantity = new_order_quantity * (1 - quantity_change)
return quantity
if paid_fees:
base, quote = symbol_util.parse_symbol(self.symbol).base_and_quote()
fees_in_base = trading_personal_data.get_fees_for_currency(paid_fees, base)
fees_in_base += trading_personal_data.get_fees_for_currency(paid_fees, quote) / filled_price
if fees_in_base == trading_constants.ZERO:
self.logger.debug(
f"Zero fees for trade on {self.symbol}"
)
else:
self.logger.debug(
f"No fees given to compute {self.symbol} mirror order size, using default ratio of {self.max_fees}"
)
fees_in_base = new_order_quantity * self.max_fees
return new_order_quantity - fees_in_base

async def _lock_portfolio_and_create_order_when_possible(self, new_order, filled_price):
await asyncio.wait_for(self.allowed_mirror_orders.wait(), timeout=None)
Expand Down Expand Up @@ -1335,7 +1350,9 @@ def _get_quantity_from_recent_trades(self, price, max_quantity, recent_trades, c
if trade is None:
return None
now_selling = trade.side == trading_enums.TradeOrderSide.BUY
return self._compute_mirror_order_volume(now_selling, trade.executed_price, price, trade.executed_quantity)
return self._compute_mirror_order_volume(
now_selling, trade.executed_price, price, trade.executed_quantity, trade.fee
)

def _get_associated_trade(self, price, trades, selling):
increment_window = self.flat_increment / 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1148,35 +1148,74 @@ async def test_order_fill_callback_with_mirror_delay():
async def test_compute_mirror_order_volume():
async with _get_tools("BTC/USD", fees=0) as tools:
producer, _, exchange_manager = tools
# no profit reinvesting
# no ignore_exchange_fees
# no fixed volumes
producer.reinvest_profits = producer.use_fixed_volume_for_mirror_orders = False
producer.ignore_exchange_fees = producer.use_fixed_volume_for_mirror_orders = False
# 1% max fees
producer.max_fees = 0.01
producer.max_fees = decimal.Decimal("0.01")
# take exchange fees into account
assert producer._compute_mirror_order_volume(True, 100, 120, 2) == 2 * (1 - producer.max_fees)
assert producer._compute_mirror_order_volume(False, 100, 80, 2) == 2 * (100 / 80) * (1 - producer.max_fees)
assert producer._compute_mirror_order_volume(
True, decimal.Decimal("100"), decimal.Decimal("120"), decimal.Decimal("2"), None
) == 2 * (1 - producer.max_fees)
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), {}
) == 2 * (decimal.Decimal("100") / decimal.Decimal("80")) * (1 - producer.max_fees)
# with given fees
fees = {
trading_enums.FeePropertyColumns.COST.value: decimal.Decimal("0.032"),
trading_enums.FeePropertyColumns.CURRENCY.value: "BTC"
}
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), fees
) == 2 * (decimal.Decimal("100") / decimal.Decimal("80")) - decimal.Decimal("0.032")
fees = {
trading_enums.FeePropertyColumns.COST.value: decimal.Decimal("2.3"),
trading_enums.FeePropertyColumns.CURRENCY.value: "USD"
}
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), fees
) == 2 * (decimal.Decimal("100") / decimal.Decimal("80")) - (decimal.Decimal("2.3") / decimal.Decimal("100"))

# with profits reinvesting
producer.reinvest_profits = True
# with ignore_exchange_fees
producer.ignore_exchange_fees = True
# consider fees already taken, sell everything
assert producer._compute_mirror_order_volume(True, 100, 120, 2) == 2
assert producer._compute_mirror_order_volume(False, 100, 80, 2) == 2 * (100 / 80)
assert producer._compute_mirror_order_volume(
True, decimal.Decimal("100"), decimal.Decimal("120"), decimal.Decimal("2"), None
) == 2
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), {}
) == 2 * (decimal.Decimal("100") / decimal.Decimal("80"))
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), fees
) == 2 * (decimal.Decimal("100") / decimal.Decimal("80"))

# with fixed volumes
producer.reinvest_profits = False
producer.ignore_exchange_fees = False
producer.sell_volume_per_order = 3
# consider fees already taken, sell everything
assert producer._compute_mirror_order_volume(True, 100, 120, 2) == 3
assert producer._compute_mirror_order_volume(
True, decimal.Decimal("100"), decimal.Decimal("120"), decimal.Decimal("2"), fees
) == 3
# buy order
assert producer._compute_mirror_order_volume(False, 100, 80, 2) == 2 * (100 / 80) * (1 - producer.max_fees)
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), None
) == 2 * (decimal.Decimal("100") / decimal.Decimal("80")) * (1 - producer.max_fees)
producer.buy_volume_per_order = 5
assert producer._compute_mirror_order_volume(False, 100, 80, 2) == 5

# with fixed volumes and profits reinvesting
producer.reinvest_profits = True
assert producer._compute_mirror_order_volume(True, 100, 120, 2) == 3
assert producer._compute_mirror_order_volume(False, 100, 80, 2) == 5
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), {}
) == 5

# with fixed volumes and ignore_exchange_fees
producer.ignore_exchange_fees = True
assert producer._compute_mirror_order_volume(
True, decimal.Decimal("100"), decimal.Decimal("120"), decimal.Decimal("2"), None
) == 3
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), {}
) == 5
assert producer._compute_mirror_order_volume(
False, decimal.Decimal("100"), decimal.Decimal("80"), decimal.Decimal("2"), fees
) == 5


async def test_create_order():
Expand Down
6 changes: 3 additions & 3 deletions profiles/grid_trading/specific_config/GridTradingMode.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"starting_price": 0,
"buy_volume_per_order": 0,
"sell_volume_per_order": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_fixed_volume_for_mirror_orders": false,
"mirror_order_delay": 0,
"use_existing_orders_only": false
Expand All @@ -28,7 +28,7 @@
"starting_price": 0,
"buy_volume_per_order": 0,
"sell_volume_per_order": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_fixed_volume_for_mirror_orders": false,
"mirror_order_delay": 0,
"use_existing_orders_only": false
Expand All @@ -44,7 +44,7 @@
"starting_price": 0,
"buy_volume_per_order": 0,
"sell_volume_per_order": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_fixed_volume_for_mirror_orders": false,
"mirror_order_delay": 0,
"use_existing_orders_only": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"allow_instant_fill": true,
"operational_depth": 100,
"mirror_order_delay": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_existing_orders_only": false
},
{
Expand All @@ -24,7 +24,7 @@
"allow_instant_fill": true,
"operational_depth": 50,
"mirror_order_delay": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_existing_orders_only": false
},
{
Expand All @@ -37,7 +37,7 @@
"allow_instant_fill": true,
"operational_depth": 50,
"mirror_order_delay": 0,
"reinvest_profits": false,
"ignore_exchange_fees": false,
"use_existing_orders_only": false
}
]
Expand Down