Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync: main to stage #63

Merged
merged 2 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/app/api/symbols/[symbol]/historical/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { NextResponse } from "next/server";
import yf from 'yahoo-finance2';

export async function GET(request: Request, p: { params: { symbol: string } }) {
const resp = await yf.historical(p.params.symbol, {
interval: '1d',
period1: new Date(2023, 0, 1),
period2: new Date()
});
return NextResponse.json({
data: resp,
symbol: p.params.symbol
});
}
101 changes: 4 additions & 97 deletions src/app/api/symbols/[symbol]/options/analyze/tradier/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import ky from "ky";
import dayjs from 'dayjs';
import { NextResponse } from "next/server";
import { OptionsHedgingDataset } from "@/lib/socket";
import { TradierOptionData } from "@/lib/types";
import { calculateHedging, getCalculatedStrikes } from "@/lib/dgHedgingHelper";
const tradierBaseUri = process.env.TRADIER_BASE_URI || 'https://sandbox.tradier.com/';
const optionsChain = `${tradierBaseUri}v1/markets/options/chains`;
const optionsExpiration = `${tradierBaseUri}v1/markets/options/expirations`;
Expand Down Expand Up @@ -36,79 +38,7 @@ export async function GET(request: Request, p: { params: { symbol: string } }) {
const allOptionChains = await Promise.all(allDates.map(d => getOptionData(symbol, d)));

const allStrikes = getCalculatedStrikes(currentPrice, strikeCountValue, [...new Set(allOptionChains.flatMap(j => j.options.option.map(s => s.strike)))]);

const allOp = allOptionChains.flatMap(j => j.options.option.map(s => s));

//console.log(`Rendering with dates: ${allDates} and strikes: ${allStrikes}`);
// const model: Record<number, { puts: number[], calls: number[], data: number[] }> = {};
const dmodel: OptionsHedgingDataset[] = [];
const gmodel: OptionsHedgingDataset[] = [];
let maxPosition = 0, gmaxPosition = 0;
for (const sp of allStrikes) {
const deltaExposure: OptionsHedgingDataset = { strike: sp };
const gammaExposure: OptionsHedgingDataset = { strike: sp };
let sumOfPv = 0, sumOfCv = 0;
let sumOfGPv = 0, sumOfGCv = 0;
dmodel.push(deltaExposure);
gmodel.push(gammaExposure);
// model[sp] = {
// calls: [],
// puts: [],
// data: []
// }
for (const dt of allDates) {
const cv_o = allOp.find(j => j.strike == sp && j.expiration_date == dt && j.option_type == 'call');
const pv_o = allOp.find(j => j.strike == sp && j.expiration_date == dt && j.option_type == 'put');

const cv = (cv_o?.open_interest || 0) * (cv_o?.greeks?.delta || 0) * 100 * currentPrice;
const pv = (pv_o?.open_interest || 0) * (pv_o?.greeks?.delta || 0) * 100 * currentPrice;

const gcv = (cv_o?.open_interest || 0) * (cv_o?.greeks?.gamma || 0) * 100 * currentPrice;
const gpv = (pv_o?.open_interest || 0) * (pv_o?.greeks?.gamma || 0) * 100 * currentPrice;
// model[sp].calls.push(cv);
// model[sp].puts.push(pv);
// model[sp].data.push(-cv, pv);

deltaExposure[`${dt}-call`] = -cv;
deltaExposure[`${dt}-put`] = -pv;

const gv = gcv - gpv;

if (gv > 0) {
gammaExposure[`${dt}-call`] = -gv;
gammaExposure[`${dt}-put`] = 0;
} else {
gammaExposure[`${dt}-call`] = 0;
gammaExposure[`${dt}-put`] = -gv;
}

sumOfPv = sumOfPv + Math.abs(pv);
sumOfCv = sumOfCv + Math.abs(cv);

sumOfGPv = sumOfGPv + Math.abs(gpv);
sumOfGCv = sumOfGCv + Math.abs(gcv);
}
maxPosition = Math.max(maxPosition, sumOfPv, sumOfCv);
gmaxPosition = Math.max(gmaxPosition, sumOfGPv, sumOfGCv);
}

const finalResponse = {
exposureData: {
strikes: allStrikes,
expirations: allDates,
currentPrice,
maxPosition,
deltaDataset: {
dataset: dmodel,
maxPosition
},
gammaDataset: {
dataset: gmodel,
maxPosition: gmaxPosition
}
},
raw: allOptionChains
}
const finalResponse = calculateHedging(allOptionChains, allStrikes, allDates, currentPrice)
return NextResponse.json(finalResponse);
}

Expand All @@ -119,20 +49,7 @@ function getOptionData(symbol: string, expiration: string) {
expiration,
'greeks': 'true'
}
}).json<{
options: {
option: {
strike: number,
open_interest: number,
expiration_date: string,
option_type: 'put' | 'call',
greeks: {
delta: number,
gamma: number
}
}[]
}
}>();
}).json<TradierOptionData>();
}

async function getCurrentPrice(symbol: string) {
Expand All @@ -153,13 +70,3 @@ async function getCurrentPrice(symbol: string) {
.last;
}

///responsible for returning the strikes which we have to return in response.
function getCalculatedStrikes(currentPrice: number, maxStrikes: number, strikes: number[]) {
const currentOrAboveStrikes = strikes.filter(j => j >= currentPrice).sort((a, b) => a - b).reverse();
const belowCurrentStrikes = strikes.filter(j => j < currentPrice).sort((a, b) => a - b);
let result = [];
while (result.length < maxStrikes && (currentOrAboveStrikes.length > 0 || belowCurrentStrikes.length > 0)) {
result.push(...[currentOrAboveStrikes.pop(), belowCurrentStrikes.pop()].filter(j => j));
}
return result.map(Number).sort((a, b) => a - b);
}
24 changes: 21 additions & 3 deletions src/components/DeltaGammaHedging.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Dialog, DialogContent, DialogActions, Button, Typography, LinearProgress, FormControl, InputLabel, MenuItem, Select, Tab, Tabs, Paper, Box } from "@mui/material";
import { BarChart } from '@mui/x-charts/BarChart';
import { ChartsReferenceLine } from '@mui/x-charts';
import { OptionsHedgingData, useDeltaGammaHedging } from "@/lib/socket";
import { OptionsHedgingData, useCachedDates, useDeltaGammaHedging } from "@/lib/socket";
import { getColorPallete } from "@/lib/color";
import { humanAbsCurrencyFormatter } from "@/lib/formatters";
import { useQueryState, parseAsInteger, parseAsStringEnum } from "nuqs";
import { useState } from "react";

interface ITickerProps {
symbol: string,
Expand Down Expand Up @@ -112,8 +113,9 @@ export const DeltaGammaHedging = (props: ITickerProps) => {
const { onClose } = props;
const [dte, setDte] = useQueryState('dte', parseAsInteger.withDefault(50));
const [strikeCounts, setStrikesCount] = useQueryState('sc', parseAsInteger.withDefault(30));

const { data, isLoading } = useDeltaGammaHedging(props.symbol, dte, strikeCounts);
const { cachedDates } = useCachedDates(props.symbol);
const [dataMode, setDataMode] = useState('Live');
const { data, isLoading } = useDeltaGammaHedging(props.symbol, dte, strikeCounts, dataMode);
const [gexTab, setGexTab] = useQueryState<DexGexType>('dgextab', parseAsStringEnum<DexGexType>(Object.values(DexGexType)).withDefault(DexGexType.DEX));

// if (isLoading) return <LinearProgress />;
Expand Down Expand Up @@ -153,6 +155,22 @@ export const DeltaGammaHedging = (props: ITickerProps) => {
<MenuItem value={100}>100</MenuItem>
</Select>
</FormControl>
<FormControl sx={{ m: 1, minWidth: 120 }} size="small">
<InputLabel>Data Mode</InputLabel>
<Select
id="data-mode"
value={dataMode}
label="Data Mode"
onChange={(e) => setDataMode(e.target.value)}
>
<MenuItem value="Live">Live</MenuItem>
{
cachedDates.map(c=> {
return <MenuItem key={c.dt} value={c.dt}>{c.dt}</MenuItem>
})
}
</Select>
</FormControl>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={gexTab}
Expand Down
74 changes: 74 additions & 0 deletions src/components/TradingViewWidgetDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Dialog, DialogContent, DialogActions, Button } from "@mui/material";
import { useRef, useEffect } from "react";

interface ITickerProps {
symbol: string,
onClose: () => void
}

const TradingViewWidget = (props: { symbol: string }) => {
const container = useRef(null);
useEffect(
() => {
const script = document.createElement("script");
script.src = "https://s3.tradingview.com/external-embedding/embed-widget-advanced-chart.js";
script.type = "text/javascript";
script.async = true;
script.innerHTML = `
{
"container_id": "technical-analysis-chart-demo",
"width": "100%",
"height": "100%",
"autosize": true,
"symbol": "${props.symbol}",
"interval": "D",
"timezone": "exchange",
"theme": "light",
"style": "1",
"withdateranges": true,
"hide_side_toolbar": false,
"allow_symbol_change": true,
"save_image": false,
"studies": [
"ROC@tv-basicstudies",
"StochasticRSI@tv-basicstudies",
"MASimple@tv-basicstudies"
],
"show_popup_button": true,
"popup_width": "1000",
"popup_height": "650",
"support_host": "https://www.tradingview.com"
}`;
const c = (container.current as unknown as HTMLDivElement);
c?.appendChild(script);

return () => {
script.remove(); //removes self. In dev mode react somehow adding two nodes, so need to remove it explicitly.
}
}, [props.symbol]);

return (
<div className="tradingview-widget-container" ref={container} style={{ height: "100%", width: "100%" }}>
<div className="tradingview-widget-container__widget" style={{ height: "calc(100% - 32px)", width: "100%" }}></div>
<div className="tradingview-widget-copyright"><a href="https://www.tradingview.com/" rel="noopener nofollow" target="_blank"><span className="blue-text">Track all markets on TradingView</span></a></div>
</div>
);
}


export const TradingViewWidgetDialog = (props: ITickerProps) => {
const { onClose } = props;

return (
<Dialog fullWidth={true} fullScreen={true} open={true} onClose={onClose} aria-labelledby="trading-view-dialog" >
<DialogContent style={{ padding: '8px' }}>
<TradingViewWidget symbol={props.symbol} />
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={onClose} color="secondary">
Close
</Button>
</DialogActions>
</Dialog>
);
};
18 changes: 16 additions & 2 deletions src/components/Watchlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AddTradeDialog } from './AddTradeDialog';
import { GridLinkAction } from './GridLinkAction';
import { SearchTickerItem } from '@/lib/types';
import { TickerSearch } from './TickerSearch';
import { TradingViewWidgetDialog } from './TradingViewWidgetDialog';


interface IWatchlistProps {
Expand Down Expand Up @@ -73,16 +74,27 @@ export const Watchlist = (props: IWatchlistProps) => {
setCurrentStock(row);
setOpenAddTrade(true);
}}
/>,
<GridActionsCellItem
key='ViewTradingView'
icon={<AddTradeIcon />}
label="Show in Trading view"
showInMenu
onClick={() => {
setCurrentStock(row);
setOpenTradingViewDialog(true);
}}
/>
]
}
}
];


const [openAddTrade, setOpenAddTrade] = useState(false);
const [openTradingViewDialog, setOpenTradingViewDialog] = useState(false);
const [openAddToWatchlist, setOpenAddToWatchlist] = useState(false);

const handleCloseAddTrade = () => { setOpenAddTrade(false); };
const handleAddToWatchlist = (item: SearchTickerItem) => { addToWatchlist(item); setOpenAddToWatchlist(false); }
return <div>
Expand Down Expand Up @@ -121,5 +133,7 @@ export const Watchlist = (props: IWatchlistProps) => {
<TickerSearch onChange={handleAddToWatchlist} />
</DialogContent>
</Dialog>

{openTradingViewDialog && currentStock?.symbol && <TradingViewWidgetDialog symbol={currentStock.symbol} onClose={() => { setOpenTradingViewDialog(false) }} />}
</div>
}
Loading
Loading