fix biome kvetches

main
avraham 9 months ago
parent 39bb6c85f8
commit eba5344b15

@ -5,143 +5,143 @@ import type { Aggregate } from "./interfaces.js";
import { nextDate } from "./lib/util.js"; import { nextDate } from "./lib/util.js";
type BacktestInput = { type BacktestInput = {
symbol: string; symbol: string;
startDate: string; startDate: string;
endDate: string; endDate: string;
/** Between 0 and 1. The frequency that similar calendars have historically ended (i.e. within the last hour) at a higher price than the current calendar's price. */ /** Between 0 and 1. The frequency that similar calendars have historically ended (i.e. within the last hour) at a higher price than the current calendar's price. */
historicalProbabilityOfSuccess?: number; historicalProbabilityOfSuccess?: number;
initialAvailableValue?: number; initialAvailableValue?: number;
}; };
export async function backtest({ export async function backtest({
symbol, symbol,
startDate, startDate,
endDate, endDate,
historicalProbabilityOfSuccess = 0.8, historicalProbabilityOfSuccess = 0.8,
initialAvailableValue: initialBuyingPower = 2000, initialAvailableValue: initialBuyingPower = 2000,
}: BacktestInput) { }: BacktestInput) {
let buyingPower = initialBuyingPower; let buyingPower = initialBuyingPower;
const portfolio = new Set<CalendarKey>(); const portfolio = new Set<CalendarKey>();
// for each day: // for each day:
for ( for (
let date = startDate, didBuyCalendar = false; let date = startDate, didBuyCalendar = false;
date <= endDate; date <= endDate;
date = nextDate(date), didBuyCalendar = false date = nextDate(date), didBuyCalendar = false
) { ) {
console.log("Current Date:", date); console.log("Current Date:", date);
const calendars = await calendarDatabase.getCalendars({ const calendars = await calendarDatabase.getCalendars({
key: { symbol }, key: { symbol },
date, date,
}); });
const stockAggregates = await stockDatabase.getAggregates({ const stockAggregates = await stockDatabase.getAggregates({
key: symbol, key: symbol,
date, date,
}); });
const calendarsAggregates = new Map< const calendarsAggregates = new Map<
CalendarKey, CalendarKey,
Array<Pick<Aggregate<CalendarKey>, "tsStart" | "open" | "close">> Array<Pick<Aggregate<CalendarKey>, "tsStart" | "open" | "close">>
>(); >();
for (const calendar of calendars) { for (const calendar of calendars) {
calendarsAggregates.set( calendarsAggregates.set(
calendar, calendar,
await calendarDatabase.getAggregates({ await calendarDatabase.getAggregates({
key: { key: {
...calendar, ...calendar,
}, },
date, date,
}), })
); );
} }
// 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 }) =>
Math.abs((stockAggregate.open - strike) / stockAggregate.open) < 0.1, Math.abs((stockAggregate.open - strike) / stockAggregate.open) < 0.1
); );
// for each relevant calendar on that day: // for each relevant calendar on that day:
for (const calendar of calendarsNearTheMoney) { for (const calendar of calendarsNearTheMoney) {
const strikePercentageFromTheMoney = Math.abs( const strikePercentageFromTheMoney = Math.abs(
(stockAggregate.open - calendar.strike) / stockAggregate.open, (stockAggregate.open - calendar.strike) / stockAggregate.open
); );
/** In days. */ /** In days. */
const calendarSpan = const calendarSpan =
(new Date(calendar.backExpirationDate).valueOf() - (new Date(calendar.backExpirationDate).valueOf() -
new Date(calendar.frontExpirationDate).valueOf()) / new Date(calendar.frontExpirationDate).valueOf()) /
(1000 * 60 * 60 * 24); (1000 * 60 * 60 * 24);
const targetCalendarPrice = const targetCalendarPrice =
await calendarDatabase.getTargetPriceByProbability({ await calendarDatabase.getTargetPriceByProbability({
symbol, symbol,
calendarSpan, calendarSpan,
strikePercentageFromTheMoney, strikePercentageFromTheMoney,
historicalProbabilityOfSuccess, historicalProbabilityOfSuccess,
}); });
const calendarAggregates = calendarsAggregates.get(calendar); const calendarAggregates = calendarsAggregates.get(calendar);
const calendarAggregateAtCurrentTime = calendarAggregates.find( const calendarAggregateAtCurrentTime = calendarAggregates.find(
({ tsStart }) => tsStart === stockAggregate.tsStart, ({ 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:
if (calendarAggregateAtCurrentTime) { if (calendarAggregateAtCurrentTime) {
// if the current candlestick is a good price (i.e. less than the target price): // if the current candlestick is a good price (i.e. less than the target price):
const minCalendarPriceInCandlestick = Math.min( const minCalendarPriceInCandlestick = Math.min(
calendarAggregateAtCurrentTime.open, calendarAggregateAtCurrentTime.open,
calendarAggregateAtCurrentTime.close, calendarAggregateAtCurrentTime.close
); );
if ( if (
minCalendarPriceInCandlestick < targetCalendarPrice && minCalendarPriceInCandlestick < targetCalendarPrice &&
minCalendarPriceInCandlestick > minCalendarPriceInCandlestick >
0.07 /* sometimes the calendar price is zero or negative, which is of course impossible; some institution got a good deal */ 0.07 /* sometimes the calendar price is zero or negative, which is of course impossible; some institution got a good deal */
) { ) {
// if we can afford to buy the calendar: // if we can afford to buy the calendar:
if (buyingPower > minCalendarPriceInCandlestick) { if (buyingPower > minCalendarPriceInCandlestick) {
// buy the calendar, and continue to the next day: // buy the calendar, and continue to the next day:
portfolio.add(calendar); portfolio.add(calendar);
buyingPower = buyingPower - minCalendarPriceInCandlestick * 100; buyingPower = buyingPower - minCalendarPriceInCandlestick * 100;
console.log( console.log(
"Bought", "Bought",
calendar, calendar,
"for", "for",
minCalendarPriceInCandlestick * 100, minCalendarPriceInCandlestick * 100,
"...$", "...$",
buyingPower, buyingPower,
"left", "left"
); );
didBuyCalendar = true; didBuyCalendar = true;
} }
} }
} }
if (didBuyCalendar) { if (didBuyCalendar) {
break; break;
} }
} }
if (didBuyCalendar) { if (didBuyCalendar) {
break; break;
} }
} }
// for each calendar in portfolio, if today is the last day, close the position: // for each calendar in portfolio, if today is the last day, close the position:
for (const calendar of portfolio.values()) { for (const calendar of portfolio.values()) {
if (calendar.frontExpirationDate === date) { if (calendar.frontExpirationDate === date) {
const calendarClosingPrice = await calendarDatabase.getClosingPrice({ const calendarClosingPrice = await calendarDatabase.getClosingPrice({
key: { key: {
...calendar, ...calendar,
}, },
}); });
portfolio.delete(calendar); portfolio.delete(calendar);
buyingPower = buyingPower + calendarClosingPrice * 100; buyingPower = buyingPower + calendarClosingPrice * 100;
console.log( console.log(
"Sold", "Sold",
calendar, calendar,
"for", "for",
calendarClosingPrice, calendarClosingPrice,
"...$", "...$",
buyingPower, buyingPower,
"left", "left"
); );
} }
} }
} }
console.log("Ending Buying Power:", buyingPower); console.log("Ending Buying Power:", buyingPower);
console.log("Portfolio:", portfolio.values()); console.log("Portfolio:", portfolio.values());
} }

@ -5,151 +5,151 @@ import type { CalendarDatabase } from "./calendardb.interfaces.js";
const MAXIMUM_KEY = Buffer.from([0xff]); const MAXIMUM_KEY = Buffer.from([0xff]);
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 optionContracts = await optionContractDatabase.getOptionContracts({ const optionContracts = await optionContractDatabase.getOptionContracts({
date, date,
key: { symbol }, key: { symbol },
}); });
return optionContracts.flatMap( return optionContracts.flatMap(
(frontOptionContract, i, optionContracts) => (frontOptionContract, i, optionContracts) =>
optionContracts optionContracts
.filter((_, j) => i !== j) .filter((_, j) => i !== j)
.map((backOptionContract) => ({ .map((backOptionContract) => ({
symbol, symbol,
frontExpirationDate: frontOptionContract.expirationDate, frontExpirationDate: frontOptionContract.expirationDate,
backExpirationDate: backOptionContract.expirationDate, backExpirationDate: backOptionContract.expirationDate,
strike: frontOptionContract.strike, strike: frontOptionContract.strike,
type: frontOptionContract.type, type: frontOptionContract.type,
})), })),
); );
}, },
getAggregates: async ({ getAggregates: async ({
key: { symbol, frontExpirationDate, backExpirationDate, strike, type }, key: { symbol, frontExpirationDate, backExpirationDate, strike, type },
date, date,
}) => { }) => {
const frontOptionContractAggregates = const frontOptionContractAggregates =
await optionContractDatabase.getAggregates({ await optionContractDatabase.getAggregates({
date, date,
key: { symbol, expirationDate: frontExpirationDate, strike, type }, key: { symbol, expirationDate: frontExpirationDate, strike, type },
}); });
const backOptionContractAggregates = const backOptionContractAggregates =
await optionContractDatabase.getAggregates({ await optionContractDatabase.getAggregates({
date, date,
key: { symbol, expirationDate: backExpirationDate, strike, type }, key: { symbol, expirationDate: backExpirationDate, strike, type },
}); });
const calendarAggregates = []; const calendarAggregates = [];
let i = 0; let i = 0;
let j = 0; let j = 0;
while ( while (
i < frontOptionContractAggregates.length && i < frontOptionContractAggregates.length &&
j < backOptionContractAggregates.length j < backOptionContractAggregates.length
) { ) {
if ( if (
frontOptionContractAggregates[i].tsStart === frontOptionContractAggregates[i].tsStart ===
backOptionContractAggregates[j].tsStart backOptionContractAggregates[j].tsStart
) { ) {
calendarAggregates.push({ calendarAggregates.push({
tsStart: frontOptionContractAggregates[i].tsStart, tsStart: frontOptionContractAggregates[i].tsStart,
open: open:
backOptionContractAggregates[j].open - backOptionContractAggregates[j].open -
frontOptionContractAggregates[i].open, frontOptionContractAggregates[i].open,
close: close:
backOptionContractAggregates[j].close - backOptionContractAggregates[j].close -
frontOptionContractAggregates[i].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: // the high and low are not exactly correct since we don't know if each contract's high and low happened at the same moment as the other:
high: high:
backOptionContractAggregates[j].high - backOptionContractAggregates[j].high -
frontOptionContractAggregates[i].high, frontOptionContractAggregates[i].high,
low: low:
backOptionContractAggregates[j].low - backOptionContractAggregates[j].low -
frontOptionContractAggregates[i].low, frontOptionContractAggregates[i].low,
}); });
i++; i++;
j++; j++;
} else if ( } else if (
frontOptionContractAggregates[i].tsStart > frontOptionContractAggregates[i].tsStart >
backOptionContractAggregates[j].tsStart backOptionContractAggregates[j].tsStart
) { ) {
j++; j++;
} else { } else {
i++; i++;
} }
} }
return calendarAggregates; return calendarAggregates;
}, },
insertAggregates: async (aggregates) => { insertAggregates: async (aggregates) => {
// right now, no-op // right now, no-op
}, },
getClosingPrice: async ({ getClosingPrice: async ({
key: { symbol, strike, type, frontExpirationDate, backExpirationDate }, key: { symbol, strike, type, frontExpirationDate, backExpirationDate },
}) => { }) => {
const startOfLastHourUnix = new Date( const startOfLastHourUnix = new Date(
`${frontExpirationDate}T00:00:00Z`, `${frontExpirationDate}T00:00:00Z`,
).valueOf(); ).valueOf();
const endOfLastHourUnix = startOfLastHourUnix + 3600 * 1000; const endOfLastHourUnix = startOfLastHourUnix + 3600 * 1000;
const frontOptionContractAggregates = ( const frontOptionContractAggregates = (
await optionContractDatabase.getAggregates({ await optionContractDatabase.getAggregates({
date: frontExpirationDate, date: frontExpirationDate,
key: { symbol, expirationDate: frontExpirationDate, strike, type }, key: { symbol, expirationDate: frontExpirationDate, strike, type },
}) })
).filter( ).filter(
({ tsStart }) => ({ tsStart }) =>
tsStart >= startOfLastHourUnix && tsStart < endOfLastHourUnix, tsStart >= startOfLastHourUnix && tsStart < endOfLastHourUnix,
); );
const backOptionContractAggregates = ( const backOptionContractAggregates = (
await optionContractDatabase.getAggregates({ await optionContractDatabase.getAggregates({
date: frontExpirationDate, date: frontExpirationDate,
key: { symbol, expirationDate: backExpirationDate, strike, type }, key: { symbol, expirationDate: backExpirationDate, strike, type },
}) })
).filter( ).filter(
({ tsStart }) => ({ tsStart }) =>
tsStart >= startOfLastHourUnix && tsStart < endOfLastHourUnix, tsStart >= startOfLastHourUnix && tsStart < endOfLastHourUnix,
); );
let i = 0; let i = 0;
let j = 0; let j = 0;
let minPrice = 0; let minPrice = 0;
while ( while (
i < frontOptionContractAggregates.length && i < frontOptionContractAggregates.length &&
j < backOptionContractAggregates.length j < backOptionContractAggregates.length
) { ) {
if ( if (
frontOptionContractAggregates[i].tsStart === frontOptionContractAggregates[i].tsStart ===
backOptionContractAggregates[j].tsStart backOptionContractAggregates[j].tsStart
) { ) {
const calendarClosePrice = const calendarClosePrice =
backOptionContractAggregates[j].close - backOptionContractAggregates[j].close -
frontOptionContractAggregates[j].close; frontOptionContractAggregates[j].close;
if (calendarClosePrice < minPrice || minPrice === 0) { if (calendarClosePrice < minPrice || minPrice === 0) {
minPrice = calendarClosePrice; minPrice = calendarClosePrice;
} }
i++; i++;
j++; j++;
} else if ( } else if (
frontOptionContractAggregates[i].tsStart > frontOptionContractAggregates[i].tsStart >
backOptionContractAggregates[j].tsStart backOptionContractAggregates[j].tsStart
) { ) {
j++; j++;
} else { } else {
i++; i++;
} }
} }
return minPrice; return minPrice;
}, },
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();

@ -17,7 +17,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);
} }

Loading…
Cancel
Save