Skip to content

Commit

Permalink
add seasonal view of symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
mnsrulz committed Oct 4, 2024
1 parent 2b8d3f1 commit a9346d0
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 90 deletions.
284 changes: 212 additions & 72 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.15",
"@mui/material": "^5.15.15",
"@mui/x-charts": "^7.3.0",
"@mui/x-charts": "^7.18.0",
"@mui/x-data-grid": "7.3",
"@mui/x-date-pickers": "^7.3.2",
"@prisma/client": "^5.12.1",
"@uidotdev/usehooks": "^2.4.1",
"collect.js": "^4.36.1",
"core-js": "^3.38.1",
"dayjs": "^1.11.10",
"he": "^1.2.0",
"human-format": "^1.2.0",
"ky": "^1.2.3",
"ky": "^1.7.2",
"localforage": "^1.10.0",
"lodash.debounce": "^4.0.8",
"match-sorter": "^6.3.4",
Expand All @@ -43,6 +44,7 @@
"yahoo-finance2": "^2.12.3"
},
"devDependencies": {
"@types/core-js": "^2.5.8",
"@types/he": "^1.2.3",
"@types/node": "^20",
"@types/react": "^18",
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import TabsRouter from "./routes";
import { CssBaseline, Grid } from "@mui/material";
import { SessionProvider } from "next-auth/react";

const inter = Inter({ subsets: ["latin"] });
const inter = Inter({ subsets: ["latin"], display: 'swap', adjustFontFallback: false });

export const metadata: Metadata = {
title: "My trading view app",
Expand Down
1 change: 1 addition & 0 deletions src/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const pages = [
{ title: 'Trades', href: '/trades' },
{ title: 'Option analyzer', href: '/options/analyze' },
{ title: 'History', href: '/history' },
{ title: 'Seasonal', href: '/seasonal' },
{ title: 'Calculator', href: '/calculator' }
];
const settings = ['Profile', 'Logout'];
Expand Down
15 changes: 15 additions & 0 deletions src/app/seasonal/[symbol]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { HistoricalSeason } from '@/components/HistoricalSeason';
import { TickerSearchNavigation } from '@/components/TickerSearchNavigation';
import { getSeasonalView } from '@/lib/tradierService';

export default async function Page({ params }: { params: { symbol: string } }) {
const { symbol } = params;
const dt = await getSeasonalView(symbol, '5y', 'monthly');
return <>
{/* not working due to clientside and serverside battle.. let's look at it later */}
{/* <TickerSearchNavigation basePath='/seasonal' /> */}
<HistoricalSeason data={dt} />
</>
}


10 changes: 10 additions & 0 deletions src/app/seasonal/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client';
import { TickerSearch } from '@/components/TickerSearch';
import { useRouter } from 'next/navigation';

export default function Page() {
const router = useRouter();
return <div>
<TickerSearch onChange={(v) => router.push(`/seasonal/${v.symbol}`)} />
</div>
}
2 changes: 1 addition & 1 deletion src/components/AddTradeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const AddTradeDialog = (props: ITickerProps) => {
<LocalizationProvider dateAdapter={AdapterDayjs}>
<TextFieldElement name={'symbol'} label={'Symbol'} required />
<SelectElement name={'contractType'} label={'Type'} options={options} fullWidth />
<SliderElement name={"numberOfContracts"} label='Number of contracts' max={20} min={1} />
<SliderElement name={"numberOfContracts"} label='Number of contracts' max={100} min={1} />
<Stack direction="row" spacing={2}>
<DatePickerElement label="Transaction Start Date" name="transactionStartDate" required disableFuture={true} disablePast={false} />
<DatePickerElement label="Expiry Date" name="contractExpiry" required disableFuture={false} disablePast={false} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/ConditionalFormattingBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Box } from "@mui/material";
type IConditionalFormattingBoxProps = { value: number, formattedValue: string }
export const ConditionalFormattingBox = (props: IConditionalFormattingBoxProps) => {
const { value, formattedValue } = props;
if (!Number.isNaN(value)) {
if (value && !Number.isNaN(value)) {
let color = getColor(value);
return <Box
sx={{
Expand Down
110 changes: 110 additions & 0 deletions src/components/HistoricalSeason.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use client'

import { HistoricalDataResponse } from "@/lib/types"
import { GridColDef, DataGrid, gridClasses } from "@mui/x-data-grid";
import dayjs from "dayjs";
import { ConditionalFormattingBox } from "./ConditionalFormattingBox";
import { numberFormatter, percentageFormatter } from "@/lib/formatters";


const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];

export const HistoricalSeason = (props: { data: HistoricalDataResponse }) => {
const currentYear = dayjs().year();
// use to render the graph/chart
// const dkk = dt.history.day.reduce((acc: Record<string, number[]>, current) => {
// const year = dayjs(current.date).format('YYYY');
// if (!acc[year]) {
// const currentMonth = dayjs().month();
// if (year === `${currentYear}`) {
// acc[year] = new Array(currentMonth + 1).fill(0);
// } else {
// acc[year] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// }
// };
// const pm = ((current.close - current.open) / current.open) * 100;
// const month = dayjs(current.date).month();
// acc[year][month] = pm;
// return acc;
// }, {});

/*
{month: 'Jan', d2022: 1, d2023: 1.6...},
{}
const seriesData = Object.keys(dkk).map(year => ({
label: year,
data: dkk[year]
}));
*/
const dt = props.data;
const years: string[] = []
const dkk = dt.history.day.reduce((acc: { id: string }[], current) => {
const year = dayjs(current.date).format('YYYY');
const pm = ((current.close - current.open) / current.open);
const month = dayjs(current.date).month();
(acc.find(j => j.id === months[month]) as any)[`d${year}`] = pm.toFixed(2);
if (!years.includes(year)) years.push(year);
return acc
}, months.map(j => ({ id: j })));
debugger;

const columns: GridColDef[] = [
{ field: 'id', width: 120, headerName: 'month' },
...years.map(j => {
return {
field: `d${j}`, width: 120, headerName: j,
valueFormatter: percentageFormatter,
type: 'number',
renderCell: (p) => <ConditionalFormattingBox value={p.value} formattedValue={p.formattedValue} />
} as GridColDef
})
]

return <div>
<DataGrid rows={dkk}
disableColumnMenu={true}
disableColumnFilter={true}
disableColumnSorting={true}
columns={columns}
density="compact"
disableRowSelectionOnClick
columnHeaderHeight={32}
rowHeight={32}
hideFooter={true}
showColumnVerticalBorder={true}
showCellVerticalBorder={true}
sx={{
[`& .${gridClasses.cell}:focus, & .${gridClasses.cell}:focus-within`]: {
outline: 'none',
},
[`& .${gridClasses.columnHeader}:focus, & .${gridClasses.columnHeader}:focus-within`]:
{
outline: 'none',
},
[`& .${gridClasses.columnHeader}`]:
{
fontSize: '0.7rem',
fontWeight: 500
},
[`& .${gridClasses.cell}`]:
{
fontSize: '0.7rem',
padding: 0
},
}}
/>
</div>
}
12 changes: 12 additions & 0 deletions src/components/TickerSearchNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { TickerSearch } from '@/components/TickerSearch';
import { useRouter } from 'next/navigation';

interface ITickerProps {
basePath: string,
label?: string
}

export const TickerSearchNavigation = (props: ITickerProps) => {
const router = useRouter();
return <TickerSearch onChange={(v) => router.push(`${props.basePath}/${v.symbol}`)} />
}
42 changes: 30 additions & 12 deletions src/lib/tradierService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ky from "ky";
import { TradierOptionData } from "./types";
import { HistoricalDataResponse, TradierOptionData } from "./types";
import dayjs from "dayjs";
const tradierBaseUri = process.env.TRADIER_BASE_URI || 'https://sandbox.tradier.com/';
const optionsChain = `${tradierBaseUri}v1/markets/options/chains`;
Expand All @@ -8,6 +8,19 @@ const historical = `${tradierBaseUri}v1/markets/history`;
const optionsExpiration = `${tradierBaseUri}v1/markets/options/expirations`;
const getQuotes = `${tradierBaseUri}v1/markets/quotes`;

type Symbol = {
symbol: string,
description: string
}

type LookupSymbolResponse = {
securities: {
security: Symbol | Symbol[]
}
}



const client = ky.create({
headers: {
'Authorization': `Bearer ${process.env.TRADIER_TOKEN}`,
Expand Down Expand Up @@ -53,16 +66,6 @@ export const getCurrentPrice = async (symbol: string) => {
.last;
}

type Symbol = {
symbol: string,
description: string
}

type LookupSymbolResponse = {
securities: {
security: Symbol | Symbol[]
}
}

export const lookupSymbol = (q: string) => {
return client(lookup, {
Expand All @@ -77,7 +80,7 @@ export const lookupSymbol = (q: string) => {
export const getPriceAtDate = async (s: string, dt: string) => {
console.log(`${s} -- ${dt}`);
const start = dayjs(dt.substring(0, 10)).format('YYYY-MM-DD');

const result = await client(historical, {
searchParams: {
'symbol': s,
Expand All @@ -97,4 +100,19 @@ export const getPriceAtDate = async (s: string, dt: string) => {
const dtresult = result.history.day;
if (dtresult) return dtresult.open;
throw new Error('unable to determine price');
}

export const getSeasonalView = async (s: string, duration: '1y' | '2y' | '3y' | '4y' | '5y', interval: 'daily' | 'weekly' | 'monthly') => {
const years = parseInt(duration.substring(0, 1));
const startDay = dayjs().startOf('year').subtract(years, 'year').format('YYYY-MM-DD')
const result = await client(historical, {
searchParams: {
'symbol': s,
'interval': interval,
'start': startDay,
'end': dayjs().startOf('month').format('YYYY-MM-DD'), //dayjs(dt.substring(0, 10)).add(1, 'days').format('YYYY-MM-DD'),
'session_filter': 'all'
}
}).json<HistoricalDataResponse>();
return result;
}
20 changes: 19 additions & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,22 @@ export type OptionsInnerData = {

export type SearchTickerResult = { items: SearchTickerItem[] };
export type SearchTickerItem = { symbol: string, name: string }
export type AddTickerToMyListResult = { success: boolean }
export type AddTickerToMyListResult = { success: boolean }


type HistoricalDataItem = {
date: string;
open: number;
high: number;
low: number;
close: number;
volume: number;
};

type HistoricalData = {
day: HistoricalDataItem[];
};

export type HistoricalDataResponse = {
history: HistoricalData;
};

0 comments on commit a9346d0

Please sign in to comment.