Compare commits
2 Commits
bf094de461
...
eba5344b15
| Author | SHA1 | Date | |
|---|---|---|---|
| eba5344b15 | |||
| 39bb6c85f8 |
@@ -86,7 +86,7 @@ function chooseStrike(strike: string) {
|
||||
.query({
|
||||
underlying: chosenUnderlying.value,
|
||||
expirationDate: chosenExpiration.value,
|
||||
strike: parseFloat(strike),
|
||||
strike: Number.parseFloat(strike),
|
||||
})
|
||||
.then((getOpensForOptionContractResponse) => {
|
||||
optionContractUplotData.value = getOpensForOptionContractResponse;
|
||||
@@ -201,8 +201,9 @@ export function CalendarOptimizer() {
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3, height: '100%' }}>
|
||||
{chosenUnderlying.value !== null && underlyingUplotData.value.length > 0 ? (
|
||||
<Paper elevation={3} sx={{ p: 3, height: "100%" }}>
|
||||
{chosenUnderlying.value !== null &&
|
||||
underlyingUplotData.value.length > 0 ? (
|
||||
<Scatter
|
||||
data={{
|
||||
datasets: [
|
||||
@@ -220,7 +221,7 @@ export function CalendarOptimizer() {
|
||||
text: "Time",
|
||||
},
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
callback: (value, index, ticks) => {
|
||||
return new Date((value as number) * 1000)
|
||||
.toISOString()
|
||||
.substring(0, 10);
|
||||
@@ -230,8 +231,8 @@ export function CalendarOptimizer() {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
return "$" + value.toString();
|
||||
callback: (value, index, ticks) => {
|
||||
return `$${value.toString()}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -287,7 +288,7 @@ export function CalendarOptimizer() {
|
||||
text: "Time",
|
||||
},
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
callback: (value, index, ticks) => {
|
||||
return new Date((value as number) * 1000)
|
||||
.toISOString()
|
||||
.substring(0, 10);
|
||||
@@ -297,8 +298,8 @@ export function CalendarOptimizer() {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
return "$" + value.toString();
|
||||
callback: (value, index, ticks) => {
|
||||
return `$${value.toString()}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -333,4 +334,4 @@ export function CalendarOptimizer() {
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,20 +49,20 @@ const maxChartPrice = computed(() =>
|
||||
Math.max(
|
||||
Math.max.apply(
|
||||
null,
|
||||
historicalCalendarQuoteChartData.value.map((d) => d.y)
|
||||
historicalCalendarQuoteChartData.value.map((d) => d.y),
|
||||
),
|
||||
Math.max.apply(
|
||||
null,
|
||||
historicalCalendarExitQuoteChartData.value.map((d) => d.y)
|
||||
)
|
||||
)
|
||||
historicalCalendarExitQuoteChartData.value.map((d) => d.y),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const maxN = computed(() =>
|
||||
Math.max.apply(
|
||||
null,
|
||||
historicalCalendarExitQuoteChartData.value.map((d) => d.n)
|
||||
)
|
||||
historicalCalendarExitQuoteChartData.value.map((d) => d.n),
|
||||
),
|
||||
);
|
||||
|
||||
const refreshHistoricalStockQuoteChartData = () => {
|
||||
@@ -134,16 +134,19 @@ const handleUnderlyingChange = (e) => {
|
||||
}
|
||||
};
|
||||
const handleDaysToFrontExpirationChange = (e) => {
|
||||
if (chosenDaysToFrontExpiration.value !== parseInt(e.target.value)) {
|
||||
chosenDaysToFrontExpiration.value = parseInt(e.target.value);
|
||||
if (chosenDaysToFrontExpiration.value !== Number.parseInt(e.target.value)) {
|
||||
chosenDaysToFrontExpiration.value = Number.parseInt(e.target.value);
|
||||
refreshHistoricalCalendarQuoteChartData();
|
||||
}
|
||||
};
|
||||
const handleDaysBetweenFrontAndBackExpirationChange = (e) => {
|
||||
if (
|
||||
chosenDaysBetweenFrontAndBackExpiration.value !== parseInt(e.target.value)
|
||||
chosenDaysBetweenFrontAndBackExpiration.value !==
|
||||
Number.parseInt(e.target.value)
|
||||
) {
|
||||
chosenDaysBetweenFrontAndBackExpiration.value = parseInt(e.target.value);
|
||||
chosenDaysBetweenFrontAndBackExpiration.value = Number.parseInt(
|
||||
e.target.value,
|
||||
);
|
||||
refreshHistoricalCalendarQuoteChartData();
|
||||
refreshHistoricalCalendarExitQuoteChartData();
|
||||
}
|
||||
@@ -151,10 +154,10 @@ const handleDaysBetweenFrontAndBackExpirationChange = (e) => {
|
||||
const handleStrikePercentageFromUnderlyingPriceChange = (e) => {
|
||||
if (
|
||||
chosenStrikePercentageFromUnderlyingPrice.value !==
|
||||
parseFloat(e.target.value)
|
||||
Number.parseFloat(e.target.value)
|
||||
) {
|
||||
chosenStrikePercentageFromUnderlyingPrice.value = parseFloat(
|
||||
e.target.value
|
||||
chosenStrikePercentageFromUnderlyingPrice.value = Number.parseFloat(
|
||||
e.target.value,
|
||||
);
|
||||
refreshHistoricalCalendarQuoteChartData();
|
||||
}
|
||||
@@ -162,17 +165,17 @@ const handleStrikePercentageFromUnderlyingPriceChange = (e) => {
|
||||
const handleStrikePercentageFromUnderlyingPriceRadiusChange = (e) => {
|
||||
if (
|
||||
chosenStrikePercentageFromUnderlyingPriceRadius.value !==
|
||||
parseFloat(e.target.value)
|
||||
Number.parseFloat(e.target.value)
|
||||
) {
|
||||
chosenStrikePercentageFromUnderlyingPriceRadius.value = parseFloat(
|
||||
e.target.value
|
||||
chosenStrikePercentageFromUnderlyingPriceRadius.value = Number.parseFloat(
|
||||
e.target.value,
|
||||
);
|
||||
refreshHistoricalCalendarQuoteChartData();
|
||||
}
|
||||
};
|
||||
const handleExitToFrontExpirationChange = (e) => {
|
||||
if (chosenExitToFrontExpiration.value !== parseInt(e.target.value)) {
|
||||
chosenExitToFrontExpiration.value = parseInt(e.target.value);
|
||||
if (chosenExitToFrontExpiration.value !== Number.parseInt(e.target.value)) {
|
||||
chosenExitToFrontExpiration.value = Number.parseInt(e.target.value);
|
||||
refreshHistoricalCalendarExitQuoteChartData();
|
||||
}
|
||||
};
|
||||
@@ -198,320 +201,324 @@ export function HistoricalCalendarPrices() {
|
||||
useEffect(handleInit, []);
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Historical Calendar Prices
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Available Underlyings</InputLabel>
|
||||
<Select
|
||||
value={chosenUnderlying.value || ""}
|
||||
onChange={handleUnderlyingChange}
|
||||
label="Available Underlyings"
|
||||
>
|
||||
{availableUnderlyings.value.map((underlying) => (
|
||||
<MenuItem key={underlying} value={underlying}>
|
||||
{underlying}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Now-to-Front-Month Days to Expiration"
|
||||
type="number"
|
||||
value={chosenDaysToFrontExpiration.value}
|
||||
onChange={handleDaysToFrontExpirationChange}
|
||||
InputProps={{ endAdornment: "Days" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Front-to-Back-Month Days to Expiration Difference"
|
||||
type="number"
|
||||
value={chosenDaysBetweenFrontAndBackExpiration.value}
|
||||
onChange={handleDaysBetweenFrontAndBackExpirationChange}
|
||||
InputProps={{ endAdornment: "Days Difference" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Strike % From Underlying Price"
|
||||
type="number"
|
||||
value={chosenStrikePercentageFromUnderlyingPrice.value}
|
||||
onChange={handleStrikePercentageFromUnderlyingPriceChange}
|
||||
InputProps={{ endAdornment: "%" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Strike % Radius"
|
||||
type="number"
|
||||
value={chosenStrikePercentageFromUnderlyingPriceRadius.value}
|
||||
onChange={handleStrikePercentageFromUnderlyingPriceRadiusChange}
|
||||
InputProps={{ endAdornment: "%" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Exit-to-Front-Month Days to Expiration"
|
||||
type="number"
|
||||
value={chosenExitToFrontExpiration.value}
|
||||
onChange={handleExitToFrontExpirationChange}
|
||||
InputProps={{ endAdornment: "Days" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Lookback Period Start"
|
||||
type="date"
|
||||
value={chosenLookbackPeriodStart.value}
|
||||
onChange={(e) => handleLookbackPeriodStartChange({ target: { value: e.target.value } })}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Lookback Period End"
|
||||
type="date"
|
||||
value={chosenLookbackPeriodEnd.value}
|
||||
onChange={(e) => handleLookbackPeriodEndChange({ target: { value: e.target.value } })}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3, height: '100%' }}>
|
||||
{chosenUnderlying.value !== null &&
|
||||
historicalStockQuoteChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
data={{
|
||||
datasets: [
|
||||
{
|
||||
label: "Stock Open Price",
|
||||
data: historicalStockQuoteChartData.value,
|
||||
},
|
||||
],
|
||||
}}
|
||||
options={{
|
||||
scales: {
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "Time",
|
||||
},
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
return new Date((value as number) * 1000)
|
||||
.toISOString()
|
||||
.substring(0, 10);
|
||||
},
|
||||
},
|
||||
min:
|
||||
new Date(chosenLookbackPeriodStart.value).getTime() /
|
||||
1000,
|
||||
max:
|
||||
new Date(chosenLookbackPeriodEnd.value).getTime() /
|
||||
1000,
|
||||
},
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
return "$" + value.toString();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 1,
|
||||
borderWidth: 0,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: "Stock Price",
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
events: [],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography>Loading Chart...</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
{chosenUnderlying.value !== null &&
|
||||
historicalCalendarQuoteChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
data={{
|
||||
datasets: [
|
||||
{
|
||||
label: "Calendar Open Price",
|
||||
data: historicalCalendarQuoteChartData.value,
|
||||
},
|
||||
],
|
||||
}}
|
||||
options={{
|
||||
scales: {
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "Time",
|
||||
},
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
return new Date((value as number) * 1000)
|
||||
.toISOString()
|
||||
.substring(0, 10);
|
||||
},
|
||||
},
|
||||
min:
|
||||
new Date(chosenLookbackPeriodStart.value).getTime() /
|
||||
1000,
|
||||
max:
|
||||
new Date(chosenLookbackPeriodEnd.value).getTime() /
|
||||
1000,
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
return "$" + value.toString();
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
max: maxChartPrice.value,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: "Calendar Price (Under Like Conditions)",
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
events: [],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography>Loading Chart...</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
{chosenUnderlying.value !== null &&
|
||||
historicalCalendarQuoteChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
data={{
|
||||
datasets: [
|
||||
{
|
||||
label: "Calendar Exit Price",
|
||||
data: historicalCalendarExitQuoteChartData.value,
|
||||
},
|
||||
],
|
||||
}}
|
||||
options={{
|
||||
scales: {
|
||||
x: {
|
||||
type: "linear",
|
||||
beginAtZero: false,
|
||||
title: {
|
||||
display: true,
|
||||
text: "%-From-the-Money",
|
||||
},
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
return value.toString() + "%";
|
||||
},
|
||||
},
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function (value, index, ticks) {
|
||||
return "$" + value.toString();
|
||||
},
|
||||
},
|
||||
min: 0,
|
||||
max: maxChartPrice.value,
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
borderWidth: 0,
|
||||
backgroundColor: function (context) {
|
||||
const n = (
|
||||
context.raw as { x: number; y: number; n: number }
|
||||
).n;
|
||||
const alpha = n / maxN.value;
|
||||
return `rgba(0, 0, 0, ${alpha})`;
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: [
|
||||
"Calendar Prices at Exit",
|
||||
"by %-age from-the-money",
|
||||
],
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
events: [],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography>Loading Chart...</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Container maxWidth="lg">
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Historical Calendar Prices
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Container>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Available Underlyings</InputLabel>
|
||||
<Select
|
||||
value={chosenUnderlying.value || ""}
|
||||
onChange={handleUnderlyingChange}
|
||||
label="Available Underlyings"
|
||||
>
|
||||
{availableUnderlyings.value.map((underlying) => (
|
||||
<MenuItem key={underlying} value={underlying}>
|
||||
{underlying}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Now-to-Front-Month Days to Expiration"
|
||||
type="number"
|
||||
value={chosenDaysToFrontExpiration.value}
|
||||
onChange={handleDaysToFrontExpirationChange}
|
||||
InputProps={{ endAdornment: "Days" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Front-to-Back-Month Days to Expiration Difference"
|
||||
type="number"
|
||||
value={chosenDaysBetweenFrontAndBackExpiration.value}
|
||||
onChange={handleDaysBetweenFrontAndBackExpirationChange}
|
||||
InputProps={{ endAdornment: "Days Difference" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Strike % From Underlying Price"
|
||||
type="number"
|
||||
value={chosenStrikePercentageFromUnderlyingPrice.value}
|
||||
onChange={handleStrikePercentageFromUnderlyingPriceChange}
|
||||
InputProps={{ endAdornment: "%" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Strike % Radius"
|
||||
type="number"
|
||||
value={chosenStrikePercentageFromUnderlyingPriceRadius.value}
|
||||
onChange={
|
||||
handleStrikePercentageFromUnderlyingPriceRadiusChange
|
||||
}
|
||||
InputProps={{ endAdornment: "%" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Exit-to-Front-Month Days to Expiration"
|
||||
type="number"
|
||||
value={chosenExitToFrontExpiration.value}
|
||||
onChange={handleExitToFrontExpirationChange}
|
||||
InputProps={{ endAdornment: "Days" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Lookback Period Start"
|
||||
type="date"
|
||||
value={chosenLookbackPeriodStart.value}
|
||||
onChange={(e) =>
|
||||
handleLookbackPeriodStartChange({
|
||||
target: { value: e.target.value },
|
||||
})
|
||||
}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Lookback Period End"
|
||||
type="date"
|
||||
value={chosenLookbackPeriodEnd.value}
|
||||
onChange={(e) =>
|
||||
handleLookbackPeriodEndChange({
|
||||
target: { value: e.target.value },
|
||||
})
|
||||
}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper elevation={3} sx={{ p: 3, height: "100%" }}>
|
||||
{chosenUnderlying.value !== null &&
|
||||
historicalStockQuoteChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
data={{
|
||||
datasets: [
|
||||
{
|
||||
label: "Stock Open Price",
|
||||
data: historicalStockQuoteChartData.value,
|
||||
},
|
||||
],
|
||||
}}
|
||||
options={{
|
||||
scales: {
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "Time",
|
||||
},
|
||||
ticks: {
|
||||
callback: (value, index, ticks) =>
|
||||
new Date((value as number) * 1000)
|
||||
.toISOString()
|
||||
.substring(0, 10),
|
||||
},
|
||||
min:
|
||||
new Date(chosenLookbackPeriodStart.value).getTime() /
|
||||
1000,
|
||||
max:
|
||||
new Date(chosenLookbackPeriodEnd.value).getTime() /
|
||||
1000,
|
||||
},
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
ticks: {
|
||||
callback: (value, index, ticks) =>
|
||||
`$${value.toString()}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 1,
|
||||
borderWidth: 0,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: "Stock Price",
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
events: [],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography>Loading Chart...</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
{chosenUnderlying.value !== null &&
|
||||
historicalCalendarQuoteChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
data={{
|
||||
datasets: [
|
||||
{
|
||||
label: "Calendar Open Price",
|
||||
data: historicalCalendarQuoteChartData.value,
|
||||
},
|
||||
],
|
||||
}}
|
||||
options={{
|
||||
scales: {
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "Time",
|
||||
},
|
||||
ticks: {
|
||||
callback: (value, index, ticks) =>
|
||||
new Date((value as number) * 1000)
|
||||
.toISOString()
|
||||
.substring(0, 10),
|
||||
},
|
||||
min:
|
||||
new Date(chosenLookbackPeriodStart.value).getTime() /
|
||||
1000,
|
||||
max:
|
||||
new Date(chosenLookbackPeriodEnd.value).getTime() /
|
||||
1000,
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: (value, index, ticks) =>
|
||||
`$${value.toString()}`,
|
||||
},
|
||||
min: 0,
|
||||
max: maxChartPrice.value,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: "Calendar Price (Under Like Conditions)",
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
events: [],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography>Loading Chart...</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper elevation={3} sx={{ p: 3 }}>
|
||||
{chosenUnderlying.value !== null &&
|
||||
historicalCalendarQuoteChartData.value.length > 0 ? (
|
||||
<Scatter
|
||||
data={{
|
||||
datasets: [
|
||||
{
|
||||
label: "Calendar Exit Price",
|
||||
data: historicalCalendarExitQuoteChartData.value,
|
||||
},
|
||||
],
|
||||
}}
|
||||
options={{
|
||||
scales: {
|
||||
x: {
|
||||
type: "linear",
|
||||
beginAtZero: false,
|
||||
title: {
|
||||
display: true,
|
||||
text: "%-From-the-Money",
|
||||
},
|
||||
ticks: {
|
||||
callback: (value, index, ticks) =>
|
||||
`${value.toString()}%`,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: (value, index, ticks) =>
|
||||
`$${value.toString()}`,
|
||||
},
|
||||
min: 0,
|
||||
max: maxChartPrice.value,
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
borderWidth: 0,
|
||||
backgroundColor: (context) => {
|
||||
const n = (
|
||||
context.raw as { x: number; y: number; n: number }
|
||||
).n;
|
||||
const alpha = n / maxN.value;
|
||||
return `rgba(0, 0, 0, ${alpha})`;
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: [
|
||||
"Calendar Prices at Exit",
|
||||
"by %-age from-the-money",
|
||||
],
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
events: [],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography>Loading Chart...</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
+134
-134
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user