Added --hedging-target-balance parameter to marketmaker.

This commit is contained in:
Geoff Taylor 2021-10-15 17:27:09 +01:00
parent 62b8ce8c49
commit 69f2fd49d6
5 changed files with 34 additions and 74 deletions

View File

@ -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(

View File

@ -83,3 +83,18 @@ 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.
> 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`.

View File

@ -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

View File

@ -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}")

View File

@ -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}"