Skip to content

Commit

Permalink
using another service for lookup symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
mnsrulz committed Sep 10, 2024
1 parent 374c3d5 commit 2cad055
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 91 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@mui/x-data-grid": "7.3",
"@mui/x-date-pickers": "^7.3.2",
"@prisma/client": "^5.12.1",
"@uidotdev/usehooks": "^2.4.1",
"dayjs": "^1.11.10",
"human-format": "^1.2.0",
"ky": "^1.2.3",
Expand Down
54 changes: 4 additions & 50 deletions src/app/api/symbols/[symbol]/options/analyze/tradier/route.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,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`;
const getQuotes = `${tradierBaseUri}v1/markets/quotes`;

const client = ky.create({
headers: {
'Authorization': `Bearer ${process.env.TRADIER_TOKEN}`,
'Accept': 'application/json'
},
cache: 'no-cache'
});
import { getCurrentPrice, getOptionData, getOptionExpirations } from '@/lib/tradierService';

export async function GET(request: Request, p: { params: { symbol: string } }) {
const { searchParams } = new URL(request.url);
Expand All @@ -26,11 +13,7 @@ export async function GET(request: Request, p: { params: { symbol: string } }) {
const currentPrice = await getCurrentPrice(symbol);
if (!currentPrice) throw new Error('Unable to evaluate current price')

const expresp = await client(optionsExpiration, {
searchParams: {
symbol
}
}).json<{ expirations: { date: string[] } }>();
const expresp = await getOptionExpirations(symbol);

const tillDate = dayjs().add(dteValue, 'days');
console.log(`all expirations: ${expresp.expirations.date}`);
Expand All @@ -40,33 +23,4 @@ export async function GET(request: Request, p: { params: { symbol: string } }) {
const allStrikes = getCalculatedStrikes(currentPrice, strikeCountValue, [...new Set(allOptionChains.flatMap(j => j.options.option.map(s => s.strike)))]);
const finalResponse = calculateHedging(allOptionChains, allStrikes, allDates, currentPrice)
return NextResponse.json(finalResponse);
}

function getOptionData(symbol: string, expiration: string) {
return client(optionsChain, {
searchParams: {
symbol,
expiration,
'greeks': 'true'
}
}).json<TradierOptionData>();
}

async function getCurrentPrice(symbol: string) {
const cp = await client(getQuotes, {
searchParams: {
symbols: symbol
}
}).json<{
quotes: {
quote: {
symbol: string,
last: number
}
}
}>();
return cp.quotes.quote
//.find(x => x.symbol === symbol)?
.last;
}

}
22 changes: 18 additions & 4 deletions src/app/api/symbols/search/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { lookupSymbol } from "@/lib/tradierService";
import { NextRequest, NextResponse } from "next/server";
import yf from 'yahoo-finance2';

export async function GET(request: NextRequest, p: { params: { symbol: string } }) {
export async function GET(request: NextRequest) {
const q = request.nextUrl.searchParams.get('q');
if (!q) return NextResponse.json({ 'error': 'q parameter is not provided in the request.' }, {
status: 400
});
const resp = await yf.search(q);

const res = await lookupSymbol(q)
if (!res.securities) return NextResponse.json({ items: [] });
const { security } = res.securities
const t1 = Array.isArray(security) ? security : [security];
return NextResponse.json({
items: resp
items: t1.map(j => {
return {
symbol: j.symbol,
name: j.description
};
})
});

// const resp = await yf.search(q);
// return NextResponse.json({
// items: resp
// });
}
34 changes: 10 additions & 24 deletions src/components/TickerSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
import { Autocomplete, CircularProgress, TextField, debounce } from '@mui/material';
import { Autocomplete, CircularProgress, TextField } from '@mui/material';
import { useDebounce } from "@uidotdev/usehooks";
import * as React from 'react';
import { searchTicker } from '../lib/socket';
import { useEffect, useMemo, useState } from 'react';
import { useTickerSearch } from '../lib/socket';
import { SearchTickerItem } from '@/lib/types';


interface ITickerProps {
onChange: (value: SearchTickerItem) => void,
label?: string
}

export const TickerSearch = (props: ITickerProps) => {
const [options, setOptions] = useState<SearchTickerItem[]>([]);
const [loading, setLoading] = useState(false);
const onInputChange = (ev: any, value: any, reason: any) => {
if (value) {
getData(value);
} else {
setOptions([]);
}
};
const [searchTerm, setSearchTerm] = React.useState('');
//const [options, setOptions] = useState<SearchTickerItem[]>([]);

const debouncedEventHandler = useMemo(
() => debounce(onInputChange, 300)
, []);

const getData = async (searchTerm: string) => {
setLoading(true);
const result = await searchTicker(searchTerm);
setOptions(result);
setLoading(false);
};
const debouncedSearchTerm = useDebounce(searchTerm, 300);
const { options, loading } = useTickerSearch(debouncedSearchTerm);

return <Autocomplete filterOptions={(x) => x}
onInputChange={debouncedEventHandler}
onInputChange={(ev, v) => setSearchTerm(v)}
size='small'
onChange={(ev, value) => value && props.onChange(value)}
renderInput={(params) => (
Expand All @@ -54,6 +40,6 @@ export const TickerSearch = (props: ITickerProps) => {
)}
options={options}
getOptionLabel={(option: SearchTickerItem) => `${option.symbol} - ${option.name}`}
fullWidth
fullWidth
/>
}
48 changes: 35 additions & 13 deletions src/lib/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,21 @@ socket.on("connect", () => {
})


export const searchTicker = async (searchTerm: string) => {
export const searchTicker = async (searchTerm: string, signal?: AbortSignal) => {
const { items } = await ky('/api/symbols/search', {
searchParams: {
q: searchTerm
}
},
signal: signal
}).json<{
items: {
quotes: {
symbol: string,
longname: string,
isYahooFinance: boolean
}[]
}
items: SearchTickerItem[]
}>();

return items.quotes.filter(f => f.isYahooFinance).map(r => ({
symbol: r.symbol,
name: r.longname
}));
return items;
// return items.quotes.filter(f => f.isYahooFinance).map(r => ({
// symbol: r.symbol,
// name: r.longname
// }));
// return new Promise<SearchTickerResult>((res, rej) => {
// socket.emit('stock-list-request', {
// q: searchTerm
Expand All @@ -50,6 +46,32 @@ export const searchTicker = async (searchTerm: string) => {
// });
}

export const useTickerSearch = (v: string) => {
const [options, setOptions] = useState<SearchTickerItem[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!v) {
setOptions([]);
return;
}
const ab = new AbortController();
const getData = async () => {
setLoading(true);
try {
const result = await searchTicker(v, ab.signal);
setOptions(result);
} catch (error) {
//do nothing
}
setLoading(false);
};
getData();
return () => { ab.abort(); }
}, [v]);

return { options, loading };
}

export const AddTickerToMyList = (item: SearchTickerItem) => {
socket.emit('mystocks-add-request', item);
}
Expand Down
71 changes: 71 additions & 0 deletions src/lib/tradierService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import ky from "ky";
import { TradierOptionData } from "./types";
const tradierBaseUri = process.env.TRADIER_BASE_URI || 'https://sandbox.tradier.com/';
const optionsChain = `${tradierBaseUri}v1/markets/options/chains`;
const lookup = `${tradierBaseUri}v1/markets/lookup`;
const optionsExpiration = `${tradierBaseUri}v1/markets/options/expirations`;
const getQuotes = `${tradierBaseUri}v1/markets/quotes`;

const client = ky.create({
headers: {
'Authorization': `Bearer ${process.env.TRADIER_TOKEN}`,
'Accept': 'application/json'
},
cache: 'no-cache'
});

export const getOptionExpirations = (symbol: string) => {
return client(optionsExpiration, {
searchParams: {
symbol
}
}).json<{ expirations: { date: string[] } }>();

}

export const getOptionData = (symbol: string, expiration: string) => {
return client(optionsChain, {
searchParams: {
symbol,
expiration,
'greeks': 'true'
}
}).json<TradierOptionData>();
}

export const getCurrentPrice = async (symbol: string) => {
const cp = await client(getQuotes, {
searchParams: {
symbols: symbol
}
}).json<{
quotes: {
quote: {
symbol: string,
last: number
}
}
}>();
return cp.quotes.quote
//.find(x => x.symbol === symbol)?
.last;
}

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

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

export const lookupSymbol = (q: string) => {
return client(lookup, {
searchParams: {
q,
//'types': 'stock, etf, index'
}
}).json<LookupSymbolResponse>();

}

0 comments on commit 2cad055

Please sign in to comment.