Skip to content

Commit

Permalink
Merge pull request #11 from mnsrulz/main
Browse files Browse the repository at this point in the history
sync: main to stage
  • Loading branch information
mnsrulz authored Apr 17, 2024
2 parents b635ca9 + 118bb05 commit aea7a8f
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 13 deletions.
9 changes: 9 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-date-pickers": "^6.19.9",
"@prisma/client": "^5.12.1",
"dayjs": "^1.11.10",
"human-format": "^1.2.0",
"ky": "^1.2.3",
"localforage": "^1.10.0",
"lodash.debounce": "^4.0.8",
Expand Down
12 changes: 12 additions & 0 deletions src/app/api/watchlist/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,16 @@ export async function POST(request: Request) {
})
}
return NextResponse.json(inputJson);
}

//may be better to expose as a separte endoint??
export async function DELETE(request: Request) {
const inputJson: AddToWatchlistRequest = await request.json();
const { symbol } = inputJson;
await prisma.watchlist.delete({
where: {
symbol
}
});
return NextResponse.json(inputJson);
}
189 changes: 185 additions & 4 deletions src/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,25 @@ import {
} from 'react-router-dom';

import { StaticRouter } from 'react-router-dom/server';
import { ButtonGroup, Button } from '@mui/material';
import { ButtonGroup, Button, AppBar, Avatar, Container, IconButton, Menu, MenuItem, Toolbar, Tooltip } from '@mui/material';
import { useState } from 'react';
import MenuIcon from '@mui/icons-material/Menu';
import AdbIcon from '@mui/icons-material/Adb';
import AccountCircle from '@mui/icons-material/AccountCircle';
import { signOut } from 'next-auth/react'

/*
<Button><Link href="/" className=''>Home</Link></Button>
<Button><Link href="/trades" className=''>Trades</Link></Button>
<Button><Link href="/history">History</Link></Button>
*/

const pages = [
{ title: 'Home', href: '/' },
{ title: 'Trades', href: '/trades' },
{ title: 'History', href: '/history' }
];
const settings = ['Profile', 'Logout'];

function Router(props: { children?: React.ReactNode }) {
const { children } = props;
Expand Down Expand Up @@ -74,14 +92,177 @@ function CurrentRoute() {
}

export default function TabsRouter() {
const [anchorElNav, setAnchorElNav] = useState<null | HTMLElement>(null);
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);

const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElNav(event.currentTarget);
};
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElUser(event.currentTarget);
};

const handleCloseNavMenu = () => {
setAnchorElNav(null);
};

const handleCloseUserMenu = () => {
setAnchorElUser(null);
};

const handleSignout = () => {
handleCloseUserMenu();
signOut();
}

return (
<Router>
<Box sx={{ width: '100%', padding: '0px 0px 20px 0px' }} >
{/* <Routes>
{/* <Routes>
<Route path="*" element={<CurrentRoute />} />
</Routes> */}
{/* <Box sx={{ width: '100%', padding: '0px 0px 20px 0px' }} >
<MyTabs />
</Box>
</Box> */}
<AppBar position="static" sx={{ margin: '0px 0px 20px 0px' }}>
<Container maxWidth="xl">
<Toolbar disableGutters>
{/* <AdbIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} /> */}
{/* <Typography
variant="h6"
noWrap
component="a"
href="#app-bar-with-responsive-menu"
sx={{
mr: 2,
display: { xs: 'none', md: 'flex' },
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
MZ
</Typography> */}

<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: { xs: 'block', md: 'none' },
}}
>
{pages.map((page) => (
<MenuItem key={page.title} onClick={handleCloseNavMenu}>
<Button LinkComponent={Link} href={page.href}>
<Typography textAlign="center">{page.title}</Typography>
</Button>
{/* <Link role="menuitem" href={page.href}>
</Link> */}
{/* {page.title} */}

{/* <Button
key={page.title}
onClick={handleCloseNavMenu}
href={page.href}
LinkComponent={Link}
sx={{ my: 2, color: 'white', display: 'block' }}>
</Button> */}
</MenuItem>
))}
</Menu>
</Box>
{/* <AdbIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
<Typography
variant="h5"
noWrap
component="a"
href="#app-bar-with-responsive-menu"
sx={{
mr: 2,
display: { xs: 'flex', md: 'none' },
flexGrow: 1,
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
LOGO
</Typography> */}
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{pages.map((page) => (
<Button
key={page.title}
onClick={handleCloseNavMenu}
href={page.href}
LinkComponent={Link}
sx={{ my: 2, color: 'white', display: 'block' }}>
{page.title}
{/* <Link href={page.href}>
</Link> */}
</Button>
))}
</Box>

<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<AccountCircle />
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
{/* {settings.map((setting) => (
<MenuItem key={setting} onClick={handleCloseUserMenu}>
<Typography textAlign="center">{setting}</Typography>
</MenuItem>
))} */}
<MenuItem key='signout' onClick={handleSignout}>
<Typography textAlign="center">Sign Out</Typography>
</MenuItem>
</Menu>
</Box>
</Toolbar>
</Container>
</AppBar>
</Router>
);
}
46 changes: 42 additions & 4 deletions src/components/trades.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@ import CloseIcon from '@mui/icons-material/Close';
import dayjs from 'dayjs';
import { CloseTradeCloseDialogReason, CloseTradeDialog } from "./close-trade";
import { ConfirmDialog } from "./confirm-dialog";
import { FormControlLabel, Switch } from "@mui/material";
import { Card, CardContent, FormControlLabel, Grid, LinearProgress, Paper, Switch, Typography } from "@mui/material";

import { ConditionalFormattingBox } from "./conditional-formatting";
import { useTrades } from "@/lib/useTrades";
import { currencyFormatter, fixedCurrencyFormatter, percentageFormatter } from "@/lib/formatters";
import { ITradeView } from "@/lib/types";
import { TickerName } from "./TickerName";
import humanFormat from "human-format";

const dateFormatter = (v: string) => v && dayjs(v.substring(0, 10)).format('DD/MM/YYYY'); //to avoid utc conversion strip the time part
export const shortDateFormatter = (v: string) => v && dayjs(v.substring(0, 10)).format('DD/MM/YYYY'); //to avoid utc conversion strip the time part

export const TradeList = () => {
const { trades, deleteTrade, reloadTrade } = useTrades();
const { trades, deleteTrade, reloadTrade, isLoading } = useTrades();
const [showCloseTrades, toggleShowCloseTrades] = useState(false);//useShowCloseTrades();
const traderows = showCloseTrades ? trades : trades.filter(x => !x.isClosed);
const [currentTrade, setCurrentTrade] = useState<ITradeView | null>(null);
const apiRef = useGridApiRef();
const totalRisk = trades.filter(t => !t.transactionEndDate).map(t => t.maximumRisk).reduce((a, b) => a + b, 0); //arr.reduce((a, b) => a + b, 0);
const potentialProfit = trades.filter(t => !t.transactionEndDate).map(t => t.maximumProfit).reduce((a, b) => a + b, 0); //arr.reduce((a, b) => a + b, 0);
const openTradesGainAndLoss = trades.filter(t => !t.transactionEndDate).map(t => t.actualProfit).reduce((a, b) => a + b, 0); //arr.reduce((a, b) => a + b, 0);

const [openCloseTrade, setOpenCloseTrade] = useState(false);
const [isDeleteTradeOpen, setisDeleteTradeOpen] = useState(false);
Expand All @@ -49,7 +53,7 @@ export const TradeList = () => {
// { field: 'strikePrice', width: 90, headerName: 'Strike Price', type: 'number', valueFormatter: currencyFormatter },
// { field: 'numberOfContracts', width: 60, sortable: false, filterable: false, hideable: false, headerName: 'Size', type: 'number' },
{ field: 'contractPrice', width: 60, headerName: 'Price', type: 'number', valueFormatter: currencyFormatter },
{ field: 'lastContractPrice', width: 60, headerName: 'Price', type: 'number', valueFormatter: currencyFormatter },
{ field: 'lastContractPrice', width: 80, headerName: 'Last Price', type: 'number', valueFormatter: currencyFormatter },
{ field: 'buyCost', width: 70, headerName: 'Buy Cost', type: 'number', valueFormatter: fixedCurrencyFormatter },
{ field: 'sellCost', width: 70, headerName: 'Sell Cost', type: 'number', valueFormatter: fixedCurrencyFormatter },
{
Expand All @@ -64,6 +68,10 @@ export const TradeList = () => {
field: 'actualProfitPerDay', width: 70, headerName: 'PnL/day', type: 'number', valueFormatter: currencyFormatter,
renderCell: (p) => <ConditionalFormattingBox value={p.value * 10} formattedValue={p.formattedValue} />
},
{
field: 'remainingProfitPerDay', width: 70, headerName: 'Remaining PnL/day', type: 'number', valueFormatter: currencyFormatter,
renderCell: (p) => <ConditionalFormattingBox value={p.value * 10} formattedValue={p.formattedValue} />
},
{
field: 'maximumRisk', width: 60, headerName: 'Risk', type: 'number', valueFormatter: fixedCurrencyFormatter,
renderCell: (p) => <ConditionalFormattingBox value={-p.value / 100} formattedValue={p.formattedValue} />
Expand Down Expand Up @@ -123,7 +131,37 @@ export const TradeList = () => {
deleteTrade(deleteTradeId);
}

return <div>
return isLoading ? <LinearProgress /> : <div>
<Card>
<CardContent>
<Grid container >
<Grid item xs={4} md={3}>
<Typography color="text.secondary" gutterBottom>
Risk
</Typography>
<Typography variant="h5">
{humanFormat(totalRisk)}
</Typography>
</Grid>
<Grid item xs={4} md={3}>
<Typography color="text.secondary" gutterBottom>
Max Profit
</Typography>
<Typography variant="h5">
{humanFormat(potentialProfit)}
</Typography>
</Grid>
<Grid item xs={4} md={3}>
<Typography color="text.secondary" gutterBottom>
PnL
</Typography>
<Typography variant="h5">
{humanFormat(openTradesGainAndLoss)}
</Typography>
</Grid>
</Grid>
</CardContent>
</Card>
<FormControlLabel control={<Switch checked={showCloseTrades} onChange={(e, v) => toggleShowCloseTrades(v)} />} label="Show closed trades?" />
<DataGrid rows={traderows}
disableColumnMenu={true}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const useMyStockList = () => {
}, []);

const removeFromWatchlist = useCallback((item: SearchTickerItem) => {
ky.delete(`/api/watchlist`, { json: item }).json().then(r => setMyTickers(mytickers.filter(i => i.symbol != item.symbol)));
ky.delete(`/api/watchlist`, { json: item }).json().then(r => setMyTickers((ii) => ii.filter(i => i.symbol != item.symbol)));
}, []);

return { mytickers, addToWatchlist, removeFromWatchlist };
Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface ITradeView extends Trade {
actualProfit: number
actualAnnualizedReturn: number
actualProfitPerDay: number
remainingProfitPerDay: number
buyCost: number,
sellCost: number,
isClosed: boolean,
Expand Down
Loading

0 comments on commit aea7a8f

Please sign in to comment.