diff --git a/protocol/0053-PERP-product_builtin_perpetual_future.md b/protocol/0053-PERP-product_builtin_perpetual_future.md
index 7323fd116..c6df79c66 100644
--- a/protocol/0053-PERP-product_builtin_perpetual_future.md
+++ b/protocol/0053-PERP-product_builtin_perpetual_future.md
@@ -83,62 +83,63 @@ Every time a [mark to market settlement](./0003-MTMK-mark_to_market_settlement.m
When the `settlement_schedule` event is received we need to calculate the funding payment. Store the current vega time as `funding_period_end`.
-If there are no oracle data points with a timestamp less than `funding_period_end` available then funding payment is skipped and `funding_period_start` gets overwritten with `funding_period_end`.
+Skip the funding payment calculation (set payment to `0`) if any of the following conditions is met:
-If such points are available then the calculations discussed in the following subsections get executed and funding payments get exchanged.
+- no spot (external) data has been ingested since market was created,
+- market has been in auction throughout the entire funding period.
-#### TWAP spot price calculation
+Please refer to the following subsections for the details of calculation of the funding payment if none of the above conditions are met.
-Traverse all the available oracle data point tuples `(s,t)` and calculate the time-weighted average spot price (`s_twap`) as:
+#### TWAP calculation
+
+Same methodology applies to spot (external) and perps (internal). The available prices (spot and perps considered separately of course) should be used to calculate the time weighted average price within the funding period. If no observations at or prior to the funding period start exist then the start of the period used for calculation should be moved forward to the time of the first observation. An observation is assumed to be applying until a subsequent observation is available. Periods spent in auction should be excluded from the calculation. This implies that spot datapoints received during the auction except for the latest one should be disregarded. Please refer to the acceptance criteria for a detailed example.
+
+Calculation of the TWAP is carried out by maintaining the following variables: `numerator`, `denominator`, `previous_price` and `previous_time`. It's also assumed that a function `current_time()` is available which returns the current Vega time, and a function `in_auction()` exists which returns `true` if the market being considered is currently in auction and `false` otherwise.
+The variables are maintained as follows.
+
+When a new `price` observation arrives:
```go
-var previous_point
-sum_product := 0
-
-for p := range oracle_data_points {
- if p.t <= funding_period_start {
- previous_point = p
- continue
- }
- if p.t >= funding_period_end {
- break
- }
- if previous_point != nil {
- sum_product += previous_point*(p.t-max(funding_period_start,previous_point.t))
- }
- previous_point = p
+if previous_price != nil && in_auction() {
+ time_delta = current_time()-previous_time
+ numerator += previous_price*time_delta
+ denominator += time_delta
}
+previous_price = price
+previous_time = current_time()
+```
+
+When the market goes into auction:
-sum_product += previous_point.s*(funding_period_end-max(funding_period_start,previous_point.t))
-s_twap = sum_product / (funding_period_end - max(funding_period_start, oracle_data_points[0].t))
+```go
+time_delta = current_time()-previous_time
+numerator += previous_price*time_delta
+denominator += time_delta
```
-Only the oracle data point with largest timestamp that's less than or equal to `funding_period_end` (and any data points with larger timestamps) need to be kept from that point on.
+When the market goes out of auction:
-#### TWAP mark price calculation
+```go
+previous_time = current_time()
+```
-Traverse all the available internal data point tuples `(f,t)` and calculated the time-weighted average mark price (`f_twap`) as:
+When the funding payment cue arrives TWAP gets calculated and returned as:
```go
-var previous_point
-sum_product := 0
-
-for p := range internal_data_points {
- if p.t <= funding_period_start {
- previous_point = p
- continue
- }
- if previous_point != nil {
- sum_product += previous_point*(p.t-max(funding_period_start,previous_point.t))
- }
- previous_point = p
+if !in_auction() {
+ time_delta = current_time()-previous_time
+ numerator += previous_price*time_delta
+ denominator += time_delta
}
+previous_time = current_time
+if denominator == 0 {
+ return 0
+}
+return numerator / denominator
-sum_product += previous_point.f*(funding_period_end-max(funding_period_start,previous_point.t))
-f_twap = sum_product / (funding_period_end - max(funding_period_start, internal_data_points[0].t))
```
-Only the internal data point with largest timestamp needs to be kept from that point on.
+Note that depending on what type of oracle is used for the spot price it may be that the oracle points only become known shortly before or at the funding payment cue time, so the above pseudocode is just an illustration of how these quantities should be calculated and the implementation will need to be able to apply such calculation retrospectively.
#### Funding payment calculation
@@ -234,3 +235,57 @@ In both cases the estimates are for a hypothetical position of size 1.
1. A perpetual market which is active and has open orders, after checkpoint restart, is in opening auction. All margin accounts are transferred to general accounts. (0053-PERP-022)
1. A perpetual market which is active and has open orders. Wait for a new network history snapshot to be created. Load a new data node from network history. All market data is preserved. (0053-PERP-023)
1. When the funding payment does not coincide with mark to market settlement time, a party has insufficient funds to fully cover their funding payment such that the shortfall amount if $x$ and the balance of market's insurance pool is $\frac{x}{3}$, then the entire insurance pool balance gets used to cover the shortfall and the remaining missing amount $\frac{2x}{3}$ gets dealt with using loss socialisation. (0053-PERP-024)
+
+1. Assume a market trades steadily generating a stream in mark price observations, but the first spot price observation only arrives during the 4th funding period of that market. Then funding payments for periods 1, 2 and 3 all equal 0. (0053-PERP-025)
+
+1. Assume the market has been in a long auction so that a funding period has started and ended while the market never went back into continuous trading. In that case the funding payment should be equal to 0 and no transfers should be exchanged. (0053-PERP-026)
+
+1. Assume a 10 minute funding period. Assume a few funding periods have already passed for this market.
+
+Assume the last known mark price before the start of the period to be `10` and that it gets updated every 2 minutes as follows:
+| Time (min) since period start | mark price |
+| ----------------------------- | ----------- |
+| 1 | 11 |
+| 3 | 10 |
+| 5 | 9 |
+| 7 | 8 |
+| 9 | 7 |
+
+Assume the last known spot price before this funding period is `11`. Then assume the subsequent spot price observations get ingested according to the schedule specified below:
+| Time (min) since period start | spot price |
+| ----------------------------- | ----------- |
+| 1 | 9 |
+| 3 | 10 |
+| 5 | 12 |
+| 6 | 11 |
+| 7 | 8 |
+| 9 | 14 |
+
+Then, assuming no auctions during the period we get:
+$\text{internal TWAP}= \frac{10\cdot(1-0)+11\cdot(3-1)+10\cdot(5-3)+9\cdot(7-5)+8\cdot(9-7)+7\cdot(10-9)}{10}=9.3$,
+$\text{external TWAP}=\frac{11\cdot(1-0)+9\cdot(3-1)+10\cdot(5-3)+12\cdot(6-5)+11\cdot(7-6)+8\cdot(9-7)+14\cdot(10-9)}{10}=10.3$. (0053-PERP-027)
+
+1. Assume a 10 minute funding period. Assume a few funding periods have already passed for this market. Furthermore, assume that in this period that market is in an auction which starts 5 minutes into the period and ends 7 minutes into the period.
+
+Assume the last known mark price before the start of the period to be `10` and that it gets updated as follows:
+| Time (min) since period start | mark price |
+| ----------------------------- | ----------- |
+| 1 | 11 |
+| 3 | 10 |
+| 7 | 9 |
+| 8 | 8 |
+| 10 | 30 |
+
+Assume the last known spot price before this funding period is `11`. Then assume the subsequent spot price observations get ingested according to the schedule specified below:
+| Time (min) since period start | spot price |
+| ----------------------------- | ----------- |
+| 1 | 9 |
+| 3 | 10 |
+| 5 | 12 |
+| 6 | 11 |
+| 8 | 8 |
+| 9 | 14 |
+
+Then, taking the auction into account we get:
+$\text{internal TWAP}= \frac{10\cdot(1-0)+11\cdot(3-1)+10\cdot(5-3)+9\cdot(8-7)+8\cdot(10-8)+30\cdot(10-10)}{8}=9.625$,
+$\text{external TWAP}=\frac{11\cdot(1-0)+9\cdot(3-1)+10\cdot(5-3)+11\cdot(8-7)+8\cdot(9-8)+14\cdot(10-9)}{10}=10.25$. (0053-PERP-028)
diff --git a/protocol/features.json b/protocol/features.json
index 3c174a427..96f61c6cd 100644
--- a/protocol/features.json
+++ b/protocol/features.json
@@ -43,7 +43,15 @@
"0073-LIMN-110",
"0073-LIMN-111",
"0081-SUCM-015",
- "0053-PERP-024"
+ "0014-ORDT-120",
+ "0014-ORDT-121",
+ "0014-ORDT-122",
+ "0014-ORDT-123",
+ "0053-PERP-024",
+ "0053-PERP-025",
+ "0053-PERP-026",
+ "0053-PERP-027",
+ "0053-PERP-028"
]
},
"Team rewards": {
diff --git a/wordlist.txt b/wordlist.txt
index 0211e4688..bae7b667c 100644
--- a/wordlist.txt
+++ b/wordlist.txt
@@ -59,6 +59,7 @@ DAI
dApp
datanode
datapoint
+datapoints
datatypes
datetime
decentralised
@@ -200,6 +201,8 @@ PDP
performant
permissioned
permissionless
+PERP
+perps
PME
PnL
PoS