From 69f2fd49d6ca8c0bb277c810b6d1d7519df49aa0 Mon Sep 17 00:00:00 2001 From: Geoff Taylor Date: Fri, 15 Oct 2021 17:27:09 +0100 Subject: [PATCH] Added --hedging-target-balance parameter to marketmaker. --- bin/marketmaker | 8 +++- docs/MarketmakingHedging.md | 17 +++++++- mango/__init__.py | 1 - mango/hedging/perptospothedger.py | 13 +++++- mango/perphedger.py | 69 ------------------------------- 5 files changed, 34 insertions(+), 74 deletions(-) delete mode 100644 mango/perphedger.py diff --git a/bin/marketmaker b/bin/marketmaker index 4287da7..e8bf387 100755 --- a/bin/marketmaker +++ b/bin/marketmaker @@ -8,6 +8,7 @@ import rx import rx.operators import sys import threading +import typing from decimal import Decimal from solana.publickey import PublicKey @@ -43,6 +44,8 @@ parser.add_argument("--hedging-max-price-slippage-factor", type=Decimal, default help="the maximum value the IOC hedging order price can slip by when hedging (default is 0.05 for 5%%)") parser.add_argument("--hedging-max-chunk-quantity", type=Decimal, default=Decimal(0), help="the maximum quantity of the hedge asset that will be traded in a single pulse. Trades larger than this size will be 'chunked' and spread across subsequent hedge pulses.") +parser.add_argument("--hedging-target-balance", type=mango.parse_fixed_target_balance, required=False, + help="hedged balance to maintain - format is a token symbol plus target value, separated by a colon (e.g. 'ETH:2.5')") parser.add_argument("--account-address", type=PublicKey, help="address of the specific account to use, if more than one available") parser.add_argument("--notify-errors", type=mango.parse_subscription_target, action="append", default=[], @@ -118,9 +121,12 @@ if args.hedging_market is not None: hedging_market_operations: mango.MarketOperations = mango.create_market_operations( context, wallet, account, hedging_market, args.dry_run) + target_balance: typing.Optional[mango.TargetBalance] = args.hedging_target_balance + if target_balance is None: + target_balance = mango.FixedTargetBalance(hedging_market.base.symbol, Decimal(0)) hedger = mango.hedging.PerpToSpotHedger(group, underlying_market, hedging_market, hedging_market_operations, args.hedging_max_price_slippage_factor, - args.hedging_max_chunk_quantity) + args.hedging_max_chunk_quantity, target_balance) order_reconciler = mango.marketmaking.ToleranceOrderReconciler( diff --git a/docs/MarketmakingHedging.md b/docs/MarketmakingHedging.md index 98a80ef..819ebb7 100644 --- a/docs/MarketmakingHedging.md +++ b/docs/MarketmakingHedging.md @@ -82,4 +82,19 @@ This parameter allows you to specify a maximum quantity to trade on a single 'pu For example, if the marketmaker wants to hedge 50 SOL, it might be better to spread that out over 5 pulses with 10 SOL each. That gives the spot marketmakers a chance to adjust and put up fresh orders instead of just sweeping all the orders on the book. -Using this may give a better overall price than a single order, or it may introduce a new timing risk as the market moves further away while some of the position is unhedged. \ No newline at end of file +Using this may give a better overall price than a single order, or it may introduce a new timing risk as the market moves further away while some of the position is unhedged. + + +> Parameter: `--hedging-target-balance` + +> Example Usage: `--hedging-target-balance BTC:2.5` + +Sometimes when hedging, it's preferable to have a specific balance to hedge from rather than purely zero. + +For example, you might want to add 2.5 BTC to your Mango account, earn interest on it, and use it for collateral for your marketmaking. You wouln't want the hedging functionality to immediately sell it to aim for a neutral position. + +Instead you'd want your 2.4 BTC to be your neutral position. If you sold 0.5 BTC-PERP short, instead of the hedger aiming for 0.5 BTC you'd want it to take your 2.5 BTC into account and aim for 3 BTC. + +The `--hedging-target-balance` parameter allows you to specify what the target (neutral) position should be. The hedging functionality will then aim to achieve that target, with perp positions and spot balances adding and subtracting from it instead of 0. + +The format for the parameter is the same format as target balances for the `balance-account` and `balance-wallet` commands: it's the symbol, followed by a colon, followed by a fixed number. Percentage target balances aren't supported for this parameter, and the symbol must match the symbol of the base token on the `--hedging-market`. \ No newline at end of file diff --git a/mango/__init__.py b/mango/__init__.py index e4e379e..7df2fd4 100644 --- a/mango/__init__.py +++ b/mango/__init__.py @@ -50,7 +50,6 @@ from .oraclefactory import create_oracle_provider from .parse_account_info_to_orders import parse_account_info_to_orders from .perpaccount import PerpAccount from .perpeventqueue import PerpEvent, PerpFillEvent, PerpOutEvent, PerpUnknownEvent, PerpEventQueue, UnseenPerpEventChangesTracker -from .perphedger import PerpHedger from .perpmarket import PerpMarket, PerpMarketStub from .perpmarketdetails import PerpMarketDetails from .perpmarketinfo import PerpMarketInfo diff --git a/mango/hedging/perptospothedger.py b/mango/hedging/perptospothedger.py index 415240e..0f8f1de 100644 --- a/mango/hedging/perptospothedger.py +++ b/mango/hedging/perptospothedger.py @@ -31,18 +31,26 @@ from .hedger import Hedger class PerpToSpotHedger(Hedger): def __init__(self, group: mango.Group, underlying_market: mango.PerpMarket, hedging_market: mango.SpotMarket, market_operations: mango.MarketOperations, - max_price_slippage_factor: Decimal, max_hedge_chunk_quantity: Decimal): + max_price_slippage_factor: Decimal, max_hedge_chunk_quantity: Decimal, + target_balance: mango.TargetBalance): super().__init__() if (underlying_market.base != hedging_market.base) or (underlying_market.quote != hedging_market.quote): raise Exception( f"Market {hedging_market.symbol} cannot be used to hedge market {underlying_market.symbol}.") + if target_balance.symbol != hedging_market.base.symbol: + raise Exception(f"Cannot target {target_balance.symbol} when hedging on {hedging_market.symbol}") + self.underlying_market: mango.PerpMarket = underlying_market self.hedging_market: mango.SpotMarket = hedging_market self.market_operations: mango.MarketOperations = market_operations self.buy_price_adjustment_factor: Decimal = Decimal("1") + max_price_slippage_factor self.sell_price_adjustment_factor: Decimal = Decimal("1") - max_price_slippage_factor self.max_hedge_chunk_quantity: Decimal = max_hedge_chunk_quantity + + resolved_target: mango.TokenValue = target_balance.resolve(hedging_market.base, Decimal(0), Decimal(0)) + self.target_balance: Decimal = self.hedging_market.lot_size_converter.round_base(resolved_target.value) + self.market_index: int = group.find_perp_market_index(underlying_market.address) def pulse(self, context: mango.Context, model_state: mango.ModelState): @@ -66,7 +74,8 @@ class PerpToSpotHedger(Hedger): token_balance_rounded: Decimal = self.hedging_market.lot_size_converter.round_base(token_balance.value) # When we add the rounded perp position and token balances, we should get zero if we're delta-neutral. - delta: Decimal = perp_position_rounded + token_balance_rounded + # If we have a target balance, subtract that to get our targetted delta neutral balance. + delta: Decimal = perp_position_rounded + token_balance_rounded - self.target_balance self.logger.debug( f"Delta from {self.underlying_market.symbol} to {self.hedging_market.symbol} is {delta:,.8f} {basket_token.token_info.token.symbol}") diff --git a/mango/perphedger.py b/mango/perphedger.py deleted file mode 100644 index 0045d95..0000000 --- a/mango/perphedger.py +++ /dev/null @@ -1,69 +0,0 @@ -# # ⚠ Warning -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# [πŸ₯­ Mango Markets](https://mango.markets/) support is available at: -# [Docs](https://docs.mango.markets/) -# [Discord](https://discord.gg/67jySBhxrg) -# [Twitter](https://twitter.com/mangomarkets) -# [Github](https://github.com/blockworks-foundation) -# [Email](mailto:hello@blockworks.foundation) - - -import logging -import traceback - -from decimal import Decimal -from solana.publickey import PublicKey - -from .context import Context -from .orders import Side, OrderType, Order -from .marketoperations import MarketOperations -from .perpeventqueue import PerpFillEvent - - -# # πŸ₯­ PerpHedger class -# -# A `PerpHedger` takes a `PerpFill` event and tries to hedge (apply the reverse trade) on the spot market. -# -class PerpHedger: - def __init__(self, own_address: PublicKey, hedge_market_operations: MarketOperations, max_price_slippage_factor: Decimal): - self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) - self.own_address: PublicKey = own_address - self.hedge_market_operations: MarketOperations = hedge_market_operations - self.buy_price_adjustment_factor: Decimal = Decimal("1") + max_price_slippage_factor - self.sell_price_adjustment_factor: Decimal = Decimal("1") - max_price_slippage_factor - self.hedging_on: str = self.hedge_market_operations.market.symbol - - def hedge(self, context: Context, fill_event: PerpFillEvent) -> Order: - side: Side - if fill_event.taker == self.own_address: - opposite_side: Side = Side.BUY if fill_event.taker_side == Side.SELL else Side.SELL - side = opposite_side - else: - # We were the opposite side in the filled trade, so to hedge we want to be the side the taker just took. - side = fill_event.taker_side - up_or_down: str = "up to" if side == Side.BUY else "down to" - - price_adjustment_factor: Decimal = self.sell_price_adjustment_factor if side == Side.SELL else self.buy_price_adjustment_factor - adjusted_price: Decimal = fill_event.price * price_adjustment_factor - quantity: Decimal = fill_event.quantity - order: Order = Order.from_basic_info(side, adjusted_price, fill_event.quantity, OrderType.IOC) - self.logger.info( - f"Hedging perp {fill_event.taker_side} ({fill_event.maker_order_id}) of {fill_event.quantity:,.8f} at {fill_event.price:,.8f} with {side} of {quantity:,.8f} at {up_or_down} {adjusted_price:,.8f} on {self.hedging_on}\n\t{order}") - try: - self.hedge_market_operations.place_order(order) - except Exception: - self.logger.error( - f"[{context.name}] Failed to hedge on {self.hedging_on} using order {order} - {traceback.format_exc()}") - return order - - def __str__(self) -> str: - return f"""Β« π™ΏπšŽπš›πš™π™·πšŽπšπšπšŽπš› hedging on '{self.hedging_on}' [BUY * {self.buy_price_adjustment_factor} / SELL * {self.sell_price_adjustment_factor}] Β»""" - - def __repr__(self) -> str: - return f"{self}"