Closed Klapeyron closed 2 months ago
To store historical oracle price data for each market, we need to create a new table in the database, define a corresponding TypeORM model, update the GraphQL schema to include a new type and query, implement a resolver to fetch the historical data, and modify the event processor to save historical price data. This ensures that we can track and query historical oracle prices efficiently.
Create a new TypeORM model to represent the historical oracle price data.
File: src/model/generated/historicalOraclePrice.model.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
@Entity('historical_oracle_price')
export class HistoricalOraclePrice {
@PrimaryGeneratedColumn()
id: number;
@Column()
marketId: string;
@Column('decimal', { precision: 18, scale: 8 })
price: number;
@CreateDateColumn()
timestamp: Date;
}
Update the index file to export the newly created model.
File: src/model/index.ts
export * from "./generated";
export { HistoricalOraclePrice } from "./generated/historicalOraclePrice.model";
Add a new type and query definition for historical oracle price data.
File: schema.graphql
type HistoricalOraclePrice @entity {
id: ID! @unique @index
marketId: String! @index
price: BigDecimal!
timestamp: DateTime!
}
type Query {
historicalOraclePrices(marketId: ID!, startDate: DateTime, endDate: DateTime): [HistoricalOraclePrice!]!
}
Add a new resolver function to fetch historical oracle price data from the database.
File: src/server-extension/resolvers/oraclePriceResolver.ts
import { Arg, Field, ObjectType, Query, Resolver } from "type-graphql";
import { HistoricalOraclePrice } from "../../model/generated/historicalOraclePrice.model";
import { getRepository } from "typeorm";
@ObjectType()
export class HistoricalOraclePriceData {
@Field(() => String, { nullable: false })
price!: BigInt;
@Field(() => Date, { nullable: false })
timestamp!: Date;
constructor(props: Partial<HistoricalOraclePriceData>) {
Object.assign(this, props);
}
}
@Resolver()
export class OraclePriceResolver {
@Query(() => [HistoricalOraclePriceData])
async getHistoricalOraclePrices(
@Arg("marketId", { nullable: false }) marketId: string,
@Arg("startDate", { nullable: true }) startDate?: Date,
@Arg("endDate", { nullable: true }) endDate?: Date,
): Promise<HistoricalOraclePriceData[]> {
if (marketId.length == 0) {
throw new Error("MarketId is empty");
}
const query = getRepository(HistoricalOraclePrice)
.createQueryBuilder("price")
.where("price.marketId = :marketId", { marketId });
if (startDate) {
query.andWhere("price.timestamp >= :startDate", { startDate });
}
if (endDate) {
query.andWhere("price.timestamp <= :endDate", { endDate });
}
const prices = await query.getMany();
return prices.map(price => new HistoricalOraclePriceData(price));
}
}
Modify the processor to insert historical oracle price data into the new table.
File: src/eventprocessor/market/latestOraclePriceProcessor.ts
import { EventProcessor } from "../eventProcessor";
import { Store } from "@subsquid/typeorm-store";
import { Market, HistoricalOraclePrice } from "../../model";
import { DataHandlerContext, Block, Event } from "@subsquid/substrate-processor";
import { oracle } from "../../types/events";
import { oracle as storage } from "../../types/storage";
import { BigDecimal } from "@subsquid/big-decimal";
import { USDC_DECIMALS } from "../../utils";
type PriceData = { [key: string]: Set<bigint> };
export class LatestOraclePriceProcessor implements EventProcessor {
private blockData: PriceData = {};
private blockNumber = 0;
getHandledEventName(): string {
return "Oracle.NewFeedData";
}
async process(
ctx: DataHandlerContext<Store, any>,
block: Block<any>,
event: Event,
) {
console.log("Latest oracle price event");
const receivedEvent = oracle.newFeedData.v2;
if (receivedEvent.is(event)) {
const decodedEvent = receivedEvent.decode(event);
if (block.header.height != this.blockNumber) {
delete this.blockData[this.blockNumber];
this.blockNumber = block.header.height;
this.blockData[this.blockNumber] = new Set<bigint>();
}
for (const eventTicker of decodedEvent.values) {
this.blockData[this.blockNumber].add(eventTicker[0]);
}
for await (const marketId of this.blockData[this.blockNumber]) {
const marketPrice = await storage.values.v2.get(block.header, marketId);
let market = await ctx.store.findOne(Market, {
where: {
id: marketId.toString(),
},
});
if (market === undefined) {
console.error(`Market with market Id ${marketId} does not exist.`);
continue;
}
if (marketPrice === undefined) {
console.error(
`Price for market with market Id ${marketId} is not available.`,
);
continue;
}
const price = BigDecimal(marketPrice.value, USDC_DECIMALS);
market.oraclePrice = price;
await ctx.store.save(market);
// Create and save historical oracle price entry
const historicalPrice = new HistoricalOraclePrice();
historicalPrice.marketId = market.id;
historicalPrice.price = price;
historicalPrice.timestamp = new Date(block.header.timestamp);
await ctx.store.save(historicalPrice);
}
}
}
}
Add a new table definition for historical oracle price data.
File: db/migrations/1718277827116-Data.js
module.exports = class Data1718277827116 {
name = 'Data1718277827116'
async up(db) {
await db.query(`CREATE TABLE "market" ("id" character varying NOT NULL, "ticker" text NOT NULL, "tick_size" numeric NOT NULL, "lifetime" numeric NOT NULL, "initial_margin" integer NOT NULL, "maintenance_margin" integer NOT NULL, "contract_unit" numeric NOT NULL, "block_height" numeric NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "daily_volume" numeric NOT NULL, "oracle_price" numeric, "status" character varying(5) NOT NULL, CONSTRAINT "PK_1e9a2963edfd331d92018e3abac" PRIMARY KEY ("id"))`)
await db.query(`CREATE TABLE "order" ("id" character varying NOT NULL, "price" numeric NOT NULL, "quantity" numeric NOT NULL, "side" character varying(5) NOT NULL, "who" text, "block_height" numeric NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "initial_quantity" numeric NOT NULL, "status" character varying(23) NOT NULL, "type" jsonb NOT NULL, "market_id" character varying, CONSTRAINT "PK_1031171c13130102495201e3e20" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_d91cc35ada00c918781b7f0599" ON "order" ("market_id") `)
await db.query(`CREATE TABLE "position" ("id" character varying NOT NULL, "create_price" numeric NOT NULL, "price" numeric NOT NULL, "quantity" numeric NOT NULL, "quantity_left" numeric NOT NULL, "long" text NOT NULL, "short" text NOT NULL, "block_height" numeric NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "status" character varying(6), "market_id" character varying, CONSTRAINT "PK_b7f483581562b4dc62ae1a5b7e2" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_d744886149158961e1b796182f" ON "position" ("market_id") `)
await db.query(`CREATE TABLE "aggregated_orders_by_price" ("id" character varying NOT NULL, "price" numeric NOT NULL, "quantity" numeric NOT NULL, "side" character varying(5) NOT NULL, "market_id" character varying, CONSTRAINT "PK_43d8c277d4b42f0ecd9e93d1d76" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_6a3c7cf051dee6be40f560f2f0" ON "aggregated_orders_by_price" ("market_id") `)
await db.query(`CREATE TABLE "withdraw" ("id" character varying NOT NULL, "amount" numeric NOT NULL, "user" text NOT NULL, "status" character varying(9) NOT NULL, CONSTRAINT "PK_5c172f81689173f75bf5906ef22" PRIMARY KEY ("id"))`)
await db.query(`CREATE TABLE "market_settlements" ("id" character varying NOT NULL, "amount" numeric NOT NULL, "user" text NOT NULL, "type" character varying(8) NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "market_id" character varying, CONSTRAINT "PK_e29485c52f72700921fc2eb736f" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_b5f2240a59c8d1b72ec029d3d1" ON "market_settlements" ("user") `)
await db.query(`CREATE INDEX "IDX_7576b8515e52f177ca4797d594" ON "market_settlements" ("market_id") `)
await db.query(`CREATE TABLE "user_balance" ("id" character varying NOT NULL, "user" text NOT NULL, "balance_change" numeric NOT NULL, "market_id" character varying, CONSTRAINT "PK_f3edf5a1907e7b430421b9c2ddd" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_0f7286bb3c5c88d8b5a7612b11" ON "user_balance" ("user") `)
await db.query(`CREATE INDEX "IDX_8927a4646227f7ad15ec1264db" ON "user_balance" ("balance_change") `)
await db.query(`CREATE INDEX "IDX_99b139cd3e9dadefb8263133d7" ON "user_balance" ("market_id") `)
await db.query(`CREATE TABLE "general_leaderboard" ("id" character varying NOT NULL, "balance_change" numeric NOT NULL, CONSTRAINT "PK_c5c71acd0600bd847910441dadd" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_3da0cfd74db44b12fe9013b691" ON "general_leaderboard" ("balance_change") `)
await db.query(`ALTER TABLE "order" ADD CONSTRAINT "FK_d91cc35ada00c918781b7f0599d" FOREIGN KEY ("market_id") REFERENCES "market"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "position" ADD CONSTRAINT "FK_d744886149158961e1b796182f8" FOREIGN KEY ("market_id") REFERENCES "market"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "aggregated_orders_by_price" ADD CONSTRAINT "FK_6a3c7cf051dee6be40f560f2f07" FOREIGN KEY ("market_id") REFERENCES "market"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "market_settlements" ADD CONSTRAINT "FK_7576b8515e52f177ca4797d594f" FOREIGN KEY ("market_id") REFERENCES "market"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "user_balance" ADD CONSTRAINT "FK_99b139cd3e9dadefb8263133d7c" FOREIGN KEY ("market_id") REFERENCES "market"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
// Add the new table for historical oracle price data
await db.query(`CREATE TABLE "historical_oracle_price" (
"id" SERIAL PRIMARY KEY,
"market_id" character varying NOT NULL,
"price" numeric NOT NULL,
"timestamp" TIMESTAMP WITH TIME ZONE NOT NULL,
CONSTRAINT "FK_market" FOREIGN KEY ("market_id") REFERENCES "market"("id") ON DELETE CASCADE
)`)
}
async down(db) {
await db.query(`DROP TABLE "market"`)
await db.query(`DROP TABLE "order"`)
await db.query(`DROP INDEX "public"."IDX_d91cc35ada00c918781b7f0599"`)
await db.query(`DROP TABLE "position"`)
await db.query(`DROP INDEX "public"."IDX_d744886149158961e1b796182f"`)
await db.query(`DROP TABLE "aggregated_orders_by_price"`)
await db.query(`DROP INDEX "public"."IDX_6a3c7cf051dee6be40f560f2f0"`)
await db.query(`DROP TABLE "withdraw"`)
await db.query(`DROP TABLE "market_settlements"`)
await db.query(`DROP INDEX "public"."IDX_b5f2240a59c8d1b72ec029d3d1"`)
await db.query(`DROP INDEX "public"."IDX_7576b8515e52f177ca4797d594"`)
await db.query(`DROP TABLE "user_balance"`)
await db.query(`DROP INDEX "public"."IDX_0f7286bb3c5c88d8b5a7612b11"`)
await db.query(`DROP INDEX "public"."IDX_8927a4646227f7ad15ec1264db"`)
await db.query(`DROP INDEX "public"."IDX_99b139cd3e9dadefb8263133d7"`)
await db.query(`DROP TABLE "general_leaderboard"`)
await db.query(`DROP INDEX "public"."IDX_3da0cfd74db44b12fe9013b691"`)
await db.query(`ALTER TABLE "order" DROP CONSTRAINT "FK_d91cc35ada00c918781b7f0599d"`)
await db.query(`ALTER TABLE "position" DROP CONSTRAINT "FK_d744886149158961e1b796182f8"`)
await db.query(`ALTER TABLE "aggregated_orders_by_price" DROP CONSTRAINT "FK_6a3c7cf051dee6be40f560f2f07"`)
await db.query(`ALTER TABLE "market_settlements" DROP CONSTRAINT "FK_7576b8515e52f177ca4797d594f"`)
await db.query(`ALTER TABLE "user_balance" DROP CONSTRAINT "FK_99b139cd3e9dadefb8263133d7c"`)
// Drop the new table for historical oracle price data
await db.query(`DROP TABLE "historical_oracle_price"`)
}
}
up
method: Adds the new table historical_oracle_price
with columns id
, market_id
, price
, and timestamp
. The market_id
column references the market
table.down
method: Drops the historical_oracle_price
table if the migration is rolled back.This change ensures that historical oracle price data is stored in a separate table, as requested.
Click here to create a Pull Request with the proposed solution
Files used for this task:
Would be great to store in separate table historical data of oracle prices for each market