|
|
|
@ -1,7 +1,6 @@
|
|
|
|
|
import { stockDatabase } from "./stockdb/clickhouse.js";
|
|
|
|
|
import { stockDatabase } from "./stockdb/lmdbx.js";
|
|
|
|
|
import { calendarDatabase } from "./calendardb/optiondb-lmdbx.js";
|
|
|
|
|
import type { CalendarKey } from "./calendardb/interfaces.js";
|
|
|
|
|
import type { Aggregate } from "./interfaces.js";
|
|
|
|
|
import { nextDate } from "./lib/utils/nextDate.js";
|
|
|
|
|
|
|
|
|
|
type BacktestInput = {
|
|
|
|
@ -38,49 +37,51 @@ export async function backtest({
|
|
|
|
|
// for each minute of that day for which we have a stock candlestick:
|
|
|
|
|
for (const stockAggregate of stockAggregates) {
|
|
|
|
|
// console.log("Current Time:", new Date(stockAggregate.tsStart));
|
|
|
|
|
// filter-out calendars that are far-from-the-money (10%)
|
|
|
|
|
console.log("Current Date:", date, stockAggregate.tsStart);
|
|
|
|
|
// filter-out calendars that are far-from-the-money (5%)
|
|
|
|
|
const calendarsNearTheMoney = calendars.filter(
|
|
|
|
|
({ strike }) =>
|
|
|
|
|
Math.abs((stockAggregate.open - strike) / stockAggregate.open) < 0.1,
|
|
|
|
|
Math.abs((stockAggregate.open - strike) / stockAggregate.open) < 0.05
|
|
|
|
|
);
|
|
|
|
|
console.log(
|
|
|
|
|
"Current Date:",
|
|
|
|
|
new Intl.DateTimeFormat("en-US", {
|
|
|
|
|
timeZone: "America/New_York",
|
|
|
|
|
dateStyle: "full",
|
|
|
|
|
timeStyle: "long",
|
|
|
|
|
}).format(new Date(stockAggregate.tsStart)),
|
|
|
|
|
";",
|
|
|
|
|
`${calendarsNearTheMoney.length} Calendars Near The Money`
|
|
|
|
|
);
|
|
|
|
|
// for each relevant calendar on that day:
|
|
|
|
|
for (const calendar of calendarsNearTheMoney) {
|
|
|
|
|
const strikePercentageFromTheMoney = Math.abs(
|
|
|
|
|
(stockAggregate.open - calendar.strike) / stockAggregate.open,
|
|
|
|
|
(stockAggregate.open - calendar.strike) / stockAggregate.open
|
|
|
|
|
);
|
|
|
|
|
/** In days. */
|
|
|
|
|
const calendarSpan =
|
|
|
|
|
/** Number of days between the back and front expiration dates. */
|
|
|
|
|
const calendarSpanInDays =
|
|
|
|
|
(new Date(calendar.backExpirationDate).valueOf() -
|
|
|
|
|
new Date(calendar.frontExpirationDate).valueOf()) /
|
|
|
|
|
(1000 * 60 * 60 * 24);
|
|
|
|
|
const targetCalendarPrice =
|
|
|
|
|
await calendarDatabase.getTargetPriceByProbability({
|
|
|
|
|
symbol,
|
|
|
|
|
calendarSpan,
|
|
|
|
|
calendarSpan: calendarSpanInDays,
|
|
|
|
|
strikePercentageFromTheMoney,
|
|
|
|
|
historicalProbabilityOfSuccess,
|
|
|
|
|
});
|
|
|
|
|
const calendarAggregates = calendarDatabase.getAggregatesSync({
|
|
|
|
|
const calendarAggregateAtCurrentTime =
|
|
|
|
|
await calendarDatabase.getAggregate({
|
|
|
|
|
key: {
|
|
|
|
|
...calendar,
|
|
|
|
|
},
|
|
|
|
|
date,
|
|
|
|
|
tsStart: stockAggregate.tsStart,
|
|
|
|
|
});
|
|
|
|
|
// console.log(
|
|
|
|
|
// "Calendar Aggregates:",
|
|
|
|
|
// calendar,
|
|
|
|
|
// calendarAggregates.length,
|
|
|
|
|
// );
|
|
|
|
|
const calendarAggregateAtCurrentTime = calendarAggregates.find(
|
|
|
|
|
({ tsStart }) => tsStart === stockAggregate.tsStart,
|
|
|
|
|
);
|
|
|
|
|
// if there exists a matching calendar candlestick for the current minute:
|
|
|
|
|
if (calendarAggregateAtCurrentTime) {
|
|
|
|
|
// if the current candlestick is a good price (i.e. less than the target price):
|
|
|
|
|
const minCalendarPriceInCandlestick = Math.min(
|
|
|
|
|
calendarAggregateAtCurrentTime.open,
|
|
|
|
|
calendarAggregateAtCurrentTime.close,
|
|
|
|
|
calendarAggregateAtCurrentTime.close
|
|
|
|
|
);
|
|
|
|
|
if (
|
|
|
|
|
minCalendarPriceInCandlestick < targetCalendarPrice &&
|
|
|
|
@ -99,7 +100,7 @@ export async function backtest({
|
|
|
|
|
minCalendarPriceInCandlestick * 100,
|
|
|
|
|
"...$",
|
|
|
|
|
buyingPower,
|
|
|
|
|
"left",
|
|
|
|
|
"left"
|
|
|
|
|
);
|
|
|
|
|
didBuyCalendar = true;
|
|
|
|
|
}
|
|
|
|
@ -131,7 +132,7 @@ export async function backtest({
|
|
|
|
|
calendarClosingPrice,
|
|
|
|
|
"...$",
|
|
|
|
|
buyingPower,
|
|
|
|
|
"left",
|
|
|
|
|
"left"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|