---
# System prepended metadata

title: Portfolio - dynamic order
tags: [dynamic-order]

---

# Folio strat

## Context

We want a long running strat that can rebalance itself according to some weighting.

Ideally

- the weighting would be decoupled from the mechanics of rebalancing
- the rebalancing mechanics behave no worse than well understood AMM IL/divergence loss + fee profit principles
- the rebalancing mechanics are competetive with similar offchain mechanics in tradfi, while being non custodial
- the strat owner is free to deposit/withdraw/reweight their assets while the strat is live and it will adjust itself internally
- We can support arbitrarily many assets (potentially hundreds) in a single folio without prohibitive onchain/offchain costs or complexity
- We are free to experiment with full Rainlang expressivity on dynamic fee structures e.g.
    - increasing fees during volatility
    - increasing fees during trends ontrend, reducing off trend
    - other stateful P&L calculations to mitigate risks
    - increase fees for higher risk/underperforming tokens
    - etc.

## Comparison to AMM

This strat will behave similarly to being an LP on uniswap or balancer.

The main difference is that uniswap style AMMs offer a "swap" interface.

Specifically, the *user* defines how much input/output of a token that they are willing to accept, and then the uniswap contract calculates how much it offers/accepts *in response* to the user.

Raindex doesn't work that way because it's fundamentally always offering a specific (max) amount and price ratio. It *could* be modified to work differently, but for the sake of this document, let's continue as how it is currently coded.

Uniswap changes the ratio it offers according to the size of the swap requested by the user, to remain on its curve, raindex doesn't do that.

## Constraints

We're going to start with some of the same core constraints as an AMM, which lead to constant sum/product formulations, then apply them to a raindex context and see what falls out most naturally.

- Assume that the current vault balances are aligned with their weights and the external market valuations of the assets
- Only offer incremental trades, it should never be possible to completely sell out of an asset unless the weight is 0 (uni v2 logic, not concentrated liq)
- Offer trades such that only one of two outcomes is possible:
    - The assumption that the vault balances were aligned is incorrect, and the trade brings the balances closer to alignment, meeting the goal of the owner to maintain a weighted portfolio
    - The trade moves the balances further from alignment, which deepens an arbitrage opportunity back to alignment, and taking that arbitrage opportunity to restore alignment favours the strat owner (profit)

The last point should be satisfied if we can show the hysteresis in any path away from any given starting position, back to the original position, benefits the owner. AMMs have largely proven out the effectiveness of this principle, even if the external market is moving relative to the strat, thus causing "alignment" to be a transient idea at best.

### Base case

The base case is that we offer some fixed percentage x% of one of the tokens in our folio, for some amount of another token in our folio.

We assume everything is equally weighted, and that the amount we offer will be taken completely by the counterparty.

In this case, we price the assets according to their current vault balances. E.g. if we have 1000 of TKN A and 100 of TKN B at the start then we assume that the economic value of 1 B is 10x A.

Let's say the external market prices 1 B at 9.5x A rather than 10x, then we have a 5% discrepency between our vaults and the market. I.e. we currently have too much A and not enough B, as we should only have 950 A if we have 100 B, but we have 1000.

The issue is that from the perspective of the strat, there's no such thing as a knowable external market price (assuming we don't want to rely on oracles). We don't and can't know how much of A to sell in the above example to perfectly rebalance.

We can define a step size, such as 1% and always offer *both* 1% of our supply of A and 1% of our B supply.

In the "correct" direction we'd be giving up 10 A for our first trade, and need to know how much B to ask for in return.

If we considered our starting positions balanced then a fair price in that position is equal to the ratio of the vault balances. I.e. we'd consider 1 B a fair trade for 10 A.

This is an iterative process, so the trades would look like

| A start | B start | price | A sold (1%) | B bought |
|   --    |   --    |  --   |   --        |    --    |
| 1000    | 100     | 10    | 10          | 1        |
| 990     | 101     | 9.80198019802 | 9.9 | 1.01 |
| 980.1   | 102.01  | 9.60788158024 | 9.801 | 1.0201 |
| 970.299 | 103.0301 | *9.41762649944* | - | - |

Note that the last price exceeds the arbitrageable market of 9.5 so it cannot actually happen.

This means the final state from the iterative approach is 970.299 A and 103.0301 B.

#### Comparison with constant product

We can compare the above iteration to a constant product AMM moving to the same 970.299 endpoint for A.
```
x*y = k

1000 * 100 = 100000
z = 100000 / 970.299
= 103.061015213
```
We can see that the AMM actually would enforce slightly *more* B over the bonding curve, based on the simply iterations. As the AMM result is path independent, there's no further calculation required.

This implies that the round trip in our iterative process is likely to bleed tokens from the vault, as the constant product bonding curve without fees has zero hysteresis.

#### Bad hysteresis

Starting from 970.299 A and 103.0301 B we can move back towards our starting position in 1% increments, this time selling B for A.

| B start | A start | price | B sold (1%) | A bought |
| -- | -- | -- | -- | -- |
| 103.0301 | 970.299 | 0.10618386703 | 1.030301 | 9.70299000044 |
| 101.999799 | 980.00199 | 0.1040812162 | 1.01999799 | 9.80001990023 |
| 100.97980101 | 989.8020099 | 0.10202020202 | 1.0097980101 | 9.89802009902 |
| 99.9700029999 | 999.700029999 | *0.1* | - | - |

We indeed see bad hysteresis on the round trip, by the time the price returns to 0.1 which is where we started, we've bled some of both A and B. It's a very small amount, but the number of times that this round trip can be repeated is unbounded, over many market fluctuations, the folio would lose significant value, trending towards being completely drained.

#### Removing hysteresis

The simplest special case to consider is a single 1% increment from A to B and back to A again. Then we can consider if/how this generalises to all paths.

If we start with some vault balances `Va` and `Vb` and we are selling `A` then

- the output increment can be `Va*x` where `x` would be `0.01` for a 1% sale.
- the base IO ratio `IO` can still be `Vb/Va` so for `Vb=100` and `Va=1000` then `IO=1/10` 
- we can scale the base IO ratio up by `1/(1-x)` to compensate for the round trip, e.g. if `x=0.01` then the corrected IO ratio `IOc` = `(1/10)(1/0.99)` = `1/9.9`

Let's check the round trip for a single step:
```
Start A = 1000
Start B = 100
Output 1% A = 10
Base IO = 100/1000 = 1/10
Corrected IO = (1/10)(1/0.99) = 1/9.9
Input B = 10/9.9 = 1.0101010101
Final A = 990
Final B = 101.010101..
```
Then round:
```
Start A = 990
Start B = 100+(10/9.9) = 101.01010101
Output 1% B = (100+(10/9.9)) * 0.01 = 1 + 0.1/9.9 = 1.0101010101
Base IO = 990/(100+(10/9.9)) = 9.801
Corrected IO = (990/(100+(10/9.9)))(1/0.99) = 9.9
Input A = (1 + 0.1/9.9) * (990/(100+(10/9.9)))(1/0.99) = 10
```
The correction factor causes the round trip to no longer have any hysteresis at all.

By induction, this generalises to whole step increments because each step in either direction is exactly offset to returning to that position.

We can compare again to the constant product `x*y=k` for this step.
```
1000*100=100000
B=100000/990
=>B=101.01010101
```
So here we see that we've simply reforumlated the basic familiar constant product curve in terms of discrete dynamic raindex limit orders.

#### Generalizing to partial clears

In Raindex the amount returned by the strat in each evaluation is merely the _maximum_ that can clear.

We need to consider the case that less than the full allowable amount from the limit order is cleared in each iteration.

Generally this is safe because the correction factor `1/(1-x)` increases monotonically with the trade amount `x`. This means that in the case of a partial clear, if we still set the same price as the full clear, we're effectively overcompensating rather than undercompensating for the round trip.

For example, consider the same parameters as above, but while we offer 1% of A, which is 10 absolute units, the counterparty only takes 5 units (0.5%).

Step 1:

```
Start A = 1000
Start B = 100
Output 1% A = 10
Counterparty takes only = 5
Base IO = 100/1000 = 1/10
Corrected IO = (1/10)(1/0.99) = 1/9.9
Input B = 5/9.9 = 0.50505050505
Final A = 995
Final B = 100.50505050505
```

Round Trip (also buying back only what we previously sold):

```
Start A = 995
Start B = 100+(5/9.9) = 100.50505050505
Output 1% B = (100+(5/9.9)) * 0.01 = 1 + 0.05/9.9 = 1.00505050505
Roundtrip cap B = 0.50505050505
Base IO = 995/(100+(5/9.9)) = 9.9
Corrected IO = (995/(100+(5/9.9)))(1/0.99) = 10
Input A = (5/9.9) * (995/(100+(5/9.9)))(1/0.99) = 5.05050505051
Final A = 1000.05050505
Final B = 100
```

Here we can see the round trip results in an excess of A relative to our starting position and all our B is preserved. Most likely in a real scenario we wouldn't exactly round trip, as subsequent trades would be full or differently partial clears, but the basic invariants all still hold.