re-organized code; implemented getAggregate() where it was missing
This commit is contained in:
+94
-45
@@ -1,13 +1,13 @@
|
|||||||
import type { CalendarDatabase, CalendarKey } from "./interfaces.js";
|
import type { CalendarDatabase, CalendarKey } from "./interfaces.js";
|
||||||
import type { Aggregate } from "../interfaces.js";
|
import type { Aggregate } from "../interfaces.js";
|
||||||
import { query } from "../lib/clickhouse.js";
|
import { query } from "../../lib/clickhouse.js";
|
||||||
|
|
||||||
function makeCalendarDatabase(): CalendarDatabase {
|
function makeCalendarDatabase(): CalendarDatabase {
|
||||||
const calendarDatabase: Omit<CalendarDatabase, "getCalendars"> = {
|
const calendarDatabase: Omit<CalendarDatabase, "getCalendars"> = {
|
||||||
getKeys: async ({ key: { symbol }, date }) => {
|
getKeys: async ({ key: { symbol }, date }) => {
|
||||||
const calendarsForSymbolOnDate = await query<
|
const calendarsForSymbolOnDate = await query<
|
||||||
Omit<CalendarKey, "symbol">
|
Omit<CalendarKey, "symbol">
|
||||||
>(`
|
>(`
|
||||||
WITH today_option_contracts AS (
|
WITH today_option_contracts AS (
|
||||||
SELECT expirationDate, strike, type
|
SELECT expirationDate, strike, type
|
||||||
FROM option_contract_existences
|
FROM option_contract_existences
|
||||||
@@ -26,17 +26,66 @@ function makeCalendarDatabase(): CalendarDatabase {
|
|||||||
AND front_option_contract.expirationDate < back_option_contract.expirationDate
|
AND front_option_contract.expirationDate < back_option_contract.expirationDate
|
||||||
`);
|
`);
|
||||||
|
|
||||||
return calendarsForSymbolOnDate.map((calendarWithoutSymbol) => ({
|
return calendarsForSymbolOnDate.map((calendarWithoutSymbol) => ({
|
||||||
...calendarWithoutSymbol,
|
...calendarWithoutSymbol,
|
||||||
symbol,
|
symbol,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
getAggregates: async ({
|
getAggregate: async ({
|
||||||
key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
|
key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
|
||||||
date,
|
tsStart,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
const tsStartString = new Date(tsStart).toISOString();
|
||||||
await query<Omit<Aggregate<CalendarKey>, "key">>(`
|
return (
|
||||||
|
await query<Omit<Aggregate<CalendarKey>, "key">>(`
|
||||||
|
WITH front_option_contract_candlestick AS (
|
||||||
|
SELECT
|
||||||
|
tsStart,
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
high,
|
||||||
|
low
|
||||||
|
FROM option_contract_aggregates
|
||||||
|
WHERE symbol = '${symbol}'
|
||||||
|
AND type = '${type}'
|
||||||
|
AND strike = '${strike}'
|
||||||
|
AND expirationDate = '${frontExpirationDate}'
|
||||||
|
AND tsStart = '${tsStartString}'
|
||||||
|
),
|
||||||
|
back_option_contract_candlestick AS (
|
||||||
|
SELECT
|
||||||
|
tsStart,
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
high,
|
||||||
|
low
|
||||||
|
FROM option_contract_aggregates
|
||||||
|
WHERE symbol = '${symbol}'
|
||||||
|
AND type = '${type}'
|
||||||
|
AND strike = '${strike}'
|
||||||
|
AND expirationDate = '${backExpirationDate}'
|
||||||
|
AND tsStart = '${tsStartString}'
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
toUnixTimestamp(front_option_contract_candlestick.tsStart) as tsStart,
|
||||||
|
back_option_contract_candlestick.open - front_option_contract_candlestick.open as open,
|
||||||
|
back_option_contract_candlestick.close - front_option_contract_candlestick.close as close
|
||||||
|
FROM front_option_contract_candlestick
|
||||||
|
INNER JOIN back_option_contract_candlestick
|
||||||
|
ON front_option_contract_candlestick.tsStart = back_option_contract_candlestick.tsStart
|
||||||
|
ORDER BY front_option_contract_candlestick.tsStart ASC
|
||||||
|
`)
|
||||||
|
).map((aggregate) => ({
|
||||||
|
...aggregate,
|
||||||
|
tsStart: aggregate.tsStart * 1000, // unfortunately, `toUnixTimestamp` only returns second-precision
|
||||||
|
}))[0];
|
||||||
|
},
|
||||||
|
getAggregates: async ({
|
||||||
|
key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
|
||||||
|
date,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
await query<Omit<Aggregate<CalendarKey>, "key">>(`
|
||||||
WITH front_option_contract_candlestick AS (
|
WITH front_option_contract_candlestick AS (
|
||||||
SELECT
|
SELECT
|
||||||
tsStart,
|
tsStart,
|
||||||
@@ -74,19 +123,19 @@ function makeCalendarDatabase(): CalendarDatabase {
|
|||||||
ON front_option_contract_candlestick.tsStart = back_option_contract_candlestick.tsStart
|
ON front_option_contract_candlestick.tsStart = back_option_contract_candlestick.tsStart
|
||||||
ORDER BY front_option_contract_candlestick.tsStart ASC
|
ORDER BY front_option_contract_candlestick.tsStart ASC
|
||||||
`)
|
`)
|
||||||
).map((aggregate) => ({
|
).map((aggregate) => ({
|
||||||
...aggregate,
|
...aggregate,
|
||||||
tsStart: aggregate.tsStart * 1000, // unfortunately, `toUnixTimestamp` only returns second-precision
|
tsStart: aggregate.tsStart * 1000, // unfortunately, `toUnixTimestamp` only returns second-precision
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
insertAggregates: async (aggregates) => {
|
insertAggregates: async (aggregates) => {
|
||||||
// no-op: we insert individual option contracts, not calendars
|
// no-op: we insert individual option contracts, not calendars
|
||||||
},
|
},
|
||||||
getClosingPrice: async ({
|
getClosingPrice: async ({
|
||||||
key: { symbol, strike, type, frontExpirationDate, backExpirationDate },
|
key: { symbol, strike, type, frontExpirationDate, backExpirationDate },
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
await query<{ calendarClosingPrice: number }>(`
|
await query<{ calendarClosingPrice: number }>(`
|
||||||
WITH front_option_contract_candlestick AS (
|
WITH front_option_contract_candlestick AS (
|
||||||
SELECT
|
SELECT
|
||||||
tsStart,
|
tsStart,
|
||||||
@@ -121,22 +170,22 @@ function makeCalendarDatabase(): CalendarDatabase {
|
|||||||
INNER JOIN back_option_contract_candlestick
|
INNER JOIN back_option_contract_candlestick
|
||||||
ON front_option_contract_candlestick.tsStart = back_option_contract_candlestick.tsStart
|
ON front_option_contract_candlestick.tsStart = back_option_contract_candlestick.tsStart
|
||||||
`)
|
`)
|
||||||
)[0]?.calendarClosingPrice;
|
)[0]?.calendarClosingPrice;
|
||||||
},
|
},
|
||||||
getTargetPriceByProbability: async ({
|
getTargetPriceByProbability: async ({
|
||||||
symbol,
|
symbol,
|
||||||
calendarSpan,
|
calendarSpan,
|
||||||
strikePercentageFromTheMoney,
|
strikePercentageFromTheMoney,
|
||||||
historicalProbabilityOfSuccess,
|
historicalProbabilityOfSuccess,
|
||||||
}) => {
|
}) => {
|
||||||
return 0.24;
|
return 0.24;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...calendarDatabase,
|
...calendarDatabase,
|
||||||
getCalendars: calendarDatabase.getKeys,
|
getCalendars: calendarDatabase.getKeys,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const calendarDatabase: CalendarDatabase = makeCalendarDatabase();
|
export const calendarDatabase: CalendarDatabase = makeCalendarDatabase();
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { AggregateDatabase } from "../interfaces.js";
|
||||||
|
|
||||||
|
export type CalendarKey = {
|
||||||
|
symbol: string;
|
||||||
|
type: "call" | "put";
|
||||||
|
strike: number;
|
||||||
|
frontExpirationDate: string;
|
||||||
|
backExpirationDate: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CalendarDatabase = AggregateDatabase<CalendarKey> & {
|
||||||
|
getCalendars: AggregateDatabase<CalendarKey>["getKeys"];
|
||||||
|
getTargetPriceByProbability: ({
|
||||||
|
symbol,
|
||||||
|
calendarSpan,
|
||||||
|
strikePercentageFromTheMoney,
|
||||||
|
historicalProbabilityOfSuccess,
|
||||||
|
}: {
|
||||||
|
symbol: string;
|
||||||
|
calendarSpan: number;
|
||||||
|
strikePercentageFromTheMoney: number;
|
||||||
|
historicalProbabilityOfSuccess: number;
|
||||||
|
}) => Promise<number>;
|
||||||
|
};
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { optionContractDatabase } from "../optiondb/lmdbx.js";
|
import { optionContractDatabase } from "../OptionContract/lmdbx.js";
|
||||||
import type { CalendarDatabase } from "./interfaces.js";
|
import type { CalendarDatabase } from "./interfaces.js";
|
||||||
|
|
||||||
/** Largest possible key according to the `ordered-binary` (used by lmdbx) docs. */
|
/** Largest possible key according to the `ordered-binary` (used by lmdbx) docs. */
|
||||||
+27
-2
@@ -3,7 +3,7 @@ import type {
|
|||||||
OptionContractKey,
|
OptionContractKey,
|
||||||
} from "./interfaces.js";
|
} from "./interfaces.js";
|
||||||
import type { Aggregate } from "../interfaces.js";
|
import type { Aggregate } from "../interfaces.js";
|
||||||
import { clickhouse, query } from "../lib/clickhouse.js";
|
import { clickhouse, query } from "../../lib/clickhouse.js";
|
||||||
|
|
||||||
function makeOptionContractDatabase(): OptionContractDatabase {
|
function makeOptionContractDatabase(): OptionContractDatabase {
|
||||||
const optionContractDatabase: Omit<
|
const optionContractDatabase: Omit<
|
||||||
@@ -48,6 +48,31 @@ function makeOptionContractDatabase(): OptionContractDatabase {
|
|||||||
tsStart: aggregate.tsStart * 1000, // unfortunately, `toUnixTimestamp` only returns second-precision
|
tsStart: aggregate.tsStart * 1000, // unfortunately, `toUnixTimestamp` only returns second-precision
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
getAggregate: async ({
|
||||||
|
key: { symbol, expirationDate, strike, type },
|
||||||
|
tsStart,
|
||||||
|
}) => {
|
||||||
|
const tsStartString = new Date(tsStart).toISOString();
|
||||||
|
return (
|
||||||
|
await query<Omit<Aggregate<OptionContractKey>, "key">>(`
|
||||||
|
SELECT
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
high,
|
||||||
|
low
|
||||||
|
FROM option_contract_aggregates
|
||||||
|
WHERE symbol = '${symbol}'
|
||||||
|
AND type = '${type}'
|
||||||
|
AND strike = '${strike}'
|
||||||
|
AND expirationDate = '${expirationDate}'
|
||||||
|
AND tsStart = '${tsStartString}'
|
||||||
|
ORDER BY tsStart ASC
|
||||||
|
`)
|
||||||
|
).map((aggregate) => ({
|
||||||
|
...aggregate,
|
||||||
|
tsStart,
|
||||||
|
}))[0];
|
||||||
|
},
|
||||||
insertAggregates: async (aggregates) => {
|
insertAggregates: async (aggregates) => {
|
||||||
// stock existence is taken care of by clickhouse materialized view
|
// stock existence is taken care of by clickhouse materialized view
|
||||||
await clickhouse.insert({
|
await clickhouse.insert({
|
||||||
@@ -70,7 +95,7 @@ function makeOptionContractDatabase(): OptionContractDatabase {
|
|||||||
close,
|
close,
|
||||||
high,
|
high,
|
||||||
low,
|
low,
|
||||||
}),
|
})
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
+19
-2
@@ -1,6 +1,6 @@
|
|||||||
import type { StockDatabase, StockKey } from "./interfaces.js";
|
import type { StockDatabase, StockKey } from "./interfaces.js";
|
||||||
import type { Aggregate } from "../interfaces.js";
|
import type { Aggregate } from "../interfaces.js";
|
||||||
import { clickhouse, query } from "../lib/clickhouse.js";
|
import { clickhouse, query } from "../../lib/clickhouse.js";
|
||||||
|
|
||||||
function makeStockDatabase(): StockDatabase {
|
function makeStockDatabase(): StockDatabase {
|
||||||
const stockDatabase: Omit<StockDatabase, "getSymbols"> = {
|
const stockDatabase: Omit<StockDatabase, "getSymbols"> = {
|
||||||
@@ -31,6 +31,23 @@ function makeStockDatabase(): StockDatabase {
|
|||||||
tsStart: aggregate.tsStart * 1000, // unfortunately, `toUnixTimestamp` only returns second-precision
|
tsStart: aggregate.tsStart * 1000, // unfortunately, `toUnixTimestamp` only returns second-precision
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
getAggregate: async ({ key: { symbol }, tsStart }) => {
|
||||||
|
return (
|
||||||
|
await query<Omit<Aggregate<StockKey>, "key">>(`
|
||||||
|
SELECT
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
high,
|
||||||
|
low
|
||||||
|
FROM stock_aggregates
|
||||||
|
WHERE symbol = '${symbol}'
|
||||||
|
AND tsStart = '${tsStart}'
|
||||||
|
`)
|
||||||
|
).map((aggregate) => ({
|
||||||
|
...aggregate,
|
||||||
|
tsStart,
|
||||||
|
}))[0];
|
||||||
|
},
|
||||||
insertAggregates: async (aggregates) => {
|
insertAggregates: async (aggregates) => {
|
||||||
// stock existence is taken care of by clickhouse materialized view
|
// stock existence is taken care of by clickhouse materialized view
|
||||||
await clickhouse.insert({
|
await clickhouse.insert({
|
||||||
@@ -43,7 +60,7 @@ function makeStockDatabase(): StockDatabase {
|
|||||||
close,
|
close,
|
||||||
high,
|
high,
|
||||||
low,
|
low,
|
||||||
}),
|
})
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -45,6 +45,9 @@ function makeStockDatabase(): StockDatabase {
|
|||||||
low: value.low,
|
low: value.low,
|
||||||
})).asArray;
|
})).asArray;
|
||||||
},
|
},
|
||||||
|
getAggregate: async ({ key: { symbol }, tsStart }) => {
|
||||||
|
return stockAggregatesDb.get([symbol, tsStart]);
|
||||||
|
},
|
||||||
insertAggregates: async (aggregates) => {
|
insertAggregates: async (aggregates) => {
|
||||||
await stockExistenceDb.batch(() => {
|
await stockExistenceDb.batch(() => {
|
||||||
for (const aggregate of aggregates) {
|
for (const aggregate of aggregates) {
|
||||||
@@ -53,7 +56,7 @@ function makeStockDatabase(): StockDatabase {
|
|||||||
new Date(aggregate.tsStart).toISOString().substring(0, 10),
|
new Date(aggregate.tsStart).toISOString().substring(0, 10),
|
||||||
aggregate.key.symbol,
|
aggregate.key.symbol,
|
||||||
],
|
],
|
||||||
null,
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
+16
-6
@@ -1,6 +1,6 @@
|
|||||||
import { stockDatabase } from "./stockdb/lmdbx.js";
|
import { stockDatabase } from "./AggregateDatabase/Stock/lmdbx.js";
|
||||||
import { calendarDatabase } from "./calendardb/optiondb-lmdbx.js";
|
import { calendarDatabase } from "./AggregateDatabase/Calendar/optiondb-lmdbx.js";
|
||||||
import type { CalendarKey } from "./calendardb/interfaces.js";
|
import type { CalendarKey } from "./AggregateDatabase/Calendar/interfaces.js";
|
||||||
import { nextDate } from "./lib/utils/nextDate.js";
|
import { nextDate } from "./lib/utils/nextDate.js";
|
||||||
|
|
||||||
type BacktestInput = {
|
type BacktestInput = {
|
||||||
@@ -71,9 +71,9 @@ export async function backtest({
|
|||||||
});
|
});
|
||||||
const calendarAggregateAtCurrentTime =
|
const calendarAggregateAtCurrentTime =
|
||||||
await calendarDatabase.getAggregate({
|
await calendarDatabase.getAggregate({
|
||||||
key: {
|
key: {
|
||||||
...calendar,
|
...calendar,
|
||||||
},
|
},
|
||||||
tsStart: stockAggregate.tsStart,
|
tsStart: stockAggregate.tsStart,
|
||||||
});
|
});
|
||||||
// if there exists a matching calendar candlestick for the current minute:
|
// if there exists a matching calendar candlestick for the current minute:
|
||||||
@@ -141,3 +141,13 @@ export async function backtest({
|
|||||||
console.log("Ending Buying Power:", buyingPower);
|
console.log("Ending Buying Power:", buyingPower);
|
||||||
console.log("Portfolio:", portfolio.values());
|
console.log("Portfolio:", portfolio.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length > 0) {
|
||||||
|
if (args.length < 3) {
|
||||||
|
console.error("Please provide at least 3 command line arguments.");
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
await backtest({ symbol: args[0], startDate: args[1], endDate: args[2] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import type { AggregateDatabase } from "../interfaces.js";
|
|
||||||
|
|
||||||
export type CalendarKey = {
|
|
||||||
symbol: string;
|
|
||||||
type: "call" | "put";
|
|
||||||
strike: number;
|
|
||||||
frontExpirationDate: string;
|
|
||||||
backExpirationDate: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CalendarDatabase = AggregateDatabase<CalendarKey> & {
|
|
||||||
getCalendars: AggregateDatabase<CalendarKey>["getKeys"];
|
|
||||||
getTargetPriceByProbability: ({
|
|
||||||
symbol,
|
|
||||||
calendarSpan,
|
|
||||||
strikePercentageFromTheMoney,
|
|
||||||
historicalProbabilityOfSuccess,
|
|
||||||
}: {
|
|
||||||
symbol: string;
|
|
||||||
calendarSpan: number;
|
|
||||||
strikePercentageFromTheMoney: number;
|
|
||||||
historicalProbabilityOfSuccess: number;
|
|
||||||
}) => Promise<number>;
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { AggregateDatabase } from "../interfaces.js";
|
import type { AggregateDatabase } from "../AggregateDatabase/interfaces.js";
|
||||||
import { stockDatabase as stockDatabaseClickhouse } from "../stockdb/clickhouse.js";
|
import { stockDatabase as stockDatabaseClickhouse } from "../stockdb/clickhouse.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";
|
||||||
@@ -20,7 +20,7 @@ async function syncAggregates<T>({
|
|||||||
date: string;
|
date: string;
|
||||||
}) {
|
}) {
|
||||||
const aggregatesFrom = (await fromDatabase.getAggregates({ key, date })).map(
|
const aggregatesFrom = (await fromDatabase.getAggregates({ key, date })).map(
|
||||||
(aggregateWithoutKey) => ({ ...aggregateWithoutKey, key }),
|
(aggregateWithoutKey) => ({ ...aggregateWithoutKey, key })
|
||||||
);
|
);
|
||||||
await toDatabase.insertAggregates(aggregatesFrom);
|
await toDatabase.insertAggregates(aggregatesFrom);
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,10 @@ const symbols = ["AMD", "AAPL", "MSFT", "GOOGL", "NFLX", "NVDA"];
|
|||||||
async function run<T extends StockKey | OptionContractKey>({
|
async function run<T extends StockKey | OptionContractKey>({
|
||||||
fromDatabase,
|
fromDatabase,
|
||||||
toDatabase,
|
toDatabase,
|
||||||
}: { fromDatabase: AggregateDatabase<T>; toDatabase: AggregateDatabase<T> }) {
|
}: {
|
||||||
|
fromDatabase: AggregateDatabase<T>;
|
||||||
|
toDatabase: AggregateDatabase<T>;
|
||||||
|
}) {
|
||||||
const startDate = process.argv[2];
|
const startDate = process.argv[2];
|
||||||
const endDate = process.argv[3];
|
const endDate = process.argv[3];
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@ async function run<T extends StockKey | OptionContractKey>({
|
|||||||
key: { symbol } as T,
|
key: { symbol } as T,
|
||||||
date,
|
date,
|
||||||
}),
|
}),
|
||||||
{ shouldRetry: retryOnTimeout },
|
{ shouldRetry: retryOnTimeout }
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
@@ -61,7 +64,7 @@ async function run<T extends StockKey | OptionContractKey>({
|
|||||||
key,
|
key,
|
||||||
date,
|
date,
|
||||||
}),
|
}),
|
||||||
{ shouldRetry: retryOnTimeout },
|
{ shouldRetry: retryOnTimeout }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user