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"} +

@@ -343,13 +353,16 @@ function RateComparisonCard({ aWins ? "text-emerald-500" : "text-ink" }`} > - {fmt(venueA.rateBps, 2)} + {fmt(aBps, 2)} bps

{venueA.rateIsLive ? : }

{venueA.rateNote}

+ {venueA.effectiveRateNote && ( +

{venueA.effectiveRateNote}

+ )}
@@ -371,13 +384,16 @@ function RateComparisonCard({ bWins ? "text-emerald-500" : "text-ink" }`} > - {fmt(venueB.rateBps, 2)} + {fmt(bBps, 2)} bps

{venueB.rateIsLive ? : }

{venueB.rateNote}

+ {venueB.effectiveRateNote && ( +

{venueB.effectiveRateNote}

+ )}
{diff > 0.01 ? ( @@ -385,15 +401,13 @@ function RateComparisonCard({ {aWins ? (

{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 !== undefined && Math.abs(sim.fundingUsd) > 0.5 && ( +

+ +{" "} + {sim.fundingUsd > 0 + ? `${fmtUsd(sim.fundingUsd)} funding received` + : `${fmtUsd(Math.abs(sim.fundingUsd))} funding paid`}{" "} + on {thisName} +

+ )}
); } @@ -584,7 +605,16 @@ function WalletSide({

{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

+ )} )}