From f65555c0b923a37194ed54ff37cfcdb206667e97 Mon Sep 17 00:00:00 2001 From: Marcos Date: Wed, 16 Sep 2026 12:37:35 +0200 Subject: [PATCH] feat: gate APR liquidity on the routing engine's idle volume threshold --- .../provisioning/dashboards/routing.json | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/docker/grafana/provisioning/dashboards/routing.json b/docker/grafana/provisioning/dashboards/routing.json index 5e797d7a..7e0a7650 100644 --- a/docker/grafana/provisioning/dashboards/routing.json +++ b/docker/grafana/provisioning/dashboards/routing.json @@ -5699,7 +5699,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH bounds AS (SELECT $__timeFrom()::timestamptz AS window_start, $__timeTo()::timestamptz AS window_end, GREATEST(EXTRACT(EPOCH FROM ($__timeTo()::timestamptz - $__timeFrom()::timestamptz)) / 86400.0, 0.000001) AS window_days), total_fees AS (SELECT COALESCE(ROUND(SUM(\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"Outcome\" = 1 AND \"ManagedNodePubKey\" IN ($node) AND \"FeeMsat\" IS NOT NULL), total_yield_costs AS (SELECT COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats FROM (SELECT s.\"NodeId\", $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats FROM \"SwapOuts\" s WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\") UNION ALL SELECT r.\"NodeId\", $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats FROM \"Rebalances\" r WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")) yc JOIN \"Nodes\" n ON n.\"Id\" = yc.\"NodeId\" WHERE n.\"PubKey\" IN ($node)), participating_chans AS (SELECT DISTINCT u.chan_id FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\"), (f.\"OutgoingChannelId\")) AS u(chan_id) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"ManagedNodePubKey\" IN ($node) AND f.\"FeeMsat\" IS NOT NULL UNION SELECT c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $activeChannelsOnly = 0 AND n.\"PubKey\" IN ($node)), qualifying_liquidity AS (SELECT COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE b.window_end END, b.window_end) - GREATEST(c.\"CreationDatetime\", b.window_start))) / 86400.0, 0.0) / b.window_days), 0) AS liquidity_sats FROM \"Channels\" c CROSS JOIN bounds b JOIN participating_chans p ON p.chan_id = c.\"ChanId\" WHERE (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0)) SELECT CASE WHEN ql.liquidity_sats > 0 THEN ((tf.fee_sats - tyc.yield_cost_sats)::numeric / ql.liquidity_sats) * (365.0 / b.window_days)::numeric ELSE NULL END AS \"APR\" FROM total_fees tf, total_yield_costs tyc, qualifying_liquidity ql, bounds b", + "rawSql": "WITH bounds AS (SELECT $__timeFrom()::timestamptz AS window_start, $__timeTo()::timestamptz AS window_end, GREATEST(EXTRACT(EPOCH FROM ($__timeTo()::timestamptz - $__timeFrom()::timestamptz)) / 86400.0, 0.000001) AS window_days), total_fees AS (SELECT COALESCE(ROUND(SUM(\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"Outcome\" = 1 AND \"ManagedNodePubKey\" IN ($node) AND \"FeeMsat\" IS NOT NULL), total_yield_costs AS (SELECT COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats FROM (SELECT s.\"NodeId\", $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats FROM \"SwapOuts\" s WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\") UNION ALL SELECT r.\"NodeId\", $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats FROM \"Rebalances\" r WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")) yc JOIN \"Nodes\" n ON n.\"Id\" = yc.\"NodeId\" WHERE n.\"PubKey\" IN ($node)), chan_flow AS (SELECT u.chan_id, SUM(COALESCE(u.amt_msat, 0)) / 1000.0 AS volume_sats FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\", f.\"IncomingAmountMsat\"), (f.\"OutgoingChannelId\", f.\"OutgoingAmountMsat\")) AS u(chan_id, amt_msat) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node) GROUP BY 1), chan_open_days AS (SELECT c.\"ChanId\", GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE $__timeTo()::timestamptz END, $__timeTo()::timestamptz) - GREATEST(c.\"CreationDatetime\", $__timeFrom()::timestamptz))) / 86400.0, 0.0) AS open_days FROM \"Channels\" c), participating_chans AS (SELECT cf.chan_id FROM chan_flow cf LEFT JOIN chan_open_days od ON od.\"ChanId\" = cf.chan_id WHERE $liquidityActivity < 2 OR cf.volume_sats >= $flowMinSats * COALESCE(od.open_days, 0) / 21.0 UNION SELECT c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $liquidityActivity = 0 AND n.\"PubKey\" IN ($node)), qualifying_liquidity AS (SELECT COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE b.window_end END, b.window_end) - GREATEST(c.\"CreationDatetime\", b.window_start))) / 86400.0, 0.0) / b.window_days), 0) AS liquidity_sats FROM \"Channels\" c CROSS JOIN bounds b JOIN participating_chans p ON p.chan_id = c.\"ChanId\" WHERE (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0)) SELECT CASE WHEN ql.liquidity_sats > 0 THEN ((tf.fee_sats - tyc.yield_cost_sats)::numeric / ql.liquidity_sats) * (365.0 / b.window_days)::numeric ELSE NULL END AS \"APR\" FROM total_fees tf, total_yield_costs tyc, qualifying_liquidity ql, bounds b", "refId": "A" } ], @@ -5770,7 +5770,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH bounds AS (SELECT $__timeFrom()::timestamptz AS window_start, $__timeTo()::timestamptz AS window_end), total_fees AS (SELECT COALESCE(ROUND(SUM(\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"Outcome\" = 1 AND \"ManagedNodePubKey\" IN ($node) AND \"FeeMsat\" IS NOT NULL), total_yield_costs AS (SELECT COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats FROM (SELECT s.\"NodeId\", $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats FROM \"SwapOuts\" s WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\") UNION ALL SELECT r.\"NodeId\", $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats FROM \"Rebalances\" r WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")) yc JOIN \"Nodes\" n ON n.\"Id\" = yc.\"NodeId\" WHERE n.\"PubKey\" IN ($node)), participating_chans AS (SELECT DISTINCT u.chan_id FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\"), (f.\"OutgoingChannelId\")) AS u(chan_id) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"ManagedNodePubKey\" IN ($node) AND f.\"FeeMsat\" IS NOT NULL UNION SELECT c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $activeChannelsOnly = 0 AND n.\"PubKey\" IN ($node)), qualifying_liquidity AS (SELECT COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE b.window_end END, b.window_end) - GREATEST(c.\"CreationDatetime\", b.window_start))) / 86400.0, 0.0) / GREATEST(EXTRACT(EPOCH FROM (b.window_end - b.window_start)) / 86400.0, 0.000001)), 0) AS liquidity_sats FROM \"Channels\" c CROSS JOIN bounds b JOIN participating_chans p ON p.chan_id = c.\"ChanId\" WHERE (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0)) SELECT CASE WHEN ql.liquidity_sats > 0 THEN ((tf.fee_sats - tyc.yield_cost_sats)::numeric / ql.liquidity_sats) ELSE NULL END AS \"Raw Yield\" FROM total_fees tf, total_yield_costs tyc, qualifying_liquidity ql", + "rawSql": "WITH bounds AS (SELECT $__timeFrom()::timestamptz AS window_start, $__timeTo()::timestamptz AS window_end), total_fees AS (SELECT COALESCE(ROUND(SUM(\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"Outcome\" = 1 AND \"ManagedNodePubKey\" IN ($node) AND \"FeeMsat\" IS NOT NULL), total_yield_costs AS (SELECT COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats FROM (SELECT s.\"NodeId\", $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats FROM \"SwapOuts\" s WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\") UNION ALL SELECT r.\"NodeId\", $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats FROM \"Rebalances\" r WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")) yc JOIN \"Nodes\" n ON n.\"Id\" = yc.\"NodeId\" WHERE n.\"PubKey\" IN ($node)), chan_flow AS (SELECT u.chan_id, SUM(COALESCE(u.amt_msat, 0)) / 1000.0 AS volume_sats FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\", f.\"IncomingAmountMsat\"), (f.\"OutgoingChannelId\", f.\"OutgoingAmountMsat\")) AS u(chan_id, amt_msat) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node) GROUP BY 1), chan_open_days AS (SELECT c.\"ChanId\", GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE $__timeTo()::timestamptz END, $__timeTo()::timestamptz) - GREATEST(c.\"CreationDatetime\", $__timeFrom()::timestamptz))) / 86400.0, 0.0) AS open_days FROM \"Channels\" c), participating_chans AS (SELECT cf.chan_id FROM chan_flow cf LEFT JOIN chan_open_days od ON od.\"ChanId\" = cf.chan_id WHERE $liquidityActivity < 2 OR cf.volume_sats >= $flowMinSats * COALESCE(od.open_days, 0) / 21.0 UNION SELECT c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $liquidityActivity = 0 AND n.\"PubKey\" IN ($node)), qualifying_liquidity AS (SELECT COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE b.window_end END, b.window_end) - GREATEST(c.\"CreationDatetime\", b.window_start))) / 86400.0, 0.0) / GREATEST(EXTRACT(EPOCH FROM (b.window_end - b.window_start)) / 86400.0, 0.000001)), 0) AS liquidity_sats FROM \"Channels\" c CROSS JOIN bounds b JOIN participating_chans p ON p.chan_id = c.\"ChanId\" WHERE (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0)) SELECT CASE WHEN ql.liquidity_sats > 0 THEN ((tf.fee_sats - tyc.yield_cost_sats)::numeric / ql.liquidity_sats) ELSE NULL END AS \"Raw Yield\" FROM total_fees tf, total_yield_costs tyc, qualifying_liquidity ql", "refId": "A" } ], @@ -5849,7 +5849,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH bounds AS (SELECT $__timeFrom()::timestamptz AS window_start, $__timeTo()::timestamptz AS window_end, GREATEST(EXTRACT(EPOCH FROM ($__timeTo()::timestamptz - $__timeFrom()::timestamptz)) / 86400.0, 0.000001) AS window_days), node_fees AS (SELECT \"ManagedNodePubKey\", MAX(\"ManagedNodeName\") AS managed_node_name, ROUND(SUM(\"FeeMsat\") / 1000.0)::bigint AS fee_sats FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"Outcome\" = 1 AND \"ManagedNodePubKey\" IN ($node) AND \"FeeMsat\" IS NOT NULL GROUP BY \"ManagedNodePubKey\"), node_yield_costs AS (SELECT n.\"PubKey\" AS managed_node_pubkey, COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats FROM (SELECT s.\"NodeId\", $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats FROM \"SwapOuts\" s WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\") UNION ALL SELECT r.\"NodeId\", $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats FROM \"Rebalances\" r WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")) yc JOIN \"Nodes\" n ON n.\"Id\" = yc.\"NodeId\" WHERE n.\"PubKey\" IN ($node) GROUP BY n.\"PubKey\"), node_participating_chans AS (SELECT DISTINCT f.\"ManagedNodePubKey\", u.chan_id FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\"), (f.\"OutgoingChannelId\")) AS u(chan_id) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"ManagedNodePubKey\" IN ($node) AND f.\"FeeMsat\" IS NOT NULL UNION SELECT n.\"PubKey\" AS \"ManagedNodePubKey\", c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $activeChannelsOnly = 0 AND n.\"PubKey\" IN ($node)), node_liquidity AS (SELECT npc.\"ManagedNodePubKey\", SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE b.window_end END, b.window_end) - GREATEST(c.\"CreationDatetime\", b.window_start))) / 86400.0, 0.0) / b.window_days) AS liquidity_sats FROM node_participating_chans npc JOIN \"Channels\" c ON c.\"ChanId\" = npc.chan_id CROSS JOIN bounds b WHERE (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0) GROUP BY npc.\"ManagedNodePubKey\") SELECT nf.managed_node_name AS \"Node\", CASE WHEN nl.liquidity_sats > 0 THEN ((nf.fee_sats - COALESCE(nyc.yield_cost_sats, 0))::numeric / nl.liquidity_sats) * (365.0 / b.window_days)::numeric ELSE NULL END AS \"APR\" FROM node_fees nf LEFT JOIN node_liquidity nl ON nl.\"ManagedNodePubKey\" = nf.\"ManagedNodePubKey\" LEFT JOIN node_yield_costs nyc ON nyc.managed_node_pubkey = nf.\"ManagedNodePubKey\" CROSS JOIN bounds b ORDER BY \"APR\" DESC NULLS LAST", + "rawSql": "WITH bounds AS (SELECT $__timeFrom()::timestamptz AS window_start, $__timeTo()::timestamptz AS window_end, GREATEST(EXTRACT(EPOCH FROM ($__timeTo()::timestamptz - $__timeFrom()::timestamptz)) / 86400.0, 0.000001) AS window_days), node_fees AS (SELECT \"ManagedNodePubKey\", MAX(\"ManagedNodeName\") AS managed_node_name, ROUND(SUM(\"FeeMsat\") / 1000.0)::bigint AS fee_sats FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"Outcome\" = 1 AND \"ManagedNodePubKey\" IN ($node) AND \"FeeMsat\" IS NOT NULL GROUP BY \"ManagedNodePubKey\"), node_yield_costs AS (SELECT n.\"PubKey\" AS managed_node_pubkey, COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats FROM (SELECT s.\"NodeId\", $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats FROM \"SwapOuts\" s WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\") UNION ALL SELECT r.\"NodeId\", $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats FROM \"Rebalances\" r WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")) yc JOIN \"Nodes\" n ON n.\"Id\" = yc.\"NodeId\" WHERE n.\"PubKey\" IN ($node) GROUP BY n.\"PubKey\"), chan_flow AS (SELECT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id, SUM(COALESCE(u.amt_msat, 0)) / 1000.0 AS volume_sats FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\", f.\"IncomingAmountMsat\"), (f.\"OutgoingChannelId\", f.\"OutgoingAmountMsat\")) AS u(chan_id, amt_msat) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node) GROUP BY 1, 2), chan_open_days AS (SELECT c.\"ChanId\", GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE $__timeTo()::timestamptz END, $__timeTo()::timestamptz) - GREATEST(c.\"CreationDatetime\", $__timeFrom()::timestamptz))) / 86400.0, 0.0) AS open_days FROM \"Channels\" c), node_participating_chans AS (SELECT cf.pubkey AS \"ManagedNodePubKey\", cf.chan_id FROM chan_flow cf LEFT JOIN chan_open_days od ON od.\"ChanId\" = cf.chan_id WHERE $liquidityActivity < 2 OR cf.volume_sats >= $flowMinSats * COALESCE(od.open_days, 0) / 21.0 UNION SELECT n.\"PubKey\" AS \"ManagedNodePubKey\", c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $liquidityActivity = 0 AND n.\"PubKey\" IN ($node)), node_liquidity AS (SELECT npc.\"ManagedNodePubKey\", SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE b.window_end END, b.window_end) - GREATEST(c.\"CreationDatetime\", b.window_start))) / 86400.0, 0.0) / b.window_days) AS liquidity_sats FROM node_participating_chans npc JOIN \"Channels\" c ON c.\"ChanId\" = npc.chan_id CROSS JOIN bounds b WHERE (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0) GROUP BY npc.\"ManagedNodePubKey\") SELECT nf.managed_node_name AS \"Node\", CASE WHEN nl.liquidity_sats > 0 THEN ((nf.fee_sats - COALESCE(nyc.yield_cost_sats, 0))::numeric / nl.liquidity_sats) * (365.0 / b.window_days)::numeric ELSE NULL END AS \"APR\" FROM node_fees nf LEFT JOIN node_liquidity nl ON nl.\"ManagedNodePubKey\" = nf.\"ManagedNodePubKey\" LEFT JOIN node_yield_costs nyc ON nyc.managed_node_pubkey = nf.\"ManagedNodePubKey\" CROSS JOIN bounds b ORDER BY \"APR\" DESC NULLS LAST", "refId": "A" } ], @@ -6639,7 +6639,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "WITH days AS (SELECT generate_series(date_trunc('hour', $__timeFrom()::timestamptz), $__timeTo()::timestamptz, INTERVAL '1 hour') AS bucket_start), day_buckets AS (SELECT bucket_start, bucket_start + INTERVAL '1 hour' AS bucket_end FROM days), nodes AS (SELECT \"ManagedNodePubKey\" AS pubkey, MAX(\"ManagedNodeName\") AS node_name FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"ManagedNodePubKey\" IN ($node) GROUP BY 1), participating_node_chans AS (SELECT DISTINCT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\"), (f.\"OutgoingChannelId\")) u(chan_id) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node)), day_node_grid AS (SELECT db.bucket_start, db.bucket_end, n.pubkey, n.node_name FROM day_buckets db CROSS JOIN nodes n), day_fees AS (SELECT g.bucket_start, g.pubkey, g.node_name, COALESCE(ROUND(SUM(f.\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats FROM day_node_grid g LEFT JOIN \"ForwardingHtlcEvents\" f ON f.\"EventTimestamp\" >= g.bucket_start AND f.\"EventTimestamp\" < g.bucket_end AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" = g.pubkey GROUP BY g.bucket_start, g.pubkey, g.node_name), day_yield_costs AS (SELECT g.bucket_start, g.pubkey, COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats FROM day_node_grid g LEFT JOIN \"Nodes\" n ON n.\"PubKey\" = g.pubkey LEFT JOIN (SELECT s.\"NodeId\", s.\"CreationDatetime\" AS cost_time, $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats FROM \"SwapOuts\" s WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\") UNION ALL SELECT r.\"NodeId\", r.\"CreationDatetime\" AS cost_time, $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats FROM \"Rebalances\" r WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")) yc ON yc.\"NodeId\" = n.\"Id\" AND yc.cost_time >= g.bucket_start AND yc.cost_time < g.bucket_end GROUP BY g.bucket_start, g.pubkey), day_liquidity AS (SELECT g.bucket_start, g.pubkey, COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE g.bucket_end END, g.bucket_end) - GREATEST(c.\"CreationDatetime\", g.bucket_start))) / 86400.0, 0.0)), 0) AS sat_days FROM day_node_grid g LEFT JOIN participating_node_chans pc ON pc.pubkey = g.pubkey LEFT JOIN \"Channels\" c ON c.\"ChanId\" = pc.chan_id AND (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0) GROUP BY g.bucket_start, g.pubkey), rolling AS (SELECT df.bucket_start, df.pubkey, df.node_name, SUM(df.fee_sats) OVER w AS r_fee, SUM(COALESCE(dyc.yield_cost_sats, 0)) OVER w AS r_cost, SUM(COALESCE(dl.sat_days, 0)) OVER w AS r_satdays FROM day_fees df LEFT JOIN day_yield_costs dyc ON dyc.bucket_start = df.bucket_start AND dyc.pubkey = df.pubkey LEFT JOIN day_liquidity dl ON dl.bucket_start = df.bucket_start AND dl.pubkey = df.pubkey WINDOW w AS (PARTITION BY df.pubkey ORDER BY df.bucket_start ROWS BETWEEN (CASE '$aprWindow' WHEN 'day' THEN 23 WHEN 'week' THEN 167 ELSE 719 END) PRECEDING AND CURRENT ROW)) SELECT bucket_start AS \"time\", node_name || ' APR' AS metric, CASE WHEN r_satdays > 0 THEN (r_fee - r_cost)::numeric * 365.0 / r_satdays ELSE NULL END AS value FROM rolling ORDER BY bucket_start, metric", + "rawSql": "WITH days AS (SELECT generate_series(date_trunc('hour', $__timeFrom()::timestamptz), $__timeTo()::timestamptz, INTERVAL '1 hour') AS bucket_start), day_buckets AS (SELECT bucket_start, bucket_start + INTERVAL '1 hour' AS bucket_end FROM days), nodes AS (SELECT \"ManagedNodePubKey\" AS pubkey, MAX(\"ManagedNodeName\") AS node_name FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"ManagedNodePubKey\" IN ($node) GROUP BY 1), chan_flow AS (SELECT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id, SUM(COALESCE(u.amt_msat, 0)) / 1000.0 AS volume_sats FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\", f.\"IncomingAmountMsat\"), (f.\"OutgoingChannelId\", f.\"OutgoingAmountMsat\")) AS u(chan_id, amt_msat) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node) GROUP BY 1, 2), chan_open_days AS (SELECT c.\"ChanId\", GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE $__timeTo()::timestamptz END, $__timeTo()::timestamptz) - GREATEST(c.\"CreationDatetime\", $__timeFrom()::timestamptz))) / 86400.0, 0.0) AS open_days FROM \"Channels\" c), participating_node_chans AS (SELECT cf.pubkey, cf.chan_id FROM chan_flow cf LEFT JOIN chan_open_days od ON od.\"ChanId\" = cf.chan_id WHERE $liquidityActivity < 2 OR cf.volume_sats >= $flowMinSats * COALESCE(od.open_days, 0) / 21.0 UNION SELECT n.\"PubKey\" AS pubkey, c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $liquidityActivity = 0 AND n.\"PubKey\" IN ($node)), day_node_grid AS (SELECT db.bucket_start, db.bucket_end, n.pubkey, n.node_name FROM day_buckets db CROSS JOIN nodes n), day_fees AS (SELECT g.bucket_start, g.pubkey, g.node_name, COALESCE(ROUND(SUM(f.\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats FROM day_node_grid g LEFT JOIN \"ForwardingHtlcEvents\" f ON f.\"EventTimestamp\" >= g.bucket_start AND f.\"EventTimestamp\" < g.bucket_end AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" = g.pubkey GROUP BY g.bucket_start, g.pubkey, g.node_name), day_yield_costs AS (SELECT g.bucket_start, g.pubkey, COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats FROM day_node_grid g LEFT JOIN \"Nodes\" n ON n.\"PubKey\" = g.pubkey LEFT JOIN (SELECT s.\"NodeId\", s.\"CreationDatetime\" AS cost_time, $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats FROM \"SwapOuts\" s WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\") UNION ALL SELECT r.\"NodeId\", r.\"CreationDatetime\" AS cost_time, $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats FROM \"Rebalances\" r WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")) yc ON yc.\"NodeId\" = n.\"Id\" AND yc.cost_time >= g.bucket_start AND yc.cost_time < g.bucket_end GROUP BY g.bucket_start, g.pubkey), day_liquidity AS (SELECT g.bucket_start, g.pubkey, COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE g.bucket_end END, g.bucket_end) - GREATEST(c.\"CreationDatetime\", g.bucket_start))) / 86400.0, 0.0)), 0) AS sat_days FROM day_node_grid g LEFT JOIN participating_node_chans pc ON pc.pubkey = g.pubkey LEFT JOIN \"Channels\" c ON c.\"ChanId\" = pc.chan_id AND (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0) GROUP BY g.bucket_start, g.pubkey), rolling AS (SELECT df.bucket_start, df.pubkey, df.node_name, SUM(df.fee_sats) OVER w AS r_fee, SUM(COALESCE(dyc.yield_cost_sats, 0)) OVER w AS r_cost, SUM(COALESCE(dl.sat_days, 0)) OVER w AS r_satdays FROM day_fees df LEFT JOIN day_yield_costs dyc ON dyc.bucket_start = df.bucket_start AND dyc.pubkey = df.pubkey LEFT JOIN day_liquidity dl ON dl.bucket_start = df.bucket_start AND dl.pubkey = df.pubkey WINDOW w AS (PARTITION BY df.pubkey ORDER BY df.bucket_start ROWS BETWEEN (CASE '$aprWindow' WHEN 'day' THEN 23 WHEN 'week' THEN 167 ELSE 719 END) PRECEDING AND CURRENT ROW)) SELECT bucket_start AS \"time\", node_name || ' APR' AS metric, CASE WHEN r_satdays > 0 THEN (r_fee - r_cost)::numeric * 365.0 / r_satdays ELSE NULL END AS value FROM rolling ORDER BY bucket_start, metric", "refId": "A" } ], @@ -6735,7 +6735,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "WITH days AS (SELECT generate_series(date_trunc('hour', $__timeFrom()::timestamptz), $__timeTo()::timestamptz, INTERVAL '1 hour') AS bucket_start), day_buckets AS (SELECT bucket_start, bucket_start + INTERVAL '1 hour' AS bucket_end FROM days), nodes AS (SELECT \"ManagedNodePubKey\" AS pubkey, MAX(\"ManagedNodeName\") AS node_name FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"ManagedNodePubKey\" IN ($node) GROUP BY 1), participating_node_chans AS (SELECT DISTINCT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\"), (f.\"OutgoingChannelId\")) u(chan_id) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node) UNION SELECT n.\"PubKey\" AS pubkey, c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $activeChannelsOnly = 0 AND n.\"PubKey\" IN ($node)), day_node_grid AS (SELECT db.bucket_start, db.bucket_end, n.pubkey, n.node_name FROM day_buckets db CROSS JOIN nodes n), day_liquidity AS (SELECT g.bucket_start, g.pubkey, g.node_name, COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE g.bucket_end END, g.bucket_end) - GREATEST(c.\"CreationDatetime\", g.bucket_start))) / 86400.0, 0.0)), 0) AS sat_days FROM day_node_grid g LEFT JOIN participating_node_chans pc ON pc.pubkey = g.pubkey LEFT JOIN \"Channels\" c ON c.\"ChanId\" = pc.chan_id AND (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0) GROUP BY g.bucket_start, g.pubkey, g.node_name), rolling AS (SELECT dl.bucket_start, dl.pubkey, dl.node_name, SUM(dl.sat_days) OVER w AS r_satdays, COUNT(*) OVER w AS r_count FROM day_liquidity dl WINDOW w AS (PARTITION BY dl.pubkey ORDER BY dl.bucket_start ROWS BETWEEN (CASE '$aprWindow' WHEN 'day' THEN 23 WHEN 'week' THEN 167 ELSE 719 END) PRECEDING AND CURRENT ROW)) SELECT bucket_start AS \"time\", node_name || ' Liquidity (BTC)' AS metric, CASE WHEN r_count > 0 THEN (r_satdays * 24.0 / r_count / 100000000.0)::numeric(20,8) ELSE NULL END AS value FROM rolling ORDER BY bucket_start, metric", + "rawSql": "WITH days AS (SELECT generate_series(date_trunc('hour', $__timeFrom()::timestamptz), $__timeTo()::timestamptz, INTERVAL '1 hour') AS bucket_start), day_buckets AS (SELECT bucket_start, bucket_start + INTERVAL '1 hour' AS bucket_end FROM days), nodes AS (SELECT \"ManagedNodePubKey\" AS pubkey, MAX(\"ManagedNodeName\") AS node_name FROM \"ForwardingHtlcEvents\" WHERE $__timeFilter(\"EventTimestamp\") AND \"ManagedNodePubKey\" IN ($node) GROUP BY 1), chan_flow AS (SELECT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id, SUM(COALESCE(u.amt_msat, 0)) / 1000.0 AS volume_sats FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\", f.\"IncomingAmountMsat\"), (f.\"OutgoingChannelId\", f.\"OutgoingAmountMsat\")) AS u(chan_id, amt_msat) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node) GROUP BY 1, 2), chan_open_days AS (SELECT c.\"ChanId\", GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE $__timeTo()::timestamptz END, $__timeTo()::timestamptz) - GREATEST(c.\"CreationDatetime\", $__timeFrom()::timestamptz))) / 86400.0, 0.0) AS open_days FROM \"Channels\" c), participating_node_chans AS (SELECT cf.pubkey, cf.chan_id FROM chan_flow cf LEFT JOIN chan_open_days od ON od.\"ChanId\" = cf.chan_id WHERE $liquidityActivity < 2 OR cf.volume_sats >= $flowMinSats * COALESCE(od.open_days, 0) / 21.0 UNION SELECT n.\"PubKey\" AS pubkey, c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $liquidityActivity = 0 AND n.\"PubKey\" IN ($node)), day_node_grid AS (SELECT db.bucket_start, db.bucket_end, n.pubkey, n.node_name FROM day_buckets db CROSS JOIN nodes n), day_liquidity AS (SELECT g.bucket_start, g.pubkey, g.node_name, COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE g.bucket_end END, g.bucket_end) - GREATEST(c.\"CreationDatetime\", g.bucket_start))) / 86400.0, 0.0)), 0) AS sat_days FROM day_node_grid g LEFT JOIN participating_node_chans pc ON pc.pubkey = g.pubkey LEFT JOIN \"Channels\" c ON c.\"ChanId\" = pc.chan_id AND (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0) GROUP BY g.bucket_start, g.pubkey, g.node_name), rolling AS (SELECT dl.bucket_start, dl.pubkey, dl.node_name, SUM(dl.sat_days) OVER w AS r_satdays, COUNT(*) OVER w AS r_count FROM day_liquidity dl WINDOW w AS (PARTITION BY dl.pubkey ORDER BY dl.bucket_start ROWS BETWEEN (CASE '$aprWindow' WHEN 'day' THEN 23 WHEN 'week' THEN 167 ELSE 719 END) PRECEDING AND CURRENT ROW)) SELECT bucket_start AS \"time\", node_name || ' Liquidity (BTC)' AS metric, CASE WHEN r_count > 0 THEN (r_satdays * 24.0 / r_count / 100000000.0)::numeric(20,8) ELSE NULL END AS value FROM rolling ORDER BY bucket_start, metric", "refId": "A" } ], @@ -7714,7 +7714,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH days AS (\n SELECT generate_series(date_trunc('day', $__timeFrom()::timestamptz), $__timeTo()::timestamptz, INTERVAL '1 day') AS bucket_start\n), day_buckets AS (\n SELECT bucket_start, bucket_start + INTERVAL '1 day' AS bucket_end\n FROM days\n WHERE bucket_start + INTERVAL '1 day' <= LEAST($__timeTo()::timestamptz, now())\n), nodes AS (\n SELECT \"ManagedNodePubKey\" AS pubkey, MAX(\"ManagedNodeName\") AS node_name\n FROM \"ForwardingHtlcEvents\"\n WHERE $__timeFilter(\"EventTimestamp\") AND \"ManagedNodePubKey\" IN ($node)\n GROUP BY 1\n), participating_node_chans AS (\n SELECT DISTINCT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id\n FROM \"ForwardingHtlcEvents\" f\n CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\"), (f.\"OutgoingChannelId\")) u(chan_id)\n WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node)\n UNION\n SELECT n.\"PubKey\" AS pubkey, c.\"ChanId\" AS chan_id\n FROM \"Channels\" c\n JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\")\n WHERE $activeChannelsOnly = 0 AND n.\"PubKey\" IN ($node)\n), day_node_grid AS (\n SELECT db.bucket_start, db.bucket_end, n.pubkey, n.node_name\n FROM day_buckets db CROSS JOIN nodes n\n), day_fees AS (\n SELECT g.bucket_start, g.pubkey, g.node_name,\n COALESCE(ROUND(SUM(f.\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats\n FROM day_node_grid g\n LEFT JOIN \"ForwardingHtlcEvents\" f\n ON f.\"EventTimestamp\" >= g.bucket_start AND f.\"EventTimestamp\" < g.bucket_end\n AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" = g.pubkey\n GROUP BY g.bucket_start, g.pubkey, g.node_name\n), day_yield_costs AS (\n SELECT g.bucket_start, g.pubkey, COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats\n FROM day_node_grid g\n LEFT JOIN \"Nodes\" n ON n.\"PubKey\" = g.pubkey\n LEFT JOIN (\n SELECT s.\"NodeId\", s.\"CreationDatetime\" AS cost_time,\n $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats\n FROM \"SwapOuts\" s\n WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\")\n UNION ALL\n SELECT r.\"NodeId\", r.\"CreationDatetime\" AS cost_time,\n $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats\n FROM \"Rebalances\" r\n WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")\n ) yc ON yc.\"NodeId\" = n.\"Id\" AND yc.cost_time >= g.bucket_start AND yc.cost_time < g.bucket_end\n GROUP BY g.bucket_start, g.pubkey\n), day_liquidity AS (\n SELECT g.bucket_start, g.pubkey,\n COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE g.bucket_end END, g.bucket_end) - GREATEST(c.\"CreationDatetime\", g.bucket_start))) / 86400.0, 0.0)), 0) AS sat_days\n FROM day_node_grid g\n LEFT JOIN participating_node_chans pc ON pc.pubkey = g.pubkey\n LEFT JOIN \"Channels\" c ON c.\"ChanId\" = pc.chan_id AND (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0)\n GROUP BY g.bucket_start, g.pubkey\n)\nSELECT\n df.node_name AS node,\n (dl.sat_days / 100000000.0)::float8 AS liquidity_btc,\n (df.fee_sats - COALESCE(dyc.yield_cost_sats, 0))::float8 AS profit_sats_per_day,\n df.bucket_start AS \"day\"\nFROM day_fees df\nLEFT JOIN day_yield_costs dyc ON dyc.bucket_start = df.bucket_start AND dyc.pubkey = df.pubkey\nLEFT JOIN day_liquidity dl ON dl.bucket_start = df.bucket_start AND dl.pubkey = df.pubkey\nWHERE COALESCE(dl.sat_days, 0) > 0\nORDER BY node, \"day\"\n", + "rawSql": "WITH days AS (\n SELECT generate_series(date_trunc('day', $__timeFrom()::timestamptz), $__timeTo()::timestamptz, INTERVAL '1 day') AS bucket_start\n), day_buckets AS (\n SELECT bucket_start, bucket_start + INTERVAL '1 day' AS bucket_end\n FROM days\n WHERE bucket_start + INTERVAL '1 day' <= LEAST($__timeTo()::timestamptz, now())\n), nodes AS (\n SELECT \"ManagedNodePubKey\" AS pubkey, MAX(\"ManagedNodeName\") AS node_name\n FROM \"ForwardingHtlcEvents\"\n WHERE $__timeFilter(\"EventTimestamp\") AND \"ManagedNodePubKey\" IN ($node)\n GROUP BY 1\n), chan_flow AS (SELECT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id, SUM(COALESCE(u.amt_msat, 0)) / 1000.0 AS volume_sats FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\", f.\"IncomingAmountMsat\"), (f.\"OutgoingChannelId\", f.\"OutgoingAmountMsat\")) AS u(chan_id, amt_msat) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node) GROUP BY 1, 2), chan_open_days AS (SELECT c.\"ChanId\", GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE $__timeTo()::timestamptz END, $__timeTo()::timestamptz) - GREATEST(c.\"CreationDatetime\", $__timeFrom()::timestamptz))) / 86400.0, 0.0) AS open_days FROM \"Channels\" c), participating_node_chans AS (SELECT cf.pubkey, cf.chan_id FROM chan_flow cf LEFT JOIN chan_open_days od ON od.\"ChanId\" = cf.chan_id WHERE $liquidityActivity < 2 OR cf.volume_sats >= $flowMinSats * COALESCE(od.open_days, 0) / 21.0 UNION SELECT n.\"PubKey\" AS pubkey, c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $liquidityActivity = 0 AND n.\"PubKey\" IN ($node)), day_node_grid AS (\n SELECT db.bucket_start, db.bucket_end, n.pubkey, n.node_name\n FROM day_buckets db CROSS JOIN nodes n\n), day_fees AS (\n SELECT g.bucket_start, g.pubkey, g.node_name,\n COALESCE(ROUND(SUM(f.\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats\n FROM day_node_grid g\n LEFT JOIN \"ForwardingHtlcEvents\" f\n ON f.\"EventTimestamp\" >= g.bucket_start AND f.\"EventTimestamp\" < g.bucket_end\n AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" = g.pubkey\n GROUP BY g.bucket_start, g.pubkey, g.node_name\n), day_yield_costs AS (\n SELECT g.bucket_start, g.pubkey, COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats\n FROM day_node_grid g\n LEFT JOIN \"Nodes\" n ON n.\"PubKey\" = g.pubkey\n LEFT JOIN (\n SELECT s.\"NodeId\", s.\"CreationDatetime\" AS cost_time,\n $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats\n FROM \"SwapOuts\" s\n WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\")\n UNION ALL\n SELECT r.\"NodeId\", r.\"CreationDatetime\" AS cost_time,\n $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats\n FROM \"Rebalances\" r\n WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")\n ) yc ON yc.\"NodeId\" = n.\"Id\" AND yc.cost_time >= g.bucket_start AND yc.cost_time < g.bucket_end\n GROUP BY g.bucket_start, g.pubkey\n), day_liquidity AS (\n SELECT g.bucket_start, g.pubkey,\n COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE g.bucket_end END, g.bucket_end) - GREATEST(c.\"CreationDatetime\", g.bucket_start))) / 86400.0, 0.0)), 0) AS sat_days\n FROM day_node_grid g\n LEFT JOIN participating_node_chans pc ON pc.pubkey = g.pubkey\n LEFT JOIN \"Channels\" c ON c.\"ChanId\" = pc.chan_id AND (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0)\n GROUP BY g.bucket_start, g.pubkey\n)\nSELECT\n df.node_name AS node,\n (dl.sat_days / 100000000.0)::float8 AS liquidity_btc,\n (df.fee_sats - COALESCE(dyc.yield_cost_sats, 0))::float8 AS profit_sats_per_day,\n df.bucket_start AS \"day\"\nFROM day_fees df\nLEFT JOIN day_yield_costs dyc ON dyc.bucket_start = df.bucket_start AND dyc.pubkey = df.pubkey\nLEFT JOIN day_liquidity dl ON dl.bucket_start = df.bucket_start AND dl.pubkey = df.pubkey\nWHERE COALESCE(dl.sat_days, 0) > 0\nORDER BY node, \"day\"\n", "refId": "A" } ], @@ -7865,7 +7865,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH days AS (\n SELECT generate_series(date_trunc('day', $__timeFrom()::timestamptz), $__timeTo()::timestamptz, INTERVAL '1 day') AS bucket_start\n), day_buckets AS (\n SELECT bucket_start, bucket_start + INTERVAL '1 day' AS bucket_end\n FROM days\n WHERE bucket_start + INTERVAL '1 day' <= LEAST($__timeTo()::timestamptz, now())\n), nodes AS (\n SELECT \"ManagedNodePubKey\" AS pubkey, MAX(\"ManagedNodeName\") AS node_name\n FROM \"ForwardingHtlcEvents\"\n WHERE $__timeFilter(\"EventTimestamp\") AND \"ManagedNodePubKey\" IN ($node)\n GROUP BY 1\n), participating_node_chans AS (\n SELECT DISTINCT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id\n FROM \"ForwardingHtlcEvents\" f\n CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\"), (f.\"OutgoingChannelId\")) u(chan_id)\n WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node)\n UNION\n SELECT n.\"PubKey\" AS pubkey, c.\"ChanId\" AS chan_id\n FROM \"Channels\" c\n JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\")\n WHERE $activeChannelsOnly = 0 AND n.\"PubKey\" IN ($node)\n), day_node_grid AS (\n SELECT db.bucket_start, db.bucket_end, n.pubkey, n.node_name\n FROM day_buckets db CROSS JOIN nodes n\n), day_fees AS (\n SELECT g.bucket_start, g.pubkey, g.node_name,\n COALESCE(ROUND(SUM(f.\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats\n FROM day_node_grid g\n LEFT JOIN \"ForwardingHtlcEvents\" f\n ON f.\"EventTimestamp\" >= g.bucket_start AND f.\"EventTimestamp\" < g.bucket_end\n AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" = g.pubkey\n GROUP BY g.bucket_start, g.pubkey, g.node_name\n), day_yield_costs AS (\n SELECT g.bucket_start, g.pubkey, COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats\n FROM day_node_grid g\n LEFT JOIN \"Nodes\" n ON n.\"PubKey\" = g.pubkey\n LEFT JOIN (\n SELECT s.\"NodeId\", s.\"CreationDatetime\" AS cost_time,\n $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats\n FROM \"SwapOuts\" s\n WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\")\n UNION ALL\n SELECT r.\"NodeId\", r.\"CreationDatetime\" AS cost_time,\n $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats\n FROM \"Rebalances\" r\n WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")\n ) yc ON yc.\"NodeId\" = n.\"Id\" AND yc.cost_time >= g.bucket_start AND yc.cost_time < g.bucket_end\n GROUP BY g.bucket_start, g.pubkey\n), day_liquidity AS (\n SELECT g.bucket_start, g.pubkey,\n COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE g.bucket_end END, g.bucket_end) - GREATEST(c.\"CreationDatetime\", g.bucket_start))) / 86400.0, 0.0)), 0) AS sat_days\n FROM day_node_grid g\n LEFT JOIN participating_node_chans pc ON pc.pubkey = g.pubkey\n LEFT JOIN \"Channels\" c ON c.\"ChanId\" = pc.chan_id AND (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0)\n GROUP BY g.bucket_start, g.pubkey\n)\nSELECT\n df.node_name AS node,\n (dl.sat_days / 100000000.0)::float8 AS liquidity_btc,\n ((df.fee_sats - COALESCE(dyc.yield_cost_sats, 0))::numeric * 365.0 / dl.sat_days)::float8 AS apr,\n df.bucket_start AS \"day\"\nFROM day_fees df\nLEFT JOIN day_yield_costs dyc ON dyc.bucket_start = df.bucket_start AND dyc.pubkey = df.pubkey\nLEFT JOIN day_liquidity dl ON dl.bucket_start = df.bucket_start AND dl.pubkey = df.pubkey\nWHERE COALESCE(dl.sat_days, 0) > 0\nORDER BY node, \"day\"\n", + "rawSql": "WITH days AS (\n SELECT generate_series(date_trunc('day', $__timeFrom()::timestamptz), $__timeTo()::timestamptz, INTERVAL '1 day') AS bucket_start\n), day_buckets AS (\n SELECT bucket_start, bucket_start + INTERVAL '1 day' AS bucket_end\n FROM days\n WHERE bucket_start + INTERVAL '1 day' <= LEAST($__timeTo()::timestamptz, now())\n), nodes AS (\n SELECT \"ManagedNodePubKey\" AS pubkey, MAX(\"ManagedNodeName\") AS node_name\n FROM \"ForwardingHtlcEvents\"\n WHERE $__timeFilter(\"EventTimestamp\") AND \"ManagedNodePubKey\" IN ($node)\n GROUP BY 1\n), chan_flow AS (SELECT f.\"ManagedNodePubKey\" AS pubkey, u.chan_id, SUM(COALESCE(u.amt_msat, 0)) / 1000.0 AS volume_sats FROM \"ForwardingHtlcEvents\" f CROSS JOIN LATERAL (VALUES (f.\"IncomingChannelId\", f.\"IncomingAmountMsat\"), (f.\"OutgoingChannelId\", f.\"OutgoingAmountMsat\")) AS u(chan_id, amt_msat) WHERE $__timeFilter(f.\"EventTimestamp\") AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" IN ($node) GROUP BY 1, 2), chan_open_days AS (SELECT c.\"ChanId\", GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE $__timeTo()::timestamptz END, $__timeTo()::timestamptz) - GREATEST(c.\"CreationDatetime\", $__timeFrom()::timestamptz))) / 86400.0, 0.0) AS open_days FROM \"Channels\" c), participating_node_chans AS (SELECT cf.pubkey, cf.chan_id FROM chan_flow cf LEFT JOIN chan_open_days od ON od.\"ChanId\" = cf.chan_id WHERE $liquidityActivity < 2 OR cf.volume_sats >= $flowMinSats * COALESCE(od.open_days, 0) / 21.0 UNION SELECT n.\"PubKey\" AS pubkey, c.\"ChanId\" AS chan_id FROM \"Channels\" c JOIN \"Nodes\" n ON n.\"Id\" IN (c.\"SourceNodeId\", c.\"DestinationNodeId\") WHERE $liquidityActivity = 0 AND n.\"PubKey\" IN ($node)), day_node_grid AS (\n SELECT db.bucket_start, db.bucket_end, n.pubkey, n.node_name\n FROM day_buckets db CROSS JOIN nodes n\n), day_fees AS (\n SELECT g.bucket_start, g.pubkey, g.node_name,\n COALESCE(ROUND(SUM(f.\"FeeMsat\") / 1000.0)::bigint, 0) AS fee_sats\n FROM day_node_grid g\n LEFT JOIN \"ForwardingHtlcEvents\" f\n ON f.\"EventTimestamp\" >= g.bucket_start AND f.\"EventTimestamp\" < g.bucket_end\n AND f.\"Outcome\" = 1 AND f.\"FeeMsat\" IS NOT NULL AND f.\"ManagedNodePubKey\" = g.pubkey\n GROUP BY g.bucket_start, g.pubkey, g.node_name\n), day_yield_costs AS (\n SELECT g.bucket_start, g.pubkey, COALESCE(SUM(yc.cost_sats), 0) AS yield_cost_sats\n FROM day_node_grid g\n LEFT JOIN \"Nodes\" n ON n.\"PubKey\" = g.pubkey\n LEFT JOIN (\n SELECT s.\"NodeId\", s.\"CreationDatetime\" AS cost_time,\n $applySwapCosts * (COALESCE(s.\"ServiceFeeSats\", 0) + COALESCE(s.\"LightningFeeSats\", 0) + COALESCE(s.\"OnChainFeeSats\", 0)) AS cost_sats\n FROM \"SwapOuts\" s\n WHERE s.\"Status\" = 1 AND $__timeFilter(s.\"CreationDatetime\")\n UNION ALL\n SELECT r.\"NodeId\", r.\"CreationDatetime\" AS cost_time,\n $applyRebalanceCosts * COALESCE(r.\"FeePaidSats\", 0) AS cost_sats\n FROM \"Rebalances\" r\n WHERE r.\"Status\" = 3 AND r.\"FeePaidSats\" IS NOT NULL AND $__timeFilter(r.\"CreationDatetime\")\n ) yc ON yc.\"NodeId\" = n.\"Id\" AND yc.cost_time >= g.bucket_start AND yc.cost_time < g.bucket_end\n GROUP BY g.bucket_start, g.pubkey\n), day_liquidity AS (\n SELECT g.bucket_start, g.pubkey,\n COALESCE(SUM(c.\"SatsAmount\" * GREATEST(EXTRACT(EPOCH FROM (LEAST(CASE WHEN c.\"Status\" = 2 THEN COALESCE((to_jsonb(c) ->> 'ClosedAt')::timestamptz, c.\"UpdateDatetime\") ELSE g.bucket_end END, g.bucket_end) - GREATEST(c.\"CreationDatetime\", g.bucket_start))) / 86400.0, 0.0)), 0) AS sat_days\n FROM day_node_grid g\n LEFT JOIN participating_node_chans pc ON pc.pubkey = g.pubkey\n LEFT JOIN \"Channels\" c ON c.\"ChanId\" = pc.chan_id AND (c.\"CreatedByNodeGuard\" = TRUE OR $liquidityScope = 0)\n GROUP BY g.bucket_start, g.pubkey\n)\nSELECT\n df.node_name AS node,\n (dl.sat_days / 100000000.0)::float8 AS liquidity_btc,\n ((df.fee_sats - COALESCE(dyc.yield_cost_sats, 0))::numeric * 365.0 / dl.sat_days)::float8 AS apr,\n df.bucket_start AS \"day\"\nFROM day_fees df\nLEFT JOIN day_yield_costs dyc ON dyc.bucket_start = df.bucket_start AND dyc.pubkey = df.pubkey\nLEFT JOIN day_liquidity dl ON dl.bucket_start = df.bucket_start AND dl.pubkey = df.pubkey\nWHERE COALESCE(dl.sat_days, 0) > 0\nORDER BY node, \"day\"\n", "refId": "A" } ], @@ -8596,32 +8596,58 @@ { "current": { "selected": true, - "text": "Yes", + "text": "Routed in window", "value": "1" }, - "description": "When set to Yes, aggregate APR denominators only include channels that settled at least one HTLC in the selected window (\"active\" channels). When No, all NodeGuard-managed channels for the selected nodes contribute liquidity, regardless of whether they routed.", + "description": "Which channels contribute liquidity to the APR denominators. All channels: every channel of the selected nodes (subject to Liquidity Scope). Routed in window: only channels that settled at least one HTLC in the window. Above volume gate: only channels whose settled volume clears the routing engine's idle threshold - $flowMinSats per 21 days, prorated by the days the channel was open inside the window - i.e. the channels the engine would not classify as Idle.", "hide": 0, "includeAll": false, - "label": "Active Channels Only", + "label": "Liquidity Activity", "multi": false, - "name": "activeChannelsOnly", + "name": "liquidityActivity", "options": [ + { + "selected": false, + "text": "All channels", + "value": "0" + }, { "selected": true, - "text": "Yes", + "text": "Routed in window", "value": "1" }, { "selected": false, - "text": "No", - "value": "0" + "text": "Above volume gate", + "value": "2" } ], - "query": "Yes : 1, No : 0", + "query": "All channels : 0, Routed in window : 1, Above volume gate : 2", "queryValue": "", "skipUrlSync": false, "type": "custom" }, + { + "current": { + "selected": false, + "text": "10000000", + "value": "10000000" + }, + "description": "Idle volume gate in sats per 21 days. Mirrors ROUTING_ENGINE_FLOW_MIN_MSAT (default 10000000 sats = 0.1 BTC). Only read when Liquidity Activity = Above volume gate.", + "hide": 0, + "label": "Idle Volume Gate (sats / 21d)", + "name": "flowMinSats", + "options": [ + { + "selected": true, + "text": "10000000", + "value": "10000000" + } + ], + "query": "10000000", + "skipUrlSync": false, + "type": "textbox" + }, { "current": { "selected": true,