fix biome kvetches
This commit is contained in:
+134
-134
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user