Skip to content

Commit

Permalink
Merge pull request #30 from Se7en-Seas/main
Browse files Browse the repository at this point in the history
OHLC Graph Cleanup
  • Loading branch information
philipjames44 authored Aug 12, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 71ea720 + 3d844b3 commit 5a8bc11
Showing 2 changed files with 93 additions and 80 deletions.
151 changes: 75 additions & 76 deletions src/components/charts/ohlcChart.tsx
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ const OHLCChart = ({ asset1Token, asset2Token }: OHLCChartProps) => {
>([]); // [[date, open, close, low, high]]
const [volumeData, setVolumeData] = useState<[string, number][]>([]);
// Time aggregate, 1m, 5m, 1h, 1D to start off
const [timeAggregateSeconds, setTimeAggregateSeconds] = useState<number>(60 * 60); // 1h default
const [timeAggregateSeconds, setTimeAggregateSeconds] = useState<number>(60 * 60* 24); // 1D default
const [isAggregating, setIsAggregating] = useState(true);

// Potentially show last n days of data based on current block - n days via avg block time
@@ -190,11 +190,9 @@ const OHLCChart = ({ asset1Token, asset2Token }: OHLCChartProps) => {
}

// Process the data and make a list of OHLC heights
// format needed is '/api/blockTimestamps/{height1}/{height2}/{height3}'
// format needed is '/api/blockTimestamps/range/{startHeight}/{endHeight}'
const timestampsForHeights = fetch(
`/api/blockTimestamps/${originalOHLCData
.map((ohlc) => ohlc["height"])
.join("/")}`
`/api/blockTimestamps/range/${originalOHLCData[0]["height"]}/${originalOHLCData[originalOHLCData.length - 1]["height"]}`
).then((res) => res.json());

Promise.all([timestampsForHeights])
@@ -327,107 +325,108 @@ const OHLCChart = ({ asset1Token, asset2Token }: OHLCChartProps) => {
setIsLoading(true);
setIsAggregating(true);

// Function to aggregate OHLC data into time intervals
const aggregateOHLCData = () => {
const aggregatedData: any[] = [];
const blocksPerInterval = Math.trunc(
timeAggregateSeconds / blockTimeSeconds
);
const batchOHLCData = (batch: any[], intervalStart: Date) => {
const aggregatedOHLC: any = {
open: batch[0]["open"],
high: Math.max(...batch.map((ohlc) => ohlc["high"])),
low: Math.min(...batch.map((ohlc) => ohlc["low"])),
close: batch[batch.length - 1]["close"],
directVolume: batch.reduce((acc, ohlc) => acc + ohlc["directVolume"], 0),
swapVolume: batch.reduce((acc, ohlc) => acc + ohlc["swapVolume"], 0),
height: batch[0]["height"],
};

let currentBatch: any[] = [];
let currentBatchStartBlock: number | null = null;
// Add the interval start time to blockToTimestamp
blockToTimestamp[aggregatedOHLC["height"]] = intervalStart.toISOString();

return aggregatedOHLC;
};

// Helper function to generate a placeholder OHLC object
const createPlaceholderOHLC = (blockHeight: number, close: number) => ({
const createPlaceholderOHLC = (intervalStart: Date, close: number) => {
const placeholderHeight = `placeholder_${intervalStart.getTime()}`;
const placeholderOHLC = {
open: close,
high: close,
low: close,
close: close,
directVolume: 0,
swapVolume: 0,
height: blockHeight,
});
height: placeholderHeight,
};

// Add the interval start time to blockToTimestamp
blockToTimestamp[placeholderHeight] = intervalStart.toISOString();

return placeholderOHLC;
};

// Function to aggregate OHLC data into time intervals
const aggregateOHLCData = () => {
const aggregatedData: any[] = [];

// Helper function to get the start of the interval
const getIntervalStart = (timestamp: string) => {
const date = new Date(timestamp);
switch (timeAggregateSeconds) {
case 60: // 1 minute
date.setSeconds(0, 0);
break;
case 60 * 5: // 5 minutes
date.setMinutes(Math.floor(date.getMinutes() / 5) * 5, 0, 0);
break;
case 60 * 60: // 1 hour
date.setMinutes(0, 0, 0);
break;
case 60 * 60 * 24: // 1 day
date.setHours(0, 0, 0, 0);
break;
}
return date;
};

let currentBatch: any[] = [];
let currentIntervalStart: Date | null = null;

// Always aggregated based on the originalOHLCData
originalOHLCData.forEach((ohlc, index) => {
const blockHeight = Number(ohlc["height"]);
const timestamp = new Date(blockToTimestamp[ohlc["height"]]);
const intervalStart = getIntervalStart(timestamp.toISOString());

if (currentBatchStartBlock === null) {
currentBatchStartBlock = blockHeight;
if (currentIntervalStart === null) {
currentIntervalStart = intervalStart;
}

// Check if the current block is within the current batch interval
if (blockHeight < currentBatchStartBlock + blocksPerInterval) {
if (timestamp < new Date(currentIntervalStart.getTime() + timeAggregateSeconds * 1000)) {
currentBatch.push(ohlc);
return;
} else {
// If the current block is outside the current batch interval
// Aggregate the current batch
aggregatedData.push(batchOHLCData(currentBatch));

// Fill gaps if there are any missing intervals
while (currentBatchStartBlock + blocksPerInterval < blockHeight) {
const previousOHLC = aggregatedData[aggregatedData.length - 1];
const placeholderBlock = currentBatchStartBlock + blocksPerInterval;
const placeholderOHLC = createPlaceholderOHLC(
placeholderBlock,
previousOHLC.close
);
const lastTimestamp = blockToTimestamp[previousOHLC.height];
blockToTimestamp[placeholderBlock] = new Date(
new Date(lastTimestamp).getTime() + timeAggregateSeconds * 1000
).toISOString();
aggregatedData.push(placeholderOHLC);

currentBatchStartBlock = currentBatchStartBlock + blocksPerInterval;
if (currentBatch.length > 0) {
aggregatedData.push(batchOHLCData(currentBatch, currentIntervalStart));
}

// Fill in any missing intervals
while (new Date(currentIntervalStart.getTime() + timeAggregateSeconds * 1000) <= intervalStart) {
currentIntervalStart = new Date(currentIntervalStart.getTime() + timeAggregateSeconds * 1000);
if (currentIntervalStart < intervalStart) {
const previousOHLC = aggregatedData[aggregatedData.length - 1];
const placeholderOHLC = createPlaceholderOHLC(currentIntervalStart, previousOHLC["close"]);
aggregatedData.push(placeholderOHLC);
}
}

// Start a new batch
currentBatch = [ohlc];
currentBatchStartBlock = blockHeight;
currentIntervalStart = intervalStart;
}

// Handle the last batch
if (index === originalOHLCData.length - 1 && currentBatch.length > 0) {
aggregatedData.push(batchOHLCData(currentBatch));
aggregatedData.push(batchOHLCData(currentBatch, currentIntervalStart));
}
});

// Fill gaps at the end if there are any remaining intervals
while (
currentBatchStartBlock !== null &&
currentBatchStartBlock + blocksPerInterval <=
originalOHLCData[originalOHLCData.length - 1]["height"]
) {
const previousOHLC = aggregatedData[aggregatedData.length - 1];
const placeholderBlock = currentBatchStartBlock + blocksPerInterval;
const placeholderOHLC = createPlaceholderOHLC(
placeholderBlock,
previousOHLC.close
);
aggregatedData.push(placeholderOHLC);

currentBatchStartBlock = currentBatchStartBlock + blocksPerInterval;
}

return aggregatedData;
};

// Function to batch OHLC data into a single OHLC object
const batchOHLCData = (batch: any[]) => {
const aggregatedOHLC: any = {
open: batch[0].open,
high: Math.max(...batch.map((ohlc) => ohlc.high)),
low: Math.min(...batch.map((ohlc) => ohlc.low)),
close: batch[batch.length - 1].close,
directVolume: batch.reduce((acc, ohlc) => acc + ohlc.directVolume, 0),
swapVolume: batch.reduce((acc, ohlc) => acc + ohlc.swapVolume, 0),
height: batch[0].height,
};

return aggregatedOHLC;
};

const aggregatedData = aggregateOHLCData();
console.log("Aggregated data: ", aggregatedData);

22 changes: 18 additions & 4 deletions src/pages/api/blockTimestamps/[...params].js
Original file line number Diff line number Diff line change
@@ -9,17 +9,31 @@ if (!indexerEndpoint) {

export default async function blockTimestampsFetchHandler(req, res) {
const indexerQuerier = new IndexerQuerier(indexerEndpoint);

// if the first param is 'range' then we are fetching a range of blocks

// Params will be an arbitrarily long list of block heights
const params = req.query.params;
let blocks = [];

for (const param of params) {
if (isNaN(param)) {
res.status(400).json({ error: "Invalid block height" });
if (params[0] == "range") {
if (params.length != 3 || isNaN(params[1]) || isNaN(params[2])) {
res.status(400).json({ error: "Invalid block height range" });
return;
}
blocks.push(parseInt(param));

// define blocks as inclusive range between the two block heights
const start = parseInt(params[1]);
const end = parseInt(params[2]);
blocks = Array.from({length: end - start + 1}, (_, i) => start + i);
} else {
for (const param of params) {
if (isNaN(param)) {
res.status(400).json({ error: "Invalid block height" });
return;
}
blocks.push(parseInt(param));
}
}

try {

0 comments on commit 5a8bc11

Please sign in to comment.