Skip to content

Commit

Permalink
Statements table totals improved
Browse files Browse the repository at this point in the history
  • Loading branch information
rylorin committed Oct 12, 2024
1 parent 6c1cd47 commit 466661f
Show file tree
Hide file tree
Showing 5 changed files with 777 additions and 1,173 deletions.
60 changes: 30 additions & 30 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,75 +34,75 @@
"storybook:build": "storybook build -o dist/storybook"
},
"dependencies": {
"@chakra-ui/icons": "^2.1.1",
"@chakra-ui/icons": "^2.2.4",
"@chakra-ui/layout": "^2.3.1",
"@chakra-ui/react": "^2.8.2",
"@chakra-ui/react": "^2.10.2",
"@chakra-ui/system": "^2.6.2",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@stoqey/ib": "stoqey/ib#7b0d422920ff88f56bb27b4ca3f8bd9b1ddb6dc5",
"@tanstack/react-table": "^8.20.5",
"chakra-react-select": "^4.9.2",
"chakra-react-select": "^4.10.1",
"chart.js": "^4.4.4",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"express": "^4.21.1",
"fast-xml-parser": "^4.5.0",
"formik": "^2.4.6",
"framer-motion": "^11.7.0",
"framer-motion": "^11.11.8",
"json-stringify-safe": "^5.0.1",
"react": "^18.3.1",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.2",
"react-router-dom": "^6.27.0",
"reflect-metadata": "^0.2.2",
"sequelize": "^6.37.3",
"sequelize": "^6.37.4",
"sequelize-typescript": "^2.1.6",
"sqlite3": "^5.1.7",
"winston": "^3.14.2",
"yahoo-finance2": "^2.12.4"
"winston": "^3.15.0",
"yahoo-finance2": "^2.13.2"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@chakra-ui/storybook-addon": "^5.1.0",
"@eslint/compat": "^1.1.1",
"@storybook/addon-actions": "^8.3.3",
"@storybook/addon-essentials": "^8.3.3",
"@storybook/addon-links": "^8.3.3",
"@storybook/cli": "^8.3.3",
"@storybook/components": "^8.3.3",
"@storybook/react": "^8.3.3",
"@storybook/react-vite": "^8.3.3",
"@babel/core": "^7.25.8",
"@chakra-ui/storybook-addon": "^5.2.5",
"@eslint/compat": "^1.2.0",
"@storybook/addon-actions": "^8.3.5",
"@storybook/addon-essentials": "^8.3.5",
"@storybook/addon-links": "^8.3.5",
"@storybook/cli": "^8.3.5",
"@storybook/components": "^8.3.5",
"@storybook/react": "^8.3.5",
"@storybook/react-vite": "^8.3.5",
"@storybook/testing-library": "^0.2.2",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/json-stringify-safe": "^5.0.3",
"@types/node": "^20.16.9",
"@types/react": "^18.3.9",
"@types/react-dom": "^18.3.0",
"@types/node": "^20.16.11",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/react-router-dom": "^5.3.3",
"@types/validator": "^13.12.2",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react": "^4.3.2",
"babel-loader": "^9.2.1",
"concurrently": "^9.0.1",
"eslint": "^9.11.1",
"eslint": "^9.12.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.36.1",
"eslint-plugin-react": "^7.37.1",
"eslint-plugin-rxjs": "^5.0.3",
"eslint-plugin-storybook": "^0.9.0",
"http-proxy-middleware": "^3.0.2",
"http-proxy-middleware": "^3.0.3",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"patch-package": "^8.0.0",
"prettier": "^3.3.3",
"rxjs": "^7.8.1",
"storybook": "^8.3.3",
"stylelint": "^16.9.0",
"storybook": "^8.3.5",
"stylelint": "^16.10.0",
"stylelint-config-standard": "^36.0.1",
"ts-node-dev": "^2.0.0",
"typescript": "^5.6.2",
"typescript-eslint": "^8.7.0",
"typescript": "^5.6.3",
"typescript-eslint": "^8.8.1",
"vite": "^5.4.8",
"webpack": "^5.95.0"
},
Expand Down
52 changes: 23 additions & 29 deletions src/app/components/Portfolio/Statement/StatementsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,30 @@ type Props = { content?: StatementEntry[]; currency?: string; title?: string };
const StatementsTable: FunctionComponent<Props> = ({ content, currency, title, ..._rest }): React.ReactNode => {
const { portfolioId } = useParams();
const theStatements = content || (useLoaderData() as StatementEntry[]);

let previousId: number;

let currencyMismatch = false;
let totalFees = 0;
let totalFeesInCurrency = 0;
let totalAmount = 0;
let totalAmountInCurrency = 0;
let totalPnL = 0;
let totalPnLInCurrency = 0;

const savePrevious = (item: StatementEntry): undefined => {
if (item.trade_id) previousId = item.trade_id;
// switch (item.statementType) {
// case StatementTypes.FeeStatement:
// totalFees += item.amount * item.fxRateToBase;
// break;
// case StatementTypes.EquityStatement:
// case StatementTypes.OptionStatement:
// case StatementTypes.BondStatement:
// totalFees += item.fees * item.fxRateToBase;
// break;
// }
totalFees += "fees" in item ? item.fees * item.fxRateToBase : 0;
currencyMismatch = item.currency == currency ? currencyMismatch : true;
totalAmount += item.amount * item.fxRateToBase;
totalAmountInCurrency += item.amount;
if ("fees" in item) {
totalFees += item.fees * item.fxRateToBase;
totalFeesInCurrency += item.fees;
}
if ("pnl" in item) {
totalPnL += item.pnl * item.fxRateToBase;
totalPnLInCurrency += item.pnl;
}
return undefined;
};

Expand Down Expand Up @@ -188,29 +196,15 @@ const StatementsTable: FunctionComponent<Props> = ({ content, currency, title, .
<Tfoot>
<Tr fontWeight="bold">
<Td>Total</Td>
<Td>{currency ?? "Base"}</Td>
<Td>{currencyMismatch ? "Base" : currency}</Td>
<Td isNumeric>
<Number
value={theStatements.reduce(
(p: number, item) =>
(p += (item.amount || 0) * (currency ? (currency == item.currency ? 1 : 0) : item.fxRateToBase)),
0,
)}
/>
<Number value={currencyMismatch ? totalAmount : totalAmountInCurrency} />
</Td>
<Td isNumeric>
<Number
value={theStatements.reduce(
(p: number, item) =>
(p +=
("pnl" in item ? item.pnl || 0 : 0) *
(currency ? (currency == item.currency ? 1 : 0) : item.fxRateToBase)),
0,
)}
/>
<Number value={currencyMismatch ? totalPnL : totalPnLInCurrency} />
</Td>
<Td isNumeric>
<Number value={totalFees} />
<Number value={currencyMismatch ? totalFees : totalFeesInCurrency} />
</Td>
<Td></Td>
<Td></Td>
Expand Down
2 changes: 2 additions & 0 deletions src/bots/importer.bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const transactionDescriptionFromElement = (element: any): string => {
let description = "";
switch (transactionStatusFromElement(element)) {
case StatementStatus.ASSIGNED_STATUS:
console.log(MODULE + ".transactionDescriptionFromElement", element);
description += "Assigned";
break;
case StatementStatus.EXPIRED_STATUS:
Expand Down Expand Up @@ -294,6 +295,7 @@ export class ImporterBot extends ITradingBot {
Promise.resolve(undefined as undefined),
)
.then(() => {
logger.info(MODULE + ".processAllSecuritiesInfo", undefined, "Securities info loaded");
delete flexReport.SecuritiesInfo;
return flexReport;
});
Expand Down
83 changes: 37 additions & 46 deletions src/routers/trades.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ const updateVirtuals = (
virtuals: Record<number, VirtualPositionEntry>,
statement_entry: StatementEntry,
): Record<number, VirtualPositionEntry> => {
let id;
let virtual;
let id: number;
let virtual: VirtualPositionEntry | undefined;
switch (statement_entry.statementType) {
case StatementTypes.EquityStatement:
case StatementTypes.BondStatement:
Expand All @@ -75,7 +75,8 @@ const updateVirtuals = (
virtual.pru = virtual.cost / virtual.quantity;
virtual.price = statement_entry.underlying?.price;
virtual.pnl = virtual.pnl ? virtual.pnl + statement_entry.pnl! : statement_entry.pnl!;
virtual.quantity += statement_entry.quantity!;
// IB quantities can have 4 digits, therefore round to 4 digits to avoid floating point computation mistakes
virtual.quantity = Math.round((virtual.quantity + statement_entry.quantity!) * 10000) / 10000;
}
break;
case StatementTypes.OptionStatement:
Expand Down Expand Up @@ -194,7 +195,17 @@ export const tradeModelToTradeEntry = async (
thisTrade.expectedDuration,
thisTrade.expiryPnl,
);
return Promise.resolve({
let apy: number | undefined = undefined;
switch (thisTrade.status) {
case TradeStatus.open:
if (thisTrade.expectedDuration && thisTrade.risk)
apy = ((thisTrade.PnL! + thisTrade.expiryPnl!) / -thisTrade.risk) * (360 / thisTrade.expectedDuration);
break;
case TradeStatus.closed:
apy = (thisTrade.PnL! / -thisTrade.risk!) * (360 / thisTrade.duration);
break;
}
const trade_entry: TradeEntry = {
id: thisTrade.id,
portfolioId: thisTrade.portfolio_id,
underlying: thisTrade.underlying,
Expand All @@ -203,57 +214,37 @@ export const tradeModelToTradeEntry = async (
closingDate: thisTrade.closingDate ? thisTrade.closingDate.getTime() : undefined,
status: thisTrade.status,
duration: thisTrade.duration,
expectedExpiry: thisTrade.expectedExpiry,
expectedExpiry: thisTrade.expectedExpiry || undefined,
expectedDuration: thisTrade.expectedDuration,
strategy: thisTrade.strategy,
risk: thisTrade.risk,
pnl: thisTrade.PnL,
unrlzdPnl: thisTrade.expiryPnl,
unrlzdPnl: thisTrade.expiryPnl || undefined,
pnlInBase: thisTrade.pnlInBase,
apy:
thisTrade.risk && thisTrade.expectedDuration
? ((thisTrade.PnL! + thisTrade.expiryPnl!) / -thisTrade.risk) * (360 / thisTrade.expectedDuration)
: undefined,
apy,
comment: thisTrade.comment,
statements: undefined,
positions: undefined,
virtuals: undefined,
} as TradeEntry)
.then(async (trade_entry) => {
// Add statements
if (thisTrade.statements) {
return prepareStatements(thisTrade.statements).then((statements) => {
trade_entry.statements = statements;
return trade_entry;
});
} else return trade_entry;
})
.then(async (trade_entry) => {
// Add positions
return Portfolio.findByPk(thisTrade.portfolio_id, {
include: [
{
model: Position,
as: "positions",
where: { trade_unit_id: thisTrade.id },
include: [{ model: Contract, as: "contract" }],
required: false,
},
{ model: Currency, as: "baseRates" },
],
}).then(async (portfolio) => {
if (!portfolio) throw Error("portfolio not found: " + thisTrade.portfolio_id);
return preparePositions(portfolio).then((positions) => {
trade_entry.positions = positions;
return trade_entry;
});
});
})
.then((trade_entry) => {
const virtuals = makeVirtualPositions(trade_entry.statements);
trade_entry.virtuals = Object.values(virtuals);
return trade_entry;
});
};
if (thisTrade.statements) {
trade_entry.statements = await prepareStatements(thisTrade.statements);
}
const portfolio = await Portfolio.findByPk(thisTrade.portfolio_id, {
include: [
{
model: Position,
as: "positions",
where: { trade_unit_id: thisTrade.id },
include: [{ model: Contract, as: "contract" }],
required: false,
},
{ model: Currency, as: "baseRates" },
],
});
if (portfolio) trade_entry.positions = await preparePositions(portfolio);
trade_entry.virtuals = Object.values(makeVirtualPositions(trade_entry.statements));
return trade_entry;
};

const formatDate = (when: Date): string => {
Expand Down
Loading

0 comments on commit 466661f

Please sign in to comment.