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";
type BacktestInput = {
symbol: string;
startDate: 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. */
historicalProbabilityOfSuccess?: number;
initialAvailableValue?: number;
symbol: string;
startDate: 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. */
historicalProbabilityOfSuccess?: number;
initialAvailableValue?: number;
};
export async function backtest({
symbol,
startDate,
endDate,
historicalProbabilityOfSuccess = 0.8,
initialAvailableValue: initialBuyingPower = 2000,
symbol,
startDate,
endDate,
historicalProbabilityOfSuccess = 0.8,
initialAvailableValue: initialBuyingPower = 2000,
}: BacktestInput) {
let buyingPower = initialBuyingPower;
const portfolio = new Set<CalendarKey>();
// for each day:
for (
let date = startDate, didBuyCalendar = false;
date <= endDate;
date = nextDate(date), didBuyCalendar = false
) {
console.log("Current Date:", date);
const calendars = await calendarDatabase.getCalendars({
key: { symbol },
date,
});
const stockAggregates = await stockDatabase.getAggregates({
key: symbol,
date,
});
const calendarsAggregates = new Map<
CalendarKey,
Array<Pick<Aggregate<CalendarKey>, "tsStart" | "open" | "close">>
>();
for (const calendar of calendars) {
calendarsAggregates.set(
calendar,
await calendarDatabase.getAggregates({
key: {
...calendar,
},
date,
}),
);
}
// 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%)
const calendarsNearTheMoney = calendars.filter(
({ strike }) =>
Math.abs((stockAggregate.open - strike) / stockAggregate.open) < 0.1,
);
// for each relevant calendar on that day:
for (const calendar of calendarsNearTheMoney) {
const strikePercentageFromTheMoney = Math.abs(
(stockAggregate.open - calendar.strike) / stockAggregate.open,
);
/** In days. */
const calendarSpan =
(new Date(calendar.backExpirationDate).valueOf() -
new Date(calendar.frontExpirationDate).valueOf()) /
(1000 * 60 * 60 * 24);
const targetCalendarPrice =
await calendarDatabase.getTargetPriceByProbability({
symbol,
calendarSpan,
strikePercentageFromTheMoney,
historicalProbabilityOfSuccess,
});
const calendarAggregates = calendarsAggregates.get(calendar);
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,
);
if (
minCalendarPriceInCandlestick < targetCalendarPrice &&
minCalendarPriceInCandlestick >
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 (buyingPower > minCalendarPriceInCandlestick) {
// buy the calendar, and continue to the next day:
portfolio.add(calendar);
buyingPower = buyingPower - minCalendarPriceInCandlestick * 100;
console.log(
"Bought",
calendar,
"for",
minCalendarPriceInCandlestick * 100,
"...$",
buyingPower,
"left",
);
didBuyCalendar = true;
}
}
}
if (didBuyCalendar) {
break;
}
}
if (didBuyCalendar) {
break;
}
}
let buyingPower = initialBuyingPower;
const portfolio = new Set<CalendarKey>();
// for each day:
for (
let date = startDate, didBuyCalendar = false;
date <= endDate;
date = nextDate(date), didBuyCalendar = false
) {
console.log("Current Date:", date);
const calendars = await calendarDatabase.getCalendars({
key: { symbol },
date,
});
const stockAggregates = await stockDatabase.getAggregates({
key: symbol,
date,
});
const calendarsAggregates = new Map<
CalendarKey,
Array<Pick<Aggregate<CalendarKey>, "tsStart" | "open" | "close">>
>();
for (const calendar of calendars) {
calendarsAggregates.set(
calendar,
await calendarDatabase.getAggregates({
key: {
...calendar,
},
date,
})
);
}
// 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%)
const calendarsNearTheMoney = calendars.filter(
({ strike }) =>
Math.abs((stockAggregate.open - strike) / stockAggregate.open) < 0.1
);
// for each relevant calendar on that day:
for (const calendar of calendarsNearTheMoney) {
const strikePercentageFromTheMoney = Math.abs(
(stockAggregate.open - calendar.strike) / stockAggregate.open
);
/** In days. */
const calendarSpan =
(new Date(calendar.backExpirationDate).valueOf() -
new Date(calendar.frontExpirationDate).valueOf()) /
(1000 * 60 * 60 * 24);
const targetCalendarPrice =
await calendarDatabase.getTargetPriceByProbability({
symbol,
calendarSpan,
strikePercentageFromTheMoney,
historicalProbabilityOfSuccess,
});
const calendarAggregates = calendarsAggregates.get(calendar);
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
);
if (
minCalendarPriceInCandlestick < targetCalendarPrice &&
minCalendarPriceInCandlestick >
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 (buyingPower > minCalendarPriceInCandlestick) {
// buy the calendar, and continue to the next day:
portfolio.add(calendar);
buyingPower = buyingPower - minCalendarPriceInCandlestick * 100;
console.log(
"Bought",
calendar,
"for",
minCalendarPriceInCandlestick * 100,
"...$",
buyingPower,
"left"
);
didBuyCalendar = true;
}
}
}
if (didBuyCalendar) {
break;
}
}
if (didBuyCalendar) {
break;
}
}
// for each calendar in portfolio, if today is the last day, close the position:
for (const calendar of portfolio.values()) {
if (calendar.frontExpirationDate === date) {
const calendarClosingPrice = await calendarDatabase.getClosingPrice({
key: {
...calendar,
},
});
portfolio.delete(calendar);
buyingPower = buyingPower + calendarClosingPrice * 100;
console.log(
"Sold",
calendar,
"for",
calendarClosingPrice,
"...$",
buyingPower,
"left",
);
}
}
}
// for each calendar in portfolio, if today is the last day, close the position:
for (const calendar of portfolio.values()) {
if (calendar.frontExpirationDate === date) {
const calendarClosingPrice = await calendarDatabase.getClosingPrice({
key: {
...calendar,
},
});
portfolio.delete(calendar);
buyingPower = buyingPower + calendarClosingPrice * 100;
console.log(
"Sold",
calendar,
"for",
calendarClosingPrice,
"...$",
buyingPower,
"left"
);
}
}
}
console.log("Ending Buying Power:", buyingPower);
console.log("Portfolio:", portfolio.values());
console.log("Ending Buying Power:", buyingPower);
console.log("Portfolio:", portfolio.values());
}

@ -5,151 +5,151 @@ import type { CalendarDatabase } from "./calendardb.interfaces.js";
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;
},
};
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 at 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,
};
return {
...calendarDatabase,
getCalendars: calendarDatabase.getKeys,
};
}
export const calendarDatabase: CalendarDatabase = makeCalendarDatabase();

@ -17,7 +17,7 @@ async function syncAggregates<T>({
date: string;
}) {
const aggregatesFrom = (await fromDatabase.getAggregates({ key, date })).map(
(aggregateWithoutKey) => ({ ...aggregateWithoutKey, key }),
(aggregateWithoutKey) => ({ ...aggregateWithoutKey, key })
);
await toDatabase.insertAggregates(aggregatesFrom);
}

Loading…
Cancel
Save