improve and extract `nextDate()` function; improve clickhouse-to-lmdbx sync script

main
avraham 9 months ago
parent 85cafd985d
commit bf094de461

@ -1,13 +1,8 @@
import { stockDatabase } from "./stockdb.clickhouse.js"; import { stockDatabase } from "./stockdb.clickhouse.js";
import { calendarDatabase } from "./calendardb.clickhouse.js"; import { calendarDatabase } from "./calendardb.optiondb.lmdbx.js";
import type { CalendarKey } from "./calendardb.interfaces.js"; import type { CalendarKey } from "./calendardb.interfaces.js";
import type { Aggregate } from "./interfaces.js"; import type { Aggregate } from "./interfaces.js";
import { nextDate } from "./lib/util.js";
function nextDate(date: string) {
const dateObject = new Date(date);
dateObject.setDate(dateObject.getDate() + 1);
return dateObject.toISOString().substring(0, 10);
}
type BacktestInput = { type BacktestInput = {
symbol: string; symbol: string;
@ -58,7 +53,7 @@ export async function backtest({
} }
// for each minute of that day for which we have a stock candlestick: // for each minute of that day for which we have a stock candlestick:
for (const stockAggregate of stockAggregates) { for (const stockAggregate of stockAggregates) {
console.log("Current Time:", new Date(stockAggregate.tsStart)); // console.log("Current Time:", new Date(stockAggregate.tsStart));
// filter-out calendars that are far-from-the-money (10%) // filter-out calendars that are far-from-the-money (10%)
const calendarsNearTheMoney = calendars.filter( const calendarsNearTheMoney = calendars.filter(
({ strike }) => ({ strike }) =>

@ -0,0 +1,155 @@
import { optionContractDatabase } from "./optiondb.lmdbx.js";
import type { CalendarDatabase } from "./calendardb.interfaces.js";
/** Largest possible key according to the `ordered-binary` (used by lmdbx) docs. */
const MAXIMUM_KEY = Buffer.from([0xff]);
function makeCalendarDatabase(): CalendarDatabase {
const calendarDatabase: Omit<CalendarDatabase, "getCalendars"> = {
getKeys: async ({ key: { symbol }, date }) => {
const optionContracts = await optionContractDatabase.getOptionContracts({
date,
key: { symbol },
});
return optionContracts.flatMap(
(frontOptionContract, i, optionContracts) =>
optionContracts
.filter((_, j) => i !== j)
.map((backOptionContract) => ({
symbol,
frontExpirationDate: frontOptionContract.expirationDate,
backExpirationDate: backOptionContract.expirationDate,
strike: frontOptionContract.strike,
type: frontOptionContract.type,
})),
);
},
getAggregates: async ({
key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
date,
}) => {
const frontOptionContractAggregates =
await optionContractDatabase.getAggregates({
date,
key: { symbol, expirationDate: frontExpirationDate, strike, type },
});
const backOptionContractAggregates =
await optionContractDatabase.getAggregates({
date,
key: { symbol, expirationDate: backExpirationDate, strike, type },
});
const calendarAggregates = [];
let i = 0;
let j = 0;
while (
i < frontOptionContractAggregates.length &&
j < backOptionContractAggregates.length
) {
if (
frontOptionContractAggregates[i].tsStart ===
backOptionContractAggregates[j].tsStart
) {
calendarAggregates.push({
tsStart: frontOptionContractAggregates[i].tsStart,
open:
backOptionContractAggregates[j].open -
frontOptionContractAggregates[i].open,
close:
backOptionContractAggregates[j].close -
frontOptionContractAggregates[i].close,
// the high and low are not exactly correct since we don't know if each contract's high and low happened ata the same moment as the other:
high:
backOptionContractAggregates[j].high -
frontOptionContractAggregates[i].high,
low:
backOptionContractAggregates[j].low -
frontOptionContractAggregates[i].low,
});
i++;
j++;
} else if (
frontOptionContractAggregates[i].tsStart >
backOptionContractAggregates[j].tsStart
) {
j++;
} else {
i++;
}
}
return calendarAggregates;
},
insertAggregates: async (aggregates) => {
// right now, no-op
},
getClosingPrice: async ({
key: { symbol, strike, type, frontExpirationDate, backExpirationDate },
}) => {
const startOfLastHourUnix = new Date(
`${frontExpirationDate}T00:00:00Z`,
).valueOf();
const endOfLastHourUnix = startOfLastHourUnix + 3600 * 1000;
const frontOptionContractAggregates = (
await optionContractDatabase.getAggregates({
date: frontExpirationDate,
key: { symbol, expirationDate: frontExpirationDate, strike, type },
})
).filter(
({ tsStart }) =>
tsStart >= startOfLastHourUnix && tsStart < endOfLastHourUnix,
);
const backOptionContractAggregates = (
await optionContractDatabase.getAggregates({
date: frontExpirationDate,
key: { symbol, expirationDate: backExpirationDate, strike, type },
})
).filter(
({ tsStart }) =>
tsStart >= startOfLastHourUnix && tsStart < endOfLastHourUnix,
);
let i = 0;
let j = 0;
let minPrice = 0;
while (
i < frontOptionContractAggregates.length &&
j < backOptionContractAggregates.length
) {
if (
frontOptionContractAggregates[i].tsStart ===
backOptionContractAggregates[j].tsStart
) {
const calendarClosePrice =
backOptionContractAggregates[j].close -
frontOptionContractAggregates[j].close;
if (calendarClosePrice < minPrice || minPrice === 0) {
minPrice = calendarClosePrice;
}
i++;
j++;
} else if (
frontOptionContractAggregates[i].tsStart >
backOptionContractAggregates[j].tsStart
) {
j++;
} else {
i++;
}
}
return minPrice;
},
getTargetPriceByProbability: async ({
symbol,
calendarSpan,
strikePercentageFromTheMoney,
historicalProbabilityOfSuccess,
}) => {
return 0.24;
},
};
return {
...calendarDatabase,
getCalendars: calendarDatabase.getKeys,
};
}
export const calendarDatabase: CalendarDatabase = makeCalendarDatabase();

@ -0,0 +1,6 @@
export function nextDate(date: string) {
const [year, month, day] = date.split('-').map(Number);
const nextDay = new Date(Date.UTC(year, month - 1, day + 1));
const nextDateString = nextDay.toISOString().substring(0, 10);
return nextDateString;
}

@ -3,43 +3,48 @@ import type { AggregateDatabase } from "../interfaces.js";
// import { stockDatabase as stockDatabaseLmdbx } from "../stockdb.lmdbx.js"; // import { stockDatabase as stockDatabaseLmdbx } from "../stockdb.lmdbx.js";
import { optionContractDatabase as optionContractDatabaseClickhouse } from "../optiondb.clickhouse.js"; import { optionContractDatabase as optionContractDatabaseClickhouse } from "../optiondb.clickhouse.js";
import { optionContractDatabase as optionContractDatabaseLmdbx } from "../optiondb.lmdbx.js"; import { optionContractDatabase as optionContractDatabaseLmdbx } from "../optiondb.lmdbx.js";
import { nextDate } from "../lib/util.js";
function nextDate(date: string) {
const dateObject = new Date(date);
dateObject.setDate(dateObject.getDate() + 1);
return dateObject.toISOString().substring(0, 10);
}
async function syncAggregates<T>({ async function syncAggregates<T>({
from, fromDatabase,
to, toDatabase,
key, key,
date, date,
}: { }: {
from: AggregateDatabase<T>; fromDatabase: AggregateDatabase<T>;
to: AggregateDatabase<T>; toDatabase: AggregateDatabase<T>;
key: T; key: T;
date: string; date: string;
}) { }) {
const aggregatesFrom = (await from.getAggregates({ key, date })).map( const aggregatesFrom = (await fromDatabase.getAggregates({ key, date })).map(
(aggregateWithoutKey) => ({ ...aggregateWithoutKey, key }), (aggregateWithoutKey) => ({ ...aggregateWithoutKey, key }),
); );
await to.insertAggregates(aggregatesFrom); await toDatabase.insertAggregates(aggregatesFrom);
} }
const symbols = ["AMD", "AAPL", "MSFT", "GOOGL", "NFLX", "NVDA"]; const symbols = ["AMD", "AAPL", "MSFT", "GOOGL", "NFLX", "NVDA"];
async function run() { async function run() {
const startDate = "2022-02-01"; const startDate = process.argv[2];
const endDate = "2024-07-15"; const endDate = process.argv[3];
if (!startDate || !endDate) {
console.error("Usage: node clickhouse-to-lmdbx.js <startDate> <endDate>");
console.error("Dates should be in YYYY-MM-DD format");
process.exit(1);
}
for (let date = startDate; date <= endDate; date = nextDate(date)) { for (let date = startDate; date <= endDate; date = nextDate(date)) {
// const symbols = await stockDatabaseClickhouse.getSymbols({ date }); // const symbols = await stockDatabaseClickhouse.getSymbols({ date });
for (const symbol of symbols) { for (const symbol of symbols) {
console.log(date, symbol); console.log(date, symbol);
const keys = await optionContractDatabaseClickhouse.getKeys({key: {symbol}, date}); const keys = await optionContractDatabaseClickhouse.getKeys({
for(const key of keys){ key: { symbol },
date,
});
for (const key of keys) {
// console.log(date, symbol, key.expirationDate, key.strike, key.type);
await syncAggregates({ await syncAggregates({
from: optionContractDatabaseClickhouse, fromDatabase: optionContractDatabaseClickhouse,
to: optionContractDatabaseLmdbx, toDatabase: optionContractDatabaseLmdbx,
key, key,
date, date,
}); });

Loading…
Cancel
Save