Skip to content

Commit f5bd4be

Browse files
committed
Add a Trading Fee Calculator section
Also: Add disabled prop and fix issue mounting with default value in numeric input
1 parent 65c0fde commit f5bd4be

File tree

4 files changed

+153
-5
lines changed

4 files changed

+153
-5
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"cSpell.words": ["elif", "Forex", "octocat", "TTFB", "webfonts"]
2+
"cSpell.words": ["elif", "Forex", "Nett", "octocat", "TTFB", "webfonts"]
33
}

src/components/Text.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const adornCSS: React.CSSProperties = {
1414
};
1515

1616
export default function Text({
17+
disabled,
1718
hidden,
1819
label,
1920
xs,
@@ -24,6 +25,7 @@ export default function Text({
2425
baseAdornment = "",
2526
adornment = "ABC",
2627
}: {
28+
disabled?: boolean;
2729
hidden?: boolean;
2830
label: string;
2931
xs?: boolean;
@@ -34,15 +36,18 @@ export default function Text({
3436
baseAdornment?: string;
3537
adornment?: string;
3638
}) {
39+
const [mounted, setMounted] = useState<boolean>(false);
3740
const [stringValue, setStringValue] = useState<string>("");
3841
const [hover, setHover] = useState<boolean>(false);
3942

4043
useEffect(() => {
4144
if (value === undefined) setStringValue("");
4245
else setStringValue(value.toFixed());
46+
setMounted(true);
4347
}, [value]);
4448

4549
useEffect(() => {
50+
if (!mounted) return;
4651
if (regex.test(stringValue)) {
4752
// Allow trailing decimal point when typing
4853
if (stringValue.endsWith(".")) return;
@@ -52,9 +57,11 @@ export default function Text({
5257
onChange(undefined);
5358
}
5459
} else onChange(undefined);
55-
}, [onChange, stringValue]);
60+
}, [mounted, onChange, stringValue]);
5661

57-
const helperText = (
62+
const helperText = disabled ? (
63+
<>{label}</>
64+
) : (
5865
<>
5966
{label}{" "}
6067
<IconSpan
@@ -88,6 +95,7 @@ export default function Text({
8895

8996
return (
9097
<TextField
98+
disabled={disabled}
9199
label={
92100
baseAmount && baseAdornment ? (
93101
<span style={adornCSS}>

src/positions/PositionCalculator.tsx

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export default function PositionCalculator({ type }: { type: Position }) {
2727
const [stop, setStop] = useState<Decimal>();
2828
const [riskPercent, setRiskPercent] = useState<Decimal>();
2929
const [riskAmount, setRiskAmount] = useState<Decimal>();
30+
const [feePercent, setFeePercent] = useState<Decimal | undefined>(
31+
new Decimal(0.075) // Binance Fee when using BNB with 25% discount
32+
);
3033

3134
const [error, setError] = useState<Error>(noError);
3235

@@ -72,6 +75,22 @@ export default function PositionCalculator({ type }: { type: Position }) {
7275
setRiskAmount(computed.riskAmount);
7376
};
7477

78+
const computeNettProfit = () => {
79+
if (!feePercent || !tpSellAmount || !buyAmount || !rewardAmount) return;
80+
const entryFee = buyAmount.mul(feePercent).div(100);
81+
const tpFee = tpSellAmount.mul(feePercent).div(100);
82+
const allFees = entryFee.plus(tpFee);
83+
return isShort ? rewardAmount.plus(allFees) : rewardAmount.minus(allFees);
84+
};
85+
86+
const computeNettLoss = () => {
87+
if (!feePercent || !slSellAmount || !buyAmount || !riskAmount) return;
88+
const entryFee = buyAmount.mul(feePercent).div(100);
89+
const slFee = slSellAmount.mul(feePercent).div(100);
90+
const allFees = entryFee.plus(slFee);
91+
return isShort ? riskAmount.minus(allFees) : riskAmount.plus(allFees);
92+
};
93+
7594
const resetAllState = () => {
7695
setTarget(undefined);
7796
setRewardPercent(undefined);
@@ -94,6 +113,7 @@ export default function PositionCalculator({ type }: { type: Position }) {
94113
const isError = Object.values(error).some((value) => value);
95114

96115
const baseAdornment = "XYZ";
116+
const quoteAdornment = "ABC";
97117
const green = "#003705";
98118
const red = "#5a2d2d";
99119
const config = {
@@ -116,9 +136,13 @@ export default function PositionCalculator({ type }: { type: Position }) {
116136
aboveColor: isShort ? red : green,
117137
belowHeight: isShort ? 150 : 50,
118138
belowColor: isShort ? green : red,
139+
tpFee: isShort ? "SL Fee" : "TP Fee",
140+
slFee: isShort ? "TP Fee" : "SL Fee",
141+
nettProfit: isShort ? "Nett Loss After Fees" : "Nett Profit After Fees",
142+
nettLoss: isShort ? "Nett Profit After Fees" : "Nett Loss After Fees",
119143
};
120144

121-
return (
145+
const mainCalculator = (
122146
<>
123147
<Alert severity={isError ? "error" : "info"}>
124148
{isError
@@ -142,6 +166,8 @@ export default function PositionCalculator({ type }: { type: Position }) {
142166
error={error.rewardPercent}
143167
/>
144168
<Text
169+
baseAmount={computeNettProfit()}
170+
baseAdornment={quoteAdornment}
145171
label={config.rewardAmount}
146172
xs
147173
value={rewardAmount}
@@ -258,6 +284,8 @@ export default function PositionCalculator({ type }: { type: Position }) {
258284
error={error.riskPercent}
259285
/>
260286
<Text
287+
baseAmount={computeNettLoss()}
288+
baseAdornment={quoteAdornment}
261289
label={config.riskAmount}
262290
xs
263291
value={riskAmount}
@@ -275,4 +303,108 @@ export default function PositionCalculator({ type }: { type: Position }) {
275303
</Box>
276304
</>
277305
);
306+
307+
const feeCalculator = (
308+
<>
309+
<Box
310+
sx={{
311+
display: "flex",
312+
justifyContent: "space-around",
313+
alignItems: "center",
314+
width: "100%",
315+
}}
316+
>
317+
<h6>Trading Fees</h6>
318+
<Text
319+
adornment="%"
320+
label="Trading Fee %"
321+
xs
322+
value={feePercent}
323+
onChange={setFeePercent}
324+
error={false}
325+
/>
326+
</Box>
327+
<Box
328+
sx={{
329+
display: "flex",
330+
flexDirection: isShort ? "column-reverse" : "column",
331+
}}
332+
>
333+
<TextBoxes>
334+
<Text
335+
disabled
336+
label={config.tpFee}
337+
value={tpSellAmount?.mul(feePercent || 0).div(100)}
338+
onChange={() => {}}
339+
error={false}
340+
/>
341+
<Text
342+
disabled
343+
label={config.nettProfit}
344+
value={computeNettProfit()}
345+
onChange={() => {}}
346+
error={false}
347+
/>
348+
</TextBoxes>
349+
<TextBoxes>
350+
<Text
351+
disabled
352+
label="Entry Fee"
353+
value={buyAmount?.mul(feePercent || 0).div(100)}
354+
onChange={() => {}}
355+
error={false}
356+
/>
357+
</TextBoxes>
358+
<TextBoxes>
359+
<Text
360+
disabled
361+
label={config.slFee}
362+
value={slSellAmount?.mul(feePercent || 0).div(100)}
363+
onChange={() => {}}
364+
error={false}
365+
/>
366+
<Text
367+
disabled
368+
label={config.nettLoss}
369+
value={computeNettLoss()}
370+
onChange={() => {}}
371+
error={false}
372+
/>
373+
</TextBoxes>
374+
</Box>
375+
</>
376+
);
377+
378+
return (
379+
<Box
380+
sx={{
381+
marginTop: 2,
382+
display: "flex",
383+
flexDirection: "row",
384+
justifyContent: "center",
385+
alignItems: "center",
386+
flexWrap: "wrap",
387+
gap: 5,
388+
}}
389+
>
390+
<Box
391+
sx={{
392+
display: "flex",
393+
flexDirection: "column",
394+
alignItems: "center",
395+
}}
396+
>
397+
{mainCalculator}
398+
</Box>
399+
<Box
400+
sx={{
401+
display: "flex",
402+
flexDirection: "column",
403+
alignItems: "center",
404+
}}
405+
>
406+
{feeCalculator}
407+
</Box>
408+
</Box>
409+
);
278410
}

src/utils/compute/formulae.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,12 @@ Stop = Entry Price * Risk %
2828
Entry Price = Stop / Risk %
2929
Risk Amount = Buy Amount * Risk %
3030
Risk % = Risk Amount / Buy Amount
31-
Sell Amount (USD) = Buy Amount (USD) - Risk Amount (USD)
31+
Sell Amount (USD) = Buy Amount (USD) - Risk Amount (USD)
32+
33+
Trading Fees:
34+
35+
Entry Fee = Buy Amount * Fee %
36+
TP Fee = TP Sell Amount * Fee %
37+
Nett Profit = Reward Amount - (Entry Fee + TP Fee)
38+
SL Fee = SL Sell Amount * Fee %
39+
Nett Loss = Risk Amount + (Entry Fee + SL Fee)

0 commit comments

Comments
 (0)