From a01d322c75edc58d1aee94b4e1922ab223d665ff Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:08:47 +0200 Subject: [PATCH] feat: effective per-coin rate in rate card + funding display in wallet analysis --- src/app/api/fee-compare/route.ts | 27 +++++++++++ src/components/fee-compare-client.tsx | 68 +++++++++++++++++++-------- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index b68fa1f8..00f6cd79 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -134,6 +134,8 @@ type VenueResult = { rateNote: string; rateIsLive: boolean; wallet: AnyWallet | null; + effectiveRateBps?: number; + effectiveRateNote?: string; }; type SimResult = { @@ -142,6 +144,7 @@ type SimResult = { equivFees: number; saved: number; multiple: number | null; + fundingUsd?: number; }; type ComparisonResult = { @@ -730,7 +733,14 @@ export async function GET(req: Request) { equivFees: bEquiv, saved: bEquiv - aFees, multiple: aFees > 0 ? bEquiv / aFees : null, + fundingUsd: (venueAResult.wallet as HlWalletData).fundingUsd, }; + if (aNotional > 0) { + venueAResult.effectiveRateBps = (aFees / aNotional) * 10000; + venueAResult.effectiveRateNote = `${((aFees / aNotional) * 10000).toFixed(2)} bps actual (your fills)`; + venueBResult.effectiveRateBps = (bEquiv / aNotional) * 10000; + venueBResult.effectiveRateNote = `${((bEquiv / aNotional) * 10000).toFixed(2)} bps effective (your coins)`; + } } } else { const stats = walletStats(venueA, venueAResult.wallet); @@ -743,6 +753,11 @@ export async function GET(req: Request) { saved: equivFees - stats.fees, multiple: stats.fees > 0 ? equivFees / stats.fees : null, }; + if (stats.notional > 0) { + venueAResult.effectiveRateBps = (stats.fees / stats.notional) * 10000; + venueAResult.effectiveRateNote = `${((stats.fees / stats.notional) * 10000).toFixed(2)} bps actual (your fills)`; + venueBResult.effectiveRateBps = rateB * 10000; + } } } } @@ -767,7 +782,14 @@ export async function GET(req: Request) { equivFees: aEquiv, saved: bFees - aEquiv, multiple: bFees > 0 ? aEquiv / bFees : null, + fundingUsd: (venueBResult.wallet as HlWalletData).fundingUsd, }; + if (bNotional > 0) { + venueBResult.effectiveRateBps = (bFees / bNotional) * 10000; + venueBResult.effectiveRateNote = `${((bFees / bNotional) * 10000).toFixed(2)} bps actual (your fills)`; + venueAResult.effectiveRateBps = (aEquiv / bNotional) * 10000; + venueAResult.effectiveRateNote = `${((aEquiv / bNotional) * 10000).toFixed(2)} bps effective (your coins)`; + } } } else { const stats = walletStats(venueB, venueBResult.wallet); @@ -780,6 +802,11 @@ export async function GET(req: Request) { saved: stats.fees - equivFees, multiple: stats.fees > 0 ? equivFees / stats.fees : null, }; + if (stats.notional > 0) { + venueBResult.effectiveRateBps = (stats.fees / stats.notional) * 10000; + venueBResult.effectiveRateNote = `${((stats.fees / stats.notional) * 10000).toFixed(2)} bps actual (your fills)`; + venueAResult.effectiveRateBps = rateA * 10000; + } } } } diff --git a/src/components/fee-compare-client.tsx b/src/components/fee-compare-client.tsx index 8441d702..311bf5d1 100644 --- a/src/components/fee-compare-client.tsx +++ b/src/components/fee-compare-client.tsx @@ -103,6 +103,8 @@ type VenueResult = { rateNote: string; rateIsLive: boolean; wallet: AnyWallet | null; + effectiveRateBps?: number; + effectiveRateNote?: string; }; type SimResult = { @@ -111,6 +113,7 @@ type SimResult = { equivFees: number; saved: number; multiple: number | null; + fundingUsd?: number; }; type ComparisonResult = { @@ -321,15 +324,22 @@ function RateComparisonCard({ venueA: VenueResult; venueB: VenueResult; }) { - const aWins = venueA.ratePerAction < venueB.ratePerAction; - const bWins = venueB.ratePerAction < venueA.ratePerAction; - const diff = Math.abs(venueA.rateBps - venueB.rateBps); + const aBps = venueA.effectiveRateBps ?? venueA.rateBps; + const bBps = venueB.effectiveRateBps ?? venueB.rateBps; + const aRate = venueA.effectiveRateBps !== undefined ? venueA.effectiveRateBps / 10000 : venueA.ratePerAction; + const bRate = venueB.effectiveRateBps !== undefined ? venueB.effectiveRateBps / 10000 : venueB.ratePerAction; + const aWins = aRate < bRate; + const bWins = bRate < aRate; + const diff = Math.abs(aBps - bBps); + const usingEffective = venueA.effectiveRateBps !== undefined || venueB.effectiveRateBps !== undefined; return (
Fee rates
-Per-action taker rate comparison
++ {usingEffective ? "Effective rate based on your fills" : "Per-action taker rate comparison"} +
{venueA.rateNote}
{venueA.effectiveRateNote}
+ )}{venueB.rateNote}
{venueB.effectiveRateNote}
+ )}
{venueA.name} is{" "}
- {fmt(diff, 2)} bps cheaper per
- action
- {venueB.rateBps > 0 && (
+ {fmt(diff, 2)} bps cheaper
+ {usingEffective ? " (for your coins)" : " per action"}
+ {bBps > 0 && (
(
{fmt(
- ((venueB.ratePerAction - venueA.ratePerAction) /
- venueB.ratePerAction) *
- 100,
+ ((bRate - aRate) / bRate) * 100,
0
)}
% less)
@@ -403,15 +417,13 @@ function RateComparisonCard({
) : (
{venueB.name} is{" "}
- {fmt(diff, 2)} bps cheaper per
- action
- {venueA.rateBps > 0 && (
+ {fmt(diff, 2)} bps cheaper
+ {usingEffective ? " (for your coins)" : " per action"}
+ {aBps > 0 && (
(
{fmt(
- ((venueA.ratePerAction - venueB.ratePerAction) /
- venueA.ratePerAction) *
- 100,
+ ((aRate - bRate) / aRate) * 100,
0
)}
% less)
@@ -476,6 +488,15 @@ function SimBox({
{Math.abs(sim.saved) <= 0.5 && (
Roughly equal cost
+ +{" "}
+ {sim.fundingUsd > 0
+ ? `${fmtUsd(sim.fundingUsd)} funding received`
+ : `${fmtUsd(Math.abs(sim.fundingUsd))} funding paid`}{" "}
+ on {thisName}
+
{fmtUsd((w as HlWalletData).netCostUsd)}
-after funding
+ {Math.abs((w as HlWalletData).fundingUsd) > 0.5 ? ( ++ fees {fmtUsd(fees)}{" "} + {(w as HlWalletData).fundingUsd > 0 + ? `+ ${fmtUsd((w as HlWalletData).fundingUsd)} rcvd` + : `– ${fmtUsd(Math.abs((w as HlWalletData).fundingUsd))} paid`} +
+ ) : ( +after funding
+ )} )}