Skip to content
This repository was archived by the owner on Jan 16, 2022. It is now read-only.

synthesized liquidation feeds#137

Open
mesozoic-technology wants to merge 93 commits intooverlay-market:mainfrom
mesozoic-technology:liquidation-deployment
Open

synthesized liquidation feeds#137
mesozoic-technology wants to merge 93 commits intooverlay-market:mainfrom
mesozoic-technology:liquidation-deployment

Conversation

@mesozoic-technology
Copy link
Copy Markdown
Contributor

This PR is dedicated to creating a deployment of three quanto markets with unique price feeds skewed towards liquidations for longs, shorts, and one back and forth feed.

Goals are

  • Deployment script for these three markets.
  • Price feed generation for these three markets.

mesozoic-technology and others added 18 commits January 8, 2022 22:54
…le price relative to ETH for OVL in the depth feed, and placing ETH in the correct slot as to cooperate with the decimals of the market liquidity feed which was taken from ETH/USDC where USDC has 6 decimals
…ding from them to trigger primitive version of subgraph
@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

mesozoic-technology commented Jan 10, 2022

In order to easily test liquidations as well as develop frontends, a predictable price feed will reap many benefits.

If a price feed is predictable, then the chain can be mined to that point and the feed will be on the verge of liquidation, liquidtable, or underwater, and any one of these can be arrived at deterministically to either construct a precise test, or to queue up another position for liquidation to refine the frontend experience.

If a price feed increments or decrements by 1% per iteration, say every 10 minutes, then the time to mine for in order to bring about a specific state can be derived by the following formula

epochs to mine = log(ending price / starting price) / log(1 - percent price change per period)

Constructing a price feed with this in mind, allows for comprehensive testing and development.

@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

mesozoic-technology commented Jan 10, 2022

In practice, as derived from python, the percentage change between price periods looks to amount to 0.0100486663921 rather than a clean .01. Perhaps this is just due to the granularity of the logs being used.

I wonder how much this is inherently tethered to the fact that uniswap uses a log of 1.0001 and thereby the ticks are unable to be more fine grained.

Could that be improved in the python or is the python limited to the reality of the uniswap oracle/mock?

In any case, it seems this should be sufficient.

Given an epoch of 15 seconds, an entry price of 100, a position of 1x long against a feed that decreases by 0.0100486663921 per period, and a margin maintenance of 6%, then we should expect the following to produce a liquidation for this position:

epochs = log(6/100) / log(.1 - 0.0100486663921) = log(6/100) / log(0.98995133) = 279.93201001

chain.mine(timedelta=280*15=4200) 

@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

Actually, this formula for deriving when you mine to will be insufficient given the one hour window in place for a time weighted average price.

I must examine the behavior of the spread and account for either the one hour or ten minute window.

@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

In practice, this may not matter much, at least due to the presence of a TWAP. Since the price feed in/decrements monotonically at a steady pace, it appears that the formula above shows true for all permutations - one hour, ten minute, spot, bid, ask.

The below was achieved correctly after computing

log(e;0.04873537/0.09747074120374173) / log(e;.99) = 68.96756517

chain.mine(15*69)

start_one_hr 0.09747074120374173
start_ten_min 0.035861764793353186
start_spot 0.029604644779620857
start_bid 0.03565686448110418
start_ask 0.09803085173500767
end_one_hr 0.04889067780878373
end_ten_min 0.01798802354956246
end_spot 0.014849493619202842
end_bid 0.01788524691089768
end_ask 0.04917162553918201
one_hr frame   0.501593372585402
ten min frame  0.5015933725854022
end spot frame 0.501593372585402
end bid frame  0.5015933725854022
end ask frame  0.501593372585402

Still problematic is that the bid is so different than the ask, where the bid is basically the 10 minute TWAP, the ask is the 1 hour TWAP.

@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

mesozoic-technology commented Jan 11, 2022

I succeeded in creating a function to more or less brute force this calculation using prior knowledge of the price feed incr/decrementation alongside panda's.

def find (start, end):

    tick_start = int(math.log(start, 1.0001))

    # minus 2222 to give room for ten minute twap
    tick_end = int(math.log(end, 1.0001)) - 2222

    # get prior hour for rolling average
    tick_prehistory = tick_start + (101 * 240)  

    prices = [1.0001**x for x in range(tick_prehistory, tick_end, -101)]

    hours = pd.Series(prices).rolling(240).mean().to_frame('hours')\
        .dropna().reset_index().drop(columns=['index'])

    tens = pd.Series(prices).rolling(40).mean().to_frame('tens') .dropna()
    tens = tens.iloc[len(tens) - len(hours):,:].reset_index()\
        .drop(columns=['index'])

    twaps = pd.concat([hours, tens], axis=1)

    prices = twaps.apply(
        lambda x: min(x['hours'], x['tens']) * math.exp(-.00573),
        axis=1
    )

    print(prices)

Since the periods increase or decrease by a tick value of 101 on a period of 15 seconds for these price feeds, this may be employed to calculate prices using a list comprehension and pandas for the rolling averages.

Once the time weighted averages are computed, from the tick values which I hope alleviates concerns of geometric vs arithmetic twaps, I apply the bid adjustment using a min and the negative spread value.

At this point, finding the index of the end price and determining the correct starting index should lead to the number of epochs one must mine to in order to receive those prices.

@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

mesozoic-technology commented Jan 11, 2022

Two improvements,

  1. Removing the spread to increase accuracy of start time, in this case removing the ask spread

start *= math.exp(-.00573)

  1. finding the index of the value just past the end price
    prices = twaps.apply(
        lambda x: min(x['hours'], x['tens']) * math.exp(-.00573),
        axis=1
    ).to_frame('bids')
    
    end_ix = prices.iloc[
        (prices['bids']-end)
        .abs()
        .argsort()[:2]
    ].index[1]

@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

The tick_prehistory in the above code is misleading. Since one comes armed only with the ask or the bid for their long or short, they come armed with the price from a one hour twap, when they use these synthesized liquidation feeds.

For this price finding function to work then, the entry price would have to be reworked to its attendant spot price.

@mesozoic-technology mesozoic-technology changed the title synthetic liquidation feeds synthesized liquidation feeds Jan 11, 2022
@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

mesozoic-technology commented Jan 11, 2022

All of the above is too elaborate. It forgets the first principle of feeds synthesized in this manner which does away with all of the complexity needing rolling averages.

Because the price feed increases or decreases at a steady rate, 101 ticks per period, then it is always known what the 10 minute or 1 hour twap will report relative to spot.

Therefore all that needs be done is to calculate spot, adjust it accordingly, follow it ahead into the future until it hits the desired liquidation price and tells how many periods must be mined.

So long as the synthesized feeds are de/inclining aggressively enough, the bid will always be the 10 minute twap for liquidating longs and the ask will always be the 10 minute twap for liquidating shorts.

@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

mesozoic-technology commented Jan 11, 2022


def find_bid_time(start, end):

    spread = .00573

    # remove the spread and twap
    start *= math.exp(-spread)
    start *= 0.23676027122715326

    tick_start = int(math.log(start, 1.0001))

    # minus 2222 to give room for ten minute twap
    tick_end = int(math.log(end, 1.0001)) - 2222

    prices = [1.0001**x for x in range(tick_start, tick_end, -101)]

    end_ix = None
    for i in range(len(prices)):
        price = prices[i]
        target = (price / 0.8156852954475423) * math.exp(-spread)  # add the 10 minute twap and spread
        if (end > target):
            end_ix = i
            break

    mine_time = end_ix * 15

@mesozoic-technology
Copy link
Copy Markdown
Contributor Author

mesozoic-technology commented Jan 12, 2022

Strangely enough this version below works. Why adding 26 periods or 390 seconds to the end, after setting the price in the beginning to be in line with the ten minute twap, makes this work, I have no idea. But it does. This yields the period just beyond the end price parameter to this function, which must be a price that is lesser than the start parameter.

Further work could obtain a more granular seconds value, right now per period there is a fairly large chunk of the price that is being skipped over. For instance with an ending price targeted at a margin maintenance of 6 percent, this function yields a period with a price that is 0.059533296710950735, or if 3 percent is targeted it yields 0.029858521226242985, so there's a little discrepancy here.

If it could be improved such that it'd be right on the boundary of the margin maintenance, this could provide a good liquidation test where a position is asserted to not be liquidatable just before the chain is mined a few seconds and it becomes liquidatable.

def find_bid_time(start, end):

    spread = .00573

    # remove the spread and hour twap
    start = (start * math.exp(-spread)) * 0.23676027122715326

    # adjust to ten minute twap
    start /= 0.8156852954475423 

    tick_start = int(math.log(start, 1.0001))

    # lower by 2222 to give room for ten minute twap
    tick_end = int(math.log(end, 1.0001)) - 2222

    prices = [1.0001**x for x in range(tick_start, tick_end, -101)]

    element = min(enumerate(prices), key=lambda x: abs(end - x[1]))

    # add 26 to index of element and multiply by period length
    return (26 + element[0]) * 15

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants