Created
May 22, 2024 13:37
-
-
Save thedavidmeister/d1c21bb3a6d6ebedcd44cdc70ce42597 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Strat: Recharging tranches | |
# | |
# High level idea is that the strategy offers a firm price for batches of tokens. | |
# Each batch is called a "tranche". | |
# | |
# Every time a batch of tokens fully clears a new price further from the previous | |
# trades is offered for the next tranche. | |
# | |
# For example, if 1000 BLUE was available in a tranche to buy RED at | |
# 1 BLUE per RED and this fully cleared, the next tranche might be to buy up to | |
# 1000 BLUE at 0.9 BLUE per RED, then 0.8, etc. etc. | |
# | |
# Tranches slowly recharge over time passively when no trades are happening against | |
# the current price offer. For example, the strategy might recharge 1 tranche per | |
# day, so if the last trade left 500 BLUE remaining in a 1000 BLUE tranche at | |
# 0.9 BLUE per RED then after 24 hours the strategy will be offering 500 BLUE | |
# at 1 BLUE per RED. I.e. 0.5 tranches were recharged at 0.9 ratio and then | |
# another 0.5 tranches were recharged at 1 ratio. After another 12 hours | |
# there will be 1000 BLUE on offer at 1 ratio, etc. | |
# | |
# Almost everything about the strat is bindable and chartable, e.g. | |
# - The algorithms that determine the price and amount of each tranche | |
# - The recharge rate and delay before the passive recharge starts to kick in | |
# - Whether the amounts per tranche are denominated in the input or output token | |
# - Whether the strategy is buying or selling TKN | |
# - An optional conversion between the input/output token and some external | |
# price, e.g. converting WETH in a vault to USD equivalent. | |
networks: | |
arbitrum-one: | |
rpc: https://arbitrum.llamarpc.com | |
chain-id: 42161 | |
network-id: 42161 | |
currency: ETH | |
subgraphs: | |
arbitrum-one: https://api.thegraph.com/subgraphs/name/h20liquidity/arbitrum-0x90caf23e | |
orderbooks: | |
arbitrum-one: | |
address: 0x90CAF23eA7E507BB722647B0674e50D8d6468234 | |
network: arbitrum-one | |
subgraph: arbitrum-one | |
deployers: | |
arbitrum-one: | |
address: 0x2AeE87D75CD000583DAEC7A28db103B1c0c18b76 | |
network: arbitrum-one | |
tokens: | |
arbitrum-one-red: | |
network: arbitrum-one | |
address: 0x6d3abb80c3cbae0f60ba274f36137298d8571fbe | |
arbitrum-one-blue: | |
network: arbitrum-one | |
address: 0x667f41fF7D9c06D2dd18511b32041fC6570Dc468 | |
orders: | |
# vault-id generated with `openssl rand -hex 32` | |
arbitrum-one-buy: | |
inputs: | |
- token: arbitrum-one-red | |
vault-id: 0x562bd75e19e548420f9f3da43a7d7d67c6344580256b952c9214192c445d6053 | |
outputs: | |
- token: arbitrum-one-blue | |
vault-id: 0x562bd75e19e548420f9f3da43a7d7d67c6344580256b952c9214192c445d6053 | |
arbitrum-one-sell: | |
inputs: | |
- token: arbitrum-one-blue | |
vault-id: 0x562bd75e19e548420f9f3da43a7d7d67c6344580256b952c9214192c445d6053 | |
outputs: | |
- token: arbitrum-one-red | |
vault-id: 0x562bd75e19e548420f9f3da43a7d7d67c6344580256b952c9214192c445d6053 | |
scenarios: | |
arb-red-blue-tranches: | |
network: arbitrum-one | |
deployer: arbitrum-one | |
orderbook: arbitrum-one | |
bindings: | |
# The uniswap words are only required if there is a conversion between | |
# the input/output token and some external price. Typically this is | |
# not the case as the io-ratio is defined in terms of the input/output | |
# token and the io-ratio-multiplier is set to the identity function. | |
uniswap-words: 0x5Cf7d0a8c61c8dcC6b0ECB281dF1C17264C2A517 | |
orderbook-subparser: 0x23F77e7Bc935503e437166498D7D72f2Ea290E1f | |
# How far we move through tranche space in a second. | |
# 1e18 is a whole tranche, so we divide 1e18 by the number of seconds | |
# per recharge to calculate the per-second rate. | |
# Examples: | |
# 172800 seconds in 2 days, 48 hours = 1e18 / 172800 = 5787037037037 | |
# 86400 seconds in 1 day, 24 hours = 1e18 / 86400 = 11574074074074 | |
# 43200 seconds in 12 hours, 12 hours = 1e18 / 43200 = 23148148148148 | |
# 3600 seconds in 1 hour, 1 hour = 1e18 / 3600 = 277777777777777 | |
tranche-space-per-second: 11574074074074 | |
# After any trade happens we pause before recharging. | |
# Delay is to observe market feedback to the previous trade, e.g. to | |
# potentially offer the next tranche at a different price for some time | |
# before recharging back to the previous price. | |
# Too long and people could grief to stop recharging. | |
# Too quick and it will be difficult to move between tranches. | |
# The default here is 5 minutes (units are seconds) and probably never | |
# needs to be changed. | |
tranche-space-recharge-delay: 300 | |
# When a tranche is completely cleared, the next tranche MAY be jumped | |
# into partially. For example, if tranches are 90% shy (i.e. 9e17) then | |
# if a tranche is cleared completely then the next tranche will be | |
# started at 10% of its maximum size. This means that the capital | |
# requirements for the strategy to reprice itself as the market moves | |
# are reduced. | |
# This MUST be set to a value less than 1e18, else it will entirely | |
# skip tranches. | |
# Shyness MAY be set to 0, in which case every tranche will be fully | |
# available as it is entered. | |
# tranche-space-shyness: 0 | |
# Minimum trade size, if you put in a trade for less than x% of a | |
# tranche it wont clear. | |
# Mitigates people pinging strat for dust orders to stop recharging. | |
min-tranche-space-diff: 1e17 | |
# Snap to the nearest tranche to avoid dust issues at the edges, either | |
# due to rounding in the evm or potentially malicious trades. | |
# 1e16 is 1% | |
tranche-space-snap-threshold: 1e16 | |
# This is only relevant if the tranche size/ratio is denominated in | |
# some token other than the input/output tokens. For example, if the | |
# TKN was being traded for WETH but the tranche size was denominated in | |
# USD then the reference-stable would be USD and the reference-reserve | |
# would be WETH, and the identity multiplier needs to be swapped out | |
# for e.g. a TWAP USDT based multiplier. | |
# Typically this is NOT needed as the tranche size and ratio ARE | |
# denominated in the input/output tokens. | |
io-ratio-multiplier: '''io-ratio-multiplier-identity' | |
scenarios: | |
buy: | |
bindings: | |
# If we want to denominate the amount in BLUE when we're | |
# buying RED with it, then the amount is the OUTPUT. | |
amount-is-output: 1 | |
io-ratio-expr: '''linear-growth' | |
io-ratio-base: 1e18 | |
io-ratio-growth: 5e16 | |
tranche-size-expr: '''exponential-growth' | |
tranche-size-base: 1e18 | |
tranche-size-growth: 1e16 | |
scenarios: | |
initialized: | |
bindings: | |
# This needs to be set upon going live. | |
# Generate a chart and compare to current market prices, if | |
# the market is within the chart then set this to the closest | |
# tranche that won't immediately dump into the market. | |
# If the market is outside the chart then set this to 0. | |
initial-tranche-space: 0 | |
scenarios: | |
prod: | |
bindings: | |
get-last-tranche: '''get-last-tranche-prod' | |
set-last-tranche: '''set-last-tranche-prod' | |
plottables: '''plottables-prod' | |
tranche-space-shyness: 0 | |
test: | |
runs: 100 | |
bindings: | |
get-last-tranche: '''get-last-tranche-test-init' | |
set-last-tranche: '''set-last-tranche-test' | |
plottables: '''plottables-test' | |
test-last-update-time: 0 | |
test-now: 0 | |
test-shy-tranche: | |
bindings: | |
get-last-tranche: '''get-last-tranche-prod' | |
set-last-tranche: '''set-last-tranche-prod' | |
plottables: '''plottables-prod' | |
tranche-space-shyness: 9e17 | |
test: | |
runs: 10000 | |
bindings: | |
get-last-tranche: '''get-last-tranche-test' | |
set-last-tranche: '''set-last-tranche-test' | |
plottables: '''plottables-test' | |
max-test-tranche-space: 20e18 | |
test-last-update-time: 0 | |
test-now: 0 | |
sell: | |
bindings: | |
# If we want to denominate the amount in BLUE when we're | |
# selling RED for it, then the amount is the INPUT. | |
amount-is-output: 0 | |
io-ratio-expr: '''linear-growth' | |
io-ratio-base: 10e18 | |
io-ratio-growth: 5e17 | |
tranche-size-expr: '''exponential-growth' | |
tranche-size-base: 1e18 | |
tranche-size-growth: 1e16 | |
scenarios: | |
initialized: | |
bindings: | |
# This needs to be set upon going live. | |
# Generate a chart and compare to current market prices, if | |
# the market is within the chart then set this to the closest | |
# tranche that won't immediately dump into the market. | |
# If the market is outside the chart then set this to 0. | |
initial-tranche-space: 0 | |
scenarios: | |
prod: | |
bindings: | |
get-last-tranche: '''get-last-tranche-prod' | |
set-last-tranche: '''set-last-tranche-prod' | |
plottables: '''plottables-prod' | |
tranche-space-shyness: 0 | |
test: | |
runs: 100 | |
bindings: | |
get-last-tranche: '''get-last-tranche-test-init' | |
set-last-tranche: '''set-last-tranche-test' | |
plottables: '''plottables-test' | |
test-last-update-time: 0 | |
test-now: 0 | |
test: | |
runs: 10000 | |
bindings: | |
get-last-tranche: '''get-last-tranche-test' | |
set-last-tranche: '''set-last-tranche-test' | |
plottables: '''plottables-test' | |
max-test-tranche-space: 20e18 | |
test-last-update-time: 0 | |
test-now: 0 | |
charts: | |
buy-initial-deployment: | |
scenario: arb-red-blue-tranches.buy.initialized.test | |
metrics: | |
- label: Amount of BLUE required to buy RED in the first tranche | |
value: 0.6 | |
unit-prefix: $ | |
- label: Amount of RED purchased in the first tranche | |
value: 0.5.2 | |
- label: 'Initial io-ratio (i.e. # RED purchased per BLUE spent)' | |
value: 0.7 | |
- label: Initial effective buy price (e.g. how much you pay for 1 RED in BLUE, visible on dextools) | |
value: 0.5.3 | |
unit-prefix: $ | |
- label: Starting tranche | |
value: 0.2.0 | |
plots: | |
buy-simulation: | |
scenario: arb-red-blue-tranches.buy.test | |
plots: | |
BLUE spent and recharge rate per tranche: | |
marks: | |
- type: line | |
options: | |
x: 0.0 | |
y: 0.6 | |
Number of RED purchased per tranche: | |
marks: | |
- type: line | |
options: | |
x: 0.0 | |
y: 0.5.2 | |
'io-ratio (i.e. # of RED purchased per BLUE spent) vs tranche': | |
marks: | |
- type: line | |
options: | |
x: 0.0 | |
y: 0.7 | |
Effective buy price by tranche (e.g. how much you pay for 1 RED token in BLUE e.g. visible on dextools): | |
marks: | |
- type: line | |
options: | |
x: 0.0 | |
y: 0.5.3 | |
Starting tranche: | |
marks: | |
- type: dot | |
options: | |
x: 0.2.0 | |
y: 0.0 | |
sell-initial-deployment: | |
scenario: arb-red-blue-tranches.sell.initialized.test | |
metrics: | |
- label: Amount of RED sold to meet the first tranche of BLUE | |
value: 0.6 | |
unit-prefix: $ | |
- label: Amount of BLUE received selling the first tranche of RED tokens | |
value: 0.5.2 | |
- label: 'Initial io-ratio (i.e. # of BLUE purchased per RED spent)' | |
value: 0.7 | |
- label: Initial effective sell price (Amount of RED to sell 1 BLUE e.g. visible on dextools) | |
value: 0.7 | |
unit-prefix: $ | |
- label: Starting tranche | |
value: 0.2.0 | |
plots: | |
sell-simulation: | |
scenario: arb-red-blue-tranches.sell.test | |
plots: | |
Number of RED sold per tranche: | |
marks: | |
- type: line | |
options: | |
x: 0.0 | |
y: 0.6 | |
BLUE per tranche: | |
marks: | |
- type: line | |
options: | |
x: 0.0 | |
y: 0.5.2 | |
'io-ratio (i.e. # of TKN purchased per $ spent) vs tranche': | |
marks: | |
- type: line | |
options: | |
x: 0.0 | |
y: 0.7 | |
'Effective price (e.g. visible on dextools) vs tranche': | |
marks: | |
- type: line | |
options: | |
x: 0.0 | |
y: 0.7 | |
Starting tranche: | |
marks: | |
- type: dot | |
options: | |
x: 0.2.0 | |
y: 0.0 | |
deployments: | |
arbitrum-one-buy: | |
scenario: arb-red-blue-tranches.buy.initialized.prod | |
order: arbitrum-one-buy | |
arbitrum-one-sell: | |
scenario: arb-red-blue-tranches.sell.initialized.prod | |
order: arbitrum-one-sell | |
--- | |
#tranche-space-per-second !The amount of tranche space that is recharged per second as a normalized 18 decimal fixed point value. | |
#tranche-space-recharge-delay !The duration in seconds that no recharging occurs after a trade occurs. | |
#tranche-size-expr !The binding to get the tranche size for the current tranche space. | |
#tranche-size-base !Base tranche size is the size of the smallest tranche, denominated in output token. | |
#tranche-size-growth !The exponential growth factor of the size of each tranche, as a decimal 18 fixed point number. E.g. 1e16 is 1% output amount growth per tranche. | |
#io-ratio-expr !The binding to get the IO ratio for the current tranche space. | |
#io-ratio-base !The base IO ratio, as a decimal 18 fixed point number. This is the IO ratio at tranche space 0 and grows according to the growth factor per tranche. | |
#io-ratio-growth !The exponential growth factor of the IO ratio, as a decimal 18 fixed point number. E.g. 1e16 is 1% io-ratio growth per tranche. | |
#reference-stable !The stable token that is used as a reference for the TWAP to offer dollar equivalent conversions. | |
#reference-stable-decimals !The number of decimals of the reference stable token. | |
#reference-reserve !The token that will be used to compare against the reference stable token to calculate the TWAP for dollar equivalent conversions. | |
#reference-reserve-decimals !The number of decimals of the reserve token. | |
#twap-duration !The duration in seconds of the TWAP window for dollar equivalence conversions. | |
#twap-fee !The uniswap fee tier to use for the TWAP. | |
#min-tranche-space-diff !The minimum tranche space difference that is allowed per trade, as a decimal 18 fixed point number. Prevents dusting the strat to stop it recharging. | |
#tranche-space-snap-threshold !The threshold in tranche space to snap to the nearest tranche to avoid dust issues at the edges. | |
#initial-tranche-space !The initial tranche space when the order is first deployed. | |
#get-last-tranche !The binding to get the last tranche space and update time. | |
#set-last-tranche !The binding to set the last tranche space and update time. | |
#test-tranche-space-before !Returned by get-test-last-tranche to allow the tranche space before to be bound for testing. | |
#test-last-update-time !Returned by get-test-last-tranche to allow the last update time to be bound for testing. | |
#test-now !Returned by get-test-last-tranche to allow the current time to be bound for testing. | |
#io-ratio-multiplier !The binding to get the IO ratio multiplier. | |
#amount-is-output !Whether the amount is an output or input amount. Non-zero means output (i.e. normal orderbook behaviour), zero means input. | |
#init-key "init" | |
#tranche-space-key "tranche-space" | |
#update-time-key "update-time" | |
#plottables !The binding for additional things we want to plot during testing. | |
#uniswap-words !The subparser for the Uniswap words | |
#orderbook-subparser !The subparser for the Orderbook | |
#plottables-test | |
amount | |
io-ratio:, | |
input-amount: decimal18-mul(amount io-ratio), | |
effective-price: decimal18-inv(io-ratio); | |
#plottables-prod | |
amount | |
io-ratio:; | |
#get-last-tranche-prod | |
is-initialized: get(hash(order-hash() init-key)), | |
tranche-space-before: if( | |
is-initialized | |
get(hash(order-hash() tranche-space-key)) | |
initial-tranche-space | |
), | |
last-update-time: if( | |
is-initialized | |
get(hash(order-hash() update-time-key)) | |
block-timestamp() | |
), | |
now: block-timestamp(); | |
#tranche-space-shyness !The shyness of the liquidity in tranches, as a decimal 18 fixed point number. E.g. 9e17 is 90% shy. | |
#set-last-tranche-prod | |
tranche-space now:, | |
shy-tranche-space: if( | |
is-zero(decimal18-frac(tranche-space)) | |
decimal18-add(tranche-space tranche-space-shyness) | |
tranche-space), | |
:set(hash(order-hash() init-key) 1), | |
:set(hash(order-hash() tranche-space-key) shy-tranche-space), | |
:set(hash(order-hash() update-time-key) now); | |
/* Forward the bindings through as is to the caller. */ | |
#max-test-tranche-space !The maximum tranche space that will appear on the test chart. | |
#get-last-tranche-test | |
tranche-space-before: int-mod(test-tranche-space-before max-test-tranche-space), | |
last-update-time: test-last-update-time, | |
now: test-now; | |
#get-last-tranche-test-init | |
tranche-space-before: initial-tranche-space, | |
last-update-time: test-last-update-time, | |
now: test-now; | |
/* There's nothing to set if we're just rebinding in tests. */ | |
#set-last-tranche-test | |
tranche-space now:; | |
#exponential-growth | |
base rate t:, | |
_: decimal18-exponential-growth(base rate t); | |
#linear-growth | |
base rate t:, | |
_: decimal18-linear-growth(base rate t); | |
#constant-growth | |
base _ _:, | |
_: base; | |
#calculate-tranche | |
tranche-space-before | |
last-update-time | |
now: call<'get-last-tranche>(), | |
recharge-duration: int-saturating-sub(now int-add(last-update-time tranche-space-recharge-delay)), | |
recharged-tranche-space: decimal18-mul(int-to-decimal18(recharge-duration) tranche-space-per-second), | |
/* repeat now for easy access by callers */ | |
_: now, | |
tranche-space-now: decimal18-saturating-sub(tranche-space-before recharged-tranche-space), | |
tranche-space-available: decimal18-headroom(tranche-space-now), | |
tranche-total-size: call<'tranche-size-expr>(tranche-size-base tranche-size-growth decimal18-floor(tranche-space-now)); | |
#io-ratio-multiplier-sell | |
multiplier: uniswap-v3-twap-output-ratio(reference-stable reference-stable-decimals reference-reserve reference-reserve-decimals twap-duration 0 twap-fee); | |
#io-ratio-multiplier-buy | |
multiplier: uniswap-v3-twap-output-ratio(reference-reserve reference-reserve-decimals reference-stable reference-stable-decimals twap-duration 0 twap-fee); | |
#io-ratio-multiplier-identity | |
multiplier: 1e18; | |
#calculate-io | |
using-words-from uniswap-words orderbook-subparser | |
tranche-space-now | |
tranche-space-available | |
tranche-total-size: call<'calculate-tranche>(), | |
tranche-io-ratio: call<'io-ratio-expr>(io-ratio-base io-ratio-growth decimal18-floor(tranche-space-now)), | |
final-io-ratio: decimal18-mul(tranche-io-ratio call<'io-ratio-multiplier>()), | |
amount-available: decimal18-mul(tranche-total-size tranche-space-available), | |
amount: if(amount-is-output amount-available decimal18-div(amount-available final-io-ratio)), | |
io-ratio: final-io-ratio, | |
:call<'plottables>(amount io-ratio); | |
#handle-io | |
now | |
tranche-space-before | |
_ | |
tranche-total-size: call<'calculate-tranche>(), | |
tranche-amount-diff: if( | |
amount-is-output | |
decimal18-scale-18-dynamic(output-token-decimals() output-vault-balance-decrease()) | |
decimal18-scale-18-dynamic(input-token-decimals() input-vault-balance-increase())), | |
tranche-space-diff: decimal18-div(tranche-amount-diff tranche-total-size), | |
tranche-space-after: decimal18-add(tranche-space-before tranche-space-diff), | |
/* Snap tranche space to the nearest tranche to avoid dust issues at the edges */ | |
tranche-space-after-snapped: decimal18-snap-to-unit(tranche-space-snap-threshold tranche-space-after), | |
:ensure( | |
greater-than-or-equal-to(tranche-space-after-snapped decimal18-add(tranche-space-before min-tranche-space-diff)) | |
"Minimum trade size not met." | |
), | |
:call<'set-last-tranche>(tranche-space-after-snapped now); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment