Skip to content
Draft
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@galacticcouncil/math-lbp": "^0.0.7",
"@galacticcouncil/math-omnipool": "^0.0.7",
"@galacticcouncil/math-xyk": "^0.0.7",
"@galacticcouncil/math-ema": "file:../hydra-wasm/packages/ema",
"bignumber.js": "^9.1.0",
"lodash.clonedeep": "^4.5.0"
},
Expand Down
96 changes: 96 additions & 0 deletions src/oracle/OracleMath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { low_precision_iterated_price_ema, iterated_balance_ema } from '@galacticcouncil/math-ema';
import BigNumber from 'bignumber.js';
import { ZERO, bnum } from '../utils/bignumber';

export interface OracleEntry {
price: [BigNumber, BigNumber];
volume: BigNumber[];
liquidity: [BigNumber, BigNumber];
timestamp: BigNumber;
}

export interface LowPrecisionOracleEntry {
price: BigNumber;
volume: BigNumber[];
liquidity: [BigNumber, BigNumber];
timestamp: BigNumber;
}

export enum OraclePeriod {
LastBlock = 'LastBlock',
Short = 'Short',
TenMinutes = 'TenMinutes',
Hour = 'Hour',
Day = 'Day',
Week = 'Week',
}

export class EmaLowPrecisionMath {
static iteratedPriceEma(
iterations: string,
prevN: string,
prevD: string,
incomingN: string,
incomingD: string,
smoothing: string
): string {
return low_precision_iterated_price_ema(iterations, prevN, prevD, incomingN, incomingD, smoothing);
}

static iteratedBalanceEma(iterations: string, prev: string, incoming: string, smoothing: string): string {
return iterated_balance_ema(iterations, prev, incoming, smoothing);
}
}

export class OracleMath {
// Smoothing factors for the currently supported oracle periods.
// Taken from https://github.com/galacticcouncil/warehouse/blob/0047e9ceff47b2a058ae9ecc25da96d1e827a26a/ema-oracle/src/types.rs#L198-L207
static readonly SmoothingForPeriod: Map<OraclePeriod, string> = new Map([
[OraclePeriod.LastBlock, '170141183460469231731687303715884105728'],
[OraclePeriod.Short, '34028236692093846346337460743176821146'],
[OraclePeriod.TenMinutes, '3369132345751865974884897103284833777'],
[OraclePeriod.Hour, '566193622164623067326746434994622648'],
[OraclePeriod.Day, '23629079016800115510268356880200556'],
[OraclePeriod.Week, '3375783642235081630771268215908257'],
]);

/// Calculate the current oracle values from the `outdated` and `updateWith` values using the `smoothing` factor with the old values being `iterations` out of date.
///
/// Note: The volume is always updated with zero values so it is not a parameter.
static updateOutdatedToCurrent(
outdated: OracleEntry,
updateWith: OracleEntry,
period: OraclePeriod
): LowPrecisionOracleEntry {
if (outdated.timestamp >= updateWith.timestamp) {
throw new Error('invalid timestamp (outdated should be older)');
}
let iterations = BigNumber.max(updateWith.timestamp.minus(outdated.timestamp), 0).toString();
let smoothing = OracleMath.SmoothingForPeriod.get(period);
if (!smoothing) {
throw new Error('unknown period');
}
let [prevN, prevD] = outdated.price;
let [incomingN, incomingD] = updateWith.price;
let price = bnum(
EmaLowPrecisionMath.iteratedPriceEma(
iterations,
prevN.toString(),
prevD.toString(),
incomingN.toString(),
incomingD.toString(),
smoothing
)
);
let volume = outdated.volume.map((v) =>
bnum(EmaLowPrecisionMath.iteratedBalanceEma(iterations, v.toString(), ZERO.toString(), smoothing))
);
let [prevLiq1, prevLiq2] = outdated.liquidity;
let [incomingLiq1, incomingLiq2] = updateWith.liquidity;
let liquidity: [BigNumber, BigNumber] = [
bnum(EmaLowPrecisionMath.iteratedBalanceEma(iterations, prevLiq1.toString(), incomingLiq1.toString(), smoothing)),
bnum(EmaLowPrecisionMath.iteratedBalanceEma(iterations, prevLiq2.toString(), incomingLiq2.toString(), smoothing)),
];
return { price, volume, liquidity, timestamp: updateWith.timestamp };
}
}
1 change: 1 addition & 0 deletions src/oracle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EmaLowPrecisionMath, LowPrecisionOracleEntry, OracleEntry, OracleMath, OraclePeriod } from './OracleMath';
64 changes: 64 additions & 0 deletions test/ema/emaMath.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { EmaLowPrecisionMath, OracleMath, OraclePeriod } from '../../src/oracle';
import { BigNumber, bnum } from '../../src/utils/bignumber';

function smoothingFromPeriod(period: number): BigNumber {
return bnum(2)
.pow(127)
.multipliedBy(2)
.dividedBy(period + 1);
}

describe('EMA Math', () => {
it('Should return correct price EMA for simple params', async () => {
let smoothing = smoothingFromPeriod(7);
let startPriceN = bnum(4);
let startPriceD = bnum(1);
let incomingPriceN = bnum(8);
let incomingPriceD = bnum(1);
let iterations = bnum(1);
let nextPrice = EmaLowPrecisionMath.iteratedPriceEma(
iterations.toString(),
startPriceN.toString(),
startPriceD.toString(),
incomingPriceN.toString(),
incomingPriceD.toString(),
smoothing.toString()
);
let expected = bnum(5).multipliedBy(bnum('1000000000000000000'));
expect(bnum(nextPrice)).toStrictEqual(expected);
});

it('Should return correct price EMA for hard-coded period', async () => {
let smoothing = OracleMath.SmoothingForPeriod.get(OraclePeriod.Short);
let startPriceN = bnum('100');
let startPriceD = bnum(1);
let incomingPriceN = bnum('1100');
let incomingPriceD = bnum(1);
let iterations = bnum(1);
let nextPrice = EmaLowPrecisionMath.iteratedPriceEma(
iterations.toString(),
startPriceN.toString(),
startPriceD.toString(),
incomingPriceN.toString(),
incomingPriceD.toString(),
smoothing.toString()
);
let expected = bnum('300').multipliedBy(bnum('1000000000000000000'));
expect(bnum(nextPrice)).toStrictEqual(expected);
});

it('Should return correct balance EMA for hard-coded period', async () => {
let smoothing = OracleMath.SmoothingForPeriod.get(OraclePeriod.Short);
let startBalance = bnum('100000000');
let incomingBalance = bnum('1100000000');
let iterations = bnum(1);
let nextPrice = EmaLowPrecisionMath.iteratedBalanceEma(
iterations.toString(),
startBalance.toString(),
incomingBalance.toString(),
smoothing.toString()
);
let expected = bnum('300000000');
expect(bnum(nextPrice)).toStrictEqual(expected);
});
});