diff --git a/.github/workflows/quality_check.yml b/.github/workflows/quality_check.yml
index 24b201724..0082c4434 100644
--- a/.github/workflows/quality_check.yml
+++ b/.github/workflows/quality_check.yml
@@ -10,6 +10,7 @@ name: "Quality checks"
- cosmicelevator
- palazzo
- colosseo
+ - colosseo_II
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -73,7 +74,7 @@ jobs:
- name: "Run Check file names"
run: |
- npx github:vegaprotocol/approbation check-filenames --specs="{./non-protocol-specs/**/*.md,./protocol/**/*.md,./protocol/**/*.ipynb}"
+ npx github:vegaprotocol/approbation check-filenames --specs="{./non-protocol-specs/**/*.md,./protocol/**/*.md}"
- name: "Run Check Features"
run: |
diff --git a/non-protocol-specs/0013-NP-POSE-position-estimate.md b/non-protocol-specs/0013-NP-POSE-position-estimate.md
index 22bb45fe1..28af3027d 100644
--- a/non-protocol-specs/0013-NP-POSE-position-estimate.md
+++ b/non-protocol-specs/0013-NP-POSE-position-estimate.md
@@ -46,6 +46,10 @@ The position estimate request has an additional `include_collateral_increase_in_
The endpoint request contains additional optional argument `scale_liquidation_price_to_market_decimals`. When set to `false` the liquidation price estimates are scaled to asset decimal places, when set to `true` these estimates are scaled to market decimal places.
+### Price cap
+
+When a price cap is specified it should be assumed that the estimate is to be provided for a [capped futures](./../protocol/0016-PFUT-product_builtin_future.md#1-product-parameters) market. Margin levels as well collateral increase estimate should be as per fully-collateralised margin [spec](./../protocol/0019-MCAL-margin_calculator.md#fully-collateralised). If in addition to a price cap the `fully_collateralised` flag is set to `true` then the liquidation price estimate should be `nil`.
+
## Acceptance criteria
1. In isolated margin mode the request with `0` open volume and one or more limit orders specified results in a non-zero order margin in the margin level estimate and margin mode correctly representing isolated margin mode. (0013-NP-POSE-001)
diff --git a/non-protocol-specs/0014-NP-VAMM-bounds-estimations.md b/non-protocol-specs/0014-NP-VAMM-bounds-estimations.md
new file mode 100644
index 000000000..c9fcd02ce
--- /dev/null
+++ b/non-protocol-specs/0014-NP-VAMM-bounds-estimations.md
@@ -0,0 +1,125 @@
+
+# vAMM Bounds Estimation Calculator
+
+
+## Summary
+
+The protocol contains the ability for users to create vAMMs which will automatically trade and manage a position on a given market with no further interaction from the user. These are configured through a few different parameters, which uniquely specify the behaviour and volume profile of the vAMM as the price of the market moves, however are not always immediately obvious and intuitive for a user given the set of inputs. As such, it is necessary to provide an API giving canonical conversions between these values.
+
+The API should take a pool's specification parameters and output various metrics useful for a user deciding on vAMM configuration. Concretely, the API should take the parameters:
+
+ 1. Base Price
+ 1. Upper Price
+ 1. Lower Price
+ 1. Leverage At Upper Price
+ 1. Leverage At Lower Price
+ 1. Commitment Amount
+ 1. Market ID
+ 1. Optional: Party Key
+
+And then return the metrics:
+
+ 1. Loss on Commitment at Upper Bound
+ 1. Loss on Commitment at Lower Bound
+ 1. Position Size at Upper Bound
+ 1. Position Size at Lower Bound
+ 1. Liquidation Price at Upper Bound
+ 1. Liquidation Price at Lower Bound
+ 1. If `party key` is specified:
+ 1. Approximate trade size on amendment (immediate)
+ 1. Approximate crystallised loss from position rebalancing (immediate)
+ 1. Change in Size at Upper Bound vs current specification
+ 1. Change in Size at Lower Bound vs current specification
+ 1. Change in Loss at Upper Bound vs current specification
+ 1. Change in Loss at Lower Bound vs current specification
+
+
+## Calculations
+
+There are a few values which are generally useful to calculate many of the above required outputs, so these will be calculated first.
+
+Starting with the average entry price, this value for a given range is equal no matter the absolute volume committed, so can be calculated without reference to the bond or leverage at bounds values. This is taken from the unit liquidity, $L_u$
+
+$$
+L_u = \frac{\sqrt{p_u} \sqrt{p_l}}{\sqrt{p_u} - \sqrt{p_l}} ,
+$$
+
+where $p_u$ is the price at the upper end of the range (`upper price` for the upper range and `base price` for the lower range) and $p_l$ is the corresponding lower price for the range. With this, the average entry price can be found to be
+
+$$
+p_a = L_u \sqrt{p_u} (1 - \frac{L_u}{L_u + \sqrt{p_u}}) ,
+$$
+
+where $p_a$ is the average execution price across the range and other values are as defined above. Finally, the risk factor which will be used for calculating leverage at bounds
+
+$$
+r_f = \min(l_b, \frac{1}{ (f_s + f_l) \cdotp f_i}) ,
+$$
+
+where $l_b$ is the sided value `leverage_at_bounds` (`upper ratio` if the upper band is being considered and `lower ratio` if the lower band is), $f_s$ is the market's sided risk factor (different for long and short positions), $f_l$ is the market's linear slippage component and $f_i$ is the market's initial margin factor. This will result in two separate values to use for $r_f$, one for the lower range and one for the upper range.
+
+
+### Position at Bounds
+
+With this, the volumes required to trade to the bounds of the ranges are:
+
+$$
+P_{v_l} = \frac{r_f b}{p_l (1 - r_f) + r_f p_a} ,
+$$
+$$
+P_{v_u} = \frac{r_f b}{p_u (1 + r_f) - r_f p_a} ,
+$$
+
+where $r_f$ is the `short` factor for the upper range and the `long` factor for the lower range, `b` is the current total balance of the vAMM across all accounts, $p_u$ and $p_l$ are as defined above, $P_{v_l}$ is the theoretical volume and the bottom of the lower bound and $P_{v_u}$ is the (absolute value of the) theoretical volume at the top of the upper bound.
+
+
+### Loss on Commitment at Bound
+
+For the loss on commitment at bound, one needs to use the average entry price, bound price and the position at the bounds
+
+$$
+l_c = |(p_a - p_b) \cdot P_b |,
+$$
+
+where $P_b$ is the position at bounds (Either $P_{v_l}$ or $P_{v_u}$), $p_a$ is the average entry price and $p_b$ is the price at the corresponding outer bound. Note that this is an absolute value of loss, so outstanding balance at bounds would be `initial balance - $L_c$`.
+
+
+### Liquidation Prices
+
+Using a similar methodology to the standard estimations for liquidation price and the above calculated values, an estimate for liquidation prices above and below the range can be obtained with
+
+$$
+p_{liq} = \frac{b - l_c - P_b \cdot p_b}{|P_b| \cdot (f_l + m_r) - P_b} ,
+$$
+
+where $p_{liq}$ is the liquidation price (above or below the specified ranges), $b$ is the original commitment balance, $l_c$ is the loss on commitment at the relevant bound, $P_b$ is the position at the relevant bound, $p_b$ is the price at the bound, $f_l$ is the linear slippage factor for the market and $m_r$ is the market's long or short risk factor (short for the upper price bound as the position will be negative and long for the lower).
+
+
+## Specified Key
+
+When a key is specified, the existence of any current vAMM should be checked and, if one exists, the above values also calculated for it and populated in the requisite areas of the response to allow easy comparison.
+
+
+## Acceptance criteria
+
+- For a request specifying (base, upper, lower, leverage_upper, leverage_lower, commitment, short risk factor, long risk factor, market slippage factor) as (1000, 1100, 900, 2, 2, 100, 0.01, 0.01, 0) the response is (0014-NP-VAMM-001):
+
+ 1. Loss on Commitment at Upper Bound: 8.515
+ 1. Loss on Commitment at Lower Bound: 9.762
+ 1. Position Size at Upper Bound: -0.166
+ 1. Position Size at Lower Bound: 0.201
+ 1. Liquidation Price at Upper Bound: 1633.663
+ 1. Liquidation Price at Lower Bound: 454.545
+
+
+- For a request specifying (base, upper, lower, leverage_upper, leverage_lower, commitment, short risk factor, long risk factor, market slippage factor) as (1000, 1300, 900, 1, 5, 100, 0.01, 0.01, 0) the response is (0014-NP-VAMM-004):
+
+ 1. Loss on Commitment at Upper Bound: 10.948
+ 1. Loss on Commitment at Lower Bound: 21.289
+ 1. Position Size at Upper Bound: -0.069
+ 1. Position Size at Lower Bound: 0.437
+ 1. Liquidation Price at Upper Bound: 2574.257
+ 1. Liquidation Price at Lower Bound: 727.273
+
+- A request with an empty upper *or* lower bound price is valid and will return the metrics for the bound which *was* specified with the metrics for the unspecified bound empty. (0014-NP-VAMM-002)
+- A request with an empty upper *and* lower bound is invalid and receives an error code back. (0014-NP-VAMM-003)
diff --git a/non-protocol-specs/0015-NP-OBES-order-book-estimations.md b/non-protocol-specs/0015-NP-OBES-order-book-estimations.md
new file mode 100644
index 000000000..84f0fb5e1
--- /dev/null
+++ b/non-protocol-specs/0015-NP-OBES-order-book-estimations.md
@@ -0,0 +1,33 @@
+
+# AMM Order Book Levels Estimator
+
+Whilst generating a limit order book shape, i.e. the prices and associated volumes available at each price, is trivially achievable by simply aggregating the various component limit orders, this is harder to achieve for the active AMMs on a market. This is due to the fact that the volumes at which they would trade each price level within their range is not immediately obtainable without some calculations. Whilst these calculations are not heavy, and are routinely performed as part of trading, expanding out the entire range of an AMM with a large range may be prohibitive both in time taken (when updating frequently) and also in storage space, as there may be a non-zero volume at every possible tick level over a large range, necessitating a large array in which to store it.
+
+In order to alleviate these issues, a heuristic should be used to govern how these AMMs are expanded out into levels for display, both in terms of how frequently these levels are updated and how far away from the mid price levels are calculated at each price level vs a larger gap. This spec aims to codify that heuristic.
+
+## Update Frequency
+
+The largest improvement to processing requirements from update frequency heuristics comes from the fact that an AMM will only update it's price levels when it is either amended or someone trades with it. Therefore the calculations generating a curve of volumes from AMM prices should be cached between these points and only updated when an AMM is amended or when a trade with an AMM occurs. This can be further improved if all curves from individual AMMs are stored separately alongside an aggregate volume in which case only the impacted AMM curve can be updated whilst all others remain static.
+
+## Update Depth
+
+The second consideration is the depth to which markets should be expanded at every individual price level, and what should be done after this point. As this information is purely used for informational purposes these options can be set (including some sensible defaults) at a per-datanode level rather than mandating a single set across the network. These values should be applicable across markets, so are defined in percentage terms:
+
+ 1. **amm_full_expansion_percentage**: This is the percentage difference above and below the market mid price to which the AMMs should be fully expanded at each price level.
+ 1. **amm_estimate_step_percentage**: Once above (or below) these bounds the calculation should only occur at larger steps. These step sizes are governed in percentage terms again (and are in reference to a percentage of the mid price).
+ 1. **amm_max_estimated_steps**: The maximum number of estimated steps to return. Once this number have been calculated nothing further is returned.
+
+The calculation of the order book shape should combine these three values by using the `volume to trade between two price levels` calculations utilised by the core engine to iteratively calculate the volume quoted by a given AMM at each price level outwards from the centre. (Note that AMMs which were outside of range at the initial mid price may come into range during the iteration and should be included if so.) Once the percentage full expansion bounds have been reached the gaps between prices become those specified by the step percentage value, however the calculation remains the same beyond that.
+
+
+## Acceptance criteria
+
+- With `amm_full_expansion_percentage` set to `5%`, `amm_estimate_step_percentage` set to `1%`and `amm_max_estimated_steps` set to `10`, when the mid-price is 100 then the order book expansion should return (0015-NP-OBES-001):
+ - Volume levels at every valid tick between `95` and `105`
+ - Volume levels outside that at every `1` increment from `106` to `115` and `94` to `85`
+ - No volume levels above `115` or below `85`
+
+- With `amm_full_expansion_percentage` set to `3%`, `amm_estimate_step_percentage` set to `5%`and `amm_max_estimated_steps` set to `2`, when the mid-price is 100 then the order book expansion should return (0015-NP-OBES-002):
+ - Volume levels at every valid tick between `97` and `103`
+ - Volume levels outside that at every `1` increment from `108` to `116` and `92` to `87`
+ - No volume levels above `116` or below `87`
diff --git a/protocol/0001-MKTF-market_framework.md b/protocol/0001-MKTF-market_framework.md
index 6579c5bcd..3d32d838a 100644
--- a/protocol/0001-MKTF-market_framework.md
+++ b/protocol/0001-MKTF-market_framework.md
@@ -33,7 +33,8 @@ Data:
- If this is negative e.g. -3 this means that the smallest order and position is of size 1000.
- Accepted values are `-6,...,-1,0,1,2,...,6`.
- **Tick size**: the minimum change in quote price for the market. Order prices and offsets for pegged orders must be given as an exact multiple of the tick size. For example if the tick size is 0.02 USD. then a price of 100.02 USD is acceptable and a price of 100.03 USD is not. The tick size of a market can be updated through governance. Note, the tick size should be specified in terms of the market decimals, e.g. for a scaled tick size of `0.02` (USDT) in a market using `5` decimal places, the tick size would be set to `2000`.
-- **Liquidation strategy**: A field specifying the liquidation strategy for the market. Please refer to [0012-POSR-position_resolution](./0012-POSR-position_resolution.md#managing-networks-position) for supported strategies.
+- **Liquidation strategy**: A field specifying the liquidation strategy for the market. Please refer to [0012-POSR-position_resolution](./0012-POSR-position_resolution.md#managing-networks-position) for supported strategies.
+- **Transaction Prioritisation**: A boolean, whether to enable [transaction prioritisation](./0092-TRTO-trading_transaction_ordering.md).
Note: it is agreed that initially the integer representation of the full precision of both order and positions can be required to fit into an int64, so this means that the largest position/order size possible reduces by a factor of ten for every extra decimal place used. This also means that, for instance, it would not be possible to create a `BTCUSD` market that allows order/position sizes equivalent to 1 sat.
diff --git a/protocol/0004-AMND-amends.md b/protocol/0004-AMND-amends.md
index 4e618571b..fb8434cc9 100644
--- a/protocol/0004-AMND-amends.md
+++ b/protocol/0004-AMND-amends.md
@@ -27,6 +27,7 @@
- Attempting to alter details on a cancelled order will cause the amend to be rejected (0004-AMND-018). For product spot: (0004-AMND-046)
- Attempting to alter details on an expired order will cause the amend to be rejected (0004-AMND-019). For product spot: (0004-AMND-047)
- Amending expiry time of an active GTT order to a past time whilst also simultaneously amending the price of the order will cause the order to immediately expire with the order details updated to reflect the order details requiring amendment (0004-AMND-029). For product spot: (0004-AMND-048)
+- An amendment which would result in the margin requirement for the order being smaller than `spam.order.minimalMarginQuantumMultiple` should fail with the order remaining unchanged (0004-AMND-061)
For a party with no position on a given market:
diff --git a/protocol/0012-POSR-position_resolution.md b/protocol/0012-POSR-position_resolution.md
index 8be846ea7..009e1699d 100644
--- a/protocol/0012-POSR-position_resolution.md
+++ b/protocol/0012-POSR-position_resolution.md
@@ -111,7 +111,7 @@ Currently only one liquidation strategy is supported and its defined by the foll
- `disposal time step` (min: `1s`, max: `1h`): network attempts to unload its position in a given market every time it goes out of auction and then every `disposal time step` seconds as long as market is not in auction mode and while the network's position is not equal to `0`,
- `disposal fraction` (min: `0.01`, max: `1`): fraction of network's current open volume that it will try to reduce in a single disposal attempt,
- `full disposal size` (min: `0`, max: `max int`): once net absolute value of network's open volume is at or below that value, the network will attempt to dispose the remaining amount in one go,
-- `disposal slippage range` (decimal `>0` with a value of `0.1` interpreted as `10%`, existing markets to default to their current SLA range). Just like [SLA range](./0044-LIME-lp_mechanics.md) these are taken from `mid_price` during continuous trading or indicative uncrossing price during auctions so the lower bound becomes `max(0,price x (1-range)` and upper bound `price x (1+range)`.
+- `disposal slippage range` (decimal `>0` with default of `0.1` which is interpreted as `10%`, existing markets to default to their current SLA range). Just like [SLA range](./0044-LIME-lp_mechanics.md) these are taken from `mid_price` during continuous trading or indicative uncrossing price during auctions so the lower bound becomes `max(0,price x (1-range)` and upper bound `price x (1+range)`.
- `max fraction of book side within bounds consumed` (min: `0`, max: `1`): once the network chooses the size of its order (`s_candidate`) the effective size will be calculated as `s_effective=min(m*N, s_candidate)`, where `N` is the sum of volume (on the side of the book with which the network's order will be matching) that falls within the range implied by the `disposal slippage range` and `m` is the `max fraction of book side within liquidity bounds consumed`.
When vAMMs are implemented and provide liquidity then volume implied by vAMMs that lies within the relevant range must be included in the calculation.
diff --git a/protocol/0014-ORDT-order_types.md b/protocol/0014-ORDT-order_types.md
index 582337dc9..4f814320a 100644
--- a/protocol/0014-ORDT-order_types.md
+++ b/protocol/0014-ORDT-order_types.md
@@ -106,6 +106,9 @@ Therefore the trigger level of a stop order moves with the market allowing the t
- In markets which allow leverage. If the trader's position size moves to zero exactly, and they have no open orders, all stop orders will be cancelled.
+ - In spot markets, as their is no concept of a position, the trader will need to lock the funds required to execute the wrapped order in the holding account even before the stop order is triggered. Refer to section [spot trading](./0080-SPOT-product_builtin_spot.md#7-trading) for the calculation of the required funds.
+
+
### Iceberg / transparent iceberg orders
On centralised exchanges, icebergs are a type of order that enables a trader to make an order with a relatively small visible "display quantity" and a larger hidden total size.
@@ -426,8 +429,8 @@ In Spot market, for multiple iceberg orders submitted as a batch of orders with
## Spot
-- A wrapped buy order will be rejected when triggered if the party doesn't have enough of the required quote asset to cover the order. (0014-ORDT-163)
-- A wrapped sell order will be rejected when triggered if the party doesn't have enough of the required base asset to cover the order. (0014-ORDT-164)
+- A trader submitting a stop order wrapping a buy limit order will have the funds required to execute that order locked in the relevant holding account for the quote asset. (0014-ORDT-163)
+- A trader submitting a stop order wrapping a sell limit order will have the funds required to execute that order locked in the relevant holding account for the base asset (0014-ORDT-164)
### See also
diff --git a/protocol/0016-PFUT-product_builtin_future.md b/protocol/0016-PFUT-product_builtin_future.md
index cf062b928..e1b4444ac 100644
--- a/protocol/0016-PFUT-product_builtin_future.md
+++ b/protocol/0016-PFUT-product_builtin_future.md
@@ -14,6 +14,14 @@ Futures are a simple "delta one" product and the first product supported by Vega
Validation: none required as these are validated by the asset and data source frameworks.
+Optional parameters:
+
+1. `fully_collateralised`: when set to `true` margins will be calculated so that orders and positions are [fully-collateralised](./0019-MCAL-margin_calculator.md#fully-collateralised). This flag can only be specified at market creation time. It cannot be modified on an existing market.
+1. `max_price`: specifies the price cap for the market, an integer understood in the context of market decimal places,
+1. `binary_settlement`: if set to `true` settlement price other than `0` or `max_price` will be ignored.
+
+Validation: `max_price` > 0.
+
## 2. Settlement assets
1. Returns `[cash_settled_future.settlement_asset]`.
@@ -55,6 +63,37 @@ cash_settled_future.settlement_data(event) {
}
```
+## 2. Additional considerations around optional parameters
+
+Optional parameters allow creating capped futures (all prices including settlement price must be in the range `[0, max_price]`) or binary options (all intermediate prices must be in the range `[0, max_price]`, settlement price must be either `0` or `max_price`) markets.
+
+### 2.1 Order price validation
+
+If `max_price` is specified the order prices should be validated so that the maximum price of any order is `max_price`. Peg orders should be validated so that if the resulting price would be higher at any point it gets temporarily capped at `max_price`.
+
+### 2.2 Mark price validation
+
+If `max_price` is specified, mark price candidates greater than `max_price` should be ignored and [mark-to-market settlement](./0003-MTMK-mark_to_market_settlement.md) should not be carried out until a mark price within the `[0, max_price]` range arrives.
+
+### 2.3 Settlement price validation
+
+If `max_price` is specified:
+
+- When `binary_settlement` parameter is set to `false` any value `0 <= settlement_price <= max_price` should be accepted as a settlement price.
+- When `binary_settlement` parameter is set to `true` only `settlement_price=0` or `settlement_price=max_price` should be accepted.
+- Any other values get ignored and market does not settle, instead it still waits for subsequent values from the settlement oracle until a value which passes the above conditions arrives.
+
+## 3. Binary options
+
+Please note that selecting a future product with `max_price` specified and `binary_settlement` flag set to `true` allows representing binary options markets.
+
+Validation:
+
+- `fully-collateralised mode` is only allowed when there is a `max_price` specified
+- `binary_settlement` is only allowed when there is a `max_price` specified
+
+Note that these fields are not specified on the market update types. Once a capped future is created and set to fully collateralised (and/or binary settlement), it cannot be changed.
+
## Acceptance Criteria
1. Create a Cash Settled Future with trading termination triggered by a date/time based data source (0016-PFUT-001)
@@ -69,3 +108,22 @@ cash_settled_future.settlement_data(event) {
1. Lifecycle events are processed atomically as soon as they are triggered, i.e. the above condition always holds even for two or more transactions arriving at effectively the same time - only the transaction that is sequenced first triggers final settlement (0016-PFUT-010)
1. Once a market is finally settled, the mark price is equal to the settlement data and this is exposed on event bus and market data APIs (0016-PFUT-011)
1. Assure [settment-at-expiry.feature](https://github.com/vegaprotocol/vega/blob/develop/core/integration/features/verified/0002-STTL-settlement_at_expiry.feature) executes correctly (0016-PFUT-012)
+
+Optional parameters:
+
+1. Attempt to specify a `max_price` of `0` fails. (0016-PFUT-013)
+1. When `max_price` is specified, an order with a `price > max_price` gets rejected. (0016-PFUT-014)
+1. When `max_price` is specified and the reference of a pegged sell order moves so that the implied order price is higher than `max_price` the implied order price gets capped at. `max_price` (0016-PFUT-015)
+1. When `max_price` is specified and market is setup to use oracle based mark price and the value received from oracle is less than `max_price` then it gets used as is and mark-to-market flows are calculated according to that price. (0016-PFUT-016)
+1. When `max_price` is specified and the market is setup to use oracle based mark price and the value received from oracle is greater than `max_price` then it gets ignored and mark-to-market settlement doesn't occur until a mark price candidate within the `[0, max_price]` range arrives. (0016-PFUT-017)
+1. When `max_price` is specified and `binary_settlement` flag is set to `false`, and the final settlement price candidate received from the oracle is less than or equal to `max_price` then it gets used as is and the final cashflows are calculated according to that price. (0016-PFUT-018)
+1. When `max_price` is specified and the final settlement price candidate received from the oracle is greater than `max_price` the value gets ignored, next a value equal to `max_price` comes in from the settlement oracle and market settles correctly. The market behaves in this way irrespective of how `binary_settlement` flag is set. (0016-PFUT-019)
+1. When `max_price` is specified, the `binary_settlement` flag is set to `true` and the final settlement price candidate received from the oracle is greater than `0` and less than `max_price` the value gets ignored, next a value of `0` comes in from the settlement oracle and market settles correctly. (0016-PFUT-020)
+1. When `max_price` is specified and the market is ran in a fully-collateralised mode and it has parties with open positions settling it at a price of `max_price` works correctly and the sum of all final settlement cashflows equals 0 (loss socialisation does not happen). Assuming general account balances of all parties were `0` after opening the positions and all of their funds were in the margin accounts: long parties end up with balances equal to `position size * max_price` and short parties end up with `0` balances. (0016-PFUT-021)
+1. When `max_price` is specified and the market is ran in a fully-collateralised mode and it has parties with open positions settling it at a price of `0` works correctly and the sum of all final settlement cashflows equals 0 (loss socialisation does not happen). Assuming general account balances of all parties were `0` after opening the positions and all of their funds were in the margin accounts: short parties end up with balances equal to `abs(position size) * max_price` and long parties end up with `0` balances. (0016-PFUT-022)
+1. When `max_price` is specified and the market is ran in a fully-collateralised mode and a party opens a long position at a `max_price`, no closeout happens when mark to market settlement is carried out at a price of `0`. (0016-PFUT-023)
+1. When `max_price` is specified and the market is ran in a fully-collateralised mode and a party opens a short position at a price of `0`, no closeout happens when mark to market settlement is carried out at a `max_price`. (0016-PFUT-024)
+1. Futures market can be created without specifying any of the [optional paramters](#1-product-parameters). (0016-PFUT-025)
+1. Futures market can be created with a with [hardcoded risk factors](./0018-RSKM-quant_risk_models.ipynb). (0016-PFUT-026)
+1. Updating a risk model on a futures market with regular risk model to with [hardcoded risk factors](./0018-RSKM-quant_risk_models.ipynb) results in recalculation of all margin levels in line with hardcoded values and collateral search/release where appropriate. (0016-PFUT-027)
+1. Updating a risk model on a futures market with [hardcoded risk factors](./0018-RSKM-quant_risk_models.ipynb) to a regular risk model results in recalculation of all margin levels in line with the specified risk model (hardcoded value are no longer used) and collateral search/release where appropriate. (0016-PFUT-028)
diff --git a/protocol/0018-RSKM-quant_risk_models.ipynb b/protocol/0018-RSKM-quant_risk_models.ipynb
index 09a2381b1..ae8b7633c 100644
--- a/protocol/0018-RSKM-quant_risk_models.ipynb
+++ b/protocol/0018-RSKM-quant_risk_models.ipynb
@@ -1,153 +1,170 @@
{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Quantitative risk models\n",
- "\n",
- "\n",
- "## Summary\n",
- "Vega can use different risk models as for different underlying assets and different derivatives one has to choose an appropriate model. Every quantitative risk model used by Vega needs to be able to provide the following.\n",
- "\n",
- "1. Risk factors, currently for futures but for other products as Vega develops. The risk factor *linearizes* the risk measure used for margin requirements. See `0019-MCAL-margin_calculator.md` for details of how these are used.\n",
- "1. Given a probability level $\\alpha \\in (0.9,1)$, time horizon $\\tau > 0$ and current price $S$ return the $\\Delta^-$ and $\\Delta^+$ such that \n",
- "$$\n",
- "\\mathbb P(S-\\Delta^- \\leq S_\\tau \\leq S+\\Delta^+) \\geq 1-\\alpha\\,.\n",
- "$$\n",
- "See the price monitoring spec for details of how these are used.\n",
- "1. Given current price $S$, time horizon $\\tau > 0$, a list of price levels $S < S_1 < S_2 < \\cdots < S_N^+$ return the probability of trading at each of these price levels defined below. See \"Probability weighted liquidity measure\" spec for how these are used.\n",
- "1. Calibration outputs (not required for first release of Mainnet, model parameters result from governance vote per market).\n",
- "\n",
- "The “quant risk suite” with Vega consists a _quantitative risk model_, _margin calculator_ and _calibrator_.\n",
- "\n",
- "A market parameter specifies which _quantitative risk model_ is used for a given risk universe (market, see Section 3.5 of the Vega white paper).\n",
- "\n",
- "\n",
- "## Reference-level explanation\n",
- "\n",
- "### _Quantitative risk model_\n",
- "The relevant quantitative risk model for a market is specified on a tradeable instrument. The role of the quantitative risk model is to calculate **risk factors** which are used in the **_margin calculator_** (see below)\n",
- "as well as probabilities of various price moves used for price monitoring and measuring liquidity. \n",
- "\n",
- "To achieve this it utilises the quantitative maths library.\n",
- "\n",
- "The quantitative risk model may take one or more of the following as inputs:\n",
- "* risk parameters (e.g. volatility)\n",
- "* product parameters (e.g. minimum contract size)\n",
- "* order book data (full current order book with volume aggregated at price levels)\n",
- "* position data (for each trader)\n",
- "* event data (e.g. passage of time) (Not for Futures / Nicenet )\n",
- "\n",
- "The quantitative risk model returns two risk factors:\n",
- "\n",
- "1. Rounded Long position risk factor\n",
- "1. Rounded Short position risk factor\n",
- "\n",
- "The quantitative risk model is able to utilise a relevant method from the quant math library to perform the calculations.\n",
- "\n",
- "The quant math library calculates:\n",
- "1. Long position risk factor\n",
- "1. Short position risk factor\n",
- "1. Guaranteed accuracy (applicable to both risk factors)\n",
- "1. The max move up/down ($\\Delta^-$ and $\\Delta^+$ defined in the [Summary](#Summary)) given current price level and a projection horizon such that the resulting prices are within a specified probability level.\n",
- "1. Probability of trading at a given price level (see below).\n",
- "\n",
- "#### Calculating the probability of trading\n",
- "This is well defined only if we have best bid $S_{\\text{best bid}}$ and best ask $S_{\\text{best ask}}$ on the order book. \n",
- "The rest of information comes from the quant risk model that has the probability density of price distribution at time horizon $\\tau > 0$ i.e. we have \n",
- "$$\n",
- "f(x;S) = \\mathbb P(S_\\tau \\in [x, x+dx) | S)\\,, \n",
- "$$\n",
- "where $S$ is current price level and $S_\\tau$ is a future price, after time $\\tau > 0$.\n",
- "\n",
- "Given a price level $S$, time horizon $\\tau > 0$, a list of price levels $S < S_1 < S_2 < \\cdots < S_{N^+}$ or a list of price levels $S > S_1 > S_2 > \\cdots > S_{N^-}$ return the probability of trading $p(S_i)$ at each of these as defined below. \n",
- "\n",
- "1. If $S_i \\in [S_{\\text{best bid}}, S_{\\text{best ask}}]$ for $i=1,\\ldots,N^+$ or $i=1,\\ldots,N^-$ return $p(S_i) = 1$. \n",
- "That is, between best bid and best ask the probability of trading is one. \n",
- "2. If $S_i > S_{\\text{best ask}}$ for $i=1,\\ldots,N^+$ then return \n",
- "$$\n",
- "p(S_i) = \\int_{S_i}^{S_{max}} f(x;S_{\\text{best ask}})\\,dx\n",
- "$$\n",
- "3. If $S_i < S_{\\text{best bid}}$ for $ =1,\\ldots,N^-$ then return\n",
- "$$\n",
- "p(S_i) = \\int_{S_{min}}^{S_i} f(x;S_{\\text{best bid}})\\,dx\\,.\n",
- "$$\n",
- "\n",
- "#### When to not update risk factors\n",
- "\n",
- "The call to the quantitative math library should *only* be made if any of the above inputs have changed from last time; if no input has changed then the quantitative risk model doesn't need to update the risk factors. \n",
- "\n",
- "#### When to update risk factors\n",
- "\n",
- "Risk factors are an input to the [margin calculation](./0019-MCAL-margin_calculators.md) and are calculated using a quantitative risk model.\n",
- "\n",
- "Risk factors are updated if \n",
- "* An update risk factors call is not already in progress asynchronously; AND\n",
- "* Any of the required inputs to risk factors change. Examples 1. when the calibrator has updated any risk parameters. 2. a specified period of time has elapsed (period can = 0 for always recalculate) for re-calculating risk factors. This period of time is defined as a risk parameter (see [market framework](./0001-MKTF-market_framework.md)).\n",
- "\n",
- "Risk factors are also updated if on creation of a new market that does not yet have risk factors, as any active market needs to have risk factors.\n",
- "\n",
- "#### Risk factors and consensus\n",
- "\n",
- "All new risk factors will need to be agreed via consensus when either (or both): \n",
- "- asynchronous updates\n",
- "- the other cause will be due to floating point non-determinism\n",
- "\n",
- "The rounding should remove all digits beyond the guaranteed accuracy. \n",
- "\n",
- "Example: If `Long position risk factor = 1.23456789` and `Guaranteed accuracy = 0.001` then \n",
- "`Rounded Long position risk factor = 1.234`. \n",
- "\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Acceptance Criteria\n",
- "\n",
- "1. Different markets can have a different risk model (i.e. a market A can be specified to run with risk model R1 while market B can be specified to run with risk model R2). (0018-RSKM-001)\n",
- "1. If any of the input data has changed then an update to risk factors is initiated. (0018-RSKM-003)\n",
- "1. Risk factors are agreed upon by consensus. (0018-RSKM-004)\n",
- "1. If the risk factor calculation reports \"guaranteed accuracy\" then the risk factors are appropriately rounded. (0018-RSKM-005)\n",
- "1. Quant risk suite can compute max move up/down ($\\Delta^-$ and $\\Delta^+$) given current price level and a projection horizon such that the resulting prices are within a specified probability level. (0018-RSKM-007)\n",
- "1. Quant risk suite can compute probability of trading at a given level. (0018-RSKM-008)\n",
- "1. Lognormal risk model has defined ranges of valid parameters and market proposals and market update proposals are checked against these. The ranges can be found in core (https://github.com/vegaprotocol/vega/blob/develop/commands/proposal_submission.go#L820). (0018-RSKM-009)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.9.6"
- },
- "vscode": {
- "interpreter": {
- "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Quantitative risk models\n",
+ "\n",
+ "\n",
+ "## Summary\n",
+ "Vega can use different risk models as for different underlying assets and different derivatives one has to choose an appropriate model. Every quantitative risk model used by Vega needs to be able to provide the following.\n",
+ "\n",
+ "1. Risk factors, currently for futures but for other products as Vega develops. The risk factor *linearizes* the risk measure used for margin requirements. See `0019-MCAL-margin_calculator.md` for details of how these are used.\n",
+ "1. Given a probability level $\\alpha \\in (0.9,1)$, time horizon $\\tau > 0$ and current price $S$ return the $\\Delta^-$ and $\\Delta^+$ such that \n",
+ "$$\n",
+ "\\mathbb P(S-\\Delta^- \\leq S_\\tau \\leq S+\\Delta^+) \\geq 1-\\alpha\\,.\n",
+ "$$\n",
+ "See the price monitoring spec for details of how these are used.\n",
+ "1. Given current price $S$, time horizon $\\tau > 0$, a list of price levels $S < S_1 < S_2 < \\cdots < S_N^+$ return the probability of trading at each of these price levels defined below. See \"Probability weighted liquidity measure\" spec for how these are used.\n",
+ "1. Calibration outputs (not required for first release of Mainnet, model parameters result from governance vote per market).\n",
+ "\n",
+ "The “quant risk suite” with Vega consists a _quantitative risk model_, _margin calculator_ and _calibrator_.\n",
+ "\n",
+ "A market parameter specifies which _quantitative risk model_ is used for a given risk universe (market, see Section 3.5 of the Vega white paper).\n",
+ "\n",
+ "\n",
+ "## Reference-level explanation\n",
+ "\n",
+ "### _Quantitative risk model_\n",
+ "The relevant quantitative risk model for a market is specified on a tradeable instrument. The role of the quantitative risk model is to calculate **risk factors** which are used in the **_margin calculator_** (see below)\n",
+ "as well as probabilities of various price moves used for price monitoring and measuring liquidity. \n",
+ "\n",
+ "To achieve this it utilises the quantitative maths library.\n",
+ "\n",
+ "The quantitative risk model may take one or more of the following as inputs:\n",
+ "* risk parameters (e.g. volatility)\n",
+ "* product parameters (e.g. minimum contract size)\n",
+ "* order book data (full current order book with volume aggregated at price levels)\n",
+ "* position data (for each trader)\n",
+ "* event data (e.g. passage of time) (Not for Futures / Nicenet )\n",
+ "\n",
+ "The quantitative risk model returns two risk factors:\n",
+ "\n",
+ "1. Rounded Long position risk factor\n",
+ "1. Rounded Short position risk factor\n",
+ "\n",
+ "The quantitative risk model is able to utilise a relevant method from the quant math library to perform the calculations.\n",
+ "\n",
+ "The quant math library calculates:\n",
+ "1. Long position risk factor\n",
+ "1. Short position risk factor\n",
+ "1. Guaranteed accuracy (applicable to both risk factors)\n",
+ "1. The max move up/down ($\\Delta^-$ and $\\Delta^+$ defined in the [Summary](#Summary)) given current price level and a projection horizon such that the resulting prices are within a specified probability level.\n",
+ "1. Probability of trading at a given price level (see below).\n",
+ "\n",
+ "#### Calculating the probability of trading\n",
+ "This is well defined only if we have best bid $S_{\\text{best bid}}$ and best ask $S_{\\text{best ask}}$ on the order book. \n",
+ "The rest of information comes from the quant risk model that has the probability density of price distribution at time horizon $\\tau > 0$ i.e. we have \n",
+ "$$\n",
+ "f(x;S) = \\mathbb P(S_\\tau \\in [x, x+dx) | S)\\,, \n",
+ "$$\n",
+ "where $S$ is current price level and $S_\\tau$ is a future price, after time $\\tau > 0$.\n",
+ "\n",
+ "Given a price level $S$, time horizon $\\tau > 0$, a list of price levels $S < S_1 < S_2 < \\cdots < S_{N^+}$ or a list of price levels $S > S_1 > S_2 > \\cdots > S_{N^-}$ return the probability of trading $p(S_i)$ at each of these as defined below. \n",
+ "\n",
+ "1. If $S_i \\in [S_{\\text{best bid}}, S_{\\text{best ask}}]$ for $i=1,\\ldots,N^+$ or $i=1,\\ldots,N^-$ return $p(S_i) = 1$. \n",
+ "That is, between best bid and best ask the probability of trading is one. \n",
+ "2. If $S_i > S_{\\text{best ask}}$ for $i=1,\\ldots,N^+$ then return \n",
+ "$$\n",
+ "p(S_i) = \\int_{S_i}^{S_{max}} f(x;S_{\\text{best ask}})\\,dx\n",
+ "$$\n",
+ "3. If $S_i < S_{\\text{best bid}}$ for $ =1,\\ldots,N^-$ then return\n",
+ "$$\n",
+ "p(S_i) = \\int_{S_{min}}^{S_i} f(x;S_{\\text{best bid}})\\,dx\\,.\n",
+ "$$\n",
+ "\n",
+ "#### When to not update risk factors\n",
+ "\n",
+ "The call to the quantitative math library should *only* be made if any of the above inputs have changed from last time; if no input has changed then the quantitative risk model doesn't need to update the risk factors. \n",
+ "\n",
+ "#### When to update risk factors\n",
+ "\n",
+ "Risk factors are an input to the [margin calculation](./0019-MCAL-margin_calculators.md) and are calculated using a quantitative risk model.\n",
+ "\n",
+ "Risk factors are updated if \n",
+ "* An update risk factors call is not already in progress asynchronously; AND\n",
+ "* Any of the required inputs to risk factors change. Examples 1. when the calibrator has updated any risk parameters. 2. a specified period of time has elapsed (period can = 0 for always recalculate) for re-calculating risk factors. This period of time is defined as a risk parameter (see [market framework](./0001-MKTF-market_framework.md)).\n",
+ "\n",
+ "Risk factors are also updated if on creation of a new market that does not yet have risk factors, as any active market needs to have risk factors.\n",
+ "\n",
+ "#### Risk factors and consensus\n",
+ "\n",
+ "All new risk factors will need to be agreed via consensus when either (or both): \n",
+ "- asynchronous updates\n",
+ "- the other cause will be due to floating point non-determinism\n",
+ "\n",
+ "The rounding should remove all digits beyond the guaranteed accuracy. \n",
+ "\n",
+ "Example: If `Long position risk factor = 1.23456789` and `Guaranteed accuracy = 0.001` then \n",
+ "`Rounded Long position risk factor = 1.234`. \n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Wrapped risk models\n",
+ "\n",
+ "For certain products it might be possible to certain aspects of market's reliance on risk model while keeping other dependencies in places.\n",
+ "\n",
+ "### Fully-collateralised\n",
+ "\n",
+ "For products with pre-defined finite price range (e.g. [a futures contracts with [`max_price`](./0016-PFUT-product_builtin_future.md#1-product-parameters) parameter specified]) it is possible to make positions fully-collateralised so that for any position a margin can be chosen such that the party stays solvent at any price the market may attain. Using this mode removes the reliance on risk-factors, however the risk model might still be used for other aspects of market's functioning. To signify this and make it transparent to the user a fully-collateralised wrapped risk model is used in those cases.\n",
+ "\n",
+ "### Hardcoded risk factors\n",
+ "\n",
+ "For markets which should ignore model-implied risk factors and use hardcoded values instead a wrapped risk model should be used to signify that. The specified risk factors should be used wherever risk factors are expected and other model-implied outputs should be used for all other calculations based on a risk-model."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Acceptance Criteria\n",
+ "\n",
+ "1. Different markets can have a different risk model (i.e. a market A can be specified to run with risk model R1 while market B can be specified to run with risk model R2). (0018-RSKM-001)\n",
+ "1. If any of the input data has changed then an update to risk factors is initiated. (0018-RSKM-003)\n",
+ "1. Risk factors are agreed upon by consensus. (0018-RSKM-004)\n",
+ "1. If the risk factor calculation reports \"guaranteed accuracy\" then the risk factors are appropriately rounded. (0018-RSKM-005)\n",
+ "1. Quant risk suite can compute max move up/down ($\\Delta^-$ and $\\Delta^+$) given current price level and a projection horizon such that the resulting prices are within a specified probability level. (0018-RSKM-007)\n",
+ "1. Quant risk suite can compute probability of trading at a given level. (0018-RSKM-008)\n",
+ "1. Lognormal risk model has defined ranges of valid parameters and market proposals and market update proposals are checked against these. The ranges can be found in core (https://github.com/vegaprotocol/vega/blob/develop/commands/proposal_submission.go#L820). (0018-RSKM-009)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.6"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
\ No newline at end of file
diff --git a/protocol/0019-MCAL-margin_calculator.md b/protocol/0019-MCAL-margin_calculator.md
index 3ef857909..148170e37 100644
--- a/protocol/0019-MCAL-margin_calculator.md
+++ b/protocol/0019-MCAL-margin_calculator.md
@@ -425,6 +425,22 @@ There should be an additional amount `limit price x size x margin factor = 15910
- All order margin balances are restored after a protocol upgrade (0019-MCAL-152).
- The `margin mode` and `margin factor` of any given party must be preserved after a protocol upgrade (0019-MCAL-153).
+## Acceptance Criteria (Fully collateralised mode)
+
+Assume a [capped future](./0093-CFUT-product_builtin_capped_future.md) market with a `max price = 100` and mark-to-market cashflows being exchanged every block:
+
+- Party A posts an order to buy `10` contracts at a price of `30`, there's no other volume in that price range so the order lands on the book and the maintenance and initial margin levels for the party and order margin account balance are all equal to `300`. (0019-MCAL-154)
+- Party B posts an order to sell `15` contracts at a price of `20`, a trade is generated for `10` contracts at price of `30` with party A. The maintenance and initial margin levels party A remains at `300`, order margin account balance is now `0` and margin account balance is `300`, the position is `10` and there are no open orders. The maintenance and initial margin levels for party B are equal to `10 * (100 - 30) + 5 * (100 - 20) = 1100`, the margin account balance is `700`, order margin account balance is `400`, the position is `-10` and the remaining volume on the book from this party is `5` at a price of `20`. (0019-MCAL-155)
+- Party B posts an order to buy `10` contracts at a price of `18`, the orders get placed on the book and margin levels as well margin account balances and position remain unchanged. (0019-MCAL-156)
+- Party B posts an order to buy `30` contracts at a price of `16`, the orders get placed on the book, the maintenance and initial margin levels for party B grow to `1180`, and the margin account balance remains unchanged at `700` and the order margin account balance grows to `480 = max (5 * (100 - 20), 30 * 16)`. The position remains unchanged at `-10`. (0019-MCAL-157)
+- Party A posts an order to sell `20` contracts at a price of `17`. A trade is generated for `10` contracts at a price of `18` with party B. A sell order for `10` contracts at a price of `17` from party A gets added to the book. The maintenance and initial margin levels for party A is now `10 * (100 - 17) = 830`, the position is `0` and the remaining volume on the book from this party is `10` at a price of `18`. Party A lost `120` on its position, hence `830 - (300 - 120) = 410` additional funds get moved from the general account as part of the transaction which submitted the order to sell `20` at `17`. Party B now has a position of `0` and following orders open on the book: sell `5` at `20` and buy `30` at `16`. The maintenance and initial margin levels are `max(5 * (100 - 20), 30 * 16) = 480`. The margin account momentarily becomes `820` (`700` + `120` of gains from the now closed position of `-10`), order margin account balance is `480`, hence `820` gets released back into the general account and margin account becomes `0`. (0019-MCAL-158)
+- `fully-collateralised mode` is only allowed when there is a `max_price` specified (0019-MCAL-170)
+- `binary_settlement` is only allowed when there is a `max_price` specified (0019-MCAL-171)
+
+## Acceptance Criteria (Hardcoded risk factors)
+
+- When a risk model with hardcoded risk factors is used on a regular (NOT fully-collateralised) market then margin calculations depend entirely on the hardcoded values and updating other risk model parameters has no effect on margins (0019-MCAL-159)
+
## Summary
The *margin calculator* returns the set of margin levels for a given *actual position*, along with the amount of additional margin (if any) required to support the party's *potential position* (i.e. active orders including any that are parked/untriggered/undeployed).
@@ -432,7 +448,9 @@ The *margin calculator* returns the set of margin levels for a given *actual pos
### Margining modes
-The system can operate in one of two margining modes for each position.
+#### Partially-collateralised
+
+The system can operate in one of two partially-collateralised margining modes for each position.
The current mode will be stored alongside of party's position record.
1. **Cross-margin mode (default)**: this is the mode used by all newly created positions.
@@ -444,6 +462,23 @@ This includes completely new positions and increases to position size. Other tha
*never* be searched for additional funds (a position will be allowed to be closed out instead), nor will profits be moved into the
general account from the margin account.
+#### Fully-collateralised
+
+For certain derivatives markets it may be possible to collateralise the position in full so that there's no default risk for any party.
+
+If a product specifies an upper bound on price (`max price`) (e.g. [capped future](./0093-CFUT-product_builtin_capped_future.md)) then a fully-collateralised mode can be specified for the market. If it is chosen then, it's mandatory for all parties (it's not possible to self-select any of the above partially-collateralised margining modes).
+
+In this mode long positions provide `position size * average entry price` in initial margin, whereas shorts provide `postion size * (max price - average entry price)`. The initial margin level is only re-evaluated when party changes their position. The [mark-to-market](./0003-MTMK-mark_to_market_settlement.md) is carried out as usual. Maintenance and initial margin levels should be set to the same value. Margin search and release levels are set to `0` and never used.
+
+In this mode it is not possible for a party to be liquidated. Even if the price moves to the extremes of zero or the `max price` and parties may therefore have zero in their margin account, the parties must not be liquidated. Fully collateralised means that the posted collateral explicitly covers all eventualities and positions will only be closed at final settlement at maturity.
+
+Order margin is calculated as per [isolated margin mode](#placing-an-order) with:
+
+- `side margin = limit price * size` for buy orders
+- `side margin = (max price - limit price) * size` for sell orders.
+
+Same calculation should be applied during auction (unlike in isolated margin mode).
+
### Actual position margin levels
1. **Maintenance margin**: the minimum margin a party must have in their margin account to avoid the position being liquidated.
diff --git a/protocol/0026-AUCT-auctions.md b/protocol/0026-AUCT-auctions.md
index 46c1a3320..b4a9c7504 100644
--- a/protocol/0026-AUCT-auctions.md
+++ b/protocol/0026-AUCT-auctions.md
@@ -51,6 +51,8 @@ We can also imagine that an auction period could come to an end once a give numb
Once the auction period finishes, vega needs to figure out the best price for the order range in the book which can be uncrossed. The first stage in this is to calculate the Volume Maximising Price Range - the range of prices (which will be a contiguous range in an unconstrained order book) at which the highest total quantity of trades can occur.
+For including [AMM](./0090-VAMM-automated_market_maker.md) configurations in the calculation of expected uncrossing price of the auction process and volume maximising price range, create two lists of independent copies of all currently active vAMMs. Ensure that the price range under consideration (lowest ask - highest bid) is expanded to cover the current best bid/ask prices of all vAMMs. Separately, calculate a price step size defined by either the smallest tick size or, if this would result in more steps between lowest ask and highest bid than allowed by `market.liquidity.maxAmmCalculationLevels` instead divide the price range into `market.liquidity.maxAmmCalculationLevels` and calculate the price step required for this. Additionally, add all price bounds for vAMMs which are within the auction bounds to the checked levels. Then iterate through these price levels, moving the vAMMs to each by querying their volume between the two bounds and counting this as a single trade at the price farther from mid. Add these volumes to those of individual limit orders when calculating the uncrossing price. vAMMs may quote on both sides of the book as long as they are not in reduce-only mode. When actually uncrossing the market, vAMMs should act as normal when trades from one side are being sent in to matching.
+
Initially we will use the mid price within this range. For example, if the volume maximising range is 98-102, we would price all trades in the uncrossing at 100 ((minimum price of range+maximum price of range)/2). In future there will be other options, which will be selectable via a network parameter specified at market creation, and changeable through governance. These other options are not yet specified.
## APIs related to auctions
@@ -168,13 +170,19 @@ message Market {
- Why it is in that period (e.g. Auction at open, liquidity sourcing, price monitoring)
- When the auction will next attempt to uncross or if the auction period ended and the auction cannot be resolved for whatever reason then this should come blank or otherwise indicating that the system doesn't know when the auction ought to end.
- A market with default trading mode "continuous trading" will start with an opening auction. The opening auction will run from the close of voting on the market proposal (assumed to pass successfully) until:
- 1. the enactment time assuming there are orders crossing on the book and [liquidity is supplied](./0044-LIME-lp_mechanics.md#commit-liquidity-network-transaction). (0026-AUCT-017). For product spot, the enactment time assuming there are orders crossing on the book, there is no need for the supplied liquidity to exceed a threshold to exit an auction: (0026-AUCT-029)
+ 1. the enactment time assuming there are orders crossing on the book, there is no need for the supplied liquidity to exceed a threshold to exit an auction. (0026-AUCT-038). For product spot, the enactment time assuming there are orders crossing on the book, there is no need for the supplied liquidity to exceed a threshold to exit an auction: (0026-AUCT-029)
2. past the enactment time if there is no [liquidity supplied](./0044-LIME-lp_mechanics.md#commit-liquidity-network-transaction). The auction won't end until sufficient liquidity is committed. (0026-AUCT-018)
3. past the enactment time if [liquidity is supplied](./0044-LIME-lp_mechanics.md#commit-liquidity-network-transaction) but the uncrossing volume will create open interest that is larger than what the [supplied stake can support](./0041-TSTK-target_stake.md). It will only end if
- more liquidity is committed (0026-AUCT-019)
- or if orders are cancelled such that the uncrossing volume will create open interest sufficiently small so that the original stake can support it. (0026-AUCT-020)
- 4. past the enactment time if there are orders crossing on the book and [liquidity is supplied](./0044-LIME-lp_mechanics.md#commit-liquidity-network-transaction) but after the auction uncrossing we will not have
- - best bid; it will still open. (0026-AUCT-021)
- - or best ask; it will still open. (0026-AUCT-022)
+ 4. past the enactment time if there are orders crossing on the book (there is no need for the supplied liquidity to exceed a threshold to exit an auction) but after the auction uncrossing we will not have
+ - best bid; it will still open. (0026-AUCT-039)
+ - or best ask; it will still open. (0026-AUCT-040)
- When entering an auction, all GFN orders will be cancelled. (0026-AUCT-015). For product spot: (0026-AUCT-031)
- When leaving an auction, all GFA orders will be cancelled. (0026-AUCT-016). For product spot: (0026-AUCT-032)
+
+- Leaving an auction on a market with one active vAMM and some volume of crossing limit orders (bids where the vAMM would sell or sells where the vAMM would buy) results in these orders trading with the vAMM and an un-crossed order book afterwards. (0026-AUCT-033)
+- Leaving an auction on a market with three active vAMMs with differing implied fair prices results in the vAMMs trading with each other and an un-crossed order book afterwards (all vAMMs either have the same fair price or are at one extreme of their range). (0026-AUCT-034)
+- Leaving an auction with three active vAMMs whose ranges do not overlap at all results in the vAMMs trading with each other and at least two of them hitting their position limits. (0026-AUCT-035)
+- Leaving an auction with two active vAMMs with differing implied fair prices but with overlapping ranges, and a buy order at a price within both of their upper ranges (such that they would both be short) with a volume greater than the combined short position of the vAMMs at that price, results in an uncrossed market with the remaining buy order's price as the best bid after trading with the vAMMs to move their fair prices to this price. (0026-AUCT-036)
+- Leaving an auction with two active vAMMs with differing implied fair prices but with overlapping ranges, and a sell order at a price within both of their lower ranges (such that they would both be long) with a volume greater than the combined long position of the vAMMs at that price, results in an uncrossed market with the remaining sell order's price as the best ask after trading with the vAMMs to move their fair prices to this price. (0026-AUCT-037)
diff --git a/protocol/0028-GOVE-governance.md b/protocol/0028-GOVE-governance.md
index c4caeafa8..b609255e1 100644
--- a/protocol/0028-GOVE-governance.md
+++ b/protocol/0028-GOVE-governance.md
@@ -94,6 +94,8 @@ Market change proposals can also be submitted by any party which has at least th
Moreover, market LPs can vote on market change proposals even if they don't have `governance.proposal.updateMarket.minVoterBalance` governance tokens.
So, for example, if `governance.proposal.updateMarket.minProposerEquityLikeShare = 0.05` and a party has `equity-like share` on the market of `0.3` and no governance tokens then they can make a market change proposal. If, on the other hand, a party has `equity-like share` of `0.03` and no governance tokens then they cannot submit a market change proposal.
+If a party has any sub-account running [AMM strategies](./0090-VAMM-automated_market_maker.md) then the `equity-like share` of the party's votes on that market should be the sum of the `equity-like share` for the key itself and any assigned to the sub-account.
+
### Duration of the proposal
A new proposal will have a close date specified as a timestamp. After the proposal is created in the system and before the close date, the proposal is open for votes. e.g: A proposal is created and people have 3 weeks from the day it is sent to the network in order to submit votes for it.
@@ -118,9 +120,9 @@ Note that this is validation is in units of time from current time i.e. if the p
at e.g. `09:00:00 on 1st Jan 2021` and `governance.proposal.asset.minEnact` is `72h` then the proposal must contain enactment date/time that after `09:00:00 on 4th Jan 2021`.
If there is `governance.proposal.asset.maxEnact` of e.g. `360h` then the proposed enactment date / time must be before `09:00:00 on 16th Jan 2021`.
-## Editing and/or cancelling a proposal is not possible
+## Editing a proposal
-A proposal cannot be edited, once created. The only possible action is to vote for or against a proposal, or submit a new proposal.
+A proposal cannot be edited once submitted to the network. The only possible actions are to cancel within the threshold, vote for or against a proposal, or submit a new proposal.
If a proposal is created and later a different outcome is preferred by network participants, two courses of action are possible:
@@ -129,6 +131,27 @@ If a proposal is created and later a different outcome is preferred by network p
Which of these makes most sense will depend on the type of change, the timing of the events, and how the rest of the community votes for the initial proposal.
+## Cancelling a proposal
+
+A proposal can be cancelled under certain conditions by, and only by, the party which created the proposal. To avoid situations where the community may have acted upon proposals they "expected" to pass, a party can only cancel their proposal before a certain "cancellation threshold" is reached.
+
+This cancellation threshold is a fixed point before the proposal closing time. The duration of the period in which a proposal cannot be cancelled is defined by the network parameter `governance.proposal.cancellationThreshold` (a duration string defaulting to `23h0m0s`).
+
+i.e. Given the `governance.proposal.cancellationThreshold` is `23h0m0s` a party will be able to cancel their proposal 23 hours before the proposal closing time.
+
+### Mechanics
+
+A party can cancel their proposal by simply voting against it with a no vote.
+
+- If the current time is before the cancellation threshold, the proposal is instantly rejected.
+- If the current time is after the cancellation threshold, the vote is treated normally as a vote against a proposal.
+
+Note the following additional details:
+
+- a cancelling vote against a proposal must pass all typical vote checks and spam protections, i.e. who party who removes stake after creating a proposal and no longer meets the requirement will not be able to cancel their proposal.
+
+- if at the time of creating a proposal the closing time is within the period defined by `governance.proposal.cancellationThreshold`, the proposal will still be valid and accepted by the network but the party will not have an opportunity to cancel the proposal.
+
## Outcome
At the conclusion of the voting period the network will calculate two values:
@@ -556,6 +579,8 @@ APIs should also exist for clients to:
- A market proposal with position decimal places not in `{-6,...,-1,0,1,2,...,6}` gets rejected. (0028-GOVE-062) For product spot: (0028-GOVE-075)
- A market proposal with a tick size less than or equal to `0` gets rejected (0028-GOVE-180).
- At enactment, a market change proposal updating the tick size leaves in place all orders where the quoted price is not an exact multiple of `10^-mdp` (where `mdp` is the market decimal places) (0028-GOVE-182).
+- At enactment, a market with `Transaction Prioritisation` enabled will have transactions re-prioritised as defined in [transaction prioritisation](./0092-TRTO-trading_transaction_ordering.md) (0028-GOVE-192).
+- At enactment, a market with `Transaction Prioritisation` disabled will not have transactions re-prioritised (0028-GOVE-193).
#### Market change proposals
@@ -600,6 +625,7 @@ APIs should also exist for clients to:
- A market change proposal specifying a new tick size less than or equal to `0` gets rejected (0028-GOVE-184).
- At enactment, a market change proposal updating the tick size cancels all pegged orders where their offset is no longer an exact integer multiple of the tick size (0028-GOVE-183).
- A market LP with ELS > 0 can vote on a market change proposal even if the key doesn't meet the `governance.proposal.updateMarket.minVoterBalance` for governance token. (0028-GOVE-185).
+- An amendment to a market to enable or disable `Transaction Prioritisation` will have that effect immediately upon enactment. (0028-GOVE-194)
#### Network parameter change proposals
@@ -772,3 +798,11 @@ The voting to approve the batch happens, the batch passes, the value of the prop
- A voter's equity-like share does not give them any additional voting weight when voting on a market community tags update proposal. (0028-GOVE-174)
- A proposal to add community tags with any community tags longer than `governance.proposal.market.maxCommunityTagLength` is rejected as invalid (0028-GOVE-175)
- A proposal to remove community tags with any community tags longer than `governance.proposal.market.maxCommunityTagLength` is rejected as invalid (0028-GOVE-176)
+
+#### Cancelling Proposals
+
+- Given a proposal with a closing time further than `governance.proposal.cancellationThreshold` from the current time, if the party that created the proposal votes against it, the proposal will be instantaneously rejected (0028-GOVE-188)
+- Given a proposal with a closing time less than `governance.proposal.cancellationThreshold` from the current time, if the party that created the proposal votes against it, the proposal will not be instantaneously rejected. At closing time, if the proposal meets the criteria to be accepted it will pass (0028-GOVE-189)
+- Given a proposal with a closing time less than `governance.proposal.cancellationThreshold` from the current time, if the party that created the proposal votes against it, the proposal will not be instantaneously rejected. At closing time, if the proposal does not meet the criteria to be accepted it will be rejected (0028-GOVE-190)
+
+- Given a proposal with a closing time further than `governance.proposal.cancellationThreshold` from the current time, if a party that did not create the proposal votes against it, the proposal will not be instantaneously rejected (0028-GOVE-191)
diff --git a/protocol/0031-ETHB-ethereum_bridge_spec.md b/protocol/0031-ETHB-ethereum_bridge_spec.md
index e3795e69d..391c0c834 100644
--- a/protocol/0031-ETHB-ethereum_bridge_spec.md
+++ b/protocol/0031-ETHB-ethereum_bridge_spec.md
@@ -26,9 +26,11 @@ Each bridge contains two primary functions and emits two primary events, each ta
### Block confirmations
-It is normal behaviour when validating transfers to wait a certain number of blocks for a deposit to be 'confirmed'. We need to do the same, to have acceptably high probability that the event is on the longest chain and there won't be a fork in the future which will invalidate this. We achieve this by ensuring that enough time has passed.
+It used to be normal behaviour when validating transfers to wait a certain number of blocks for a deposit to be 'confirmed'. With the new version of Ethereum, an additional mechanism exists that assures finality of a block
+that can be used instead, which leads to cleaner results.
-This will need to be configured per chain that we connect to. ETH, ERC-20, ERC-XXX can all share a value, which should be configurable and changeable via governance. For Ethereum, this should be 20 confirmations. It is safe to lower this for development networks.
+We use this mechanism for all Ethereum related finality requirements. For legacy reasons ,there is still a parameter
+defining the number of confirmations previously used, but this parameter is ignored now.
## Reference-level explanation
@@ -115,7 +117,7 @@ The Ethereum Bridge uses 1 network parameter, `blockchains.ethereumConfig`, a JS
| `staking_bridge_contract` | {string, integer} | `{address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1}` | The addresses to listen to for [staking events](./0059-STKG-simple_staking_and_delegating.md). |
| `token_vesting_contract` | {string, integer} | `{address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1}` | The addresses to listen to for [vesting contract events](./0059-STKG-simple_staking_and_delegating.md). |
| `multisig_control_contract` | {string, integer} | `{address: "0xCcB517899f714BD1B2f32931fF429aDEdDd02A93", deployment_height: 1}` | The addresses to listen to for multisig control event |
-| `confirmations` | Integer | `3` | Block confirmations to wait for before confirming an action |
+| `confirmations` | Integer | `3` | Block confirmations to wait for before confirming an action (legacy) |
### Full example
diff --git a/protocol/0032-PRIM-price_monitoring.md b/protocol/0032-PRIM-price_monitoring.md
index 480b05572..5a51f15ba 100644
--- a/protocol/0032-PRIM-price_monitoring.md
+++ b/protocol/0032-PRIM-price_monitoring.md
@@ -4,8 +4,10 @@
The dynamics of market price movements are such that prices don't always represent the participants' true average view of the price, but are instead artefacts of the market microstructure: sometimes low liquidity and/or a large quantity of order volume can cause the price to diverge from the true market price. It is impossible to tell at any point in time if this has happened or not.
-As a result, we assume that relatively small moves are "real" and that larger moves might not be. Price monitoring exists to determine the real price in the latter case. Distinguishing between small and large moves can be highly subjective and market-dependent. We are going to rely on the risk model to formalise this process. Risk model can be used to obtain the probability distribution of prices at a future point in time given the current price. A price monitoring trigger can be constructed using a fixed horizon and probability level.
+As a result, we assume that relatively small moves are "real" and that larger moves might not be. Price monitoring exists to determine the real price in the latter case. Distinguishing between small and large moves can be highly subjective and market-dependent.
+We are going to rely on the risk model to formalise this process. Risk model can be used to obtain the probability distribution of prices at a future point in time given the current price. A price monitoring trigger can be constructed using a fixed horizon and probability level.
To give an example: get the price distribution in an hour as implied by the risk model given the current mid price, if after the hour has passed and the actual mid price is beyond what the model implied (either too low or too high) with some chosen probability level (say 99%), then we'd characterise such market move as large. In general we may want to use a few such triggers per market (i.e. different horizon and probability level pairs). The framework should be able to trigger a price protection auction period with any valid trading mode.
+We're also going to allow specifying triggers directly as the maximum valid moves with respect to the reference price. In that case the `maxUpMoveFactor`, `maxDownMoveFactor` can be specified for a given horizon, such that a price is considered valid as long as it's in the range `[reference_price(horizon) * maxDownMoveFactor, [reference)price(horizon) * maxUpMoveFactor]`, where `[reference_price(horizon)` is the reference price corresponding to the specified horizon - obtained in exactly the same way as in the case of a model-based trigger.
As mentioned above, price monitoring is meant to stop large market movements that are not "real" from occurring, rather than just detect them after the fact. To that end, it is necessary to pre-process every transaction and check if it triggers the price monitoring action. If pre-processing the transaction doesn't result in the trigger being activated then it should be "committed" by generating the associated events and modifying the order book accordingly (e.g. generate a trade and take the orders that matched off the book). On the other hand if the trigger is activated and the submitted transaction is valid for auction mode, the entire order book **along with that transaction** needs to be processed via price protection auction. If the transaction which activate the trigger is not valid for auction, then it should get rejected and market should continue in the current trading mode. Auction period associated with a given distribution projection horizon and probability level will be specified as part of market setup. Once the auction period finishes the trading should resume in regular fashion (unless other triggers are active, more on that in [reference-level explanation](#reference-level-explanation)).
@@ -44,6 +46,8 @@ Likewise, pre-processing transactions will be needed as part of the [fees spec](
#### Market
+##### Model-based triggers
+
- `priceMonitoringParameters` - an array of more price monitoring parameters with the following fields:
- `horizon` - price projection horizon expressed as a year fraction over which price is to be projected by the risk model and compared to the actual market moves during that period. Must be positive.
- `probability` - probability level used in price monitoring. Must be in the [0.9,1) range.
@@ -55,6 +59,13 @@ If any of the above parameters or the risk model gets modified in any way, the p
- the auction end time implied by the currently running auction/extension should remain unchanged,
- when auction uncrosses price monitoring should get reset using the updated parameters.
+##### Model-free triggers
+
+- `modelFreePriceMonitoringParameters` - an array of more price monitoring parameters with the following fields:
+ - `horizon` - price projection horizon expressed as a year fraction over which price is to be projected by the risk model and compared to the actual market moves during that period. Must be positive.
+ - `maxUpMoveFactor` - a factor to be applied to the reference price (for the specified horizon) so that the maximum valid price is `reference_price(horizon) * maxUpMoveFactor`. Must be greater than `1`.
+ - `maxDownMoveFactor` - a factor to be applied to the reference price (for the specified horizon) so that the minimum valid price is `reference_price(horizon) * maxDownMoveFactor`. Must be less than `1`.
+
#### Network
- `market.monitor.price.defaultParameters`: Specifies default market parameters outlined in the previous paragraph. These will be used if market parameters don't get explicitly specified.
@@ -141,3 +152,7 @@ to the risk model and obtains the range of valid up/down price moves per each of
- Same as above, but more matching orders get placed during the auction extension. The volume of the trades generated by the later orders is larger than that of the original pair which triggered the auction. Hence the auction concludes generating the trades from the later orders. The overall auction duration is equal to the sum of the extension periods of the two triggers. (0032-PRIM-021). For product spot: (0032-PRIM-038)
- For all available mark price calculation methodologies: the price history used by the price monitoring engine is in line with market's mark price history. (0032-PRIM-039)
- For all available mark-price calculation methodologies: the mark price update candidate gets rejected if it violates the price monitoring engine bounds. (0032-PRIM-040)
+- Model-free triggers can be added to the market at creation time along with regular triggers. (0032-PRIM-041)
+- Model-free triggers can be added to the market during market update along with regular triggers. (0032-PRIM-042)
+- Adding a model-free trigger with `maxUpMoveFactor = 1.1` and `maxDownMoveFactor = 0.95` results in bonds with max valid price of `110` and min valid price of `95` when a reference price is `100`. When time passes so that the reference price becomes `90` then the resulting max valid price is `99` and min valid price is `85.5`. Violating any of these bounds results in an auction. (0032-PRIM-043
+)
diff --git a/protocol/0037-OPEG-pegged_orders.md b/protocol/0037-OPEG-pegged_orders.md
index 55a8faecc..a52cb01a1 100644
--- a/protocol/0037-OPEG-pegged_orders.md
+++ b/protocol/0037-OPEG-pegged_orders.md
@@ -16,28 +16,26 @@
- The order version is not updated during a repricing (0037-OPEG-012)
- Pegged orders are included in the calculation of the BEST_BID, BEST_ASK and MID prices but excluded from BEST_STATIC_BID, BEST_STATIC_ASK and STATIC_MID (0037-OPEG-013)
- A parked pegged order can be amended. (0037-OPEG-014). For product spot: (0037-OPEG-019)
-- Given a market with non-zero market and asset decimals where the asset decimals are strictly less than the market decimals (yielding a negative price factor). A pegged order specifying an offset which is not an integer multiple of the markets tick size should be rejected. (0037-OPEG-022). For product Spot (0037-OPEG-023).
-- Given a market with non-zero market and asset decimals where the asset decimals are equal to the market decimals (yielding a zero price factor). A pegged order specifying an offset which is not an integer multiple of the markets tick size should be rejected. (0037-OPEG-024). For product Spot (0037-OPEG-025).
-- Given a market with non-zero market and asset decimals where the asset decimals are strictly greater than the market decimals (yielding a positive price factor). A pegged order specifying an offset which is not an integer multiple of the markets tick size should be rejected. (0037-OPEG-026). For product Spot (0037-OPEG-027).
+- A pegged order specifying an offset which is not an integer multiple of the markets tick size should be rejected.
- A pegged order with an offset which would cause it to be priced <= 0 is parked. (0037-OPEG-017)
- An active pegged order can be amended. (0037-OPEG-016)
- A transaction submitting a pegged order with negative offset fails with an error explaining the cause was negative offset. (0037-OPEG-018)
- Given a mid-price which is not an integer multiple of the market tick size, a buy order pegged to the mid price should have it's price rounded up to the nearest market tick size (0037-OPEG-020).
- - For example, given:
- - `tick_size=10`
- - `best_bid_price=100`
- - `best_ask_price=190`
- - `mid_price=145`
- - Then:
- - A pegged buy order using the mid price as the reference and `offset=10` should be inserted at `price=140`.
+ For example, given:
+ - `tick_size=10`
+ - `best_bid_price=100`
+ - `best_ask_price=190`
+ - `mid_price=145`
+ Then:
+ - A pegged buy order using the mid price as the reference and `offset=10` should be inserted at `price=140`.
- Given a mid-price which is not an integer multiple of the market tick size, a sell order pegged to the mid price should have it's price rounded down to the nearest market tick size (0037-OPEG-021).
- - For example, given:
- - `tick_size=10`
- - `best_bid_price=100`
- - `best_ask_price=190`
- - `mid_price=145`
- - Then:
- - A pegged sell order using the mid price as the reference and `offset=10` should be inserted at `price=150`.
+ For example, given:
+ - `tick_size=10`
+ - `best_bid_price=100`
+ - `best_ask_price=190`
+ - `mid_price=145`
+ Then:
+ - A pegged sell order using the mid price as the reference and `offset=10` should be inserted at `price=150`.
## Summary
diff --git a/protocol/0042-LIQF-setting_fees_and_rewarding_lps.md b/protocol/0042-LIQF-setting_fees_and_rewarding_lps.md
index c368c472f..051c1dbe5 100644
--- a/protocol/0042-LIQF-setting_fees_and_rewarding_lps.md
+++ b/protocol/0042-LIQF-setting_fees_and_rewarding_lps.md
@@ -216,10 +216,13 @@ An existing LP has `average entry valuation 1090.9` and `S=110`. Currently the s
```
-### Calculating the instantaneous liquidity score
+### Calculating the liquidity score
At every vega time change calculate the liquidity score for each committed LP.
-This is done by taking into account all orders they have deployed within the `[min_lp_price,max_lp_price]` [range](./0044-LIME-lp_mechanics.md) and then calculating the volume-weighted [probability of trading](./0034-PROB-prob_weighted_liquidity_measure.ipynb) at each price level - call it instantaneous liquidity score.
+This is done by taking into account all orders they have deployed within the `[min_lp_price,max_lp_price]` [range](./0044-LIME-lp_mechanics.md) and then calculating the volume-weighted instantaneous liquidity score.
+
+It can be based either on [probability of trading](./0034-PROB-prob_weighted_liquidity_measure.ipynb) at each price level or an [explicit scoring function](./0091-ILSF-instantaneous_liquidity_scoring_funcion.md). The purpose of it is to decide on the relative value of volume placed close to the mid price versus that further away from it.
+
For orders outside the tightest price monitoring bounds set probability of trading to 0. For orders which have less than 10% [probability of trading], we set the probability to 0 when calculating liquidity score.
Note that parked [pegged orders](./0037-OPEG-pegged_orders.md) and not-yet-triggered [stop orders](./0014-ORDT-order_types.md) are not included.
@@ -247,7 +250,7 @@ The account is under control of the network and funds from this account will be
A network parameter `market.liquidity.providersFeeCalculationTimeStep` will control how often fees are distributed from the market's aggregate LP fee account.
Starting with the end of the opening auction the clock starts ticking and then rings every time `market.liquidity.providersFeeCalculationTimeStep` has passed. Every time this happens the balance in this account is transferred to the liquidity provider's general account for the market settlement asset.
-The liquidity fees are transferred from the market's aggregate LP fee account into the LP-per-market fee account, pro-rata depending on the `LP i equity-like share` multiplied by `LP i liquidity score` scaled back to `1` across all LPs at a given time.
+The liquidity fees are transferred from the market's aggregate LP fee account into the LP-per-market fee account from two different configurable size buckets, defined by the network parameter `market.liquidity.equityLikeShareFeeFraction`. The first bucket, a proportion equal to `market.liquidity.equityLikeShareFeeFraction` of the fee, is divided pro-rata depending on the `LP i equity-like share` multiplied by `LP i liquidity score` scaled back to `1` across all LPs at a given time. The other bucket, `1 - market.liquidity.equityLikeShareFeeFraction`, is divided purely by each LP's in-epoch liquidity score `LP i liquidity score`, scaled again across the value of all LPs at that time.
The LP parties don't control the LP-per-market fee account; the fees from there are then transferred to the LPs' general account at the end epoch as described below.
@@ -314,7 +317,7 @@ i.e. your penalty is the bigger of current epoch and average over the hysteresis
### Applying LP SLA performance penalties to accrued fees
-As defined above, for each LP for each epoch you have "penalty fraction" $p_i^n$ which is between `[0,1]` with `0` indicating LP has met commitment 100% of the time and `1` indicating that LP was below `market.liquidity.commitmentMinTimeFraction` of the time.
+As defined above, for each LP for each epoch you have "penalty fraction" $p_i^n$ which is between `[0,1]` with `0` indicating LP has met commitment 100% of the time and `1` indicating that LP was below `market.liquidity.commitmentMinTimeFraction` of the time. All vAMM LPs should also receive a $p_i^n$ value, however this will always be `0`, as they are defined as always meeting their commitment.
If for all $i$ (all the LPs) have $p_i^n = 1$ then all the fees go into the market insurance pool and we stop.
@@ -458,3 +461,48 @@ Example 1, generated with [supplementary worksheet](https://docs.google.com/spre
| LP2 | 0.05 | 100 | 95 | 2344.02439 |
| LP3 | 0.6 | 7000 | 2800 | 69087.03466 |
| LP4 | 1 | 91900 | 0 | 0 |
+
+
+### vAMM behaviour
+
+- All vAMMs active on a market at the end of an epoch receive SLA bonus rebalancing payments with `0` penalty fraction. (0042-LIQF-092)
+- A vAMM active on a market during an epoch, which was cancelled prior to the end of an epoch, receives SLA bonus rebalancing payments with `0` penalty fraction. (0042-LIQF-093)
+- A vAMMs cancelled in a previous epoch does not receive anything and is not considered during SLA rebalancing at the end of an epoch(0042-LIQF-094)
+
+- a vAMM which was active on the market throughout the epoch but with an active range which never overlapped with the SLA range is counted with an implied commitment of `0`. (0042-LIQF-107)
+- A vAMM which was active on the market with an average of `10000` liquidity units (`price * volume`) provided across the epoch, and where the `market.liquidity.stakeToCcyVolume` value is `100`, will have an implied commitment of `100`. (0042-LIQF-108)
+- A vAMM which was active on the market with an average of `10000` liquidity units (`price * volume`) provided for half the epoch, and then `0` for the second half of the epoch (as the price was out of the vAMM's configured range), and where the `market.liquidity.stakeToCcyVolume` value is `100`, will have an implied commitment of `50`. (0042-LIQF-109)
+- A vAMM which was active on the market with an average of `10000` liquidity units (`price * volume`) provided for half the epoch, and then is cancelled for the second half of the epoch, and where the `market.liquidity.stakeToCcyVolume` value is `100`, will have an implied commitment of `50`. (0042-LIQF-110)
+- A vAMM which was active on the market with an average of `10000` liquidity units (`price * volume`) provided for half the epoch, and then `5000` for the second half of the epoch (as the price was out of the vAMM's configured range), and where the `market.liquidity.stakeToCcyVolume` value is `100`, will have an implied commitment of `75`. (0042-LIQF-111)
+- If a vAMM was active during the market's opening auction if the opening auction ended and if trades were placed before the end of an epoch the vAMM should receive liquidity fee at epoch boundary (just like a normal LP that submitted bond during opening auction and then met the SLA) (0042-LIQF-112)
+
+
+### Explicit instantaneous liquidity scoring function
+
+When market is setup with [explicit instantaneous liquidity scoring function](./0091-ILSF-instantaneous_liquidity_scoring_funcion.md) as follows:
+
+- buy-side:
+ - reference: BEST_BID
+ - points: [(0,0.25),(1,0)]
+ - interpolation strategy: FLAT
+
+- sell-side:
+ - reference: BEST_ASK
+ - points: [(0,0.35),(1,0)]
+ - interpolation strategy: FLAT
+
+then all the buy orders deployed at `BEST_BID` get an instantaneous liquidity score of `0.25`, all sell orders deployed at `BEST_ASK` get a score of `0.35` and all other orders get a score of `0`. Updating the risk model has no effect on those scores. (0042-LIQF-095)
+
+When market is setup with [explicit instantaneous liquidity scoring function](./0091-ILSF-instantaneous_liquidity_scoring_funcion.md) as follows:
+
+- buy-side:
+ - reference: MID
+ - points: [(0,0.4),(200,0.2)]
+ - interpolation strategy: FLAT
+
+- sell-side:
+ - reference: MID
+ - points: [(0,0.5),(300,0.3)]
+ - interpolation strategy: FLAT
+
+the decimal places for the asset are, the decimal places for the market are and tick size is. Then buy orders pegged to MID with an offset of `100` get a score of `0.3`, orders with offset of `200` get a score of `0.2` and orders with and offset of `300` also get a score of `0.2`. Sell orders pegged to MID with an offset of `150` get a score of `0.4`, orders with an offset of `300` get a score of `0.3` and orders with an offset of `400` also get a score of `0.3`. Updating the risk model has no effect on those scores. (0042-LIQF-096)
diff --git a/protocol/0053-PERP-product_builtin_perpetual_future.md b/protocol/0053-PERP-product_builtin_perpetual_future.md
index 063494a64..b4c663bb1 100644
--- a/protocol/0053-PERP-product_builtin_perpetual_future.md
+++ b/protocol/0053-PERP-product_builtin_perpetual_future.md
@@ -369,3 +369,9 @@ Launch a perpetual futures market which sets `internalCompositePrice` to `Nil` (
Launch a perpetual futures market which sets `internalCompositePrice` to a configuration which uses the impact notional price from the order book. for the "vega side price" for funding calculation. Submit a market update proposal to change this `Nil` (so that mark price gets used for the vega side price). Observe that the new methodology for funding calculations is applied correctly from enactment onwards. (0053-PERP-045).
Launch a perpetual futures market which uses the "Last Traded Price" for the "vega side price" for funding calculation. Submit a market update proposal to change this to a composite price with a configuration which uses the impact notional price from the order book. Observe that the new methodology for funding calculations is applied correctly from enactment onwards. (0053-PERP-046).
+
+Perps market can be created with [hardcoded risk factors](./0018-RSKM-quant_risk_models.ipynb). (0053-PERP-047)
+
+Updating a risk model on a perps market with [hardcoded risk factors](./0018-RSKM-quant_risk_models.ipynb) results in recalculation of all margin levels in line with hardcoded values and collateral search/release where appropriate. (0053-PERP-048)
+
+Updating a risk model on a perps market with [hardcoded risk factors](./0018-RSKM-quant_risk_models.ipynb) to a regular risk model results in recalculation of all margin levels in line with the specified risk model (hardcoded value are no longer used) and collateral search/release where appropriate. (0053-PERP-049)
diff --git a/protocol/0056-REWA-rewards_overview.md b/protocol/0056-REWA-rewards_overview.md
index 2d72cbadb..bb2eafd9f 100644
--- a/protocol/0056-REWA-rewards_overview.md
+++ b/protocol/0056-REWA-rewards_overview.md
@@ -51,7 +51,7 @@ Although rewards are distributed at the end of an epoch, to give users of the pr
For each game, each entities (individual or team) **current reward metric** and rank in the game is to be updated every `rewards.updateFrequency` seconds. Note, the actual reward metric should be updated and exposed rather than the underlying data in the current epoch, this is important for games where the window length is greater than `1`.
-For reward metrics which can only be calculated at the end of the epoch, ie. [liquidity fees received](#fee-based-reward-metrics); scores are only required to be updated and exposed at the end of the epoch. [Market creation](#market-creation-reward-metrics) and [validator ranking](#validator-ranking-metric) scores will not be published as part of the interim reward information.
+For reward metrics which can only be calculated at the end of the epoch, e.g. [market creation](#market-creation-reward-metrics), [liquidity fees received](#fee-based-reward-metrics), and [validator ranking](#validator-ranking-metric); scores are only required to be updated and exposed at the end of the epoch.
For each game, the following data for each party should also be published every update period so APIs are able to relay information to users about their eligibility and expected rewards.
@@ -130,13 +130,15 @@ Note, as a position can not be created on a Spot market. Trading activity on a S
The return volatility metric, $m_{rv}$, measures the volatility of a parties returns across a number of epochs.
-At the end of an epoch, if a party has had net returns less than or equal to `0` over the last $N$ epochs (where $N$ is the window length specified in the recurring transfer), their reward metric $m_{rv}$ is set to `0`. Otherwise, the network should calculate the variance of the set of each parties returns over the last $N$ epochs.
+At the end of an epoch, if a party has had net returns less than or equal to `0` over the last $N$ epochs (where $N$ is the window length specified in the recurring transfer), their reward metric $m_{rv}$ is set to `0`. Otherwise, the network should calculate the variance of the set of each parties returns over the last $N$ epochs, call this variance $\sigma^2$.
Given the set:
$$R = \{r_i \mid i = 1, 2, \ldots, N\}$$
-The reward metric $m_{rv}$ is the variance of the set $R$.
+The reward metric $m_{rv}$ is the reciprocal of the variance of the set $R$.
+
+$$m_{rv} = \frac{1}{\sigma^2}$$
Note, as a position can not be created on a Spot market. Trading activity on a Spot market will not contribute to this reward metric.
@@ -239,6 +241,8 @@ The entire reward account balance is paid out every epoch unless the total value
Rewards are first [distributed amongst entities](#distributing-rewards-amongst-entities) (individuals or teams) and then any rewards distributed to teams are [distributed amongst team members](#distributing-rewards-amongst-team-members).
+Any rewards earned by an AMM sub-key should be sent as normal to the relevant vesting account for that sub-key. The party owning the sub-key will be able to withdraw any vested rewards using a regular one-off transfer specifying a `from` key (as per the mechanics detailed [here](./0057-TRAN-transfers.md)), or alternatively leave the reward in the vesting / vested accounts to receive a multiplier on any future rewards (as per the mechanics detailed [here](./0085-RVST-rewards_vesting.md#clarification-for-amm-sub-accounts).
+
### Distributing rewards amongst entities
Rewards are distributed amongst entities based on the distribution method defined in the recurring transfer.
@@ -1037,8 +1041,8 @@ At the end of epoch 2, 10000 VEGA rewards should be distributed to the `ETHUSDT`
### Returns volatility
- If an eligible party has net relative returns less than or equal to `0` over the last `window_length` epochs, their returns volatility reward metric should be zero (0056-REWA-088).
-- If an eligible party has net relative returns strictly greater than `0` over the last `window_length` epochs, their returns volatility reward metric should equal the variance of their relative returns over the last `window_length` epochs (0056-REWA-089).
-- If an eligible party has net relative returns strictly greater than `0` over the last `window_length` epochs in multiple in-scope markets, their return volatility reward metric should be the variance of their relative returns in each market (0056-REWA-090).
+- If an eligible party has net relative returns strictly greater than `0` over the last `window_length` epochs, their returns volatility reward metric should equal the reciprocal of the variance of their relative returns over the last `window_length` epochs (0056-REWA-089).
+- If an eligible party has net relative returns strictly greater than `0` over the last `window_length` epochs in multiple in-scope markets, their return volatility reward metric should be the reciprocal of the variance of their relative returns in each market (0056-REWA-090).
### Realised returns
@@ -1103,16 +1107,20 @@ At the end of epoch 2, 10000 VEGA rewards should be distributed to the `ETHUSDT`
- Given a recurring transfer where the entity scope is individuals and the dispatch metric is maker fees paid, a parties reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-136).
- Given a recurring transfer where the entity scope is individuals and the dispatch metric is maker fees received, a parties reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-137).
- Given a recurring transfer where the entity scope is individuals and the dispatch metric is liquidity fees received, a parties reward metric should be updated and published at the end of every epoch. (0056-REWA-138).
+- Given a recurring transfer where the entity scope is individuals and the dispatch metric is market creation, a parties reward metric should be updated and published at the end of every epoch. (0056-REWA-139).
- Given a recurring transfer where the entity scope is individuals and the dispatch metric is average position, a parties reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-140).
- Given a recurring transfer where the entity scope is individuals and the dispatch metric is relative returns, a parties reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-141).
- Given a recurring transfer where the entity scope is individuals and the dispatch metric is returns volatility, a parties reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-142).
+- Given a recurring transfer where the entity scope is individuals and the dispatch metric is validator ranking, a parties reward metric should be updated and published at the end of every epoch. (0056-REWA-143).
- Given a recurring transfer where the entity scope is teams and the dispatch metric is maker fees paid, a teams reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-144).
- Given a recurring transfer where the entity scope is teams and the dispatch metric is maker fees received, a teams reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-145).
- Given a recurring transfer where the entity scope is teams and the dispatch metric is liquidity fees received, a teams reward metric should be updated and published at the end of every epoch. (0056-REWA-146).
+- Given a recurring transfer where the entity scope is teams and the dispatch metric is market creation, a teams reward metric should be updated and published at the end of every epoch. (0056-REWA-147).
- Given a recurring transfer where the entity scope is teams and the dispatch metric is average position, a teams reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-148).
- Given a recurring transfer where the entity scope is teams and the dispatch metric is relative returns, a teams reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-149).
- Given a recurring transfer where the entity scope is teams and the dispatch metric is returns volatility, a teams reward metric should be updated and published every `rewards.updateFrequency` seconds. (0056-REWA-150).
+- Given a recurring transfer where the entity scope is teams and the dispatch metric is validator ranking, a teams reward metric should be updated and published at the end of every epoch. (0056-REWA-151).
### Spot markets
@@ -1132,3 +1140,7 @@ At the end of epoch 2, 10000 VEGA rewards should be distributed to the `ETHUSDT`
- Given a reward metric scoping both spot and leveraged markets, a parties trades in the spot market will correctly contribute to their maker fees paid reward metric. (0056-REWA-165).
- Given a reward metric scoping both spot and leveraged markets, a parties trades in the spot market will correctly contribute to their maker fees received reward metric. (0056-REWA-166).
- Given a reward metric scoping both spot and leveraged markets, a parties received liquidity fees from the spot market will correctly contribute to their liquidity fees received reward metric. (0056-REWA-167).
+
+## vAMMs
+
+- If an AMM sub-key earns rewards, they are transferred into the sub-keys vesting account and locked for the appropriate period before vesting (0056-REWA-170).
diff --git a/protocol/0057-TRAN-transfers.md b/protocol/0057-TRAN-transfers.md
index 65f07cfdf..73c42b703 100644
--- a/protocol/0057-TRAN-transfers.md
+++ b/protocol/0057-TRAN-transfers.md
@@ -101,6 +101,7 @@ To support entity scoping, the transaction include the following fields:
- `INDIVIDUAL_SCOPE_ALL` - all parties on the network are within the scope of this reward
- `INDIVIDUAL_SCOPE_IN_TEAM` - all parties which are part of a team are within the scope of this reward
- `INDIVIDUAL_SCOPE_NOT_IN_TEAM` - all parties which are not part of a team are within the scope of this reward
+ - `INDIVIDUAL_SCOPE_AMM` - all keys representing AMM parties (i.e. excluding those directly controlled by parties) are within the scope of this reward
- `team scope` - optional list if the reward type is `ENTITY_SCOPE_TEAMS`, field allows the funder to define a list of team ids which are eligible to be rewarded from this transfer
- `staking_requirement` - the required minimum number of governance (e.g. VEGA) tokens staked for a party to be considered eligible. Defaults to `0`.
- `notional_time_weighted_average_position_requirement` - the required minimum notional time-weighted averaged position required for a party to be considered eligible. Defaults to `0`.
@@ -410,3 +411,5 @@ If a party sets up a recurring transfer with a `transfer_interval` field strictl
If a party sets up a recurring transfer with a `transfer_interval` field strictly greater than `1`, if they cancel the recurring transfer the locked funds will not be released and the next payout event will happen regardless. (0057-TRAN-078)
If a party sets up a recurring transfer with a transfer interval strictly greater than `1` and specifies a `cap_reward_fee_multiple`. If `calculated_reward_in_quantum > cap_reward_fee_multiple × fees_paid_since_last_payout_in_quantum` then the actual amount of reward transferred to each public key during distribution for this transfer will be `cap_reward_fee_multiple × fees_paid_since_last_payout_in_quantum`(0057-TRAN-079)
+
+A recurring transfer to a reward account with entity scope set to individuals and individual scope set to `INDIVIDUAL_SCOPE_AMM` will only be divided amongst AMM parties based on their score in the relevant metric (0057-TRAN-080)
diff --git a/protocol/0062-SPAM-spam_protection.md b/protocol/0062-SPAM-spam_protection.md
index 87cc36501..d71f80ce8 100644
--- a/protocol/0062-SPAM-spam_protection.md
+++ b/protocol/0062-SPAM-spam_protection.md
@@ -83,6 +83,41 @@ Further, each party is allowed to submit up to `n` transactions per epoch where
**Note** `spam.protection.max.updatePartyProfile` must be an integer greater than or equal to `0` (and default to `5`).
+### Transaction Spam
+
+#### Derivative markets
+
+Before any order or liquidity commitment is accepted for a perpetual futures or expiring futures check that the party has `margin + order margin + general > 0` with `margin` being the balance in the margin account for the relevant, `order margin` is the balance for the order margin if party is in isolated margin mode for the for the relevant market and `general` the balance in the general account for the relevant asset. Orders from parties that don't meet this criteria are rejected. This is to be done after the PoW check.
+
+Further, given the network parameter: `spam.order.minimalMarginQuantumMultiple` (between 0 and infinite)
+
+If the maintenance margin for a given transaction is smaller than the parameter `spam.order.minimalMarginQuantumMultiple`, then the transaction is pre-block rejected.
+I.e. if `(rf + linear slippage param) x size x price < spam.order.minimalMarginQuantumMultiple x asset quantum amount` then the order is rejected. Here `rf` is the risk factor (and will be different for long and for short) `linear slippage param` is a market parameter and `size` and `price` are assumed to be correctly scaled by, PDPs and MDPs respectively.
+
+For pegged orders `mark price +/- offset` should be used in place of price. The `mark price` value can be cached from end of last block (if that helps for performance, we need this to be cheap). In opening auctions pegged orders should be rejected.
+
+For stop / position linked orders: we don't necessarily have either price or size. However there is a limit on the number per key so we let these through.
+
+Finally, if the market does not exist and thus the maintenance margin is not defined, the transaction is rejected.
+
+#### Spot markets
+
+Before any order or liquidity commitment is accepted for a spot market, check that the party has balances where `holding + general > 0` for the asset that the order is potentially disposing of / committing in terms of liquidity. Orders from parties that don't meet this criteria are rejected. This is to be done after the PoW check.
+
+Further, given the network parameter: `spam.order.minimalHoldingQuantumMultiple` (between 0 and infinite)
+
+If the holding requirement for a given transaction is smaller than the parameter `spam.order.minimalHoldingQuantumMultiple`, then the transaction is pre-block rejected.
+I.e. if `size x price < spam.order.minimalHoldingQuantumMultiple x quote asset quantum amount` then the order is rejected. Here `size` and `price` are assumed to be correctly scaled by, PDPs and MDPs respectively.
+
+For pegged orders `last traded price +/- offset` should be used in place of price. The `last traded price` value can be cached from end of last block (if that helps for performance). In opening auctions pegged orders should be rejected.
+
+#### All markets
+
+The following points apply generally to both derivative and spot markets:
+
+- For amendments and cancellations: Check that the party has at least `spam.order.minimalMarginQuantumMultiple` margin within the margin account on the market of the order, implying they have active orders or positions on the market. If they do not then reject the transaction.
+- For batch transactions: each order has to pass its own order spam check; if any order in the batch fails the check then reject the whole batch.
+- Checks should be completed before the gas cost calculation as rejected transactions should not get into the calculation of the gas cost.
### Related topics
@@ -128,4 +163,42 @@ More than 360 delegation changes in one epoch (or, respectively, the value of `s
- A party who has submitted strictly more than `spam.protection.max.updatePartyProfile` `UpdatePartyProfile` transactions in an epoch should have any future `UpdatePartyProfile` transactions in that epoch **pre-block** rejected (0062-SPAM-038).
- A party who has submitted more than `spam.protection.max.updatePartyProfile` `UpdatePartyProfile` transactions in the current epoch plus in the current block, should have their `UpdatePartyProfile` transactions submitted in the current block **pre-block** rejected (0062-SPAM-039).
+- In a derivatives market, issue a set of orders starting with the minimum price, and doubling the order price with every order. Once the first order passes the spam filter, quadruple the parameter `spam.order.minimalMarginQuantumMultiple` and continue. Once the next order passes the filter, quadruple the quantum size for the underlying asset, and continue until an order passes the filter again. Verify that all rejected orders had a margin smaller than `spam.order.minimalMarginQuantumMultiple`, and all accepted ones one bigger or equal. (0062-SPAM-043).
+- In a derivatives market, issue a set of orders for an existing, but not yet enacted market, starting with the minimum price, and doubling the order price with every order. Once the first order passes the spam filter, quadruple the parameter `spam.order.minimalMarginQuantumMultiple` and continue. Once the next order passes the filter, quadruple the quantum size for the underlying asset, and continue until an order passes the filter again. Verify that all rejected orders had a margin smaller than `spam.order.minimalMarginQuantumMultiple`, and all accepted ones one bigger or equal. (0062-SPAM-044).
+- Create an order for a non-existing derivatives market, and verify that it is rejected by the spam filter. (0062-SPAM-045).
+- In a derivatives market, pegged orders are rejected during an opening auction (0062-SPAM-047).
+- In a derivatives market, Pegged orders are accepted once the market has a mark price and the mark price is used as the reference price for the spam check purposes and the order meets `spam.order.minimalMarginQuantumMultiple` requirement (0062-SPAM-048).
+- In a derivatives market, pegged orders are rejected if the market has a mark price and the mark price is used as the reference price for the spam check purposes *but* the order fails `spam.order.minimalMarginQuantumMultiple` requirement (0062-SPAM-049).
+- In a derivatives market, batch order is accepted if all orders in batch individually meet the `spam.order.minimalMarginQuantumMultiple` requirement (0062-SPAM-050).
+- In a derivatives market, batch order is rejected if one or more orders in batch individually *fail to meet* the `spam.order.minimalMarginQuantumMultiple` requirement (0062-SPAM-062).
+- In a derivatives market, order amends are accepted if the order with the new price / size meets the `spam.order.minimalMarginQuantumMultiple` requirement (0062-SPAM-051).
+- In a derivatives market, order amends are rejected if the order with the new price / size meets *fails to meet* the `spam.order.minimalMarginQuantumMultiple` requirement (0062-SPAM-052).
+
+- In a spot market, issue a set of orders starting with the minimum price, and doubling the order price with every order. Once the first order passes the spam filter, quadruple the parameter `spam.order.minimalHoldingQuantumMultiple` and continue. Once the next order passes the filter, quadruple the quantum size for the underlying asset, and continue until an order passes the filter again. Verify that all rejected orders had a margin smaller than `spam.order.minimalHoldingQuantumMultiple`, and all accepted ones one bigger or equal. (0062-SPAM-064).
+- In a spot market, pegged orders are rejected during an opening auction (0062-SPAM-066).
+- In a spot market, pegged orders are accepted once the market has a last trade price and the last trade price is used as the reference price for the spam check purposes and the order meets `spam.order.minimalHoldingQuantumMultiple` requirement (0062-SPAM-067).
+- In a spot market, pegged orders are rejected if the market has a last trade price and the last trade price is used as the reference price for the spam check purposes *but* the order fails `spam.order.minimalHoldingQuantumMultiple` requirement (0062-SPAM-068).
+- In a spot market, batch order is accepted if all orders in batch individually meet the `spam.order.minimalHoldingQuantumMultiple` requirement (0062-SPAM-069).
+- In a spot market, batch order is rejected if one or more orders in batch individually *fail to meet* the `spam.order.minimalHoldingQuantumMultiple` requirement (0062-SPAM-070).
+- In a spot market, order amends are accepted if the order with the new price / size meets the `spam.order.minimalHoldingQuantumMultiple` requirement (0062-SPAM-071).
+- In a spot market, order amends are rejected if the order with the new price / size meets *fails to meet* the `spam.order.minimalHoldingQuantumMultiple` requirement (0062-SPAM-072).
+
+#### Balance checks
+
+On perps and futures markets order are rejected `margin + order margin + general = 0` with `margin` being the margin account balance for the relevant market, `order margin` being the order margin account balance for the relevant market and general being the general account balance for the settlement asset for the market for
+
+- market orders in cross margin mode and in isolated margin mode (0062-SPAM-053).
+- limit orders in cross margin mode and in isolated margin mode (0062-SPAM-054).
+- pegged orders in cross margin mode and in isolated margin mode (0062-SPAM-055).
+- liquidity commitment (0062-SPAM-056).
+- stop-loss / position-linked order in cross margin mode and in isolated margin mode (0062-SPAM-057).
+
+On spot markets orders are rejected if `holding + general = 0` for the asset that the order is (or potentially is) disposing of with the following order types
+
+- market orders (0062-SPAM-063).
+- limit orders (0062-SPAM-058).
+- pegged orders (0062-SPAM-059).
+- liquidity commitment (0062-SPAM-060).
+- stop-loss / position-linked order (0062-SPAM-061).
+
> **Note**: If other governance functionality (beyond delegation-changes, votes, and proposals) are added, the spec and its acceptance criteria need to be augmented accordingly. This issue will be fixed in a follow up version.
diff --git a/protocol/0068-MATC-matching_engine.md b/protocol/0068-MATC-matching_engine.md
index 2529434c6..b9fd4870f 100644
--- a/protocol/0068-MATC-matching_engine.md
+++ b/protocol/0068-MATC-matching_engine.md
@@ -118,6 +118,22 @@ Given an order book that looks like this in the market display:
| | 90 | 10 |
| | 80 | 15 |
+
+### Matching Process
+
+For all incoming active orders, the matching process will coordinate between the on- and off-book sources of liquidity. When an order comes in which may immediately trade (there are not already resting orders of the same type for the best applicable price) the following steps should be followed. If at any point the order's full volume has traded the process is immediately halted:
+
+ 1. For the first applicable price level (the first valid price with no orders on the same side, implying an order could theoretically immediately trade there, one tick above best bid for an incoming buy and one tick below best ask for an incoming sell), all on-book orders should be checked. Any volume at this price level which can be met through on-book orders will then trade.
+ 1. For any `remaining volume`, the AMMs will be checked. This requires an algorithm to ensure the protocol does not have to check every price level individually:
+ 1. Call the current price level `current price`
+ 1. Check the price level which has the next resting on-book order, set this to be the `outer price` for the check.
+ 1. Check all active AMMs, querying their quote price API with the smallest trade unit on the market in the direction of trading (if the incoming order is a `buy`, query the AMM's `ask`, or vice versa). Retain those where this price < `outer price`
+ 1. Within these, select either the minimum `upper price` (if the incoming order is a buy) or the maximum `lower price` (if the incoming order is a sell), call this `amm bound price`. This is the range where all of these AMMs are active. Finally, select either the minimum (for a buy) or maximum (for a sell) between `amm bound price` and `outer price`. From this form an interval `current price, outer price`.
+ 1. Now, for each AMM within this range, calculate the volume of trading required to move each from the `current price` to the `outer price`. Call the sum of this volume `total volume`.
+ 1. If `remaining volume <= total volume` split trades between the AMMs according to their proportional contribution to `total volume` (e.g. larger liquidity receives a higher proportion of the trade). This ensures their mid prices will move equally. Each of these trades should count as a single aggressive trade with the given AMM and pay fees accordingly.
+ 1. If `remaining volume > total volume` execute all trades to move the respective AMMs to their boundary at `outer price`. Now, return to step `1` with `current price = outer price`, checking first for on-book liquidity at the new level then following this process again until all order volume is traded or liquidity exhausted.
+ 1. For all trades generated in the previous two steps, an execution price should be calculated using the AMM's API to return a price for a given volume. These prices should be rounded to the nearest valid market tick in the AMM's favour (i.e. round upwards when the AMM is selling and downwards when the AMM is buying). This process can be optimised as when splitting trades between AMMs according to their respective commitment sizes they will each end up at the same executed price, so only one AMM's price need be calculated.
+
## See also
- [0008-TRAD-Trading Workflow](./0008-TRAD-trading_workflow.md)
diff --git a/protocol/0073-LIMN-limited_network_life.md b/protocol/0073-LIMN-limited_network_life.md
deleted file mode 100644
index 756469fc3..000000000
--- a/protocol/0073-LIMN-limited_network_life.md
+++ /dev/null
@@ -1,428 +0,0 @@
-# Limited network life
-
-Vega networks will at least initially and perhaps always run for a limited time only. This spec covers the necessary features to ensure this works smoothly.
-
-## Background
-
-Networks will have a finite lifetime because:
-
-- It is efficient to upgrade the protocol by starting again as it avoids the need to deal with multiple versions of the code (upgrades to a running chain need to respect and be able to recalculate the pre-upgrade deterministic state for earlier blocks, so all versions of critical code must remain in the system).
-This is especially important early on when rapid iteration is desirable, as the assumption that new chains can be started for new features simplifies things considerably.
-
-- Trading at 1000s of tx/sec generates a lot of data. Given that most instruments are non-perpetual (they expire), this gives the ability to create new markets on a new chain and naturally let the old one come to an end rather than dragging around its history forever.
-
-- Bugs, security breaches, or other issues during alpha could either take out the chain OR make it desirable to halt block production. It's important to consider what happens next if this occurs.
-
-## Overview
-
-There are four main features:
-
-1. Create checkpoints with relevant (but minimal) information at regular intervals, and on every withdrawal request.
-2. Ability to specify a checkpoint hash as part of genesis.
-3. A new 'Restore' transaction that contains the full checkpoint file and triggers state restoration
-4. A new 'checkpoint hash' transaction is broadcast by all validators
-
-Point two requires that at load time, each node calculates the hash of the checkpoint file. It then sends this through consensus to make sure that all the nodes in the new network agree on the state.
-
-## Creating a checkpoint
-
-Information to store:
-
-- All [network parameters](../protocol/0054-NETP-network_parameters.md), including those defined [below](#network-parameters).
-- All [asset definitions](../protocol/0040-ASSF-asset_framework.md#asset-definition).
-- Insurance pool balances (global for each asset and per-market), [Reward account balance](../protocol/0056-REWA-rewards_overview.md) and [LP committed liquidity](./0044-LIME-lp_mechanics.md) balances for the markets that have been enacted will be stored with the accepted market proposal that must have preceded the market.
-- All market proposals ([creation](../protocol/0028-GOVE-governance.md#1-create-market) and [update](../protocol/0028-GOVE-governance.md#2-change-market-parameters)) that have been *accepted* but not those where the market already started trading and reached *trading terminated* state.
-- All [asset proposals](../protocol/0028-GOVE-governance.md) that have been *accepted*.
-- All delegation info.
-- [LP fee pool](../protocol/0029-FEES-fees.md) for undistributed LP fee balances on a market will be added to the LP's general account balances (without applying any SLA penalties).
-- On chain treasury balances and on-chain rewards for staking and delegation [Staking and delegation](../protocol/0056-REWA-rewards_overview.md).
-- [Account balances](../protocol/0013-ACCT-accounts.md) for all parties per asset: sum of general, margin and LP bond accounts.
-- Event ID of the last processed deposit event for all bridged chains
-- Withdrawal transaction bundles for all bridged chains.
-- Hash of the previous block, block number and transaction id of the block from which the snapshot is derived
-- ERC-20 collateral:
- - last block height of a confirmed ERC-20 deposit on the Ethereum chain with `number_of_confirmations`. [Ethereum bridge](./0031-ETHB-ethereum_bridge_spec.md#network-parameters)
- - all pending ERC-20 deposits (not confirmed before this block) [Ethereum bridge](./0031-ETHB-ethereum_bridge_spec.md#deposits)
-- Staking:
- - last block of a confirmed stake_deposit on the staking contract on the Ethereum chain with `number_of_confirmations`. [Ethereum bridge](./0031-ETHB-ethereum_bridge_spec.md#network-parameters)
- - last block of a confirmed stake_deposit on the vesting contract on the Ethereum chain with `number_of_confirmations`. [Ethereum bridge](./0031-ETHB-ethereum_bridge_spec.md#network-parameters)
- - all the staking events from both contracts [staking](./0059-STKG-simple_staking_and_delegating.md)
- - all the pending staking events [staking](./0059-STKG-simple_staking_and_delegating.md)
-
-When to create a checkpoint:
-
-- if `current_time - network.checkpoint.timeElapsedBetweenCheckpoints > time_of_last_full_checkpoint`
-
-Information we explicitly don't try to checkpoint:
-
-- Positions, limit orders, pegged orders or any order book data. LP commitments.
-- Market and asset proposals where the voting period hasn't ended.
-
-When a checkpoint is created, each validator should calculate its hash and submit this is a transaction to the chain, so that non-validating parties can trust the hash being restored represents truly the balances.
-
-The checkpoint file should either be human-readable OR there should be a command line tool to convert into human readable form.
-
-## Specifying the checkpoint hash in genesis
-
-## Restoring a checkpoint
-
-The hash of the state file to be restored must be specified in genesis.
-Any validator will submit a transaction containing the checkpoint file. Nodes verify the hash / chain of hashes to verify the hash that is in genesis.
-
-- If the hash matches, it will be restored.
-- If it does not, the hash transaction will have no effect.
-
-If the genesis file has a previous state hash, all transactions will be rejected until the restore transaction arrives and is processed.
-
-The state will be restored in this order:
-
-- Restore network parameters.
-- Load the asset definitions.
- - The network will compare the asset coming from the restore file with the genesis assets, one by one. If there is an exact match on asset id:
- - either the rest of the asset definition matches exactly in which case move to next asset coming from restore file.
- - or any of the part of the definition differ, in which case ignore the entire restore transaction, the node should stop with an error.
- - If the asset coming from the restore file is a new asset (asset id not matching any genesis assets) then restore the asset.
-- Load the accepted market proposals. If the enactment date is in the past then set the enactment date to `now + net_param_min_enact` (so that opening auction can take place) and status to pending.
-- Replay events from bridged chains
- - Concerning bridges used to deposit collateral for trading, replay from the last block specified in the checkpoint and reload the pending deposits from the checkpoint so the network can start again to confirm these events.
- - Concerning the staking bridges, all balances will be reconciled using the staking events from the checkpoint, up to the last seen block store as part of the checkpoint, then apply again the delegations to the validators.
-
-There should be a tool to extract all assets from the restore file so that they can be added to genesis block manually, should the validators so desire.
-
-## Restoring balances
-
-- Participants need access to funds after network ends. This will be facilitated by using restoration of balances to allow participants to withdraw or continue to trade with funds during the next iteration of the chain.
-
-## Network parameters
-
-| Name | Type | Description |
-|----------------------------------------------------------|:--------:|-------------------------------------------------------------------|:--------:|
-|`network.checkpoint.timeElapsedBetweenCheckpoints` | String (duration) | sets the minimum time elapsed between checkpoints|
-
-If for `network.checkpoint.timeElapsedBetweenCheckpoints` the value is set to `0` or the parameter is undefined then no checkpoints are created. Otherwise any time-length value `>0` is valid e.g. `1min`, `2h30min10s`, `1month`. If the value is invalid Vega should not start e.g. if set to `3 fish`.
-
-## Acceptance criteria
-
-- Checkpoints are created every `network.checkpoint.timeElapsedBetweenCheckpoints` period of time passes. (0073-LIMN-001)
-- Checkpoint is created every time a party requests a withdrawal transaction on any chain. (0073-LIMN-002)
-- We can launch a network with any valid checkpoint file. (0073-LIMN-003)
-- Hash of the checkpoint file is agreed via consensus. (0073-LIMN-005)
-
-### Test case 1: Withdrawal status is correctly tracked across resets (0073-LIMN-007)
-
-1. A party has general account balance of 100 USD.
-1. The party submits a withdrawal transaction for 100 USD. A checkpoint is immediately created.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint replay transaction is submitted and processed.
-1. The check the following sub-cases:
- - If the Ethereum replay says withdrawal completed. The party has general account balance of 0 USD. The party has "signed for withdrawal" 0.
- - If the Ethereum replay hasn't seen withdrawal transaction processed and the expiry time of the withdrawal hasn't passed yet. Then the party has general account balance of 0 USD. The party has "signed for withdrawal" 100.
- - If the Ethereum replay hasn't seen withdrawal transaction processed and the expiry time of the withdrawal has passed. Then the party has general account balance of 100 USD.
-
-### Test case 2: Orders and positions are *not* maintained across resets, balances are and *accepted* markets are (0073-LIMN-008)
-
-1. There is an asset USD and no asset proposals.
-1. There is a market `id_xxx` with status active, no other markets and no market proposals.
-1. There are two parties: one LP for the market and one party that is not an LP.
-1. The LP has a long position on `LP_long_pos`.
-1. The other party has a short position `other_short_pos = LP_long_pos`.
-1. The other party has a limit order on the book.
-1. The other party has a pegged order on the book.
-1. The LP has general balance of `LP_gen_bal`, margin balance `LP_margin_bal` and bond account balance of `LP_bond_bal`, all in `USD`
-1. The other party has general balance of `other_gen_bal`, margin balance `other_margin_bal` and bond account balance of `0`, all in `USD`.
-1. Enough time passes so a checkpoint is created and no party submitted any withdrawal transactions throughout.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is an asset USD.
-1. There is a market `id_xxx` in status "pending".
-1. The party LP has a `USD` general account balance equal to `LP_gen_bal + LP_margin_bal` + `LP_bond_bal`.
-1. The other party has a `USD` general account balance equal to `other_gen_bal + other_margin_bal`.
-
-### Test case 3: Governance proposals are maintained across resets, votes are not
-
-#### Test case 3.1: Market is proposed, accepted, restored (0073-LIMN-009)
-
-1. There is an asset USD and no asset proposals.
-1. There are no markets and no market proposals.
-1. There is a party a party called `LP party` with general balance of 10 000 USD.
-1. A market is proposed by a party called `LP party` and has enactment date 1 year in the future. The market has id `id_xxx`.
-1. `LP party` commits a stake of 1000 USD to `id_xxx`.
-1. Other parties vote on the market and the proposal is accepted (passes rules for vote majority and participation). The market has id `id_xxx`.
-1. The market is in `pending` state, see [market lifecycle](../protocol/0043-MKTL-market_lifecycle.md).
-1. Another party places a limit sell order on the market and has `other_gen_bal`, margin balance `other_margin_bal`.
-1. Enough time passes so a checkpoint is created and no party submitted any withdrawal transactions throughout.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is an asset USD.
-1. There is a market with `id_xxx` with all the same parameters as the accepted proposal had.
-1. The LP party has general account balance in USD of `9000` and bond account balance `1000` on the market `id_xxx`.
-1. The other party has no open orders anywhere and general account balance in USD of `other_gen_bal + other_margin_bal`.
-
-#### Test case 3.1.2: Perpetual market is proposed, accepted, restored (0073-LIMN-105)
-
-1. There is an asset USD and no asset proposals.
-1. There are no markets and no market proposals.
-1. There is a party a party called `LP party` with general balance of 10 000 USD.
-1. A market is proposed by a party called `LP party` and has enactment date 1 year in the future. The market has id `id_xxx`.
-1. `LP party` commits a stake of 1000 USD to `id_xxx`.
-1. Other parties vote on the market and the proposal is accepted (passes rules for vote majority and participation). The market has id `id_xxx`.
-1. The market is in `pending` state, see [market lifecycle](../protocol/0043-MKTL-market_lifecycle.md).
-1. Another party places a limit sell order on the market and has `other_gen_bal`, holding balance `other_hold_bal`.
-1. Enough time passes so a checkpoint is created and no party submitted any withdrawal transactions throughout.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is an asset USD.
-1. There is a market with `id_xxx` with all the same parameters as the accepted proposal had.
-1. The LP party has general account balance in USD of `9000` and bond account balance `1000` on the market `id_xxx`.
-1. The other party has no open orders anywhere and general account balance in USD of `other_gen_bal + other_hold_bal`.
-
-#### Test case 3.2: Market is proposed, voting hasn't closed, not restored (0073-LIMN-010)
-
-for product perpetuals: (0073-LIMN-106)
-
-1. There is an asset USD and no asset proposals.
-1. There are no markets and no market proposals.
-1. There is a party a party called `LP party` with general balance of 10 000 USD.
-1. A market is proposed by a party called `LP party`.
-1. `LP party` commits a stake of 1000 USD.
-1. The voting period ends 1 year in the future. The enactment date is 2 years in the future.
-1. Enough time passes (but less than 1 year) so a checkpoint is created and no party submitted any withdrawal transactions throughout.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is an asset USD.
-1. There is no market and there are no market proposals.
-
-#### Test case 3.3: Market is proposed, voting has closed, market rejected, proposal not restored (0073-LIMN-011)
-
-for product perpetuals:(0073-LIMN-107)
-
-1. There is an asset USD and no asset proposals.
-1. There are no markets and no market proposals.
-1. There is a party a party called `LP party` with general balance of `10 000` USD.
-1. A market is proposed by a party called `LP party`.
-1. The voting period ends 1 minute in the future. The enactment date is 2 years in the future.
-1. More than 1 minute has passed and the minimum participation threshold hasn't been met. The market proposal status is `rejected`.
-1. Enough time passes after the market has been rejected so a checkpoint is created and no party submitted any withdrawal transactions throughout.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is an asset USD.
-1. There is no market and there are no market proposals.
-1. The LP party has general account balance in USD of `10 000`.
-
-#### Test case 3.4: Recovery from proposed Markets with no votes, voting is open, proposal not restored (0073-LIMN-012)
-
-
-for product perpetuals:(0073-LIMN-108)
-
-1. There is an asset USD and no asset proposals.
-1. There are no markets and no market proposals.
-1. There is a party a party called `LP party` with general balance of 10 000 USD.
-1. A market is proposed by a party called `LP party`.
-1. Checkpoint is taken during voting period.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is an asset USD.
-1. There is no market and there are no market proposals.
-1. The LP party has general account balance in USD of `10 000`.
-
-#### Test case 3.5: Recovery from proposed Markets with votes, voting is open, proposal not restored (0073-LIMN-013)
-
-
-for product perpetuals:(0073-LIMN-109)
-
-
-1. There is an asset USD and no asset proposals.
-1. There are no markets and no market proposals.
-1. There is a party a party called `LP party` with general balance of 10 000 USD.
-1. A market is proposed by a party called `LP party`.
-1. Checkpoint is taken during voting period
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is an asset USD.
-1. There is no market and there are no market proposals.
-1. The LP party has general account balance in USD of `10 000`.
-
-#### Test case 3.6: Market proposals ignored when restoring twice from same checkpoint (0073-LIMN-014)
-
-for product perpetuals:(0073-LIMN-110)
-
-
-1. A party has general account balance of 100 USD.
-1. The party submits a withdrawal transaction for 100 USD. A checkpoint is immediately created.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. A new market is proposed
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is no market and there are no market proposals.
-1. The party has general account balance in USD of `0` and The party has "signed for withdrawal" `100`.
-
-### Test case 4a: Party's Margin Account balance is put in to a General Account balance for that asset after a reset (0073-LIMN-016) (for perpetuals: 0073-LIMN-111)
-
-1. A party has USD general account balance of 100 USD.
-2. That party has USD margin account balance of 100 USD.
-3. The network is shut down.
-4. The network is restarted with the checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-5. That party has a USD general account balance of 200 USD
-
-### Test case 5: Add or remove stake during checkpoint restart (0073-LIMN-017)
-
-1. There is a Vega token asset.
-1. There are `5` validators on the network.
-1. Each validator party `validator_party_1`,...,`validator_party_5` has `1000` Vega tokens locked on the staking Ethereum bridge and this is reflected in Vega core.
-1. There are `N` other parties. Each of the other parties has `other_party_i`, `i=1,2,...,N` has locked exactly `i` tokens on that staking Ethereum bridge and these tokens are undelegated at this point.
-1. Other party `i` delegates all its tokens to `validator_party_j` with `j = i mod 5` (i.e. the remainder after integer division of `j` by `i`.). For example if `N=20000` then party `i=15123` will delegate all its `15123` tokens to validator `validator_party_3` since `15123 mod 5 = 3`.
-1. The `Staking and delegation` rewards are active so that every hour each party that has delegated tokens receives `0.01` of the delegated amount as a reward.
-1. Each of the `other_party_i` has Vega token general account balance equal to `5 x 0.01 x i`. Note that these are separate from the tokens locked on the staking Ethereum bridge.
-1. Enough time passes so that a checkpoint is created and no party submitted any withdrawal transactions throughout.
-1. The network is shut down.
-1. One party `1` with stake delegated has freed `500` tokens from the Vega Ethereum staking contract.
-1. One party `2` with stake delegated adds `500` tokens to the Vega Ethereum staking contract.
-1. The network is restarted with the same `5` validators and checkpoint hash from the above checkpoint in genesis. The checkpoint restore transaction is submitted and processed.
-1. There is a Vega token asset.
-1. Validator parties `validator_party_1`,...,`validator_party_5` has `1000` Vega tokens locked on the staking Ethereum bridge and this is reflected in Vega core.
-1. Other party `1` has `-500` Vega tokens locked on the staking Ethereum bridge and this is reflected in Vega core, including updated delegation amounts.
-1. Other party `2` has `+500` Vega tokens locked on the staking Ethereum bridge and this is reflected in Vega core, including updated delegation amounts via auto delegation.
-1. There are `N-2` other parties and the delegation info in core says that other party `i` has delegated all its tokens to `validator_party_j` with `j = i mod 5`.
-1. Each of the `other_party_i` has Vega token general account balance equal to `5 x 0.01 x i`. Note that these are separate from the tokens locked on the staking Ethereum bridge.
-
-### Test case 6: Network Parameters / Exceptional case handling
-
-#### Test case 6.1: `timeElapsedBetweenCheckpoints` not set
-
-#### Test case 6.2: `timeElapsedBetweenCheckpoints` set to value outside acceptable range
-
-### Test case 11: Rewards are distributed correctly every epoch including with the use of recurring transfers (0073-LIMN-022)
-
-- More than one party deposits stake onto Vega
-- The parties delegate stake to the validators
-- Setup the rewards:
- - A party deposits VEGA funds to their Vega general account
- - The party creates a continuing recurring transfer (for e.g: 1 token) from their general account to the reward pool
-- Assert that every end of epoch, the funds are distributed, over the parties delegating stake, at end of every epoch
-- Wait for the next checkpoint, then stop the network
-- Load the checkpoint into a new network
-- Assert that at every epoch, the recurring transfers to the reward pool continues to happen, and that the funds are properly being distributed to the delegator
-
-### Test case 12
-
-1. Enacted, listed ERC-20 asset is remembered in checkpoint (0073-LIMN-023)
-1. An ERC-20 asset loaded from checkpoint can be used in a market loaded from a checkpoint (0073-LIMN-024)
-1. An ERC-20 asset loaded from checkpoint can be updated (0073-LIMN-025)
-1. An ERC-20 asset loaded from checkpoint can be used in newly proposed markets (0073-LIMN-026).
-1. Can deposit and withdraw funds to/from ERC-20 asset loaded from checkpoint (0073-LIMN-027)
-
-1. Propose a valid ERC-20 asset.
-1. Wait for the next checkpoint, then stop the network.
-1. Load the checkpoint into a new network
-1. Assert that the proposal and the asset have been reloaded into the network with the correct settings.
-1. Propose a new market using this asset
-1. Deposit funds to traders via the bridge and assert that funds are received.
-1. Place orders on the market that will cross.
-1. Withdraw funds for one of the traders.
-1. Propose an update to the asset, and ensure that you can update the ERC20 bridge with the asset update and signature bundle.
-
-### Test case 13: A market with future enactment date can become enacted after being restored from checkpoint (0073-LIMN-028)
-
-1. There is an asset USD and no asset proposals.
-1. There are no markets and no market proposals.
-1. There is a party a party called `LP party` with general balance of 10 000 USD.
-1. A market is proposed by a party called `LP party` and has enactment date several minutes in the future. The market has id `id_xxx`.
-1. `LP party` commits a stake of 1000 USD to `id_xxx`.
-1. Other parties vote on the market and the proposal is accepted (passes rules for vote majority and participation). The market has id `id_xxx`.
-1. The market is in `pending` state, see [market lifecycle](../protocol/0043-MKTL-market_lifecycle.md).
-1. Enough time passes so a checkpoint is created and no party submitted any withdrawal transactions throughout.
-1. The network is shut down.
-1. The network is restarted with the checkpoint hash from the above checkpoint in genesis.
-1. There is an asset USD.
-1. There is a market with `id_xxx` with all the same parameters as the accepted proposal had.
-1. The LP party has general account balance in USD of `9000` and bond account balance `1000` on the market `id_xxx`.
-1. The market is still in "pending" state.
-1. The market becomes enacted when the enactment time is passed.
-1. Other parties can trade on the market, and become continuous.
-
-### Test case 14: Market with trading terminated is not restored, collateral moved correctly
-
-1. Set LP fee distribution time step to non-zero value.
-1. Propose, enact, trade in the market, close out distressed party so that market's insurance pool balance > 0, submit trading terminated.
-1. System saves LNL checkpoint at a time when undistributed LP fees for the market are > 0.
-1. Restart Vega, load LNL checkpoint.
-1. The market is not restored (it doesn't exist in core i.e. it's not possible to submit orders or LP provisions to this market) (0073-LIMN-029).
-1. If the market exists in the data node it is marked as settled with no settlement price info (0073-LIMN-030)
-1. For parties that had margin balance position on the market this is now in their general account for the asset. (0073-LIMN-031)
-1. The LP fees that were not distributed have been transferred to the Vega treasury for the asset. (0073-LIMN-032).
-1. The insurance pool balance has been transferred into the global insurance pool using the same settlement asset. (0073-LIMN-115)
-1. The LP bond account balance has been transferred to the party's general account for the asset. (0073-LIMN-034).
-
-
-### Test case 15: Market with trading terminated that settled is not restored, collateral moved correctly
-
-1. Propose, enact, trade in the market, submit trading terminated and settlement data, observe final settlement cashflows for at least 2 parties.
-1. System saves LNL checkpoint.
-1. Restart Vega, load LNL checkpoint.
-1. The market is not restored (it doesn't exist in core i.e. it's not possible to submit orders or LP provisions to this market) (0073-LIMN-040).
-1. If the market exists in the data node it is marked as settled with correct settlement data. (0073-LIMN-041)
-1. For parties that had margin balance position on the market this is now in their general account for the asset. (0073-LIMN-042
-1. The insurance pool balance has been transferred into the global insurance pool using the same settlement asset. (0073-LIMN-116)
-1. The LP bond account balance has been transferred to the party's general account for the asset. (0073-LIMN-044).
-
-### Test case 16: Markets can be settled and terminated after restore as proposed
-
-1. Propose, enact a market with some trading termination and settlement date setting. Trade in the market creating positions for at least 2 parties.
-1. System saves LNL checkpoint.
-1. Restart Vega, load LNL checkpoint.
-1. A party submits liquidity provision to the market, orders are submitted to the opening auction to allow uncrossing; at least two parties now have a position.
-1. Submit the trading terminated transaction and settlement date transaction as set out in the proposal and observe the final settlement cashflows for the parties with positions. (0073-LIMN-050)
-1. It's not possible to submit orders or LP provisions to this market. (0073-LIMN-051).
-
-### Test case 17: Markets with internal time trigger for trading terminated that fires between shutdown and restore
-
-1. Propose, enact a market with some trading terminated given by internal time trigger. Trade in the market creating positions for at least 2 parties.
-1. System saves LNL checkpoint before the trading terminated trigger is set off.
-1. Restart Vega, load LNL checkpoint at a time which is after trading terminated trigger should have rung.
-1. The market is not restored (it doesn't exist in core i.e. it's not possible to submit orders or LP provisions to this market) (0073-LIMN-060).
-1. If the market exists in the data node it is labelled as `cancelled` (0073-LIMN-061).
-1. For parties that had margin balance position on the market this is now in their general account for the asset. (0073-LIMN-062)
-1. The LP fees that were not distributed have been transferred to the Vega treasury for the asset. (0073-LIMN-063).
-1. The insurance pool balance has been transferred into the global insurance pool using the same settlement asset. (0073-LIMN-117)
-1. The LP bond account balance has been transferred to the party's general account for the asset. (0073-LIMN-065).
-
-### Test case 18: market definition is the same pre and post LNL restore
-
-- Propose a market
-- System saves LNL checkpoint.
-- Restart Vega, load LNL checkpoint.
-- The market has the same:
- - risk model and parameters (0073-LIMN-070).
- - price monitoring bounds (0073-LIMN-071).
- - oracle settings (0073-LIMN-072).
- - margin scaling factors (0073-LIMN-073)
-
-### Test case 19: Deposit tokens during checkpoint restore
-
-1. On a vega network which has some ERC20 tokens enabled.
-1. Wait for a checkpoint to be available for checkpoint restart.
-1. Stop the network.
-1. Deposit tokens to a vega party via the ERC20 assert bridge.
-1. Restart the vega network from the checkpoint created earlier.
-1. There party's newly deposited assets are available. (0073-LIMN-074).
-
-### Test case 20: Multisig updates during checkpoint restart
-
-1. On a vega network where one validator has been promoted in favour of another (do not update multisig contract to reflect this), and there are tokens in reward accounts ready for distribution.
-1. Wait for a checkpoint to be available for checkpoint restart.
-1. Retrieve the signatures to update the multisig contract (do not update yet).
-1. Stop the network.
-1. Update the multisig contract.
-1. Restart the vega network from the checkpoint created earlier.
-1. Vega observes the multisig change and rewards are paid at the end of the current epoch. (0073-LIMN-075)
-
-### Test case 21: Loading from checkpoint with invalid multisig
-
-1. On a vega network where one validator has been promoted in favour of another (do not update multisig contract to reflect this), and there are tokens in reward accounts ready for distribution.
-1. Wait for a checkpoint to be available for checkpoint restart.
-1. Retrieve the signatures to update the multisig contract (do not update yet).
-1. Stop the network.
-1. Do not update the multisig contract.
-1. Restart the vega network from the checkpoint created earlier.
-1. Vega observes the incorrect multisig, and rewards are not paid at the end of the current epoch. (0073-LIMN-076)
diff --git a/protocol/0085-RVST-rewards_vesting.md b/protocol/0085-RVST-rewards_vesting.md
index 6c4938b58..c91f37e29 100644
--- a/protocol/0085-RVST-rewards_vesting.md
+++ b/protocol/0085-RVST-rewards_vesting.md
@@ -39,6 +39,30 @@ Alternatively, they can leave their rewards in the vested account to increase th
Note, a party will be unable to transfer funds in to the vested account.
+### Clarification for AMM sub accounts
+
+For a party which has created one or more AMMs, any rewards earned by those AMMs will be paid into the relevant vesting account of the sub-key associated with that AMM (and then vested over time into the sub-keys vested account). When calculating the rewards balance used to set the multiplier in this case, the balance of each of a parties sub-keys vesting and vested accounts should be aggregated, and the resulting rewards multiplier set for all sub-keys.
+
+For example:
+
+```pseudo
+- A party has accumulated the following rewards
+ - locked_quantum_amount = 20
+ - vesting_quantum_amount = 30
+ - vested_quantum_amount = 150
+
+- And has created an AMM which has accumulated the following rewards
+ - locked_quantum_amount = 200
+ - vesting_quantum_amount = 300
+ - vested_quantum_amount = 500
+
+Their total reward balance should be (20+30+50) + (200+300+500) = (100) + (1000) = 1100
+```
+
+A party will be able to redeem rewards earned by an AMM sub-key by submitting a transfer transaction signed with their primary key. This transfer must be from the sub-keys vested account and to the primary keys general account. As with the mechanics for redeeming rewards normally from a primary key's general account, these transfers will not incur any fees and if transferring the full balance will not be subject to the minimum quantum transfer amount requirement.
+
+Note, as with redeeming rewards from primary vesting accounts, once the rewards are transferred from the sub-keys vested account, the funds will no longer contribute to the total reward balance.
+
### Determining the rewards bonus multiplier
Before [distributing rewards](./0056-REWA-rewards_overview.md#distributing-rewards-amongst-entities), each parties `reward_distribution_bonus_multiplier` should be set according to the highest tier they qualify for.
@@ -105,3 +129,26 @@ Must expose the following:
1. Funds in both the parties vesting account and vested account should contribute to their `minimum_quantum_balance`. (0085-RVST-013)
1. Assuming all parties perform equally, a party with a greater `reward_distribution_bonus_multiplier` should receive a larger share of a reward pool. (0085-RVST-014)
+
+### Contributions from AMM sub-keys
+
+- Given a party with multiple AMM sub-keys, each of the sub-keys locked rewards should contribute to the parties total quantum balance. (0085-RVST-015)
+- Given a party with multiple AMM sub-keys, each of the sub-keys vesting rewards should contribute to the parties total quantum balance. (0085-RVST-016)
+- Given a party with multiple AMM sub-keys, each of the sub-keys vested rewards should contribute to the parties total quantum balance. (0085-RVST-017)
+- Given a party with multiple AMM sub-keys, redeemed rewards should not contribute to the parties total quantum balance. (0085-RVST-018)
+- Given a party with multiple AMM sub-keys each earning rewards in assets using different quantum values, contributions from each sub-key should be scaled correctly by the assets quantum. (0085-RVST-019)
+
+- Given a party with multiple AMM sub-keys, the parties `reward_distribution_bonus_multiplier` should be set equal to the value in the highest tier where they fulfil the `minimum_quantum_balance` required. This multiplier must also be given to each of the parties sub-keys and applied for future rewards. (0085-RVST-020)
+
+### Redeeming rewards from AMM sub-keys
+
+- A party attempting to transfer funds from an AMM sub-key's vested account will be rejected if the party does not own the sub-key. (0085-RVST-021)
+- A party attempting to transfer funds from an AMM sub-key's vested account will be accepted if the party owns the sub-key. (0085-RVST-022)
+
+- Given a party owns the relevant sub-key, attempting to transfer funds into the sub-key's vesting account will be rejected. (0085-RVST-023)
+- Given a party owns the relevant sub-key, attempting to transfer funds into the sub-key's vested account will be rejected. (0085-RVST-024)
+- Given a party owns the relevant sub-key, attempting to transfer funds from the sub-key's vested account to any account other than the parties general account will be rejected. (0085-RVST-025)
+
+- Given a non-zero transfer fee factor, a party redeeming funds from an appropriate sub-key's vested account will incur no fees. (0085-RVST-026)
+- A party redeeming funds from a sub-key's vested account will not be subject to the minimum transfer requirement if transferring the full balance. The transfer should be accepted. (0085-RVST-027)
+- A party redeeming funds from a sub-key's vested account will be subject to the minimum transfer requirement if transferring less than the full balance. The transfer should be rejected. (0085-RVST-028)
diff --git a/protocol/0090-VAMM-automated_market_maker.ipynb b/protocol/0090-VAMM-automated_market_maker.ipynb
new file mode 100644
index 000000000..97711d085
--- /dev/null
+++ b/protocol/0090-VAMM-automated_market_maker.ipynb
@@ -0,0 +1,912 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "94be4268-65d7-49b6-8c37-40628437dc58",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "from typing import Optional, Tuple, List\n",
+ "from collections import namedtuple\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "955087ab",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "MMOrder = namedtuple(\"MMOrder\", [\"size\", \"price\"])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d91a510a-3fc5-46f7-a77e-971a4c1e3394",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class CFMMarketMaker:\n",
+ " def __init__(\n",
+ " self,\n",
+ " initial_price: float = 100,\n",
+ " price_width_below: float = 0.05,\n",
+ " price_width_above: float = 0.05,\n",
+ " margin_multiple_at_upper: float = 2,\n",
+ " margin_multiple_at_lower: float = 2,\n",
+ " volume_per_side: float = 10,\n",
+ " num_levels: int = 25,\n",
+ " tick_spacing: float = 1,\n",
+ " ):\n",
+ " self.base_price = initial_price\n",
+ " self.upper_price = (1 + price_width_above) * initial_price\n",
+ " self.lower_price = (1 - price_width_below) * initial_price\n",
+ "\n",
+ " self.base_price_sqrt = initial_price**0.5\n",
+ " self.upper_price_sqrt = self.upper_price**0.5\n",
+ " self.lower_price_sqrt = self.lower_price**0.5\n",
+ "\n",
+ " self.lower_liq_factor = 1 / (self.base_price_sqrt - self.lower_price_sqrt)\n",
+ " self.upper_liq_factor = 1 / (self.upper_price_sqrt - self.base_price_sqrt)\n",
+ "\n",
+ " self.margin_multiple_at_upper = margin_multiple_at_upper\n",
+ " self.margin_multiple_at_lower = margin_multiple_at_lower\n",
+ "\n",
+ " self.tick_spacing = tick_spacing\n",
+ " self.num_levels = num_levels\n",
+ " self.volume_per_side = volume_per_side\n",
+ "\n",
+ " def initialise(\n",
+ " self,\n",
+ " vega,\n",
+ " ):\n",
+ " risk_factors = vega.get_risk_factors(self.market_id)\n",
+ " self.short_factor, self.long_factor = risk_factors.short, risk_factors.long\n",
+ "\n",
+ " def _quantity_for_move(\n",
+ " self,\n",
+ " start_price_sqrt,\n",
+ " end_price_sqrt,\n",
+ " range_upper_price_sqrt,\n",
+ " liquidity_factor,\n",
+ " ) -> Optional[float]:\n",
+ " if liquidity_factor == 0:\n",
+ " return None\n",
+ " start_fut_pos = (\n",
+ " liquidity_factor\n",
+ " * (range_upper_price_sqrt - start_price_sqrt)\n",
+ " / (start_price_sqrt * range_upper_price_sqrt)\n",
+ " )\n",
+ " end_fut_pos = (\n",
+ " liquidity_factor\n",
+ " * (range_upper_price_sqrt - end_price_sqrt)\n",
+ " / (end_price_sqrt * range_upper_price_sqrt)\n",
+ " )\n",
+ "\n",
+ " return abs(start_fut_pos - end_fut_pos)\n",
+ "\n",
+ " def _generate_shape(\n",
+ " self, bid_price_depth: float, ask_price_depth: float\n",
+ " ) -> Tuple[List[MMOrder], List[MMOrder]]:\n",
+ " return self._generate_shape_calcs(\n",
+ " balance=sum(\n",
+ " a.balance\n",
+ " for a in self.vega.get_accounts_from_stream(\n",
+ " key_name=self.key_name,\n",
+ " wallet_name=self.wallet_name,\n",
+ " market_id=self.market_id,\n",
+ " )\n",
+ " ),\n",
+ " position=self.current_position,\n",
+ " )\n",
+ "\n",
+ " def _quote_price(self,\n",
+ " balance: float,\n",
+ " position: float,\n",
+ " volume: float = 0\n",
+ " ):\n",
+ " \n",
+ " # volume_at_lower = (\n",
+ " # balance * self.max_perc_loss_at_lower / (aep_lower - self.lower_price)\n",
+ " # )\n",
+ " # abs_volume_at_upper = (\n",
+ " # balance * self.max_perc_loss_at_upper / (self.upper_price - aep_upper)\n",
+ " # )\n",
+ "\n",
+ " # loss = volume_at_lower * (aep_lower - self.lower_price)\n",
+ " # margin = (volume_at_lower * self.lower_price) / (balance - loss)\n",
+ "\n",
+ " # volume_at_lower = balance * loss_at_lower_perc / (aep_lower - self.lower_price)\n",
+ " # margin = (volume_at_lower * self.lower_price) / (balance - loss)\n",
+ "\n",
+ " # volume_at_lower = margin * (balance - loss) / self.lower_price\n",
+ " # volume_at_lower = margin * (balance - volume_at_lower * (aep_lower - self.lower_price)) / self.lower_price\n",
+ " # volume_at_lower = - volume_at_lower * margin * ( aep_lower - self.lower_price) / self.lower_price + margin * balance / self.lower_price\n",
+ " # volume_at_lower (1 + margin * (aep_lower - self.lower_price) / self.lower_price) = margin * balance / self.lower_price\n",
+ " # volume_at_lower (self.lower_price (1 - margin) + margin * aep_lower) / self.lower_price = margin * balance / self.lower_price\n",
+ " # volume_at_lower = margin * balance / (self.lower_price (1 - margin) + margin * aep_lower)\n",
+ "\n",
+ "\n",
+ " # loss = abs_volume_at_upper * (self.upper_price - aep_upper)\n",
+ " # margin = (abs_volume_at_upper * self.upper_price) / (balance - loss)\n",
+ "\n",
+ " # abs_volume_at_upper = margin * (balance - loss) / self.upper_price\n",
+ " # abs_volume_at_upper = margin * (balance - abs_volume_at_upper * (self.upper_price - aep_upper)) / self.upper_price\n",
+ "\n",
+ " # abs_volume_at_upper = - abs_volume_at_upper * margin * (self.upper_price - aep_upper) / self.upper_price + margin * balance / self.upper_price\n",
+ "\n",
+ " # abs_volume_at_upper (1 + margin * (self.upper_price - aep_upper) / self.upper_price) = margin * balance / self.upper_price\n",
+ "\n",
+ " # abs_volume_at_upper (self.upper_price (1 + margin) - margin * aep_upper) / self.upper_price = margin * balance / self.upper_price\n",
+ " \n",
+ " # abs_volume_at_upper = margin * balance / (self.upper_price (1 + margin) - margin * aep_upper)\n",
+ "\n",
+ " unit_upper_L = (\n",
+ " self.upper_price_sqrt\n",
+ " * self.base_price_sqrt\n",
+ " / (self.upper_price_sqrt - self.base_price_sqrt)\n",
+ " )\n",
+ "\n",
+ " unit_lower_L = (\n",
+ " self.lower_price_sqrt\n",
+ " * self.base_price_sqrt\n",
+ " / (self.base_price_sqrt - self.lower_price_sqrt)\n",
+ " )\n",
+ " aep_lower = -1 * unit_lower_L * self.base_price_sqrt * ((unit_lower_L / (unit_lower_L + self.base_price_sqrt)) - 1)\n",
+ " aep_upper = -1 * unit_upper_L * self.upper_price_sqrt * ((unit_upper_L / (unit_upper_L + self.upper_price_sqrt)) - 1)\n",
+ "\n",
+ " volume_at_lower = self.margin_multiple_at_lower * balance / (self.lower_price * (1 - self.margin_multiple_at_lower) + self.margin_multiple_at_lower * aep_lower)\n",
+ " volume_at_upper = self.margin_multiple_at_upper * balance / (self.upper_price * (1 + self.margin_multiple_at_upper) - self.margin_multiple_at_upper * aep_upper)\n",
+ " \n",
+ " upper_L = (\n",
+ " volume_at_upper\n",
+ " * self.upper_price_sqrt\n",
+ " * self.base_price_sqrt\n",
+ " / (self.upper_price_sqrt - self.base_price_sqrt)\n",
+ " )\n",
+ "\n",
+ " lower_L = (\n",
+ " volume_at_lower\n",
+ " * self.lower_price_sqrt\n",
+ " * self.base_price_sqrt\n",
+ " / (self.base_price_sqrt - self.lower_price_sqrt)\n",
+ " )\n",
+ "\n",
+ " if position > 0 or (position == 0 and volume > 0):\n",
+ " L = lower_L\n",
+ " upper_bound = self.base_price_sqrt\n",
+ " virt_x = position + L / upper_bound\n",
+ "\n",
+ " rt_ref_price = upper_bound / (position * upper_bound / L + 1)\n",
+ " virt_y = L * rt_ref_price\n",
+ "\n",
+ " else:\n",
+ " L = upper_L\n",
+ " upper_bound = self.upper_price_sqrt\n",
+ " virt_x = (volume_at_upper + position) + L / upper_bound\n",
+ " \n",
+ " rt_ref_price = upper_bound / ((volume_at_upper + position) * upper_bound / L + 1)\n",
+ " virt_y = L * rt_ref_price\n",
+ "\n",
+ " if volume == 0:\n",
+ " return rt_ref_price ** 2\n",
+ " else:\n",
+ " out = (virt_y * virt_x) / (virt_x - volume) - virt_y\n",
+ " return out / volume\n",
+ "\n",
+ " def _generate_shape_calcs(\n",
+ " self,\n",
+ " balance: float,\n",
+ " position: float,\n",
+ " ) -> Tuple[List[MMOrder], List[MMOrder]]:\n",
+ " unit_upper_L = (\n",
+ " self.upper_price_sqrt\n",
+ " * self.base_price_sqrt\n",
+ " / (self.upper_price_sqrt - self.base_price_sqrt)\n",
+ " )\n",
+ "\n",
+ " unit_lower_L = (\n",
+ " self.lower_price_sqrt\n",
+ " * self.base_price_sqrt\n",
+ " / (self.base_price_sqrt - self.lower_price_sqrt)\n",
+ " )\n",
+ " aep_lower = -1 * unit_lower_L * self.base_price_sqrt * ((unit_lower_L / (unit_lower_L + self.base_price_sqrt)) - 1)\n",
+ " aep_upper = -1 * unit_upper_L * self.upper_price_sqrt * ((unit_upper_L / (unit_upper_L + self.upper_price_sqrt)) - 1)\n",
+ " \n",
+ " volume_at_lower = self.margin_multiple_at_lower * balance / (self.lower_price * (1 - self.margin_multiple_at_lower) + self.margin_multiple_at_lower * aep_lower)\n",
+ " volume_at_upper = self.margin_multiple_at_upper * balance / (self.upper_price * (1 + self.margin_multiple_at_upper) - self.margin_multiple_at_upper * aep_upper)\n",
+ " \n",
+ " upper_L = (\n",
+ " volume_at_upper\n",
+ " * self.upper_price_sqrt\n",
+ " * self.base_price_sqrt\n",
+ " / (self.upper_price_sqrt - self.base_price_sqrt)\n",
+ " )\n",
+ "\n",
+ " lower_L = (\n",
+ " volume_at_lower\n",
+ " * self.lower_price_sqrt\n",
+ " * self.base_price_sqrt\n",
+ " / (self.base_price_sqrt - self.lower_price_sqrt)\n",
+ " )\n",
+ "\n",
+ " if position > 0:\n",
+ " L = lower_L\n",
+ " upper_bound = self.base_price_sqrt\n",
+ " rt_ref_price = upper_bound / (position * upper_bound / L + 1)\n",
+ " else:\n",
+ " L = upper_L\n",
+ " upper_bound = self.upper_price_sqrt\n",
+ " rt_ref_price = upper_bound / ((volume_at_upper + position) * upper_bound / L + 1)\n",
+ "\n",
+ "\n",
+ " return self._calculate_price_levels(\n",
+ " ref_price=rt_ref_price ** 2,\n",
+ " balance=balance,\n",
+ " upper_L=upper_L,\n",
+ " lower_L=lower_L,\n",
+ " position=position,\n",
+ " )\n",
+ "\n",
+ " def _calculate_liq_val(\n",
+ " self, margin_frac: float, balance: float, risk_factor: float, liq_factor: float\n",
+ " ) -> float:\n",
+ " return margin_frac * (balance / risk_factor) * liq_factor\n",
+ "\n",
+ " def _calculate_price_levels(\n",
+ " self,\n",
+ " ref_price: float,\n",
+ " balance: float,\n",
+ " upper_L: float,\n",
+ " lower_L: float,\n",
+ " position: float,\n",
+ " ) -> Tuple[List[MMOrder], List[MMOrder]]:\n",
+ " if ref_price == 0:\n",
+ " ref_price = self.curr_price\n",
+ "\n",
+ " agg_bids = []\n",
+ " agg_asks = []\n",
+ "\n",
+ " pos = position\n",
+ "\n",
+ " for i in range(1, self.num_levels):\n",
+ " pre_price_sqrt = (ref_price + (i - 1) * self.tick_spacing) ** 0.5\n",
+ " price = ref_price + i * self.tick_spacing\n",
+ "\n",
+ " if price > self.upper_price or price < self.lower_price:\n",
+ " continue\n",
+ "\n",
+ " volume = self._quantity_for_move(\n",
+ " pre_price_sqrt,\n",
+ " price**0.5,\n",
+ " self.upper_price_sqrt if pos < 0 else self.base_price_sqrt,\n",
+ " upper_L if pos < 0 else lower_L,\n",
+ " )\n",
+ " if volume is not None:\n",
+ " if pos > 0 and pos - volume < 0:\n",
+ " volume = pos\n",
+ " agg_asks.append(MMOrder(volume, price))\n",
+ " pos -= volume\n",
+ "\n",
+ " pos = position\n",
+ " for i in range(1, self.num_levels):\n",
+ " pre_price_sqrt = (ref_price - (i - 1) * self.tick_spacing) ** 0.5\n",
+ " price = ref_price - i * self.tick_spacing\n",
+ "\n",
+ " if price > self.upper_price or price < self.lower_price:\n",
+ " continue\n",
+ "\n",
+ " volume = self._quantity_for_move(\n",
+ " pre_price_sqrt,\n",
+ " price**0.5,\n",
+ " self.upper_price_sqrt if pos < 0 else self.base_price_sqrt,\n",
+ " upper_L if pos < 0 else lower_L,\n",
+ " )\n",
+ " if volume is not None:\n",
+ " if pos < 0 and pos + volume > 0:\n",
+ " volume = pos\n",
+ " agg_bids.append(MMOrder(volume, price))\n",
+ " pos += volume\n",
+ "\n",
+ " return agg_bids, agg_asks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "60c5c97e",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c2cc483a",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [],
+ "source": [
+ "\n",
+ "mm = CFMMarketMaker(\n",
+ " initial_price=2000,\n",
+ " price_width_above=5,\n",
+ " price_width_below=0.99,\n",
+ " margin_multiple_at_lower=5,\n",
+ " margin_multiple_at_upper=5,\n",
+ " num_levels=8000,\n",
+ " tick_spacing=1,\n",
+ ")\n",
+ "\n",
+ "balance = 100_000\n",
+ "\n",
+ "mm.short_factor = 0.02\n",
+ "mm.long_factor = 0.02\n",
+ "\n",
+ "lower_L_unit = (\n",
+ " mm.base_price_sqrt\n",
+ " * mm.lower_price_sqrt\n",
+ " / (mm.base_price_sqrt - mm.lower_price_sqrt)\n",
+ ")\n",
+ "upper_L_unit = (\n",
+ " mm.base_price_sqrt\n",
+ " * mm.upper_price_sqrt\n",
+ " / (mm.upper_price_sqrt - mm.base_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "aep_lower = -1 * lower_L_unit * mm.base_price_sqrt * ((lower_L_unit / (lower_L_unit + mm.base_price_sqrt)) - 1)\n",
+ "volume_at_lower = mm.margin_multiple_at_lower * balance / (mm.lower_price * (1 - mm.margin_multiple_at_lower) + mm.margin_multiple_at_lower * aep_lower)\n",
+ "\n",
+ "aep_upper = -1 * upper_L_unit * mm.upper_price_sqrt * ((upper_L_unit / (upper_L_unit + mm.upper_price_sqrt)) - 1)\n",
+ "volume_at_upper = mm.margin_multiple_at_upper * balance / (mm.upper_price * (1 + mm.margin_multiple_at_upper) - mm.margin_multiple_at_upper * aep_upper)\n",
+ "\n",
+ "upper_L = (\n",
+ " volume_at_upper\n",
+ " * mm.upper_price_sqrt\n",
+ " * mm.base_price_sqrt\n",
+ " / (mm.upper_price_sqrt - mm.base_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "lower_L = (\n",
+ " volume_at_lower\n",
+ " * mm.base_price_sqrt\n",
+ " * mm.lower_price_sqrt\n",
+ " / (mm.base_price_sqrt - mm.lower_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "to_price = 3000\n",
+ "pos = mm._quantity_for_move(\n",
+ " mm.base_price_sqrt,\n",
+ " to_price**0.5,\n",
+ " mm.base_price_sqrt if to_price < mm.base_price else mm.upper_price_sqrt,\n",
+ " lower_L if to_price < mm.base_price else upper_L,\n",
+ ")\n",
+ "\n",
+ "print(\"Price is {}\".format(mm._quote_price(\n",
+ " balance=balance, position=0\n",
+ " ))\n",
+ ")\n",
+ "\n",
+ "print(\"Position is {}\".format(pos))\n",
+ "bids, asks = mm._generate_shape_calcs(\n",
+ " balance=balance, position=(-1 if to_price > mm.base_price else 1) * pos\n",
+ ")\n",
+ "\n",
+ "# x = []\n",
+ "# y = []\n",
+ "\n",
+ "# cumsum = 0\n",
+ "# for bid in bids:\n",
+ "# x.append(bid.price)\n",
+ "# cumsum += bid.size\n",
+ "# y.append(cumsum)\n",
+ "\n",
+ "# plt.plot(x, y, color=\"blue\")\n",
+ "x = []\n",
+ "y = []\n",
+ "\n",
+ "# cumsum = 0\n",
+ "# for ask in asks:\n",
+ "# x.append(ask.price)\n",
+ "# cumsum += ask.size\n",
+ "# y.append(cumsum)\n",
+ "# plt.plot(x, y, color=\"red\")\n",
+ "# plt.show()\n",
+ "\n",
+ "cumsum = 0\n",
+ "for bid in bids:\n",
+ " x.append(bid.price)\n",
+ " cumsum += bid.size\n",
+ " y.append(bid.size)\n",
+ "\n",
+ "plt.bar(x, y, color=\"blue\")\n",
+ "x = []\n",
+ "y = []\n",
+ "\n",
+ "cumsum = 0\n",
+ "for ask in asks:\n",
+ " x.append(ask.price)\n",
+ " cumsum += ask.size\n",
+ " y.append(ask.size)\n",
+ "plt.ylim((0, 0.5))\n",
+ "plt.bar(x, y, color=\"red\")\n",
+ "\n",
+ "print(f\"Mid is {(bids[0].price + asks[0].price) / 2 }\")\n",
+ "plt.show()\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d319c5bc",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mm = CFMMarketMaker(\n",
+ " initial_price=2000,\n",
+ " price_width_above=5,\n",
+ " price_width_below=0.99,\n",
+ " margin_multiple_at_upper=0.8,\n",
+ " margin_multiple_at_lower=0.8,\n",
+ " num_levels=8000,\n",
+ " tick_spacing=1,\n",
+ ")\n",
+ "\n",
+ "balance = 100_000\n",
+ "\n",
+ "mm.short_factor = 0.02\n",
+ "mm.long_factor = 0.02\n",
+ "\n",
+ "fair = mm._quote_price(\n",
+ " balance=balance, position=0, volume = 0\n",
+ ")\n",
+ "\n",
+ "single = mm._quote_price(\n",
+ " balance=balance, position=0, volume = 5\n",
+ ")\n",
+ "\n",
+ "double = mm._quote_price(\n",
+ " balance=2 * balance, position=0, volume = 10\n",
+ ")\n",
+ "\n",
+ "print(f\"f: {fair}, s: {single}, d: {double}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b37aa817",
+ "metadata": {},
+ "source": [
+ "### Incoming AMM rebaselining"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8575603a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "existing_mm = CFMMarketMaker(\n",
+ " initial_price=2000,\n",
+ " price_width_above=0.1,\n",
+ " price_width_below=0.1,\n",
+ " margin_multiple_at_lower=0.1,\n",
+ " margin_multiple_at_upper=0.1,\n",
+ " num_levels=8000,\n",
+ " tick_spacing=1,\n",
+ ")\n",
+ "\n",
+ "incoming_mm = CFMMarketMaker(\n",
+ " initial_price=2400,\n",
+ " price_width_above=5,\n",
+ " price_width_below=0.99,\n",
+ " margin_multiple_at_lower=2,\n",
+ " margin_multiple_at_upper=3,\n",
+ " num_levels=8000,\n",
+ " tick_spacing=1,\n",
+ ")\n",
+ "\n",
+ "balance = 10_000\n",
+ "existing_mm_balance = 10_000\n",
+ "\n",
+ "existing_mm.short_factor = 0.02\n",
+ "existing_mm.long_factor = 0.02\n",
+ "incoming_mm.short_factor = 0.02\n",
+ "incoming_mm.long_factor = 0.02\n",
+ "\n",
+ "lower_L_unit = (\n",
+ " incoming_mm.base_price_sqrt\n",
+ " * incoming_mm.lower_price_sqrt\n",
+ " / (incoming_mm.base_price_sqrt - incoming_mm.lower_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "aep_lower = -1 * lower_L_unit * incoming_mm.base_price_sqrt * ((lower_L_unit / (lower_L_unit + incoming_mm.base_price_sqrt)) - 1)\n",
+ "volume_at_lower = incoming_mm.margin_multiple_at_lower * balance / (incoming_mm.lower_price * (1 - incoming_mm.margin_multiple_at_lower) + incoming_mm.margin_multiple_at_lower * aep_lower)\n",
+ "\n",
+ "\n",
+ "lower_L = (\n",
+ " volume_at_lower\n",
+ " * incoming_mm.base_price_sqrt\n",
+ " * incoming_mm.lower_price_sqrt\n",
+ " / (incoming_mm.base_price_sqrt - incoming_mm.lower_price_sqrt)\n",
+ ")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6c808160",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "existing_mid = existing_mm._quote_price(existing_mm_balance, 0, 0)\n",
+ "\n",
+ "print(f\"Mid before is {existing_mid}\")\n",
+ "\n",
+ "to_rebaseline = incoming_mm._quantity_for_move(incoming_mm.base_price_sqrt, existing_mid**0.5, incoming_mm.base_price_sqrt, lower_L)\n",
+ "\n",
+ "print(f\"Rebaseline vol is {to_rebaseline}\")\n",
+ "\n",
+ "new_old_mid = incoming_mm._quote_price(balance, 0, 0)\n",
+ "\n",
+ "print(f\"mid for new was {new_old_mid}\")\n",
+ "\n",
+ "new_new_mid = incoming_mm._quote_price(balance, to_rebaseline, 0)\n",
+ "\n",
+ "print(f\"Now mid for new is {new_new_mid}\")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "58aa874e",
+ "metadata": {},
+ "source": [
+ "#### Ladder trading "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5dee9001",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "existing_mm = CFMMarketMaker(\n",
+ " initial_price=2000,\n",
+ " price_width_above=0.1,\n",
+ " price_width_below=0.1,\n",
+ " margin_multiple_at_upper=0.6,\n",
+ " margin_multiple_at_lower=0.6,\n",
+ " num_levels=8000,\n",
+ " tick_spacing=1,\n",
+ ")\n",
+ "\n",
+ "starting_pos = 0\n",
+ "px = 2000\n",
+ "balance = 8000\n",
+ "posn = 0\n",
+ "start_balance = balance\n",
+ "\n",
+ "step_size = 1\n",
+ "\n",
+ "lower_L = (\n",
+ " existing_mm.base_price_sqrt\n",
+ " * existing_mm.lower_price_sqrt\n",
+ " / (existing_mm.base_price_sqrt - existing_mm.lower_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "aep = -1 * lower_L * existing_mm.base_price_sqrt * ((lower_L / (lower_L + existing_mm.base_price_sqrt)) - 1)\n",
+ "print(f\"aep is {aep}\")\n",
+ "print(f\"Requested multiple at lower bound is {existing_mm.margin_multiple_at_lower}\")\n",
+ "\n",
+ "volume_at_lower = existing_mm.margin_multiple_at_lower * balance / (existing_mm.lower_price * (1 - existing_mm.margin_multiple_at_lower) + existing_mm.margin_multiple_at_lower * aep)\n",
+ "\n",
+ "average_trade = 0\n",
+ "\n",
+ "lower_L = (\n",
+ " volume_at_lower\n",
+ " * existing_mm.base_price_sqrt\n",
+ " * existing_mm.lower_price_sqrt\n",
+ " / (existing_mm.base_price_sqrt - existing_mm.lower_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "for i in range(1, 200):\n",
+ "\n",
+ " to_rebaseline = existing_mm._quantity_for_move(px**0.5, (px-1)**0.5, existing_mm.base_price_sqrt, lower_L)\n",
+ " to_rebaseline_price = existing_mm._quote_price(start_balance, posn, -1 * to_rebaseline)\n",
+ "\n",
+ " average_trade = (average_trade * posn + abs(to_rebaseline) * to_rebaseline_price) / (abs(to_rebaseline) + abs(posn))\n",
+ " px -= 1\n",
+ "\n",
+ " balance -= posn * step_size\n",
+ "\n",
+ " posn += to_rebaseline\n",
+ " print(f\"from {px + 1} to {px} takes volume {to_rebaseline} at price {to_rebaseline_price} with posn {posn} and balance {balance} average trade px {average_trade} implying leverage {posn * to_rebaseline_price / balance}\")\n",
+ "\n",
+ "print(f\"Loss is {start_balance - balance}\")\n",
+ "\n",
+ "for i in range(1, 200):\n",
+ "\n",
+ " to_rebaseline = existing_mm._quantity_for_move(px**0.5, (px+step_size)**0.5, existing_mm.base_price_sqrt, lower_L)\n",
+ " to_rebaseline_price = existing_mm._quote_price(start_balance, posn, to_rebaseline)\n",
+ " px += step_size\n",
+ " balance += posn\n",
+ "\n",
+ " posn -= to_rebaseline\n",
+ " print(f\"from {px + 1} to {px} takes volume {to_rebaseline} at price {to_rebaseline_price} with posn {posn} and balance {balance} average trade px {average_trade} implying leverage {posn * to_rebaseline_price / balance}\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c475107d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "starting_pos = 0\n",
+ "px = 2000\n",
+ "start_balance = 1000\n",
+ "balance = 1000\n",
+ "posn = 0\n",
+ "\n",
+ "step_size = 1\n",
+ "\n",
+ "\n",
+ "upper_L_unit = (\n",
+ " existing_mm.upper_price_sqrt\n",
+ " * existing_mm.base_price_sqrt\n",
+ " / (existing_mm.upper_price_sqrt - existing_mm.base_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "aep = -1 * upper_L_unit * existing_mm.upper_price_sqrt * ((upper_L_unit / (upper_L_unit + existing_mm.upper_price_sqrt)) - 1)\n",
+ "print(f\"aep is {aep}\")\n",
+ "print(f\"Requested multiple at upper bound is {existing_mm.margin_multiple_at_upper}\")\n",
+ "\n",
+ "volume_at_upper_incoming = existing_mm.margin_multiple_at_upper * balance / (existing_mm.upper_price * (1 + existing_mm.margin_multiple_at_upper) - existing_mm.margin_multiple_at_upper * aep)\n",
+ "\n",
+ "upper_L = (\n",
+ " volume_at_upper_incoming\n",
+ " * existing_mm.upper_price_sqrt\n",
+ " * existing_mm.base_price_sqrt\n",
+ " / (existing_mm.upper_price_sqrt - existing_mm.base_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "\n",
+ "for i in range(1, 200):\n",
+ "\n",
+ " to_rebaseline = existing_mm._quantity_for_move(px**0.5, (px+1)**0.5, existing_mm.upper_price_sqrt, upper_L)\n",
+ " to_rebaseline_price = existing_mm._quote_price(balance, posn, to_rebaseline)\n",
+ " px += 1\n",
+ "\n",
+ " balance += posn * step_size\n",
+ " posn -= to_rebaseline\n",
+ "\n",
+ " print(f\"from {px - 1} to {px} takes volume {to_rebaseline} at price {to_rebaseline_price} with posn {posn} and balance {balance} implying leverage {- posn * to_rebaseline_price / balance}\")\n",
+ "\n",
+ "print(\"Returning\")\n",
+ "\n",
+ "for i in range(1, 200):\n",
+ "\n",
+ " to_rebaseline = existing_mm._quantity_for_move(px**0.5, (px+step_size)**0.5, existing_mm.upper_price_sqrt, upper_L)\n",
+ " to_rebaseline_price = existing_mm._quote_price(balance, posn, -1 * to_rebaseline)\n",
+ " px -= step_size\n",
+ " balance -= posn\n",
+ "\n",
+ " posn += to_rebaseline\n",
+ " print(f\"from {px + 1} to {px} takes volume {to_rebaseline} at price {to_rebaseline_price} with posn {posn} and balance {balance} implying leverage {- posn * to_rebaseline_price / balance}\")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e0e75a17",
+ "metadata": {},
+ "source": [
+ "### Scenario Calculator"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f0dff6e4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "upper_factor = 1300/1000 - 1\n",
+ "lower_factor = 0.1\n",
+ "base = 1000\n",
+ "\n",
+ "mm = CFMMarketMaker(\n",
+ " initial_price=base,\n",
+ " price_width_above=upper_factor,\n",
+ " price_width_below=lower_factor,\n",
+ " margin_multiple_at_upper=10,\n",
+ " margin_multiple_at_lower=1,\n",
+ " num_levels=8000,\n",
+ " tick_spacing=1,\n",
+ ")\n",
+ "\n",
+ "starting_pos = 0\n",
+ "balance = 10000\n",
+ "posn = 0\n",
+ "start_balance = balance\n",
+ "\n",
+ "unit_upper_L = (\n",
+ " mm.upper_price_sqrt\n",
+ " * mm.base_price_sqrt\n",
+ " / (mm.upper_price_sqrt - mm.base_price_sqrt)\n",
+ ")\n",
+ "\n",
+ "unit_lower_L = (\n",
+ " mm.lower_price_sqrt\n",
+ " * mm.base_price_sqrt\n",
+ " / (mm.base_price_sqrt - mm.lower_price_sqrt)\n",
+ ")\n",
+ "aep_lower = -1 * unit_lower_L * mm.base_price_sqrt * ((unit_lower_L / (unit_lower_L + mm.base_price_sqrt)) - 1)\n",
+ "aep_upper = -1 * unit_upper_L * mm.upper_price_sqrt * ((unit_upper_L / (unit_upper_L + mm.upper_price_sqrt)) - 1)\n",
+ "\n",
+ "volume_at_lower = mm.margin_multiple_at_lower * balance / (mm.lower_price * (1 - mm.margin_multiple_at_lower) + mm.margin_multiple_at_lower * aep_lower)\n",
+ "volume_at_upper = mm.margin_multiple_at_upper * balance / (mm.upper_price * (1 + mm.margin_multiple_at_upper) - mm.margin_multiple_at_upper * aep_upper)\n",
+ "\n",
+ "loss_at_lower = volume_at_lower * (mm.lower_price - aep_lower)\n",
+ "loss_at_upper = volume_at_upper * (mm.upper_price - aep_upper)\n",
+ "\n",
+ "print(f\"Position at lower would be {volume_at_lower} and position at upper would be {-1 * volume_at_upper} (Lower AEP {aep_lower}, Upper AEP {aep_upper})\")\n",
+ "print(f\"Implying leverage {volume_at_lower * mm.lower_price / (balance + loss_at_lower)} at lower and {volume_at_upper * mm.upper_price / (balance - loss_at_upper)} at upper\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2553a194",
+ "metadata": {},
+ "source": [
+ "### Curve Estimator"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "aec95eb6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "upper_factor = 1300/1000 - 1\n",
+ "lower_factor = 0.1\n",
+ "base = 1000\n",
+ "\n",
+ "mm = CFMMarketMaker(\n",
+ " initial_price=base,\n",
+ " price_width_above=upper_factor,\n",
+ " price_width_below=lower_factor,\n",
+ " margin_multiple_at_upper=10,\n",
+ " margin_multiple_at_lower=1,\n",
+ " num_levels=8000,\n",
+ " tick_spacing=1,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e2fc7416",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "trade_size = 100\n",
+ "\n",
+ "def execution_price(volume_to_trade: float, price_levels: list[(float, float)]):\n",
+ " '''First value in price_levels is price, second is volume\n",
+ " '''\n",
+ " weighted_execution_price = 0\n",
+ " traded_vol = 0\n",
+ " for level in price_levels:\n",
+ " if level[1] > volume_to_trade:\n",
+ " weighted_execution_price += volume_to_trade * level[0]\n",
+ " traded_vol += volume_to_trade\n",
+ " volume_to_trade = 0\n",
+ " else:\n",
+ " weighted_execution_price += level[1] * level[0]\n",
+ " volume_to_trade -= level[1]\n",
+ " traded_vol += level[1]\n",
+ " if volume_to_trade <= 0:\n",
+ " break\n",
+ " return weighted_execution_price / traded_vol\n",
+ "\n",
+ "def gen_levels(start_px: float, end_px: float, num_divisions: int, amm: CFMMarketMaker, amm_balance: float) -> list[(float, float)]:\n",
+ " last_level = start_px\n",
+ "\n",
+ " unit_upper_L = (\n",
+ " amm.upper_price_sqrt\n",
+ " * amm.base_price_sqrt\n",
+ " / (amm.upper_price_sqrt - amm.base_price_sqrt)\n",
+ " )\n",
+ " aep = -1 * unit_upper_L * amm.upper_price_sqrt * ((unit_upper_L / (unit_upper_L + amm.upper_price_sqrt)) - 1)\n",
+ " volume_at_upper_incoming = amm.margin_multiple_at_upper * amm_balance / (amm.upper_price * (1 + amm.margin_multiple_at_upper) - amm.margin_multiple_at_upper * aep)\n",
+ "\n",
+ " upper_L = (\n",
+ " volume_at_upper_incoming\n",
+ " * amm.upper_price_sqrt\n",
+ " * amm.base_price_sqrt\n",
+ " / (amm.upper_price_sqrt - amm.base_price_sqrt)\n",
+ " )\n",
+ "\n",
+ " orders = []\n",
+ " for level in np.linspace(start_px, end_px, num=num_divisions):\n",
+ " if level == start_px:\n",
+ " continue\n",
+ " orders.append((level, amm._quantity_for_move(last_level**0.5, level**0.5, amm.upper_price_sqrt, upper_L)))\n",
+ " last_level = level\n",
+ " return orders"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24a0ee68",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "STEPS_IN_RANGE = 100\n",
+ "RANGE_LOW = base\n",
+ "RANGE_HIGH = 2 * RANGE_LOW\n",
+ "TICK_SIZE = 0.01\n",
+ "TRADE_SIZE = 100\n",
+ "MM_BALANCE = 10_000\n",
+ "\n",
+ "num_divisions_to_test = [x for x in range(100, 10000, 5)]\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b9b009e9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "prices = []\n",
+ "\n",
+ "for div in num_divisions_to_test:\n",
+ " levels = gen_levels(RANGE_LOW, RANGE_HIGH, div, mm, MM_BALANCE)\n",
+ " prices.append(execution_price(TRADE_SIZE, levels))\n",
+ "\n",
+ "plt.plot(num_divisions_to_test, prices)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "37264648",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.14"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/protocol/0090-VAMM-automated_market_maker.md b/protocol/0090-VAMM-automated_market_maker.md
new file mode 100644
index 000000000..a73706597
--- /dev/null
+++ b/protocol/0090-VAMM-automated_market_maker.md
@@ -0,0 +1,339 @@
+# Automated Market Maker Framework
+
+## Summary
+
+The automated market maker (AMM) framework is designed to allow for the provision of an on-chain market making methodology which automatically provides prices according to a simple set of rules based on current market data. These rulesets are not created with the expectation of providing any profit nor of remaining solvent under any specific conditions, and so should be limited to conceptually simple setups. The initial methodology follows a concentrated-liquidity style constant-function market setup, with configurable maximum and minimum price bounds.
+
+An automated market maker is configured at a per-key level, and is enabled by submitting a transaction with the requisite parameters. At this point in time the protocol will move committed funds to a sub-account which will be used to manage margin for the AMM. Once enabled, the configuration will be added to the pool of available AMMs to be utilised by the matching engine.
+
+Each party may have only one AMM configuration per market, and both Spot and Futures markets are eligible, with the behaviour differing slightly for each.
+
+## Process Overview
+
+The configuration and resultant lifecycle of an automated market maker is as follows:
+
+- Party funds a key which will be used by the strategy with desired token amounts.
+- Party submits a transaction containing configuration for the strategy on a given market. This will contain:
+ - Amount of funds to commit
+ - Price bounds (upper, lower, base)
+ - Margin ratios at upper and lower bound (ratio for leverage at bounds. Reciprocal of leverage multiplier e.g. 0.1 = 10x leverage)
+- Additionally, the transaction should contain data related to the setup of the position but which does not need to be stored:
+ - Maximum slippage (%), used for rebasing position when creating/updating vAMM
+- Once accepted, the network will transfer funds to a sub-account and use the other parameters for maintaining the position.
+- At each block, the party's available balance (including margin and general accounts) for trading on the market will be checked. If the total balance is `0` the vAMM configuration will be stopped.
+- The party is finally able to cancel the vAMM through a few different methods. They can choose to either set it into a mode in which it will only reduce position, eventually cancelling when/if the position reaches `0`, or choose to give up the existing position and associated required collateral.
+
+## Sub-Account Configuration
+
+Each main Vega key will have one associated sub account for a given market, on which an AMM may be set up. The account key should be generated through a hash of the main account key plus the ID of the market to generate a valid Vega address in a predictable manner. Outside of the AMM framework the sub-accounts are treated identically to any other account, they will have the standard associated margin/general accounts and be able to place orders if required as with any other account. The key differentiator is that no external party will have the private key to control these accounts directly. The maintenance of such an account will be performed through a few actions:
+
+- Creation: A sub-account will be funded when a user configures an AMM strategy with a set of criteria and a commitment amount. At this point in time the commitment amount will be transferred to the sub-account's general account and the AMM strategy will commence
+- Cancellation: When the vAMM is cancelled the strategy specified will be followed:
+ - For futures, either any positions associated with the vAMM will be abandoned and given up to the network liquidation engine to close out, along with any associated required collateral, or the vAMM will be set into a mode in which it can only reduce position over time.
+- Amendment: Updates the strategy or commitment for a sub-account
+
+## Interface
+
+All AMM configurations should implement two key interfaces:
+
+- One taking simply the current state (`position` and `total funds`) and a trade (`volume`, `side`) and returning a quote price. This should also handle a trade of `volume = 0` to return a notional `fair price`
+- The second taking (`position`, `total funds`, `side`, `start price`, `end price`) should return the full volume the AMM would trade between the two prices (inclusive).
+
+## AMM Configurations
+
+Initially there will only be one option for AMM behaviour, that of a constant-function curve, however there may be others available in future. As such, the parameters pertaining to this model in particular should be passed in their own structure such that the creation message is similar to:
+
+```json
+{
+ commitment,
+ market,
+ slippage_tolerance_percentage,
+ proposed_fee,
+ concentrated_liquidity_params: {
+ base_price,
+ lower_price,
+ upper_price,
+ leverage_at_upper_bound,
+ leverage_at_lower_bound,
+ }
+}
+```
+
+### Concentrated Liquidity - Futures
+
+The `Concentrated Liquidity` AMM is a market maker utilising a Uniswap v3-style pricing curve for managing price based upon current market price. This allows for the market maker to automatically provide a pricing curve for any prices within some configurable range, alongside offering the capability to control risk by only trading within certain price bounds and out to known position limits.
+
+The concentrated liquidity market maker consists of two liquidity curves of prices joined at a given `base price`, an `upper` consisting of the prices above this price point and a `lower` for prices below it. At prices below the `base price` the market maker will be in a long position, and at prices above this `base price` the market maker will be in a short position. This is configured through a number of parameters:
+
+- **Base Price**: The base price is the central price for the market maker. When trading at this level the market maker will have a position of `0`. Volumes for prices above this level will be taken from the `upper` curve and volumes for prices below will be taken from the `lower` curve.
+- **Upper Price**: The maximum price bound for market making. Prices between the `base price` and this price will have volume placed, with no orders above this price. This is optional and if not supplied no volume will be placed above `base price`. At these prices the market maker will always be short
+- **Lower Price**: The minimum price bound for market making. Prices between the `base price` and this will have volume placed, with no orders below this price. This is optional and if not supplied no volume will be placed below `base price`. At these prices the market maker will always be long
+- **Commitment**: This is the initial volume of funds to transfer into the sub account for use in market making. If this amount is not currently available in the main account's general account the transaction will fail.
+- **Leverage at Bounds**: The exact volume scaling is defined by the position at the upper and lower prices. To determine this the commitment must be compared with what leverage that might allow at the price bounds. Using this parameter allows them to set a value such that `position = remaining funds * leverage at bound*`, however with the restriction that commitment must still be `>= initial margin`. This parameter should be optional. There is a separate parameter for each potential bound.
+ - **Upper Bound Leverage**: `leverage_at_upper_bound`
+ - **Lower Bound Leverage**: `leverage_at_lower_bound`
+
+Note that the independent long and short ranges mean that at `base price` the market maker will be flat with respect to the market with a `0` position. This means that a potential market maker with some inherent exposure elsewhere (likely long in many cases as a token holder) can generate a position which is always either opposite to their position elsewhere (with a capped size), thus offsetting pre-existing exposure, or zero.
+
+Additionally, as all commitments require some processing overhead on the core, there should be a network parameter `market.amm.minCommitmentQuantum` which defines a minimum quantum for commitment. Any `create` or `amend` transaction where `commitment / asset quantum < market.amm.minCommitmentQuantum` should be rejected.
+
+### Creation/Amendment Process
+
+#### Creation
+
+##### Futures
+
+A `Concentrated Liquidity` AMM has an inherent linkage between position and implied price. By configuration, this position is `0` at `base price` but non-zero above and below that (assuming both an upper and lower bound have been provided), however it is possible to configure an AMM such that this `base price` is far from the market's current `mark price`. In order to bring the vAMM in line with where it "should" be the vAMM will determine whether the order book is currently able to synchronise the vAMM and reject the transaction if not
+
+ 1. If the AMM's `base price` is between the current `best bid` and `best ask` on the market (including other active vAMMs) it is marked as created and enters normal quoting with no trade necessary.
+ 1. If the AMM's `base price` is below the current `best bid` and the AMM has an upper range specified, then the vAMM would need to become short in order to synchronise with the market. In order to do this, the protocol will query each price level in turn starting from `best bid` and:
+ 1. At each level:
+ 1. Check the full volume available across all limit orders and other vAMMs at this price and every higher bid (store a running cumulative volume in order to track this).
+ 1. Check the volume required for the incoming vAMM to have a fair price at that level.
+ 1. If the volume required is less than the full cumulative volume including this level, place a single order on the market for the volume required at the *previous* price level, with a price equal to the *current* price level. e.g. With:
+ - `best bid` at `10 USDT`
+ - Cumulative volume at `8 USDT` equal to `4` contracts
+ - Bid offers for `4` contracts at `7 USDT`
+ - And a vAMM which would be short `5` contracts for a fair price at `8 USDT` and short `4` contracts at `7 USDT`
+ - Enter a limit order to sell `5` contracts for `7 USDT` into the order book and allow it to match.
+ 1. If a price level is reached such that the current price is more than the max slippage specified away from the best bid, reject the transaction.
+ 1. If there is no upper range specified, it is marked as created immediately.
+ 1. If the AMM's `base price` is above the current `best ask` and the AMM has a lower range specified, then the vAMM would need to become short in order to synchronise with the market. Follow an identical process as above but instead on increasing price levels from `best ask`
+ 1. If there is no lower range specified, it is marked as created immediately.
+
+#### Amendment
+
+##### Futures
+
+A similar process is followed in the case of amendments. Changes to the `upper`, `lower` or `base` price, or the `commitment amount` will affect the position implied at a certain price, meaning that the market maker may need to enter an aggressive trade to synchronise. In general, the behaviour above will be followed. As the vAMM may be currently holding a position, the existing position should be compared to that required at both sides (best bid/ask) of the order book to determine whether buying or selling is necessary. If the current position is between that required at best bid and best ask the amendment succeeds without requiring a trade.
+
+If reducing the `commitment amount` then only funds contained within the AMMs `general` account are eligible for removal. If the deduction is less than the `general` account's balance then the reduced funds will be removed immediately and the AMM will enter `single-sided` mode as specified above to reduce the position. If a deduction of greater than the `general` account is requested then the transaction is rejected and no changes are made.
+
+#### Cancellation
+
+##### Futures
+
+In addition to amending to reduce the size a user may also cancel their AMM entirely. In order to do this they must submit a transaction containing only a field `Reduction Strategy` which can take two values:
+
+- `Abandon Position`: In this case, any existing position the AMM holds is given up to the network to close as a liquidation. This is performed in two steps:
+ - All funds in the AMM's `general` account are transferred back to the party who created the AMM.
+ - The position is marked as requiring liquidation and is taken over by the network through the usual liquidation processes. All funds in the margin account are transferred to the network's insurance pool as in a forced liquidation
+- `Reduce-Only`: This moves the AMM to a reduce-only state, in which case the position is reduced over time and ranges dynamically update to ensure no further position is taken. As such:
+ - If the AMM is currently short, the `lower bound` is removed
+ - If the AMM is currently long, the `upper bound` is removed
+ - The `upper`/`lower` bound (if the AMM is currently short/long) is then set to the AMM's current `fair price`. In this mode the AMM should only ever quote on the side which will reduce it's position (it's `upper`/`lower` bound should always be equal to the current `fair price` belief).
+ - Once the position reaches `0` the AMM can be cancelled and all funds in the general account can be returned to the creating party
+ - This acts similarly to the mode when an AMM is synchronising, except that the position will be closed in pieces as the price moves towards the `base price` rather than all at once at the nearest price.
+
+Note that, whilst an `Abandon Position` transaction immediately closes the AMM a `Reduce-Only` transaction will keep it active for an uncertain amount of time into the future. During this time, any Amendment transactions received should move the AMM out of `Reduce-Only` mode and back into standard continuous operation.
+
+### Determining Volumes and Prices
+
+Although AMM prices are not placed onto the book as orders it is necessary to be able to be able to quote prices for a given volume, or know what trading volume would move the fair price to a certain level.
+
+The volume to offer at each price level is determined by whether the price level falls within the upper or lower price bands alongside the market maker's current position. In order to calculate this, use the concept of `Virtual Liquidity` from Uniswap's concentrated liquidity model, corresponding to a theoretical shifted version of the actual liquidity curve to map to an infinite range liquidity curve. The exact mathematics of this can be found in the Uniswap v3 whitepaper and are expanded in depth in the useful guide [Liquidity Math in Uniswap v3](http://atiselsts.github.io/pdfs/uniswap-v3-liquidity-math.pdf). Here will be covered cover only the steps needed to obtain prices/volumes without much exposition.
+
+The AMM position can be thought of as two separate liquidity provision curves, one between `upper price` and `base price`, and another between `base price` and `lower price`. Within each of these, the AMM is buying/selling position on the market in exchange for the quote currency. As the price lowers the AMM is buying position and reducing currency, and as the price rises the AMM is selling position and increasing currency.
+
+One outcome of this is that the curve between `base price` and `lower price` is marginally easier to conceptualise directly from our parameters. At the lowest price side of a curve (`lower price` in this case) the market should be fully in the market contract position, whilst at the highest price (`base price` in this case) it should be fully sold out into cash. This is exactly the formulation used, where at `base price` a zero position is used and a cash amount of `commitment amount`. However given that there is likely to be some degree of leverage allowed on the market this is not directly the amount of funds to calculate using. An AMM with a `commitment amount` of `X` is ultimately defined by the requirement of using `X` in margin at the outer price bounds, so work backwards from that requirement to determine the theoretical cash value. Next calculate the two ranges separately to determine two different `Liquidity` values for the two ranges, which is a value used to later define volumes required to move the price a specified value.
+
+One can calculate a scaling factor that is the smaller of a multiplier specified in the commitment (`leverage_at_bounds`, either upper or lower depending on the side considered) or the market's worst case margin option. If `leverage_at_bounds` for the relevant side is not set then the market's worst case initial margin is taken automatically
+
+$$
+r_f = \min(l_b, \frac{1}{ (f_s + f_l) \cdotp f_i}) ,
+$$
+
+where $l_b$ is the sided value `leverage_at_bounds` (`upper ratio` if the upper band is being considered and `lower ratio` if the lower band is), $f_s$ is the market's sided risk factor (different for long and short positions), $f_l$ is the market's linear slippage component and $f_i$ is the market's initial margin factor.
+
+A few components are needed to calculate the target position at the bounds, which will be used to generate a liquidity value for each curve. First, in order to calculate the average entry price for the full volume traded across the range one can calculate this liquidity, or $L_u$ value, for a range assuming a position of $1$ at the bounds (as the average entry price is invariant to scaling of volume across the curve)
+
+$$
+L_u = \frac{\sqrt{p_u} \sqrt{p_l}}{\sqrt{p_u} - \sqrt{p_l}} ,
+$$
+
+where $p_u$ is the price at the top of the range (`upper price` for the upper range and `base price` for the lower range) and $p_l$ is the price at the bottom of the range (`base price` for the upper range and `lower price` for the lower range). This gives the two `L` values for the two ranges. With this the average entry price can be calculated as
+
+$$
+p_a = L_u \sqrt{p_u} (1 - \frac{L_u}{L_u + \sqrt{p_u}}) ,
+$$
+
+where $p_a$ is the average execution price across the range and other values are as defined above. With this, the volumes required to trade to the bounds of the ranges are:
+
+$$
+P_{v_l} = \frac{r_f b}{p_l (1 - r_f) + r_f p_a} ,
+$$
+
+$$
+P_{v_u} = \frac{r_f b}{p_u (1 + r_f) - r_f p_a} ,
+$$
+
+where $r_f$ is the `short` factor for the upper range and the `long` factor for the lower range, `b` is the current total balance of the vAMM across all accounts, $P_{v_l}$ is the theoretical volume and the bottom of the lower bound and $P_{v_u}$ is the (absolute value of the) theoretical volume at the top of the upper bound. The final $L$ scores can then be reached with the equation
+
+$$
+L = P_v \cdot \frac{\sqrt{p_u} \sqrt{p_l}}{\sqrt{p_u} - \sqrt{p_l}} = P_v L_u,
+$$
+
+where $P_v$ is the virtual position from the previous formula, $p_u$ and $p_l$ are as defined above. This gives the two `L` values for the two ranges.
+
+#### Fair price
+
+From here the first step is calculating a `fair` price, which can be done by utilising the `L` value for the respective range to calculate `virtual` values for the pool balances. From here on `y` will be the cash balance of the pool and `x` the position.
+
+ 1. First, identify the current position, `P`. If it is `0` then the current fair price is the base price.
+ 1. If `P != 0` then calculate the implied price from the current position using the virtual position $P_v$ which is equal to $P$ when $P > 0$ or $P + P_{v_u}$ where $P < 0$.
+ 1. The fair price can then be calculated as
+
+$$
+p_f = \frac{\sqrt{p_u}}{P_v \cdotp \sqrt{p_u} \cdotp \frac{1}{L} + 1}^2 ,
+$$
+
+where $p_u$ is `base price` when $P > 0$ or `upper price` when $P < 0$.
+
+#### Price to trade a given volume
+
+Finally, the protocol needs to calculate the inverse of the previous section. That is, given a volume bought from/sold to the AMM, at what price should the trade be executed. This could be calculated naively by summing across all the smallest increment volume differences, however this would be computationally inefficient and can be optimised by instead considering the full trade size.
+
+
+To calculate this, the interface will need the `starting price` $p_s$, `ending price` $p_e$, `upper price of the current range` $p_u$ (`upper price` if `P < 0` else `base price`), `lower price of the current range` $p_l$ (`base price` if `P < 0` else `lower price`), the volume to trade $\Delta x$ and the `L` value for the current range. At `P = 0` use the values for the range which the volume change will cause the position to move into.
+
+First, the steps for calculating a fair price should be followed in order to obtain the implied price. Next the virtual `x` and `y` balances must be found:
+
+ 1. If `P > 0`:
+ 1. The virtual `x` of the position can be calculated as $x_v = P + \frac{L}{\sqrt{p_b}}$, where $L$ is the value for the lower range, $P$ is the market position and $p_b$ is the `base price`.
+ 1. The virtual `y` can be calculated as $y_v = L * \sqrt{p_f}$ where $p_f$ is the fair price calculated above.
+ 1. If `P < 0`:
+ 1. The virtual `x` of the position can be calculated as $x_v = P + P_{v_u} + \frac{L}{\sqrt{p_u}}$ where $p_u$ is the `upper price` and $P_{v_u}$ is the theoretical volume at the upper bound.
+ 1. The virtual `y` can be calculated as $y_v = L * \sqrt{p_f}$ where $p_f$ is the fair price calculated above.
+
+Once obtained, the price can be obtained from the fundamental requirement of the product $y \cdot x$ remaining constant. This gives the relationship
+
+$$
+y_v \cdot x_v = (y_v + \Delta y) \cdot (x_v - \Delta x) ,
+$$
+
+From which $\Delta y$ must be calculated
+
+$$
+\Delta y = \frac{y_v \cdot x_v}{x_v - \Delta x} - y_v ,
+$$
+
+Thus giving a final execution price to return of $\frac{\Delta y}{\Delta x}$.
+
+#### Volume between two prices
+
+For the second interface one needs to calculate the volume which would be posted to the book between two price levels. In order to calculate this for an AMM one is ultimately asking the question "what volume of swap would cause the fair price to move from price A to price B?"
+
+To calculate this, the interface will need the `starting price` $p_s$, `ending price` $p_e$, `upper price of the current range` $p_u$ (`upper price` if `P < 0` else `base price`) and the `L` value for the current range. At `P = 0` use the values for the range which the volume change will cause the position to move into.
+
+First, calculate the implied position at `starting price` and `ending price` and return the difference.
+
+For a given price $p$ calculate implied position $P_i$ with
+
+$$
+P_i = L \cdot \frac{\sqrt{p_u} - \sqrt{p}}{\sqrt{p} \cdotp \sqrt{p_u}} ,
+$$
+
+Then simply return the absolute difference between these two prices.
+
+## Determining Liquidity Contribution
+
+The provided liquidity from an AMM commitment must be determined for two reasons. Firstly to decide instantaneous distribution of liquidity fees between the various liquidity types and secondly to calculate a virtual liquidity commitment amount for assigning AMM users with an ELS value. This will be used for determining the distribution of ELS-eligible fees on a market along with voting weight in market change proposals.
+
+As an AMM does not directly place orders on the book this calculation first needs to infer what the orders would be at each level within the eligible price bounds (those required by SLA parameters on the given market). From here any given AMM curve should implement functionality to take two prices and return the volume it would place to trade fully across that range. Calling this function across the price range out to lower and upper SLA bounds retrieves the full order book shape for each AMM.
+
+Once these are retrieved, the price / volume points should be combined with a precomputed array of the probability of trading at each price level to calculate the liquidity supplied on each side of the orderbook as defined in [probability of trading](./0034-PROB-prob_weighted_liquidity_measure.ipynb). Once this is calculated, use this value as the instantaneous liquidity score for fee distribution as defined in [setting fees and rewards](./0042-LIQF-setting_fees_and_rewarding_lps.md).
+
+As the computation of this virtual order shape may be heavy when run across a large number of passive AMMs the number of AMMs updated per block should be throttled to a fixed maximum number, updating on a rolling frequency, or when updated/first created. Additionally, a network parameter of `market.liquidity.maxAmmCalculationLevels` should be used to determine the maximum number of levels to be used between the upper and lower SLA bounds. If this number is exceeded the space between upper and lower should be linearly interpolated to produce at most this many sampling points and an estimate using those price levels be used instead.
+
+A given AMM's average liquidity score across the epoch should also be tracked, giving a time-weighted average at the end of each epoch (including `0` values for any time when the AMM either did not exist or was not providing liquidity on one side of the book). From this, a virtual stake amount can be calculated by dividing through by the `market.liquidity.stakeToCcyVolume` value and the AMM key's ELS updated as normal.
+
+## Setting Fees
+
+The `proposed_fee` provided as part of the AMM construction contributes to the fee determination logic on the market, if a setup where LPs decide on the market fee is in use. In the case where it is the AMM's current assigned ELS, or the running average liquidity provided so far if the commitment was made in the current epoch, is used for weighting the AMM's vote for the fee.
+
+## Market Settlement
+
+At market settlement, an AMM's position will be settled alongside all others as if they are a standard party. Once settlement is complete, any remaining funds in the AMM's account will be transferred back to the creator's general account and the AMM can be removed.
+
+## Acceptance Criteria
+
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `100`, a user with `1000 USDT` is able to create a vAMM with commitment `1000`, base price `100`, upper price `150`, lower price `85` and leverage ratio at each bound `0.25`. (0090-VAMM-001)
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `100`, a user with `1000 USDT` is able to create a vAMM with commitment `1000`, base price `100`, no upper price, lower price `85` and leverage ratio at lower bound `0.25`. (0090-VAMM-002)
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `100`, a user with `1000 USDT` is able to create a vAMM with commitment `1000`, base price `100`, upper price `150`, no lower price and leverage ratio at upper bound `0.25`. (0090-VAMM-003)
+
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `100`, a user with `100 USDT` is unable to create a vAMM with commitment `1000`, and any other combination of settings. (0090-VAMM-004)
+- When `market.amm.minCommitmentQuantum` is `1000`, mid price of the market `100`, a user with `1000 USDT` is able to create a vAMM with commitment `100`, and any other combination of settings. (0090-VAMM-005)
+
+- When `market.amm.minCommitmentQuantum` is `1000`, mid price of the market `100`, and a user with `1000 USDT` creates a vAMM with commitment `1000`, base price `100`, upper price `150`, lower price `85` and leverage ratio at each bound `0.25`:
+ - If other traders trade to move the market mid price to `140` the vAMM has a short position. (0090-VAMM-006)
+ - If other traders trade to move the market mid price to `90` the vAMM has a long position (0090-VAMM-007)
+ - If other traders trade to move the market mid price to `150` the vAMM will post no further sell orders above this price, and the vAMM's position notional value will be equal to `4x` its total account balance. (0090-VAMM-008)
+ - If other traders trade to move the market mid price to `85` the vAMM will post no further buy orders below this price, and the vAMM's position notional value will be equal to `4x` its total account balance.(0090-VAMM-009)
+ - If other traders trade to move the market mid price to `110` and then trade to move the mid price back to `100` the vAMM will have a position of `0`. (0090-VAMM-010)
+ - If other traders trade to move the market mid price to `90` and then trade to move the mid price back to `100` the vAMM will have a position of `0`. (0090-VAMM-011)
+ - If other traders trade to move the market mid price to `90` and then in one trade move the mid price to `110` then trade to move the mid price back to `100` the vAMM will have a position of `0`. (0090-VAMM-012)
+ - If other traders trade to move the market mid price to `90` and then move the mid price back to `100` in several trades of varying size, the vAMM will have a position of `0`. (0090-VAMM-013)
+ - If other traders trade to move the market mid price to `90` and then in one trade move the mid price to `110` then trade to move the mid price to `120` the vAMM will have a larger (more negative) but comparable position to if they had been moved straight from `100` to `120`. (0090-VAMM-014)
+
+- A vAMM which has been created and is active contributes with it's proposed fee level to the active fee setting mechanism. (0090-VAMM-015)
+- At the end of an epoch, the vAMM's virtual ELS should be equal to the ELS of a regular LP with the same committed volume on the book who joined at the end of the same epoch as the vAMM did (i.e. if a vAMM has an average volume on each side of the book each epoch of 10k USDT, their ELS should be equal to that of a regular LP who has a commitment which requires supplying 10k USDT who joined at the same time as them). (0090-VAMM-016)
+ - A vAMM's virtual ELS should grow at the same rate as a full LP's ELS who joined at the end of the epoch in which the vAMM joined (0090-VAMM-017)
+- A vAMM can vote in market update proposals with the additional weight of their ELS (i.e. not just from governance token holdings). (0090-VAMM-018)
+
+- If a vAMM is cancelled with `Abandon Position` then it is closed immediately. All funds which were in the `general` account of the vAMM are returned to the user who created the vAMM and the remaining position and margin funds are moved to the network to close out as it would a regular defaulted position. (0090-VAMM-019)
+
+- If a vAMM is cancelled and set in `Reduce-Only` mode when it is currently long, then: (0090-VAMM-020)
+ - It creates no further buy orders even if the current price is above the configured lower price.
+ - When one of it's sell orders is executed it still does not produce buy orders, and correctly quotes sell orders from a higher price.
+ - When the position reaches `0` the vAMM is closed and all funds are released to the user after the next mark to market.
+
+
+- If a vAMM is cancelled and set in `Reduce-Only` mode when it is currently short, then: (0090-VAMM-021)
+ - It creates no further sell orders even if the current price is below the configured upper price.
+ - When one of it's buy orders is executed it still does not produce sell orders, and correctly quotes buy orders from a lower price.
+ - When the position reaches `0` the vAMM is closed and all funds are released to the user after the next mark to market.
+
+- If a vAMM is cancelled and set in `Reduce-Only` mode when it currently has no position then all funds are released after the next mark to market. (0090-VAMM-022)
+
+- If a vAMM is cancelled and set into `Reduce-Only` mode, then an amend is sent by the user who created it, the vAMM is amended according to those instructions and is moved out of `Reduce-Only` mode back into normal operation. (0090-VAMM-023)
+
+- When `market.amm.minCommitmentQuantum` is `1000`, mid price of the market `100`, and a user with `1000 USDT` creates a vAMM with commitment `1000`, base price `100`, upper price `150`, lower price `85` and leverage ratio at each bound `0.25`:
+ - If other traders trade to move the market mid price to `140` the vAMM has a short position. (0090-VAMM-024)
+ - If the vAMM is then amended such that it has a new base price of `140` it should attempt to place a trade to rebalance it's position to `0` at a mid price of `140`.
+ - If that trade can execute with the slippage as configured in the request then the transaction is accepted. (0090-VAMM-025)
+ - If the trade cannot execute with the slippage as configured in the request then the transaction is rejected and no changes to the vAMM are made. (0090-VAMM-026)
+
+- When a user with `1000 USDT` creates a vAMM with commitment `1000`, base price `100`, upper price `150`, lower price `85` and leverage ratio at each bound `0.25`, if other traders trade to move the market mid price to `140` quotes with a mid price of `140` (volume quotes above `140` should be sells, volume quotes below `140` should be buys). (0090-VAMM-027)
+
+- When a user with `1000 USDT` creates a vAMM with commitment `1000`, base price `100`, upper price `150`, lower price `85` and leverage ratio at each bound `0.25`, the volume quoted to move from price `100` to price `110` in one step is the same as the sum of the volumes to move in 10 steps of `1` e.g. `100` -> `101`, `101` -> `102` etc. (0090-VAMM-028)
+
+- When a user with `1000 USDT` creates a vAMM with commitment `1000`, base price `100`, upper price `150`, lower price `85` and leverage ratio at each bound `0.25`, the volume quoted to move from price `100` to price `90` in one step is the same as the sum of the volumes to move in 10 steps of `1` e.g. `100` -> `99`, `99` -> `98` etc. (0090-VAMM-029)
+
+- When a user with `1000 USDT` creates a vAMM with commitment `1000`, base price `100`, upper price `150`, lower price `85` and leverage ratio at each bound `0.25`:
+ 1. Take quoted volumes to move to `110` and `90`
+ 1. Execute a trade of the quoted size to move the fair price to `110`
+ 1. Take a quote to move to price `90`
+ 1. Ensure this is equal to the sum of the quotes from step `1` (with the volume from `100` to `110` negated) (0090-VAMM-030)
+
+- When an AMM is active on a market at time of settlement with a position in a well collateralised state, the market can settle successfully and then all funds on the AMM key are transferred back to the main party's account (0090-VAMM-031)
+
+- When an AMM is active on a market at time of settlement but the settlement price means that the party is closed out no funds are transferred back to the main party's account (0090-VAMM-032)
+
+- An AMM with base price `1000`, upper price `1100`, lower price `900` and current position `0`:
+ - Quotes a volume of `8.216` units to buy to move fair price to `900`
+ - Quotes a price of `948.683` to buy `8.216` units
+ - Quotes a volume of `7.814` units to sell to move fair price to `1100`
+ - Quotes a price of `1048.809` to sell `7.814` units
+
+- An AMM with base price `1000`, upper price `1100`, lower price `900` and current position short `7.814`:
+ - Quotes a volume of `0` units to buy above `1100`
+ - Quotes a volume of `7.814` units to buy to move fair price to `1000`
+ - Quotes a price of `1048.809` to buy `7.814` units
+ - Quotes a price of `997.488` to sell `16.030` units
+ - Does not quote a price to sell `17` units
+
+- With an existing book consisting solely of one vAMM (at any fair price) a new vAMM entering the market at a differing base price to the existing vAMM's current price, but where upper and lower bounds of each are far beyond the base/fair prices, triggers a trade between the two vAMMs, after which they both have the same fair price and the book is not crossed. (0090-VAMM-033)
+
+- With an existing book consisting solely of one vAMM (at any fair price) a new vAMM entering the market at a differing base price to the existing vAMM's current price, with upper and lower bounds set such that the entire structure is separate to the existing vAMM (e.g. the incoming vAMM's lower price is greater than the existing vAMM's upper price), a trade occurs between the two AMMs leaving at least one of them at the extreme edge of their quoting range. (0090-VAMM-034)
+
+- With two vAMMs existing on the market, and no other orders, both of which have the same fair price, another counterparty placing a large buy order for a given volume, followed by a large sell order for the same volume, results in the vAMMs both taking a position and then returning to `0` position, with a balance increase equal to the maker fees received plus those for the incoming trader crossing the spread. (0090-VAMM-035)
diff --git a/protocol/0091-ILSF-instantaneous_liquidity_scoring_funcion.md b/protocol/0091-ILSF-instantaneous_liquidity_scoring_funcion.md
new file mode 100644
index 000000000..88d4d1ffe
--- /dev/null
+++ b/protocol/0091-ILSF-instantaneous_liquidity_scoring_funcion.md
@@ -0,0 +1,22 @@
+# Instantaneous liquidity scoring function
+
+## Summary
+
+While by default the market uses probability of trading to calculate the [liquidity score](./0042-LIQF-setting_fees_and_rewarding_lps.md#calculating-the-instantaneous-liquidity-score) it should also be possible to explicitly prescribe the instantaneous liquidity scoring function. When such function is specified then it gets used for liquidity score calculation and probability of trading is ignored.
+
+## Specifying the function
+
+The function gets specified separately for each side of the book as:
+
+* `reference`: reference point to which offset from each `point` is to be applied. It can be `MID` or `BEST BID` / `BEST ASK` depending on the side of the book for which the function is specified.
+* `points`: collection of `(offset, value)` tuples providing a discrete representation of the function. Tuple `(10,0.4)` means that the value of the instantaneous liquidity function for a price level of reference point with an offset of `10` is `0.4` (specified in the same way as for [pegged orders](./0037-OPEG-pegged_orders.md)).
+* `interpolation strategy`: prescribes a way in which price levels not covered by `points` should be calculated. Should be either `FLAT` resulting in a piecewise-constant function (starting from a lowest offset the value specified for it is assumed to prevail until the next offset is reached) or `LINEAR` resulting in a linear interpolation between points.
+
+Flat extrapolation is always carried out, that is when price level greater than point with a highest offset or smaller than that with a lowest offset needs to be scored we use the nearest values that's been specified.
+
+Validation:
+
+* same as pegged orders for `reference` and `offset`,
+* at least two `points` must be specified.
+
+When liquidity scoring function is not specified [probability of trading](./0034-PROB-prob_weighted_liquidity_measure.ipynb) should be used for [liquidity score](./0042-LIQF-setting_fees_and_rewarding_lps.md#calculating-the-instantaneous-liquidity-score) calculation by default. It should also be possible to change it back to a `nil` value later on in market's life to stop using the function prescribed before and return to the default behaviour.
diff --git a/protocol/0092-SAMM-spot_automated_market_maker.md b/protocol/0092-SAMM-spot_automated_market_maker.md
new file mode 100644
index 000000000..e98156967
--- /dev/null
+++ b/protocol/0092-SAMM-spot_automated_market_maker.md
@@ -0,0 +1,226 @@
+# Spot Automated Market Maker Framework
+
+## Summary
+
+The automated market maker (AMM) framework is designed to allow for the provision of an on-chain market making methodology which automatically provides prices according to a simple set of rules based on current market data. These rulesets are not created with the expectation of providing any profit nor of remaining solvent under any specific conditions, and so should be limited to conceptually simple setups. The initial methodology follows a concentrated-liquidity style constant-function market setup, with configurable maximum and minimum price bounds.
+
+An automated market maker is configured at a per-key level, and is enabled by submitting a transaction with the requisite parameters. At this point in time the protocol will move committed funds to a sub-account which will be used to manage margin for the AMM. Once enabled, the configuration will be added to the pool of available AMMs to be utilised by the matching engine.
+
+Each party may have only one AMM configuration per market, and both Spot and Futures markets are eligible, with the behaviour differing slightly for each.
+
+## Process Overview
+
+The configuration and resultant lifecycle of an automated market maker is as follows:
+
+- Party funds a key which will be used by the strategy with desired token amounts.
+- Party submits a transaction containing configuration for the strategy on a given market. This will contain:
+ - Amount of base token to commit OR Amount of quote token to commit
+ - Price bounds (upper, lower)
+- Once accepted, the network will transfer funds to a sub-account and use the other parameters for maintaining the position.
+- The party can cancel the AMM at any time, with the spot balances immediately returned to their general accounts.
+
+## Sub-Account Configuration
+
+Each main Vega key will have one associated sub account for a given market, on which an AMM may be set up. The account key should be generated through a hash of the main account key plus the ID of the market to generate a valid Vega address in a predictable manner. Outside of the AMM framework the sub-accounts are treated identically to any other account, they will have the standard associated margin/general accounts and be able to place orders if required as with any other account. The key differentiator is that no external party will have the private key to control these accounts directly. The maintenance of such an account will be performed through a few actions:
+
+- Creation: A sub-account will be funded when a user configures an AMM strategy with a set of criteria and a commitment amount. At this point in time the commitment amount will be transferred to the sub-account's general account and the AMM strategy will commence
+- Cancellation: When the AMM is cancelled, balances are immediately returned to the user.
+- Amendment: Updates the strategy or commitment for a sub-account
+
+## Interface
+
+All AMM configurations should implement two key interfaces:
+
+- One taking simply the current state (`position` and `total funds`) and a trade (`volume`, `side`) and returning a quote price. This should also handle a trade of `volume = 0` to return a notional `fair price` (for a spot AMM this being the ratio of virtual balances of the two tokens.)
+- The second taking (`position`, `total funds`, `side`, `start price`, `end price`) should return the full volume the AMM would trade between the two prices (inclusive).
+
+## AMM Configurations
+
+Initially there will only be one option for AMM behaviour, that of a constant-function curve, however there may be others available in future. As such, the parameters pertaining to this model in particular should be passed in their own structure such that the creation message is similar to:
+
+```json
+{
+ base_commitment,
+ quote_commitment,
+ reference_price,
+ market,
+ slippage_tolerance_percentage,
+ proposed_fee,
+ concentrated_liquidity_params: {
+ upper_price,
+ lower_price,
+ }
+}
+```
+
+### Concentrated Liquidity - Spot
+
+The `Concentrated Liquidity` AMM is a market maker utilising a Uniswap v3-style pricing curve for managing price based upon current market price. This allows for the market maker to automatically provide a pricing curve for any prices within some configurable range.
+
+The concentrated liquidity market maker consists of a liquidity curve of prices specified by a given `upper price` at which the market maker will be fully in the `quote` currency and a `lower price` at which the market maker will be fully in the `base` currency. This is configured through a number of parameters:
+
+- **Upper Price**: The maximum price bound for market making. Prices between the `lower price` and this price will have volume placed, with no orders above this price. At this price the AMM will be fully in the `quote` currency.
+- **Lower Price**: The minimum price bound for market making. Prices between the `upper price` and this price will have volume placed, with no orders below this price. At this price the AMM will be fully in the `base` currency.
+- **Reference Price**: The price at which the specified commitment amount is the account's balance of that token (e.g. if this is the current market price, the commitment amount specified is exactly what will be taken). Note that by design if this price is above the `upper price` a non-zero base commitment specification is invalid, as is a non-zero quote commitment specification if this is below the `lower price`.
+- One of:
+ - **Commitment Base**: This is the initial volume of base token to transfer into the sub account for use in market making. If this amount is not currently available in the main account's general account the transaction will fail. If specified, the amount of quote token to transfer is implied from current market conditions.
+ - **Commitment Quote**: This is the initial volume of quote token to transfer into the sub account for use in market making. If this amount is not currently available in the main account's general account the transaction will fail. If specified, the amount of base token to transfer is implied from current market conditions.
+
+Additionally, as all commitments require some processing overhead on the core, there should be a network parameter `market.amm.minCommitmentQuantum` which defines a minimum quantum for commitment. Any `create` or `amend` transaction where `quote commitment / quote asset quantum + base commitment / base asset quantum < market.amm.minCommitmentQuantum` should be rejected.
+
+### Creation/Amendment Process
+
+#### Creation
+
+A `Concentrated Liquidity` AMM has an inherent linkage between position and implied price. By configuration, this position is fully in the quote asset at `upper price` and moves towards being fully in the base asset at `lower price`, however it is possible to configure an AMM such that this range is either covering, above or below the current market price. In order to bring the AMM in line with where it "should" be the AMM will take either the amount of base or quote asset desired and then imply the volume of the other at the current market price. The protocol will then attempt to take both amounts of assets from the user.
+
+ 1. A `market effective price` will be determined:
+ 1. If there is currently a `best bid` and `best ask` (including existing AMMs) and the market is in continuous trading then the mid price will be used.
+ 2. If there is only a bid or only an ask, and the market is in continuous trading, then that best bid or ask will be used.
+ 3. If the market is in auction the mark price will be used, or if that is unavailable then the indicative uncrossing price
+ 2. An `L` value will be calculated for the liquidity as specified below.
+ 3. The correct balances of base and quote tokens will be calculated given the `L` value and the `market effective price`
+ 4. These correct balances will be transferred from the user's balances to the AMM's. If they cannot be transferred the transaction will be rejected.
+
+#### Amendment
+
+The process as above will be followed. By utilising the new reference price, market price and calculated liquidity values the AMM's balance will be adjusted to be correct by transferring from/to the user's general accounts.
+
+#### Cancellation
+
+The AMM can be cancelled immediately and balances of both tokens will be transferred back to the user's general accounts.
+
+### Determining Volumes and Prices
+
+Although AMM prices are not placed onto the book as orders it is necessary to be able to be able to quote prices for a given volume, or know what trading volume would move the fair price to a certain level.
+
+The volume to offer at each price level is determined by whether the price level falls between the upper and lower price bands alongside the market maker's current position. In order to calculate this, use the concept of `Virtual Liquidity` from Uniswap's concentrated liquidity model, corresponding to a theoretical shifted version of the actual liquidity curve to map to an infinite range liquidity curve. The exact mathematics of this can be found in the Uniswap v3 whitepaper and are expanded in depth in the useful guide [Liquidity Math in Uniswap v3](http://atiselsts.github.io/pdfs/uniswap-v3-liquidity-math.pdf). Here will be covered cover only the steps needed to obtain prices/volumes without much exposition.
+
+The most important value to calculate is the liquidity, or $L$ value, which determines the balances of each token at any given price level. This can be uniquely determined from the specification of the bound prices, reference price and reference quantity.
+
+Calling the reference price specified $p_r$, and the price bounds $p_u$ and $p_l$ for the upper and lower bounds respectively, if $p_r <= p_l$ then
+
+$$
+L = c_b \frac{\sqrt{p_l} \sqrt{p_u}}{\sqrt{p_u} - \sqrt{p_l}} ,
+$$
+
+$$
+c_q = 0 ,
+$$
+
+where $c_b$ is the base commitment value specified (note that as $p_r <= p_l$ it would be invalid to specify a quote commitment value). Similarly, if $p_r >= p_u$ then
+
+$$
+L = \frac{c_q}{\sqrt{p_u} - \sqrt{p_l}} ,
+$$
+
+$$
+c_b = 0 ,
+$$
+
+
+where $c_q$ is the quote commitment value specified (note that as $p_r >= p_l$ it would be invalid to specify a base commitment value).
+
+In the final case where $p_l < p_r < p_u$ we can think of there being two separate ranges, one above (from $p_r$ to $p_u$) and one below ($p_l$ to $p_r$). In the upper range the AMM is fully in the base asset, in the lower it is fully in the quote asset. Thus, the protocol can take the specified commitment value, calculate $L$ with that on the relevant range.
+
+Concretely:
+
+If $c_q$ is specified
+
+$$
+L = \frac{c_q}{\sqrt{p_r} - \sqrt{p_l}} ,
+$$
+
+$$
+c_b = L \frac{\sqrt{p_u} - \sqrt{p_r}}{\sqrt{p_u} \sqrt{p_r}}
+$$
+
+and if $c_b$ is specified
+
+$$
+L = c_b \frac{\sqrt{p_l} \sqrt{p_r}}{\sqrt{p_r} - \sqrt{p_l}} ,
+$$
+
+$$
+c_q = L (\sqrt{p_r} - \sqrt{p_l}) ,
+$$
+
+#### Fair price
+
+The fair price can then be calculated as
+
+$$
+p_f = \frac{b_q + L \sqrt{p_l}}{b_b + \frac{L}{\sqrt{p_u}}} ,
+$$
+
+where $b_q$ is the current balance of the quote token and $b_b$ is the current balance of the base token.
+
+#### Price to trade a given volume
+
+Finally, the protocol needs to calculate the inverse of the previous section. That is, given a volume bought from/sold to the AMM, at what price should the trade be executed. This could be calculated naively by summing across all the smallest increment volume differences, however this would be computationally inefficient and can be optimised by instead considering the full trade size.
+
+First, the virtual `x` and `y` balances must be found (where `x` is the base balance and `y` is the quote balance):
+
+$$
+x_v = b_b + \frac{L}{\sqrt{p_u}} ,
+$$
+
+$$
+y_v = b_q + L \sqrt{p_l} ,
+$$
+
+Once obtained, the price can be obtained from the fundamental requirement of the product $y \cdot x$ remaining constant. This gives the relationship
+
+$$
+y_v \cdot x_v = (y_v + \Delta y) \cdot (x_v - \Delta x) ,
+$$
+
+From which $\Delta y$ must be calculated
+
+$$
+\Delta y = \frac{y_v \cdot x_v}{x_v - \Delta x} - y_v ,
+$$
+
+Thus giving a final execution price to return of $\frac{\Delta y}{\Delta x}$.
+
+#### Volume between two prices
+
+For the second interface one needs to calculate the volume which would be posted to the book between two price levels. In order to calculate this for an AMM one is ultimately asking the question "what volume of swap would cause the fair price to move from price A to price B?"
+
+To calculate this, the interface will need the `starting price` $p_s$, `ending price` $p_e$, `upper price` $p_u$ and the `L`. At `Q = 0` (a position of `0` in the quote asset `Q`) use the values for the range which the volume change will cause the position to move into.
+
+First, calculate the implied position at `starting price` and `ending price` and return the difference.
+
+For a given price $p$ calculate implied position in the quote asset $Q_i$ with
+
+$$
+Q_i = L \cdot \frac{\sqrt{p_u} - \sqrt{p}}{\sqrt{p} \cdotp \sqrt{p_u}} ,
+$$
+
+Then simply return the absolute difference between these two values.
+
+## Determining Liquidity Contribution
+
+Liquidity contribution for spot AMMs should be determined identically to that for futures market vAMMs in [0090-SAMM](./0090-SAMM-automated_market_maker.md)
+
+## Setting Fees
+
+The `proposed_fee` provided as part of the AMM construction contributes to the fee determination logic on the market, if a setup where LPs decide on the market fee is in use. In the case where it is the AMM's current assigned ELS, or the running average liquidity provided so far if the commitment was made in the current epoch, is used for weighting the AMM's vote for the fee.
+
+## Market Settlement
+
+At market settlement, an AMM's position will be settled alongside all others as if they are a standard party. Once settlement is complete, any remaining funds in the AMM's account will be transferred back to the creator's general account and the AMM can be removed.
+
+
+## Acceptance Criteria
+
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `ETH/USDT` is `100`, a user with `1000 USDT` is able to create an AMM with commitment `1000 USDT`, lower price `100`, upper price `150`, reference price `100`. (0092-SAMM-001)
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `ETH/USDT` is `100`, a user with `1000 USDT` is unable to create an AMM with commitment `1000 USDT`, lower price `100`, upper price `150`, reference price `150`. (0092-SAMM-002)
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `ETH/USDT` is `100`, a user with `1000 USDT` is able to create an AMM with commitment `1 ETH`, lower price `100`, upper price `150`, reference price `150`. (0092-SAMM-003)
+- When `market.amm.minCommitmentQuantum` is `100`, mid price of the market `ETH/USDT` is `100`, a user with `10 USDT` is unable to create an AMM with commitment `10 USDT`, lower price `100`, upper price `150`, reference price `150`. (0092-SAMM-004)
+
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `ETH/USDT` is `100`, a user with `1 ETH` is able to create an AMM with commitment `1 ETH`, lower price `80`, upper price `100`, reference price `100`. (0092-SAMM-005)
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `ETH/USDT` is `100`, a user with `1 ETH` is unable to create an AMM with commitment `1 ETH`, lower price `80`, upper price `100`, reference price `80`. (0092-SAMM-006)
+
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `ETH/USDT` is `100`, a user with `1 ETH` and `100 USDT` is able to create an AMM with commitment `1 ETH`, lower price `80`, upper price `130`, reference price `100`. (0092-SAMM-007)
+- When `market.amm.minCommitmentQuantum` is `1`, mid price of the market `ETH/USDT` is `100`, a user with `1 ETH` and `99 USDT` is unable to create an AMM with commitment `1 ETH`, lower price `80`, upper price `130`, reference price `100`. (0092-SAMM-008)
diff --git a/protocol/0093-TRTO-trading_transaction_ordering.md b/protocol/0093-TRTO-trading_transaction_ordering.md
new file mode 100644
index 000000000..e72eeb86e
--- /dev/null
+++ b/protocol/0093-TRTO-trading_transaction_ordering.md
@@ -0,0 +1,47 @@
+# Trading Transaction Ordering
+
+In order for an exchange to offer competitive prices for parties arriving and wishing to trade immediately, others have to be willing to place limit orders on the book which will remain there and wait for an incoming order to match with it. These are required for a limit order book to exist (as implied by the name) but expose the party placing the initial limit order to a range of risks, as often the reason these orders will eventually trade is because the price has become unfavourable to the party who placed the order, but they either have not realised or have not had a chance to cancel the order yet. This is often referred to as "toxic flow". Whilst another party obviously gains from this transaction, it is generally acknowledged that the higher a given venue's proportion of toxic flow to more balanced flow, the wider market makers will end up quoting to avoid being the victim of this. This issue is particularly present when considering a decentralised exchange with a publicly available mempool and higher latency than a centralised exchange, both giving potential toxic flow a significant edge. As such, exchange and transaction designs which allow for the reduction of this without unfairly impacting other parties using the network may allow for the exchange as a whole to provide traders with better prices than otherwise. This specification covers one such proposed methodology, comprising of a specified ordering of order executions on a given market.
+
+## Execution Ordering
+
+Trading transactions (those which create, update or remove orders of any type on any market) should be executed in a specific way once included in a block. This ordering is per-market (inter-market ordering is unspecified). The functionality can be enabled/disabled at a per-market level through market governance. When disabled for a given market, all transactions are sorted as normal with no delays applied.
+
+Chiefly, when enabled all transactions which would cancel an order or create post-only orders should be executed first before transactions which could create trades, within which all cancellations should be executed prior to post-only orders. The ordering of the transactions in this way means that, at the time of each block being created, all parties who are contributing to creating an order book have an opportunity to update their prices prior to anyone who would capitalise on temporary stale prices, regardless of which transaction reached the validator's pre-block transaction pool first. This ordering can still be seen to be a "fair" transaction ordering, as parties cannot take actions which would cause a trade, but only take action to avoid trading at a price they no longer desire (or indeed to improve a price prior to trade-creating transactions' arrival).
+
+Furthermore, transactions which can cause a trade by acting aggressively, such as non-post-only orders and amends, will be delayed by a number of blocks, governed by a network parameter `market.aggressiveOrderBlockDelay`, prior to execution. This results in the pattern where:
+
+ 1. Prior to block N, post only order A and market order B arrive to the chain, these are both included in block N.
+ 1. When block N is executed, order A takes effect.
+ 1. Prior to block N + 1, post only order C then market order D and finally a cancellation of order A arrive to the chain, these are both included in block N + 1.
+ 1. When block N + 1 is executed, the cancellation of order A first takes effect, then the post-only order C, then finally market order B.
+ 1. When block N + 2 is executed, market order D takes effect
+
+## Batch Transactions
+
+Batch transactions, as they contain different order types, must be handled slightly differently. In the initial version, they will remain to be executed as one unit. When determining execution position, the protocol will inspect the components of the batch transaction. If the transaction contains any order creation messages which are not post-only, or any order amends at all, the entire batch will be delayed as if it were a transaction which could create trades (as some component of it could). If the batch contains exclusively cancellations and/or post-only limit orders then it will be executed in the expedited head-of-block mode specified above. Batches will still be executed all at once as specified, in the order cancellations -> amendments -> creations. The total ordering of executions when including batches should be:
+
+ 1. Standalone Cancellations
+ 1. Batches (containing both cancellations and order creations)
+ 1. Standalone Creations
+
+## Acceptance criteria
+
+- A batch transaction including only cancellations and/or post-only limit orders is executed at the top of the block alongside standalone post-only limit orders and cancellations. (0093-TRTO-001)
+- A batch transaction including either a non-post-only order or an amendment is delayed by one block and then executed after the expedited transactions in that later block. (0093-TRTO-002)
+- Cancellation transactions always occur before:
+ - Market orders (0093-TRTO-003)
+ - Non post-only limit orders (0093-TRTO-004)
+ - Order Amends (0093-TRTO-005)
+ - post-only limit orders (0093-TRTO-013)
+- Post-only transactions always occur before:
+ - Market orders (0093-TRTO-006)
+ - Non post-only limit orders (0093-TRTO-007)
+ - Order Amends (0093-TRTO-008)
+- Potentially aggressive orders take effect on the market exactly one block after they are included in a block (i.e for an order which is included in block N it hits the market in block N+1). This is true for:
+ - Market orders (0093-TRTO-009)
+ - Non post-only limit orders (0093-TRTO-010)
+ - Order Amends (0093-TRTO-011)
+- An expedited batch transaction is executed after cancellations but before standalone post-only creations (0093-TRTO-012)
+- The transaction ordering functionality can be enabled/disabled on a per-market level (0093-TRTO-015)
+- With two active markets, with one having transaction ordering enabled and one disabled, transactions are correctly sorted/delayed on the market with it enabled whilst the other has transactions executed in arrival order. (0093-TRTO-014)
+- When `market.aggressiveOrderBlockDelay` is set to `1` aggressive orders are delayed by a single block on eligible markets. If it is amended via governance to `5` then all aggressive orders after enactment of this change will be delayed by `5` blocks instead. (0093-TRTO-016)
diff --git a/protocol/0094-PRAC-protective_auctions.md b/protocol/0094-PRAC-protective_auctions.md
new file mode 100644
index 000000000..5231ec8f9
--- /dev/null
+++ b/protocol/0094-PRAC-protective_auctions.md
@@ -0,0 +1,72 @@
+# Protective auctions
+
+The protocol has a number of protective [auctions](./0026-AUCT-auctions.md) to gather more information from market participants before carrying on with price discovery process at times of increased uncertainty or when continuous functioning of the network is disrupted.
+
+## Per-market auctions
+
+Per-market auction applies to an individual market only and are triggered by actions related directly to that market.
+
+### Mark price monitoring
+
+Mark [price monitoring](./0032-PRIM-price_monitoring.md) is triggered when the next mark price would be significantly higher than what the mark-price history implies.
+
+### Trade price monitoring
+
+Trade [price monitoring](./0032-PRIM-price_monitoring.md) is triggered when the next traded price would be significantly higher than what the mark-price history implies.
+
+### Governance
+
+Individual markets can also be put into protective auctions using a [governance](./0028-GOVE-governance.md#6-change-market-state) action.
+
+## Network-wide auctions
+
+Network-wide auctions put all of the markets running on a given network into auction. The triggers for those types of auctions are related to functioning of a network as a whole.
+
+### Block time auctions
+
+Block time auctions get triggered when the block time exceeds one of the predefined thresholds expressed in seconds. This should be done as soon as the time since the beginning of the last known block is more than any of the specified thresholds. Once such conditions get detected no more transactions relating to markets get processed before all the markets get put into auction mode. The duration of such an auction should be fixed and related to the block length.
+The allowed thresholds and resulting auction periods should be implemented as a lookup-up table, sorted in the ascending order of the threshold and checked backwards. The resulting auction periods should not be summed - the auction period associated with the largest allowed threshold less than the detected block time should be used as the resulting auction duration.
+
+The default settings should be:
+
+ | Threshold | Network-wide auction duration |
+ | --------- | ----------------------------- |
+ | `10s` | `1min` |
+ | `1min` | `5min` |
+ | `10min` | `1h` |
+ | `1h` | `1h` |
+ | `6h` | `3h` |
+ | `24h` | `6h` |
+
+
+## Interaction between different auction modes
+
+When market goes into auction mode from its default trading mode then the auction trigger which caused this should be listed as `trigger` on the API as long as that auction hasn't finished (including cases when it gets extended).
+
+When another trigger gets activated for the market then the end time of auction for that market should be the maximum of the original end time and that implied by the latest trigger. If the original end time is larger then nothing changes. If end time implied by the latest trigger is larger than the end time gets set to this value and the `extension_trigger` field gets set (or overwritten if market has already been in an extended auction at this point) to represent the latest trigger. Governance auction is assumed to have an infinite duration (it can only be ended with an appropriate governance auction and the timing of that action is generally unknown a priori).
+
+## Acceptance criteria
+
+- When the network resumes after a crash (irrespective of how that was achieved) no trades get generated. All markets go into an auction of the same duration. Trades may only be generated in any market once the network-wide auction ends. (0094-PRAC-001)
+
+- When the network resumes after a [protocol upgrade](./0075-PLUP-protocol_upgrades.md) no trades get generated. All markets go into an auction of the same duration. Trades may only be generated in any market once the network-wide auction ends. (0094-PRAC-002)
+
+- When the lookup table for the network-wide auction is specified as:
+
+ | Threshold | Network-wide auction duration |
+ | --------- | ----------------------------- |
+ | `3s` | `1min` |
+ | `40s` | `10min` |
+ | `2min` | `1h` |
+
+and at some point network determines that the length of the last block was 90s, the network then immediately goes into a network-wide auction with a duration of `10min`. (0094-PRAC-003)
+
+- A market which has been in a per-market auction which was triggered before the network-wide auction was initiated remains in auction mode even if the exit condition for the original per-market auction gets satisfied before the network-wide auction ends. No intermediate trades get generated even in the presence of non-zero indicative volume at the point of that market's per-market auction exit condition being satisfied. The market only goes back into its default trading mode and possibly generates trades once the network-wide auction ends. (0094-PRAC-004)
+
+- A market which has been in a per-market auction which was triggered before the network-wide auction was initiated remains in auction mode once the network-wide auction ends if the exit condition for the original per-market auction hasn't been met at that point and no intermediate trades get generated even in the presence of non-zero indicative volume at the point of network-wide auction end. (0094-PRAC-005)
+
+- When market is in a price monitoring auction which is meant to finish at `10am`, but prior to that time a long block auction finishing at 11am gets triggered then the market stays in auction till `11am`, it's auction trigger is listed as price monitoring auction and it's extension trigger is listed as long block auction. (0094-PRAC-006)
+
+- When a market's `trigger` or `extension_trigger` is set to represent a governance suspension then no other triggers can affect the market. (0094-PRAC-007)
+
+- When a market's `trigger` and `extension_trigger` are set to represent that the market went into auction due to the price monitoring mechanism and was later extended by the same mechanism and the auction is meant to finish at `11am`, but now a long block auction is being triggered so that it ends at `10am` then this market is unaffected in any way. (0094-PRAC-008)
diff --git a/protocol/cosmic-features.json b/protocol/cosmic-features.json
index fa19e42d6..d2c4b31b4 100644
--- a/protocol/cosmic-features.json
+++ b/protocol/cosmic-features.json
@@ -362,12 +362,9 @@
"0044-LIME-101",
"0044-LIME-102",
"0026-AUCT-016",
- "0026-AUCT-017",
"0026-AUCT-018",
"0026-AUCT-019",
"0026-AUCT-020",
- "0026-AUCT-021",
- "0026-AUCT-022",
"0034-PROB-004",
"0042-LIQF-055"
]
@@ -465,7 +462,7 @@
"0062-SPAM-037"
]
},
-"Rewards": {
+ "Rewards": {
"milestone": "deployment-2",
"acs": [
"0085-RVST-001",
@@ -558,7 +555,7 @@
"0028-GOVE-152"
]
},
- "Batch change proposals": {
+ "Batch change proposals": {
"milestone": "deployment-3",
"acs": [
"0028-GOVE-145",
@@ -926,18 +923,33 @@
},
"Iceberg Orders": {
"milestone": "cosmic-carryover",
- "acs": ["0014-ORDT-069"]
+ "acs": [
+ "0014-ORDT-069"
+ ]
},
"Successor Markets": {
"milestone": "cosmic-carryover",
- "acs": ["0001-MKTF-008"]
+ "acs": [
+ "0001-MKTF-008"
+ ]
},
"Ethereum oracles": {
"milestone": "cosmic-carryover",
- "acs": ["0082-ETHD-035", "0082-ETHD-041"]
+ "acs": [
+ "0082-ETHD-035",
+ "0082-ETHD-041"
+ ]
},
"Stop Orders": {
"milestone": "cosmic-carryover",
- "acs": ["0079-TGAP-004", "0079-TGAP-005", "0014-ORDT-132", "0014-ORDT-133", "0014-ORDT-134", "0014-ORDT-135", "0014-ORDT-136"]
+ "acs": [
+ "0079-TGAP-004",
+ "0079-TGAP-005",
+ "0014-ORDT-132",
+ "0014-ORDT-133",
+ "0014-ORDT-134",
+ "0014-ORDT-135",
+ "0014-ORDT-136"
+ ]
}
-}
+}
\ No newline at end of file
diff --git a/protocol/features.json b/protocol/features.json
index a391efa4d..25ed39720 100644
--- a/protocol/features.json
+++ b/protocol/features.json
@@ -25,22 +25,29 @@
},
"Isolated margin": {
"milestone": "colosseo",
- "acs": ["0019-MCAL-208"]
+ "acs": [
+ "0019-MCAL-208"
+ ]
},
"Closeout trades and auctions": {
"milestone": "colosseo",
- "acs": ["0012-POSR-030"]
+ "acs": [
+ "0012-POSR-030"
+ ]
},
"Market suspended/resumed before enactment": {
"milestone": "colosseo",
"acs": [
- "0043-MKTL-011",
- "0043-MKTL-012",
- "0043-MKTL-013"]
+ "0043-MKTL-011",
+ "0043-MKTL-012",
+ "0043-MKTL-013"
+ ]
},
"Teams": {
"milestone": "colosseo",
- "acs": ["0083-RFPR-068"]
+ "acs": [
+ "0083-RFPR-068"
+ ]
},
"Spot": {
"milestone": "colosseo",
@@ -359,64 +366,7 @@
"0056-REWA-167"
]
},
- "Spot stretch": {
- "milestone": "colosseo",
- "acs": [
- "0029-FEES-015",
- "0029-FEES-016",
- "0029-FEES-017",
- "0029-FEES-018",
- "0029-FEES-019",
- "0029-FEES-020",
- "0029-FEES-021",
- "0029-FEES-022",
- "0049-TVAL-007",
- "0049-TVAL-008",
- "0049-TVAL-009",
- "0049-TVAL-010",
- "0049-TVAL-011",
- "0049-TVAL-012",
- "0051-PROD-004",
- "0051-PROD-005",
- "0051-PROD-006",
- "0052-FPOS-003",
- "0052-FPOS-004",
- "0054-NETP-007",
- "0054-NETP-008",
- "0054-NETP-009",
- "0054-NETP-010",
- "0054-NETP-011",
- "0057-TRAN-063",
- "0065-FTCO-005",
- "0065-FTCO-006",
- "0065-FTCO-007",
- "0065-FTCO-008",
- "0070-MKTD-010",
- "0070-MKTD-011",
- "0070-MKTD-012",
- "0070-MKTD-013",
- "0070-MKTD-014",
- "0070-MKTD-015",
- "0070-MKTD-022",
- "0070-MKTD-024",
- "0070-MKTD-025",
- "0070-MKTD-026",
- "0074-BTCH-012",
- "0074-BTCH-015",
- "0074-BTCH-016",
- "0074-BTCH-019",
- "0079-TGAP-006",
- "0079-TGAP-007",
- "0028-GOVE-186",
- "0028-GOVE-187",
- "0080-SPOT-024",
- "0080-SPOT-025",
- "0080-SPOT-026",
- "0080-SPOT-027",
- "0080-SPOT-028",
- "0080-SPOT-029"
- ]
- },
+
"Spot stop orders": {
"milestone": "colosseo",
"acs": [
@@ -466,12 +416,6 @@
"milestone": "colosseo",
"acs": [
"0037-OPEG-019",
- "0037-OPEG-022",
- "0037-OPEG-023",
- "0037-OPEG-024",
- "0037-OPEG-025",
- "0037-OPEG-026",
- "0037-OPEG-027",
"0004-AMND-030",
"0004-AMND-031",
"0004-AMND-032",
@@ -483,8 +427,8 @@
"0004-AMND-038",
"0004-AMND-039",
"0004-AMND-040",
- "0004-AMND-041",
"0004-AMND-042",
+ "0004-AMND-041",
"0004-AMND-043",
"0004-AMND-044",
"0004-AMND-045",
@@ -537,38 +481,50 @@
"0009-MRKP-037"
]
},
- "Perpetual funding rates": {
- "milestone": "colosseo",
- "acs": ["0053-PERP-036"]
- },
+
"Explicit liquidation range": {
"milestone": "colosseo",
"acs": [
"0012-POSR-031",
"0012-POSR-032",
- "0012-POSR-033"
- ]
+ "0012-POSR-033"]
},
- "Community Tags": {
- "milestone": "colosseo",
+ "Order spam": {
+ "milestone": "colosseo_II",
"acs": [
- "0028-GOVE-168",
- "0028-GOVE-169",
- "0028-GOVE-170",
- "0028-GOVE-171",
- "0028-GOVE-172",
- "0028-GOVE-173",
- "0028-GOVE-174",
- "0028-GOVE-175",
- "0028-GOVE-176",
- "0028-GOVE-177",
- "0028-GOVE-178"
+ "0062-SPAM-043",
+ "0062-SPAM-044",
+ "0062-SPAM-045",
+ "0062-SPAM-047",
+ "0062-SPAM-048",
+ "0062-SPAM-049",
+ "0062-SPAM-050",
+ "0062-SPAM-051",
+ "0062-SPAM-052",
+ "0062-SPAM-053",
+ "0062-SPAM-054",
+ "0062-SPAM-055",
+ "0062-SPAM-056",
+ "0062-SPAM-057",
+ "0062-SPAM-058",
+ "0062-SPAM-059",
+ "0062-SPAM-060",
+ "0062-SPAM-061",
+ "0062-SPAM-062",
+ "0062-SPAM-063",
+ "0062-SPAM-064",
+ "0062-SPAM-066",
+ "0062-SPAM-067",
+ "0062-SPAM-068",
+ "0062-SPAM-069",
+ "0062-SPAM-070",
+ "0062-SPAM-071",
+ "0062-SPAM-072",
+ "0004-AMND-061"
]
},
- "LPs voting without gov token": {
- "milestone": "colosseo",
- "acs": ["0028-GOVE-185"]
- },
+
+
"Reward Improvements": {
"milestone": "colosseo",
"acs": [
@@ -600,21 +556,299 @@
"0056-REWA-136",
"0056-REWA-137",
"0056-REWA-138",
+ "0056-REWA-139",
"0056-REWA-140",
"0056-REWA-141",
"0056-REWA-142",
+ "0056-REWA-143",
"0056-REWA-144",
"0056-REWA-145",
"0056-REWA-146",
+ "0056-REWA-147",
"0056-REWA-148",
"0056-REWA-149",
"0056-REWA-150",
+ "0056-REWA-151",
"0057-TRAN-076",
"0057-TRAN-077",
"0057-TRAN-078",
"0057-TRAN-079"
]
},
+
+ "Capped Futures": {
+ "milestone": "colosseo_II",
+ "acs": [
+ "0016-PFUT-013",
+ "0016-PFUT-014",
+ "0016-PFUT-015",
+ "0016-PFUT-016",
+ "0016-PFUT-017",
+ "0016-PFUT-018",
+ "0016-PFUT-019",
+ "0016-PFUT-020",
+ "0016-PFUT-021",
+ "0016-PFUT-022",
+ "0016-PFUT-023",
+ "0016-PFUT-024",
+ "0016-PFUT-025",
+ "0019-MCAL-154",
+ "0019-MCAL-155",
+ "0019-MCAL-156",
+ "0019-MCAL-157",
+ "0019-MCAL-158",
+ "0019-MCAL-170",
+ "0019-MCAL-171"
+ ]
+ },
+ "Transaction Ordering": {
+ "milestone": "colosseo_II",
+ "acs": [
+ "0093-TRTO-001",
+ "0093-TRTO-002",
+ "0093-TRTO-003",
+ "0093-TRTO-004",
+ "0093-TRTO-005",
+ "0093-TRTO-006",
+ "0093-TRTO-007",
+ "0093-TRTO-008",
+ "0093-TRTO-009",
+ "0093-TRTO-010",
+ "0093-TRTO-011",
+ "0093-TRTO-012",
+ "0093-TRTO-013",
+ "0093-TRTO-014",
+ "0093-TRTO-015",
+ "0093-TRTO-016",
+ "0028-GOVE-192",
+ "0028-GOVE-194",
+ "0028-GOVE-193"
+ ]
+ },
+ "Long Block Auction": {
+ "milestone": "colosseo_II",
+ "acs": [
+ "0026-AUCT-039",
+ "0026-AUCT-040",
+ "0094-PRAC-001",
+ "0094-PRAC-002",
+ "0094-PRAC-003",
+ "0094-PRAC-004",
+ "0094-PRAC-005",
+ "0094-PRAC-006",
+ "0094-PRAC-007",
+ "0094-PRAC-008"
+ ]
+ },
+ "Model-free alternatives": {
+ "milestone": "suzuka_castle",
+ "acs": [
+ "0016-PFUT-026",
+ "0016-PFUT-027",
+ "0016-PFUT-028",
+ "0053-PERP-047",
+ "0053-PERP-048",
+ "0053-PERP-049",
+ "0032-PRIM-041",
+ "0032-PRIM-042",
+ "0032-PRIM-043",
+ "0042-LIQF-095",
+ "0042-LIQF-096",
+ "0019-MCAL-159"
+ ]
+ },
+ "Fee mechanic changes": {
+ "milestone": "suzuka_castle",
+ "acs": []
+ },
+ "vAMMs": {
+ "milestone": "suzuka_castle",
+ "acs": [
+ "0042-LIQF-092",
+ "0042-LIQF-093",
+ "0042-LIQF-094",
+ "0090-VAMM-001",
+ "0090-VAMM-002",
+ "0090-VAMM-003",
+ "0090-VAMM-004",
+ "0090-VAMM-005",
+ "0090-VAMM-006",
+ "0090-VAMM-007",
+ "0090-VAMM-008",
+ "0090-VAMM-009",
+ "0090-VAMM-010",
+ "0090-VAMM-011",
+ "0090-VAMM-012",
+ "0090-VAMM-013",
+ "0090-VAMM-014",
+ "0090-VAMM-015",
+ "0090-VAMM-016",
+ "0090-VAMM-017",
+ "0090-VAMM-018",
+ "0090-VAMM-019",
+ "0090-VAMM-020",
+ "0090-VAMM-021",
+ "0090-VAMM-022",
+ "0090-VAMM-023",
+ "0090-VAMM-024",
+ "0090-VAMM-025",
+ "0090-VAMM-026",
+ "0090-VAMM-027",
+ "0090-VAMM-028",
+ "0090-VAMM-029",
+ "0090-VAMM-030",
+ "0090-VAMM-031",
+ "0090-VAMM-032",
+ "0090-VAMM-033",
+ "0090-VAMM-034",
+ "0090-VAMM-035",
+ "0057-TRAN-070",
+ "0026-AUCT-033",
+ "0026-AUCT-034",
+ "0026-AUCT-035",
+ "0026-AUCT-036",
+ "0026-AUCT-037",
+ "0014-NP-VAMM-001",
+ "0014-NP-VAMM-002",
+ "0014-NP-VAMM-003",
+ "0014-NP-VAMM-004",
+ "0015-NP-OBES-001",
+ "0015-NP-OBES-002",
+ "0042-LIQF-107",
+ "0042-LIQF-108",
+ "0042-LIQF-109",
+ "0042-LIQF-110",
+ "0042-LIQF-111",
+ "0056-REWA-170",
+ "0085-RVST-015",
+ "0085-RVST-016",
+ "0085-RVST-017",
+ "0085-RVST-018",
+ "0085-RVST-019",
+ "0085-RVST-020",
+ "0085-RVST-021",
+ "0085-RVST-022",
+ "0085-RVST-023",
+ "0085-RVST-024",
+ "0085-RVST-025"
+ ]
+ },
+ "Community Tags": {
+ "milestone": "suzuka_castle",
+ "acs": [
+ "0028-GOVE-168",
+ "0028-GOVE-169",
+ "0028-GOVE-170",
+ "0028-GOVE-171",
+ "0028-GOVE-172",
+ "0028-GOVE-173",
+ "0028-GOVE-174",
+ "0028-GOVE-175",
+ "0028-GOVE-176",
+ "0028-GOVE-177",
+ "0028-GOVE-178"
+ ]
+ },
+ "Spot AMM": {
+ "milestone": "genbu_temple",
+ "acs": [
+ "0092-SAMM-001",
+ "0092-SAMM-002",
+ "0092-SAMM-003",
+ "0092-SAMM-004",
+ "0092-SAMM-005",
+ "0092-SAMM-006",
+ "0092-SAMM-007",
+ "0092-SAMM-008"
+ ]
+ },
+ "LP 3.0": {
+ "milestone": "genbu_temple",
+ "acs": []
+ },
+ "Token Buyback auction": {
+ "milestone": "genbu_temple",
+ "acs": []
+ },
+ "Cancelling Proposals": {
+ "milestone": "historic_distillery",
+ "acs": [
+ "0028-GOVE-188",
+ "0028-GOVE-189",
+ "0028-GOVE-190",
+ "0028-GOVE-191"
+ ]
+ },
+ "Perpetual funding rates": {
+ "milestone": "historic_distillery",
+ "acs": [
+ "0053-PERP-036"
+ ]
+ },
+ "LPs voting without gov token": {
+ "milestone": "historic_distillery",
+ "acs": [
+ "0028-GOVE-185"
+ ]
+ },
+ "Spot stretch": {
+ "milestone": "historic_distillery",
+ "acs": [
+ "0029-FEES-015",
+ "0029-FEES-016",
+ "0029-FEES-017",
+ "0029-FEES-018",
+ "0029-FEES-019",
+ "0029-FEES-020",
+ "0029-FEES-021",
+ "0029-FEES-022",
+ "0049-TVAL-007",
+ "0049-TVAL-008",
+ "0049-TVAL-009",
+ "0049-TVAL-010",
+ "0049-TVAL-011",
+ "0049-TVAL-012",
+ "0051-PROD-004",
+ "0051-PROD-005",
+ "0051-PROD-006",
+ "0052-FPOS-003",
+ "0052-FPOS-004",
+ "0054-NETP-007",
+ "0054-NETP-008",
+ "0054-NETP-009",
+ "0054-NETP-010",
+ "0054-NETP-011",
+ "0057-TRAN-063",
+ "0065-FTCO-005",
+ "0065-FTCO-006",
+ "0065-FTCO-007",
+ "0065-FTCO-008",
+ "0070-MKTD-010",
+ "0070-MKTD-011",
+ "0070-MKTD-012",
+ "0070-MKTD-013",
+ "0070-MKTD-014",
+ "0070-MKTD-015",
+ "0070-MKTD-022",
+ "0070-MKTD-024",
+ "0070-MKTD-025",
+ "0070-MKTD-026",
+ "0074-BTCH-012",
+ "0074-BTCH-015",
+ "0074-BTCH-016",
+ "0074-BTCH-019",
+ "0079-TGAP-006",
+ "0079-TGAP-007",
+ "0028-GOVE-186",
+ "0028-GOVE-187",
+ "0080-SPOT-024",
+ "0080-SPOT-025",
+ "0080-SPOT-026",
+ "0080-SPOT-027",
+ "0080-SPOT-028",
+ "0080-SPOT-029"
+ ]
+ },
"Unknown": {
"milestone": "unknown",
"acs": []
diff --git a/wordlist.txt b/wordlist.txt
index e9fa28623..1a8dd534f 100644
--- a/wordlist.txt
+++ b/wordlist.txt
@@ -6,6 +6,9 @@ ABIs
actioned
Allowlist
allowlisted
+AMM
+AMM's
+AMMs
antiwhaling
API
APIs
@@ -31,6 +34,7 @@ cashflows
CCP
ccy
cdot
+cdotp
ceil
ceiled
CFD
@@ -73,6 +77,7 @@ delegateable
delegator
delegators
deterministically
+differentiator
discoverability
disincentivise
DLT
@@ -114,6 +119,7 @@ frontend
funder
FX
GBP
+geq
Geth
Getters
GFA
@@ -147,6 +153,9 @@ int64
integrations
intrablock
IOC
+iteratively
+joinable
+joinable
JSON
keyholder
keypair
@@ -176,6 +185,8 @@ margined
margining
marketID
math
+mathbb
+mathbf
MDPs
mempool
Merkle
@@ -190,6 +201,7 @@ multisig
multisigs
MultisigControl
multisignature
+multisigs
NFTs
nonfungible
OAUTH
@@ -210,12 +222,15 @@ permissioned
permissionless
PERP
perps
+piecewise
PME
PnL
PoS
PoW
PoWs
pre
+precomputed
+priori
pro
protobuf
Pseudocode
@@ -223,7 +238,10 @@ pseudorandom
pubkey
quant
rata
+rebalance
+rebalancing
rebased
+rebasing
recollateralise
ReferralSet
reimplemented
@@ -237,8 +255,10 @@ RFQ
roadmap
Ropsten
RPC
+rulesets
runnable
runtime
+rz
scalability
scalable
Scholes
@@ -251,6 +271,7 @@ sharded
siskas
SLA
Solana
+sqrt
src
SSD
SSL
@@ -299,6 +320,8 @@ undelegations
undeployed
underlyings
unencrypted
+Uniswap
+Uniswap's
unitless
unmarshal
unnominated
@@ -307,6 +330,8 @@ unstake
unstaked
unstakes
unstaking
+unsynchronised
+Unsynchronised
untriggered
unvested
url
@@ -318,7 +343,9 @@ validator
validator's
validators
vAMM
+vAMM's
vAMMs
+vee
vega
vegaprotocol
vegatools
@@ -336,9 +363,3 @@ wBTC
wei
whitepaper
Yubikey
-joinable
-mathbb
-geq
-vee
-mathbf
-rz
\ No newline at end of file