From fa14b6197e34db24b7cca17b3b16bace6d01d7da Mon Sep 17 00:00:00 2001 From: Laurent Grawet Date: Wed, 30 Sep 2026 18:48:18 +0200 Subject: [PATCH 1/2] =?UTF-8?q?Show=20an=20LP=20executor's=20earned=20fees?= =?UTF-8?q?=20in=20its=20detail=20view=20instead=20of=20the=20transaction?= =?UTF-8?q?=20fee:=20the=20connector=20fills=20cum=5Ffees=5Fquote=20with?= =?UTF-8?q?=20what=20the=20open=20and=20close=20cost=20on-chain=20and=20pu?= =?UTF-8?q?ts=20the=20fee=20income=20in=20custom=5Finfo.fees=5Fearned=5Fqu?= =?UTF-8?q?ote,=20so=20the=20one=20number=20that=20says=20whether=20a=20ba?= =?UTF-8?q?nd=20is=20worth=20keeping=20read=20three=20orders=20of=20magnit?= =?UTF-8?q?ude=20low=20=E2=80=94=20a=20live=20DJT-USDC=20band=20showed=20$?= =?UTF-8?q?0.000023=20against=20$0.074624=20actually=20earned;=20the=20for?= =?UTF-8?q?matter=20now=20prefers=20fees=5Fearned=5Fquote=20when=20the=20e?= =?UTF-8?q?xecutor=20carries=20it=20and=20falls=20back=20to=20cum=5Ffees?= =?UTF-8?q?=5Fquote=20for=20executors=20that=20have=20no=20custom=5Finfo?= =?UTF-8?q?=20twin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hummingbot_api/formatters/executors.py | 6 +- tests/test_executor_detail_fees.py | 60 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/test_executor_detail_fees.py diff --git a/mcp_servers/hummingbot_api/formatters/executors.py b/mcp_servers/hummingbot_api/formatters/executors.py index 47fa3c0b0..de0a4edca 100644 --- a/mcp_servers/hummingbot_api/formatters/executors.py +++ b/mcp_servers/hummingbot_api/formatters/executors.py @@ -209,7 +209,11 @@ def format_executor_detail(executor: dict[str, Any]) -> str: if realized_buy is not None and realized_sell is not None: output += f"Buy Volume: {format_currency(realized_buy)} | Sell Volume: {format_currency(realized_sell)}\n" - cum_fees = get_field(executor, "cum_fees_quote", default=None) + # An LP executor fills ``cum_fees_quote`` with the transaction fee, not the + # fee income it earned; that income is ``custom_info.fees_earned_quote``. + cum_fees = custom_info.get("fees_earned_quote") + if cum_fees is None: + cum_fees = get_field(executor, "cum_fees_quote", default=None) if cum_fees is not None and cum_fees != "N/A": output += f"Cumulative Fees: {format_currency(cum_fees)}\n" diff --git a/tests/test_executor_detail_fees.py b/tests/test_executor_detail_fees.py new file mode 100644 index 000000000..0d3e9a498 --- /dev/null +++ b/tests/test_executor_detail_fees.py @@ -0,0 +1,60 @@ +"""The executor detail view must report LP fee *income*, not the tx fee. + +An LP executor fills ``cum_fees_quote`` with what the open/close transactions +cost on-chain, and puts the fee income it actually earned in +``custom_info.fees_earned_quote``. Reading the first as "Cumulative Fees" showed +a live DJT-USDC band as $0.000023 when it had earned $0.074624 — three orders of +magnitude off, on the one number that says whether a band is worth keeping. + +The detail formatter prefers ``fees_earned_quote`` when the executor carries it +and falls back to ``cum_fees_quote`` otherwise, since a CEX executor's +``cum_fees_quote`` is its real cumulative fee and has no custom_info twin. +""" + +from mcp_servers.hummingbot_api.formatters.executors import format_executor_detail + +_TX_FEE = 0.000022815 +_EARNED = 0.0746235531801429 + + +def _lp(custom_info): + return { + "id": "DvpnJsPK", + "type": "lp_executor", + "status": "RUNNING", + "connector_name": "solana-mainnet-beta", + "trading_pair": "DJT-USDC", + "cum_fees_quote": _TX_FEE, + "custom_info": custom_info, + } + + +def test_lp_detail_shows_earned_fees_not_the_transaction_fee(): + out = format_executor_detail(_lp({"fees_earned_quote": _EARNED, "tx_fee": _TX_FEE})) + assert "Cumulative Fees: $0.074624" in out + assert "$0.000023" not in out + + +def test_a_band_that_earned_nothing_shows_zero_not_its_tx_fee(): + out = format_executor_detail(_lp({"fees_earned_quote": 0.0, "tx_fee": _TX_FEE})) + assert "Cumulative Fees: $0.000000" in out + assert "$0.000023" not in out + + +def test_an_executor_without_the_earned_field_falls_back_to_cum_fees_quote(): + out = format_executor_detail( + { + "id": "y", + "type": "position_executor", + "status": "RUNNING", + "cum_fees_quote": 1.23, + } + ) + assert "Cumulative Fees: $1.23" in out + + +def test_an_executor_with_neither_field_prints_no_fee_line(): + out = format_executor_detail( + {"id": "z", "type": "position_executor", "status": "RUNNING"} + ) + assert "Cumulative Fees" not in out From 2cd78a5e630bfee4e68c57fa891d4f67494826fd Mon Sep 17 00:00:00 2001 From: Laurent Grawet Date: Thu, 1 Oct 2026 11:45:26 +0200 Subject: [PATCH 2/2] Never fall back to the tx cost for an LP executor's fees, and name the direction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An LP executor without custom_info.fees_earned_quote fell back to cum_fees_quote, which for an LP is the on-chain transaction cost — so the detail view could still show a cost as fee income, the confusion this branch exists to fix. LP now reads fees_earned_quote only; when the field is absent the income is unknown and no fee line is printed. Non-LP executors keep their cum_fees_quote, labelled "Fees Paid" so a client cannot read a paid fee as income against the LP's "Fees Earned". --- .../hummingbot_api/formatters/executors.py | 16 +++++++---- tests/test_executor_detail_fees.py | 28 +++++++++++++------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/mcp_servers/hummingbot_api/formatters/executors.py b/mcp_servers/hummingbot_api/formatters/executors.py index de0a4edca..d3f358ccf 100644 --- a/mcp_servers/hummingbot_api/formatters/executors.py +++ b/mcp_servers/hummingbot_api/formatters/executors.py @@ -209,13 +209,17 @@ def format_executor_detail(executor: dict[str, Any]) -> str: if realized_buy is not None and realized_sell is not None: output += f"Buy Volume: {format_currency(realized_buy)} | Sell Volume: {format_currency(realized_sell)}\n" - # An LP executor fills ``cum_fees_quote`` with the transaction fee, not the - # fee income it earned; that income is ``custom_info.fees_earned_quote``. - cum_fees = custom_info.get("fees_earned_quote") - if cum_fees is None: + # An LP executor's ``cum_fees_quote`` is the on-chain transaction cost, never + # its fee income (that is ``custom_info.fees_earned_quote``), so LP has no fallback. + executor_type = get_field(executor, "type", "executor_type", default="unknown") + if executor_type == "lp_executor": + fees_earned = custom_info.get("fees_earned_quote") + if fees_earned is not None and fees_earned != "N/A": + output += f"Fees Earned: {format_currency(fees_earned)}\n" + else: cum_fees = get_field(executor, "cum_fees_quote", default=None) - if cum_fees is not None and cum_fees != "N/A": - output += f"Cumulative Fees: {format_currency(cum_fees)}\n" + if cum_fees is not None and cum_fees != "N/A": + output += f"Fees Paid: {format_currency(cum_fees)}\n" output += "\n" diff --git a/tests/test_executor_detail_fees.py b/tests/test_executor_detail_fees.py index 0d3e9a498..1bfbea9cc 100644 --- a/tests/test_executor_detail_fees.py +++ b/tests/test_executor_detail_fees.py @@ -6,9 +6,12 @@ a live DJT-USDC band as $0.000023 when it had earned $0.074624 — three orders of magnitude off, on the one number that says whether a band is worth keeping. -The detail formatter prefers ``fees_earned_quote`` when the executor carries it -and falls back to ``cum_fees_quote`` otherwise, since a CEX executor's -``cum_fees_quote`` is its real cumulative fee and has no custom_info twin. +An LP executor therefore reads ``fees_earned_quote`` and nothing else: its +``cum_fees_quote`` is a cost, so it never backs the earned figure — when the +field is absent the income is simply unknown, not the tx fee. A non-LP executor +(CEX order, position, grid…) has no ``custom_info`` twin and its +``cum_fees_quote`` *is* its fee, so it reports that as "Fees Paid". The two +labels stay distinct so a client cannot read a paid fee as income. """ from mcp_servers.hummingbot_api.formatters.executors import format_executor_detail @@ -31,17 +34,24 @@ def _lp(custom_info): def test_lp_detail_shows_earned_fees_not_the_transaction_fee(): out = format_executor_detail(_lp({"fees_earned_quote": _EARNED, "tx_fee": _TX_FEE})) - assert "Cumulative Fees: $0.074624" in out + assert "Fees Earned: $0.074624" in out assert "$0.000023" not in out def test_a_band_that_earned_nothing_shows_zero_not_its_tx_fee(): out = format_executor_detail(_lp({"fees_earned_quote": 0.0, "tx_fee": _TX_FEE})) - assert "Cumulative Fees: $0.000000" in out + assert "Fees Earned: $0.000000" in out assert "$0.000023" not in out -def test_an_executor_without_the_earned_field_falls_back_to_cum_fees_quote(): +def test_an_lp_executor_without_the_earned_field_prints_no_fee_line(): + out = format_executor_detail(_lp({"tx_fee": _TX_FEE})) + assert "Fees Earned" not in out + assert "Fees Paid" not in out + assert "$0.000023" not in out + + +def test_a_non_lp_executor_reports_its_cum_fees_as_fees_paid(): out = format_executor_detail( { "id": "y", @@ -50,11 +60,13 @@ def test_an_executor_without_the_earned_field_falls_back_to_cum_fees_quote(): "cum_fees_quote": 1.23, } ) - assert "Cumulative Fees: $1.23" in out + assert "Fees Paid: $1.23" in out + assert "Fees Earned" not in out def test_an_executor_with_neither_field_prints_no_fee_line(): out = format_executor_detail( {"id": "z", "type": "position_executor", "status": "RUNNING"} ) - assert "Cumulative Fees" not in out + assert "Fees Earned" not in out + assert "Fees Paid" not in out