Skip to content

Commit 466661f

Browse files
committed
Statements table totals improved
1 parent 6c1cd47 commit 466661f

File tree

5 files changed

+777
-1173
lines changed

5 files changed

+777
-1173
lines changed

package.json

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,75 +34,75 @@
3434
"storybook:build": "storybook build -o dist/storybook"
3535
},
3636
"dependencies": {
37-
"@chakra-ui/icons": "^2.1.1",
37+
"@chakra-ui/icons": "^2.2.4",
3838
"@chakra-ui/layout": "^2.3.1",
39-
"@chakra-ui/react": "^2.8.2",
39+
"@chakra-ui/react": "^2.10.2",
4040
"@chakra-ui/system": "^2.6.2",
4141
"@emotion/react": "^11.13.3",
4242
"@emotion/styled": "^11.13.0",
4343
"@stoqey/ib": "stoqey/ib#7b0d422920ff88f56bb27b4ca3f8bd9b1ddb6dc5",
4444
"@tanstack/react-table": "^8.20.5",
45-
"chakra-react-select": "^4.9.2",
45+
"chakra-react-select": "^4.10.1",
4646
"chart.js": "^4.4.4",
4747
"cors": "^2.8.5",
4848
"dotenv": "^16.4.5",
49-
"express": "^4.21.0",
49+
"express": "^4.21.1",
5050
"fast-xml-parser": "^4.5.0",
5151
"formik": "^2.4.6",
52-
"framer-motion": "^11.7.0",
52+
"framer-motion": "^11.11.8",
5353
"json-stringify-safe": "^5.0.1",
5454
"react": "^18.3.1",
5555
"react-chartjs-2": "^5.2.0",
5656
"react-dom": "^18.3.1",
57-
"react-router-dom": "^6.26.2",
57+
"react-router-dom": "^6.27.0",
5858
"reflect-metadata": "^0.2.2",
59-
"sequelize": "^6.37.3",
59+
"sequelize": "^6.37.4",
6060
"sequelize-typescript": "^2.1.6",
6161
"sqlite3": "^5.1.7",
62-
"winston": "^3.14.2",
63-
"yahoo-finance2": "^2.12.4"
62+
"winston": "^3.15.0",
63+
"yahoo-finance2": "^2.13.2"
6464
},
6565
"devDependencies": {
66-
"@babel/core": "^7.25.2",
67-
"@chakra-ui/storybook-addon": "^5.1.0",
68-
"@eslint/compat": "^1.1.1",
69-
"@storybook/addon-actions": "^8.3.3",
70-
"@storybook/addon-essentials": "^8.3.3",
71-
"@storybook/addon-links": "^8.3.3",
72-
"@storybook/cli": "^8.3.3",
73-
"@storybook/components": "^8.3.3",
74-
"@storybook/react": "^8.3.3",
75-
"@storybook/react-vite": "^8.3.3",
66+
"@babel/core": "^7.25.8",
67+
"@chakra-ui/storybook-addon": "^5.2.5",
68+
"@eslint/compat": "^1.2.0",
69+
"@storybook/addon-actions": "^8.3.5",
70+
"@storybook/addon-essentials": "^8.3.5",
71+
"@storybook/addon-links": "^8.3.5",
72+
"@storybook/cli": "^8.3.5",
73+
"@storybook/components": "^8.3.5",
74+
"@storybook/react": "^8.3.5",
75+
"@storybook/react-vite": "^8.3.5",
7676
"@storybook/testing-library": "^0.2.2",
7777
"@types/cors": "^2.8.17",
7878
"@types/express": "^5.0.0",
7979
"@types/json-stringify-safe": "^5.0.3",
80-
"@types/node": "^20.16.9",
81-
"@types/react": "^18.3.9",
82-
"@types/react-dom": "^18.3.0",
80+
"@types/node": "^20.16.11",
81+
"@types/react": "^18.3.11",
82+
"@types/react-dom": "^18.3.1",
8383
"@types/react-router-dom": "^5.3.3",
8484
"@types/validator": "^13.12.2",
85-
"@vitejs/plugin-react": "^4.3.1",
85+
"@vitejs/plugin-react": "^4.3.2",
8686
"babel-loader": "^9.2.1",
8787
"concurrently": "^9.0.1",
88-
"eslint": "^9.11.1",
88+
"eslint": "^9.12.0",
8989
"eslint-config-prettier": "^9.1.0",
9090
"eslint-plugin-prettier": "^5.2.1",
91-
"eslint-plugin-react": "^7.36.1",
91+
"eslint-plugin-react": "^7.37.1",
9292
"eslint-plugin-rxjs": "^5.0.3",
9393
"eslint-plugin-storybook": "^0.9.0",
94-
"http-proxy-middleware": "^3.0.2",
94+
"http-proxy-middleware": "^3.0.3",
9595
"husky": "^9.1.6",
9696
"lint-staged": "^15.2.10",
9797
"patch-package": "^8.0.0",
9898
"prettier": "^3.3.3",
9999
"rxjs": "^7.8.1",
100-
"storybook": "^8.3.3",
101-
"stylelint": "^16.9.0",
100+
"storybook": "^8.3.5",
101+
"stylelint": "^16.10.0",
102102
"stylelint-config-standard": "^36.0.1",
103103
"ts-node-dev": "^2.0.0",
104-
"typescript": "^5.6.2",
105-
"typescript-eslint": "^8.7.0",
104+
"typescript": "^5.6.3",
105+
"typescript-eslint": "^8.8.1",
106106
"vite": "^5.4.8",
107107
"webpack": "^5.95.0"
108108
},

src/app/components/Portfolio/Statement/StatementsTable.tsx

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,30 @@ type Props = { content?: StatementEntry[]; currency?: string; title?: string };
3838
const StatementsTable: FunctionComponent<Props> = ({ content, currency, title, ..._rest }): React.ReactNode => {
3939
const { portfolioId } = useParams();
4040
const theStatements = content || (useLoaderData() as StatementEntry[]);
41+
4142
let previousId: number;
43+
44+
let currencyMismatch = false;
4245
let totalFees = 0;
46+
let totalFeesInCurrency = 0;
47+
let totalAmount = 0;
48+
let totalAmountInCurrency = 0;
49+
let totalPnL = 0;
50+
let totalPnLInCurrency = 0;
4351

4452
const savePrevious = (item: StatementEntry): undefined => {
4553
if (item.trade_id) previousId = item.trade_id;
46-
// switch (item.statementType) {
47-
// case StatementTypes.FeeStatement:
48-
// totalFees += item.amount * item.fxRateToBase;
49-
// break;
50-
// case StatementTypes.EquityStatement:
51-
// case StatementTypes.OptionStatement:
52-
// case StatementTypes.BondStatement:
53-
// totalFees += item.fees * item.fxRateToBase;
54-
// break;
55-
// }
56-
totalFees += "fees" in item ? item.fees * item.fxRateToBase : 0;
54+
currencyMismatch = item.currency == currency ? currencyMismatch : true;
55+
totalAmount += item.amount * item.fxRateToBase;
56+
totalAmountInCurrency += item.amount;
57+
if ("fees" in item) {
58+
totalFees += item.fees * item.fxRateToBase;
59+
totalFeesInCurrency += item.fees;
60+
}
61+
if ("pnl" in item) {
62+
totalPnL += item.pnl * item.fxRateToBase;
63+
totalPnLInCurrency += item.pnl;
64+
}
5765
return undefined;
5866
};
5967

@@ -188,29 +196,15 @@ const StatementsTable: FunctionComponent<Props> = ({ content, currency, title, .
188196
<Tfoot>
189197
<Tr fontWeight="bold">
190198
<Td>Total</Td>
191-
<Td>{currency ?? "Base"}</Td>
199+
<Td>{currencyMismatch ? "Base" : currency}</Td>
192200
<Td isNumeric>
193-
<Number
194-
value={theStatements.reduce(
195-
(p: number, item) =>
196-
(p += (item.amount || 0) * (currency ? (currency == item.currency ? 1 : 0) : item.fxRateToBase)),
197-
0,
198-
)}
199-
/>
201+
<Number value={currencyMismatch ? totalAmount : totalAmountInCurrency} />
200202
</Td>
201203
<Td isNumeric>
202-
<Number
203-
value={theStatements.reduce(
204-
(p: number, item) =>
205-
(p +=
206-
("pnl" in item ? item.pnl || 0 : 0) *
207-
(currency ? (currency == item.currency ? 1 : 0) : item.fxRateToBase)),
208-
0,
209-
)}
210-
/>
204+
<Number value={currencyMismatch ? totalPnL : totalPnLInCurrency} />
211205
</Td>
212206
<Td isNumeric>
213-
<Number value={totalFees} />
207+
<Number value={currencyMismatch ? totalFees : totalFeesInCurrency} />
214208
</Td>
215209
<Td></Td>
216210
<Td></Td>

src/bots/importer.bot.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const transactionDescriptionFromElement = (element: any): string => {
9595
let description = "";
9696
switch (transactionStatusFromElement(element)) {
9797
case StatementStatus.ASSIGNED_STATUS:
98+
console.log(MODULE + ".transactionDescriptionFromElement", element);
9899
description += "Assigned";
99100
break;
100101
case StatementStatus.EXPIRED_STATUS:
@@ -294,6 +295,7 @@ export class ImporterBot extends ITradingBot {
294295
Promise.resolve(undefined as undefined),
295296
)
296297
.then(() => {
298+
logger.info(MODULE + ".processAllSecuritiesInfo", undefined, "Securities info loaded");
297299
delete flexReport.SecuritiesInfo;
298300
return flexReport;
299301
});

src/routers/trades.utils.ts

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ const updateVirtuals = (
6262
virtuals: Record<number, VirtualPositionEntry>,
6363
statement_entry: StatementEntry,
6464
): Record<number, VirtualPositionEntry> => {
65-
let id;
66-
let virtual;
65+
let id: number;
66+
let virtual: VirtualPositionEntry | undefined;
6767
switch (statement_entry.statementType) {
6868
case StatementTypes.EquityStatement:
6969
case StatementTypes.BondStatement:
@@ -75,7 +75,8 @@ const updateVirtuals = (
7575
virtual.pru = virtual.cost / virtual.quantity;
7676
virtual.price = statement_entry.underlying?.price;
7777
virtual.pnl = virtual.pnl ? virtual.pnl + statement_entry.pnl! : statement_entry.pnl!;
78-
virtual.quantity += statement_entry.quantity!;
78+
// IB quantities can have 4 digits, therefore round to 4 digits to avoid floating point computation mistakes
79+
virtual.quantity = Math.round((virtual.quantity + statement_entry.quantity!) * 10000) / 10000;
7980
}
8081
break;
8182
case StatementTypes.OptionStatement:
@@ -194,7 +195,17 @@ export const tradeModelToTradeEntry = async (
194195
thisTrade.expectedDuration,
195196
thisTrade.expiryPnl,
196197
);
197-
return Promise.resolve({
198+
let apy: number | undefined = undefined;
199+
switch (thisTrade.status) {
200+
case TradeStatus.open:
201+
if (thisTrade.expectedDuration && thisTrade.risk)
202+
apy = ((thisTrade.PnL! + thisTrade.expiryPnl!) / -thisTrade.risk) * (360 / thisTrade.expectedDuration);
203+
break;
204+
case TradeStatus.closed:
205+
apy = (thisTrade.PnL! / -thisTrade.risk!) * (360 / thisTrade.duration);
206+
break;
207+
}
208+
const trade_entry: TradeEntry = {
198209
id: thisTrade.id,
199210
portfolioId: thisTrade.portfolio_id,
200211
underlying: thisTrade.underlying,
@@ -203,57 +214,37 @@ export const tradeModelToTradeEntry = async (
203214
closingDate: thisTrade.closingDate ? thisTrade.closingDate.getTime() : undefined,
204215
status: thisTrade.status,
205216
duration: thisTrade.duration,
206-
expectedExpiry: thisTrade.expectedExpiry,
217+
expectedExpiry: thisTrade.expectedExpiry || undefined,
207218
expectedDuration: thisTrade.expectedDuration,
208219
strategy: thisTrade.strategy,
209220
risk: thisTrade.risk,
210221
pnl: thisTrade.PnL,
211-
unrlzdPnl: thisTrade.expiryPnl,
222+
unrlzdPnl: thisTrade.expiryPnl || undefined,
212223
pnlInBase: thisTrade.pnlInBase,
213-
apy:
214-
thisTrade.risk && thisTrade.expectedDuration
215-
? ((thisTrade.PnL! + thisTrade.expiryPnl!) / -thisTrade.risk) * (360 / thisTrade.expectedDuration)
216-
: undefined,
224+
apy,
217225
comment: thisTrade.comment,
218226
statements: undefined,
219227
positions: undefined,
220228
virtuals: undefined,
221-
} as TradeEntry)
222-
.then(async (trade_entry) => {
223-
// Add statements
224-
if (thisTrade.statements) {
225-
return prepareStatements(thisTrade.statements).then((statements) => {
226-
trade_entry.statements = statements;
227-
return trade_entry;
228-
});
229-
} else return trade_entry;
230-
})
231-
.then(async (trade_entry) => {
232-
// Add positions
233-
return Portfolio.findByPk(thisTrade.portfolio_id, {
234-
include: [
235-
{
236-
model: Position,
237-
as: "positions",
238-
where: { trade_unit_id: thisTrade.id },
239-
include: [{ model: Contract, as: "contract" }],
240-
required: false,
241-
},
242-
{ model: Currency, as: "baseRates" },
243-
],
244-
}).then(async (portfolio) => {
245-
if (!portfolio) throw Error("portfolio not found: " + thisTrade.portfolio_id);
246-
return preparePositions(portfolio).then((positions) => {
247-
trade_entry.positions = positions;
248-
return trade_entry;
249-
});
250-
});
251-
})
252-
.then((trade_entry) => {
253-
const virtuals = makeVirtualPositions(trade_entry.statements);
254-
trade_entry.virtuals = Object.values(virtuals);
255-
return trade_entry;
256-
});
229+
};
230+
if (thisTrade.statements) {
231+
trade_entry.statements = await prepareStatements(thisTrade.statements);
232+
}
233+
const portfolio = await Portfolio.findByPk(thisTrade.portfolio_id, {
234+
include: [
235+
{
236+
model: Position,
237+
as: "positions",
238+
where: { trade_unit_id: thisTrade.id },
239+
include: [{ model: Contract, as: "contract" }],
240+
required: false,
241+
},
242+
{ model: Currency, as: "baseRates" },
243+
],
244+
});
245+
if (portfolio) trade_entry.positions = await preparePositions(portfolio);
246+
trade_entry.virtuals = Object.values(makeVirtualPositions(trade_entry.statements));
247+
return trade_entry;
257248
};
258249

259250
const formatDate = (when: Date): string => {

0 commit comments

Comments
 (0)