Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CVI cache smoothing #2888

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions packages/composites/crypto-volatility-index/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,15 @@ See the [Composite Adapter README](../README.md) for more information on how to

### Input Params

| Required? | Name | Description | Options | Defaults to |
| :-------: | :---------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----: | :--------------: |
| ✅ | `contractAddress`, `contract` | The address of the on-chain crypto volatility index aggregator contract | | |
| | `network` | The blockchain network to use. | | `'ETHEREUM'` |
| | `heartBeat` | Interval between updates | | `60` |
| | `multiply` | Multiply amount for the on-chain value, which also determines the result precision | | `1e18` |
| | `isAdaptive` | Indicates whether the calculation result should be adaptively smoothed with its latest on-chain value. This must be set to `false` if the reference contract has not yet completed a valid round. | | `true` |
| | `cryptoCurrencies` | which curencies to use to calculate the index | | `['BTC', 'ETH']` |
| | `deviationThreshold` | the threshold for smoothing calculation | | `0.11` |
| | `lambdaMin` | Lambda min value for adaptive smoothing calculation | | `0.031` |
| | `lambdaK` | Lambda K value for adaptive smoothing calculation | | `0.31` |
| Required? | Name | Description | Options | Defaults to |
| :-------: | :------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----: | :--------------: |
| | `heartBeat` | Interval between updates | | `20` |
| | `multiply` | Multiply amount for the on-chain value, which also determines the result precision | | `1e18` |
| | `isAdaptive` | Indicates whether the calculation result should be adaptively smoothed with its latest on-chain value. This must be set to `false` if the reference contract has not yet completed a valid round. | | `true` |
| | `cryptoCurrencies` | which curencies to use to calculate the index | | `['BTC', 'ETH']` |
| | `deviationThreshold` | the threshold for smoothing calculation | | `0.2` |
| | `lambdaMin` | Lambda min value for adaptive smoothing calculation | | `0.008` |
| | `lambdaK` | Lambda K value for adaptive smoothing calculation | | `0.4` |

### Sample Input

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,16 @@ import type { Config, ExecuteWithConfig, InputParameters } from '@chainlink/ea-b
export const supportedEndpoints = ['volatilityIndex']

export type TInputParameters = {
contract: string
multiply?: number
heartbeatMinutes?: number
isAdaptive?: boolean
cryptoCurrencies?: string[]
deviationThreshold?: number
lambdaMin?: number
lambdaK?: number
network?: string
}

const inputParameters: InputParameters<TInputParameters> = {
contract: {
required: true,
aliases: ['contractAddress'],
},
multiply: {
required: false,
},
Expand All @@ -42,9 +36,6 @@ const inputParameters: InputParameters<TInputParameters> = {
lambdaK: {
required: false,
},
network: {
required: false,
},
}

export const execute: ExecuteWithConfig<Config> = async (request, context) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import { Logger } from '@chainlink/ea-bootstrap'
import { getRpcLatestRound } from '@chainlink/ea-reference-data-reader'
import { getDerivativesData, CurrencyDerivativesData } from './derivativesDataProvider'
import { SigmaCalculator } from './sigmaCalculator'
import { Decimal } from 'decimal.js'
import moment from 'moment'
import { dominanceByCurrency, getDominanceAdapter } from './dominanceDataProvider'
import type { AdapterContext, AdapterRequest, AdapterRequestData } from '@chainlink/ea-bootstrap'
import { DEFAULT_NETWORK } from '../config'
import { TInputParameters } from '../endpoint/volatilityIndex'
import { saveToCache, loadFromCache } from './cviCache'

export const calculate = async (
validated: { data: AdapterRequestData<TInputParameters>; id: string },
requestParams: AdapterRequestData,
context: AdapterContext,
): Promise<number> => {
const {
contract: oracleAddress,
multiply = 1e18,
heartbeatMinutes = 60,
heartbeatMinutes = 20,
isAdaptive = true,
cryptoCurrencies = ['BTC', 'ETH'],
deviationThreshold = 0.11,
lambdaMin = 0.031,
lambdaK = 0.31,
network = DEFAULT_NETWORK,
deviationThreshold = 0.2,
lambdaMin = 0.008,
lambdaK = 0.4,
} = validated.data

// Get all of the required derivatives data for the calculations, for all the relevant currencies
Expand All @@ -40,21 +37,20 @@ export const calculate = async (
)
// Smooth CVI with previous on-chain value if exists
const cvi = !isAdaptive
? toOnChainValue(weightedCVI, multiply)
? weightedCVI
: await applySmoothing(
weightedCVI,
oracleAddress,
multiply,
heartbeatMinutes,
deviationThreshold,
lambdaMin,
lambdaK,
network,
context,
)

Logger.info(`CVI: ${cvi}`)
Logger.info(`${isAdaptive ? 'Adaptive ' : ''}CVI: ${cvi}`)
validateIndex(cvi)
return cvi
await saveToCache(context, cvi, moment.duration(heartbeatMinutes, 'minutes').asSeconds())
return toOnChainValue(cvi, multiply)
}

const calculateVixValues = async (
Expand Down Expand Up @@ -132,22 +128,19 @@ const getDominanceByCurrency = async (

const applySmoothing = async (
weightedCVI: number,
oracleAddress: string,
multiply: number,
heartBeatMinutes: number,
deviationThreshold: number,
lambdaMin: number,
lambdaK: number,
network: string,
context: AdapterContext,
): Promise<number> => {
const roundData = await getRpcLatestRound(network, oracleAddress)
const latestIndex = new Decimal(roundData.answer.toString()).div(multiply)
const updatedAt = roundData.updatedAt.mul(1000).toNumber()

if (latestIndex.lte(0)) {
Logger.warn('No on-chain index value found - Is first run of adapter?')
const cachedValue = await loadFromCache(context)
if (!cachedValue) {
Logger.warn('No cached index value found - Is first run of adapter?')
return weightedCVI
}
const latestIndex = new Decimal(cachedValue.value)
const updatedAt = cachedValue.timestamp

const now = moment().utc()
const dtSeconds = moment.duration(now.diff(updatedAt)).asSeconds()
Expand Down
64 changes: 64 additions & 0 deletions packages/composites/crypto-volatility-index/src/utils/cviCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import moment from 'moment'
import { Logger } from '@chainlink/ea-bootstrap'
import type { AdapterContext } from '@chainlink/ea-bootstrap'

const CACHE_KEY = 'cachedCVIValue'

interface CachedCVIValue {
value: number
timestamp: number
}

export const saveToCache = async (
context: AdapterContext,
result: number,
renewPeriodSeconds: number,
): Promise<void> => {
if (!context || !context.cache) {
return
}
const cache = context.cache?.instance
if (!cache) {
return
}
const now = moment().utc().unix()
const value = await cache.getResponse(CACHE_KEY)
if (value === undefined || (value && value.maxAge && now - value.maxAge > renewPeriodSeconds)) {
const c = cache.setResponse(
CACHE_KEY,
{
maxAge: now,
result,
statusCode: 200,
data: { statusCode: 200, result },
},
renewPeriodSeconds * 2000,
)
if (typeof c !== 'boolean') {
await c
}
Logger.info(`Saved to cache - value: ${result}, timestamp: ${now}`)
}
}

export const loadFromCache = async (
context: AdapterContext,
): Promise<CachedCVIValue | undefined> => {
if (!context) {
return
}
const cache = context.cache?.instance
if (!cache) {
return
}
const cached = await cache.getResponse(CACHE_KEY)
if (!cached) {
return
}
const resultString = cached.result?.toString()
if (!resultString) {
return
}
Logger.info(`Value from cache - value: ${resultString}, timestamp: ${cached.maxAge}`)
return { value: +resultString, timestamp: cached.maxAge }
}