From 98e95663a4a2a71a4e4842b08e88ab9b358e461a Mon Sep 17 00:00:00 2001 From: bracr10 Date: Thu, 30 Jul 2026 07:29:37 -0600 Subject: [PATCH] docs: normalize book metrics pages into subsections Restructures each metrics page into a consistent What it is / Source / How to read it shape instead of long unbroken prose paragraphs, keeping the fuller explanations but easier to scan. --- book/src/metrics/README.md | 28 ++--- book/src/metrics/activity-grid.md | 82 +++++++-------- book/src/metrics/context-signals.md | 90 ++++++++-------- book/src/metrics/disputes-bond-policy.md | 83 +++++++-------- book/src/metrics/longevity-liveness.md | 113 ++++++++++----------- book/src/metrics/trade-size-consistency.md | 88 ++++++++-------- 6 files changed, 218 insertions(+), 266 deletions(-) diff --git a/book/src/metrics/README.md b/book/src/metrics/README.md index 5bdd40f..1a1cb73 100644 --- a/book/src/metrics/README.md +++ b/book/src/metrics/README.md @@ -1,27 +1,21 @@ # Metrics -Everything in this section is computed directly from Nostr events the node itself has -published — nothing here is self-reported by the node operator, and nothing is a -subjective score. Each event is first scoped to the node being queried: its pubkey must -match as the event's author, the event's `z` tag must match what that kind expects -(order, dispute, dev-fee-payment, or instance info), and its `y` tag must read -`mostro`. Any event that fails that scoping, or belongs to any other kind entirely, is -silently excluded before a single metric is computed — so a report never mixes in -another node's activity, or another application's use of the same Nostr kinds. +Every metric here is computed from Nostr events the node itself has published — nothing +is self-reported or a subjective score. Each event is first scoped to the queried node +(pubkey as author, expected `z` tag, `y=mostro`); anything failing that scope, or +belonging to another kind, is silently excluded before any metric is computed. -Every metric follows the same rule when there isn't enough underlying data to compute -it: it reports its absence explicitly (`N/A` in console/plain text, `null` in JSON) -rather than a fabricated zero. A `0` you see in the report is always a real, computed -answer — for example, a node can genuinely have zero disputes on a healthy trade -history — never a stand-in for missing data. Each page below explains, for its own -metrics, exactly what triggers the not-applicable case. +A missing value always reports as not applicable (`N/A` in console/plain text, `null` +in JSON), never a fabricated zero — a `0` you see is always a real computed answer. +Each page states the exact rule for its own metrics. -The pages are grouped by what they help you evaluate: +Each metric page follows the same structure: **What it is**, **Source** (the Nostr +event kind/tag it's computed from), and **How to read it**. - [Longevity and liveness](longevity-liveness.md) — how long the node has run, and whether it's still active. -- [Trade size and consistency](trade-size-consistency.md) — how much it trades, and - how uniform those trades are. +- [Trade size and consistency](trade-size-consistency.md) — how much it trades, and how + uniform those trades are. - [Disputes and bond policy](disputes-bond-policy.md) — how its trades have gone wrong, and what protection it offers traders. - [Fiat, payment method, and premium](context-signals.md) — what kind of trading it diff --git a/book/src/metrics/activity-grid.md b/book/src/metrics/activity-grid.md index fea1c07..d0848b2 100644 --- a/book/src/metrics/activity-grid.md +++ b/book/src/metrics/activity-grid.md @@ -1,33 +1,29 @@ # Activity grid -The activity grid is the one part of the report that shows change over time instead of -a single lifetime summary: a table with one row per time bucket, and for each bucket, -how many successful trades happened, how much volume they moved, and the median trade -size within that bucket. Where [cumulative performance](trade-size-consistency.md) tells -you the totals, the activity grid tells you *when* that activity happened — steadily -across the node's history, concentrated in one burst, or trailing off recently. +### What it is -It's built from the same qualifying successful orders used everywhere else in the -report (see [Trade size and consistency](trade-size-consistency.md#what-counts-as-a-qualifying-order)), -bucketed by each order's timestamp. +A time-bucketed table of successful trades, volume, and median trade size per bucket — +where [cumulative performance](trade-size-consistency.md#cumulative-performance) shows +totals, this shows *when* that activity happened. + +### Source + +The same qualifying successful orders used throughout the report (see +[Trade size and consistency](trade-size-consistency.md#what-counts-as-a-qualifying-order)), +bucketed by timestamp. ## Range -By default, the grid spans the node's own observed lifetime: from its earliest -qualifying order to its latest. `--since` and `--until` narrow that to an explicit -window instead, and once given, that window is authoritative — even if it turns out to -contain zero orders. In that case the grid still renders every bucket across the -requested range, each showing zero trades, rather than collapsing to the empty/null -result that's reserved specifically for a node with no successful orders at all. That -distinction matters: an empty grid over a requested range tells you "nothing happened -here," which is different information from "this node has no order history to -report." +`--since`/`--until` set an explicit window; without them, the range is inferred from +the node's own earliest/latest qualifying order. + +An explicit range always wins, even with zero orders inside it: the grid still renders +every bucket across that range showing zero trades, rather than the empty/null result +reserved for a node with no successful orders at all. ## Granularity and its threshold -Each row in the grid represents a day, a month, or a year, depending on the -granularity. `--view` lets you force one explicitly; without it, `mostro-score` picks -automatically based on how wide the requested (or inferred) range is: +`--view` forces `daily`, `monthly`, or `yearly`. Without it: | Range | Granularity | |---|---| @@ -35,33 +31,27 @@ automatically based on how wide the requested (or inferred) range is: | ≤ 730 days (~2 years) | monthly | | beyond that | yearly | -The reasoning behind these two boundaries is practical, not statistical: a daily grid -over a two-year range would produce over 700 rows, which is unreadable in a terminal -table, so the tool switches to coarser buckets before that happens. The 90-day and -730-day cutoffs were chosen by thinking through what a usable table size looks like, -not derived from a formula. +**Source of the boundaries:** practical, not statistical — reasoned from usable +terminal-table row count (a daily grid over 2 years would produce 700+ rows), not +measured or derived from a formula. -Because a `--view` override can still force daily granularity over a much wider range -than the automatic rule would ever choose on its own, `mostro-score` prints a stderr -warning whenever that happens, naming the exact number of rows the result will have. -The warning reuses the same 90-day boundary the automatic selection uses, so the two -can never disagree about what counts as "too wide." +If `--view` forces daily granularity over a range wider than 90 days anyway, +`mostro-score` prints a stderr warning naming the resulting row count, using the same +90-day boundary so the warning and the automatic rule never disagree. -One more detail worth knowing if you inspect `range_start`/`range_end` closely: even a -range you didn't set explicitly still gets aligned to the chosen granularity's -boundaries. A grid forced to monthly view snaps its displayed range to the first and -last day of the calendar month, not to the raw timestamp of whichever order happened to -be first or last. +A defaulted range still snaps to the chosen granularity's boundaries — e.g. a forced +monthly view snaps to the first/last day of the calendar month, not a raw timestamp. ## Progress indicator threshold -Unrelated to the grid itself, but worth documenting here since it's the report's other -numeric threshold: while fetching data from relays, `mostro-score` prints a "still -fetching" message to stderr if the fetch takes longer than **3 seconds**, so you're not -left wondering whether the tool has stalled. Unlike the granularity boundaries above, -this number came from direct measurement rather than reasoning: three real -connect-and-fetch round trips against the default relay -(`wss://relay.mostro.network`) took 2.06s, 1.96s, and 1.69s. Normal single-relay -operation sits around two seconds, so three seconds sits comfortably above that normal -variance while still catching a fetch that's genuinely running slow. Pass `--quiet` to -suppress it along with the tool's other transient status messages. +### What it is + +A "still fetching" message printed to stderr when a relay fetch runs past **3 +seconds**, so a slow fetch doesn't look like a stall. Suppressed by `--quiet`. + +### Source + +Direct measurement, not reasoning: 3 real connect-and-fetch round trips against +`wss://relay.mostro.network` took 2.06s/1.96s/1.69s. Normal operation sits around 2s, +so 3s sits comfortably above that variance while still catching a genuinely slow +fetch. diff --git a/book/src/metrics/context-signals.md b/book/src/metrics/context-signals.md index e154560..664cda3 100644 --- a/book/src/metrics/context-signals.md +++ b/book/src/metrics/context-signals.md @@ -1,63 +1,59 @@ # Fiat, payment method, and premium -These three signals are less about whether a node is trustworthy and more about what -kind of trading it actually does — which currencies it settles in, how traders pay, -and how its pricing compares to the market rate. All three read tags on the same -qualifying successful orders described in +These three describe what kind of trading a node does, not whether it's trustworthy. +All three read tags on the same qualifying successful orders described in [Trade size and consistency](trade-size-consistency.md#what-counts-as-a-qualifying-order), -and all three compare their values byte-for-byte: no trimming whitespace, no case -folding. `"USD"` and `"usd"` are different values here, and so are `"Cash"` and -`" Cash"` — because the Mostro protocol itself doesn't normalize these values before -publishing them, and silently merging them could hide a real formatting bug in a -node's own software. +and all compare values byte-for-byte: no trimming, no case folding. `"USD"`/`"usd"` and +`"Cash"`/`" Cash"` are distinct values, since the protocol itself doesn't normalize them. ## Fiat currency breakdown -This shows which fiat currencies the node's trades settle in, and how much of its -volume each one represents — a currency's order count and its percentage share, -ranked from most to least common (ties are broken alphabetically, so the ranking is -always deterministic). It's built from the `f` tag on each qualifying successful -order. +### What it is -If no qualifying order carries a non-empty `f` value, there's no distribution to build, -and the field reports as not applicable rather than an empty list. +Which fiat currencies the node's trades settle in, ranked by share of orders, +descending (ties broken alphabetically). -**How to read it:** this tells you what to expect if you trade with the node — a node -that mostly settles in EUR isn't necessarily a bad fit if you want USD, but it's useful -context before you start. +### Source + +The `f` tag on each qualifying successful order. Not applicable when no order carries +a non-empty value. + +### How to read it + +Tells you what to expect before trading — a EUR-heavy node isn't a bad fit for USD +trading, but it's useful context up front. ## Payment method breakdown -Similar in shape to the fiat breakdown, but built from the `pm` tag, which records the -payment methods (bank transfer, cash, a specific app) buyers and sellers have used. -Unlike most tags in this report, `pm` can carry more than one value per order — a -single order might list several accepted methods — so this breakdown counts every -individual *mention* across all qualifying orders, not one count per order. A node -where every order lists three payment methods will show three times as many mentions -as orders, and that's expected, not a bug. +### What it is + +Ranked distribution of payment methods used, by mention count, not order count. + +### Source + +The `pm` tag, a multi-value Nostr tag: one order can mention several methods, and each +mention counts individually. An order listing 3 methods contributes 3 mentions, not 1. +Not applicable when there are no mentions at all. -It reports as not applicable when there are no `pm` mentions at all across the node's -qualifying orders. +### How to read it -**How to read it:** use this to gauge whether the node typically supports the payment -method you plan to use, before you commit to a trade. +Use it to check whether the node typically supports your preferred payment method. ## Premium signal -Mostro orders are usually priced at some premium or discount relative to the market -rate, expressed as a signed percentage in the `premium` tag (a negative value means a -discount, a positive one a markup). This signal reports two numbers computed from that -tag across the node's qualifying successful orders: `premium_baseline_percent`, the -median premium the node has actually charged, and `premium_dispersion_percent`, the -population standard deviation around that median — how much the premium tends to swing -from order to order. - -Both numbers need at least two data points to mean anything, so they report as not -applicable whenever fewer than two qualifying orders carry a valid, parseable `premium` -value. - -**How to read it:** the baseline tells you roughly what premium to expect from this -node on a typical trade. The dispersion tells you how much that can vary — a low -dispersion means the node prices consistently near its baseline, while a high one means -premiums swing widely between orders, so the baseline alone is a less reliable -predictor of what you'll actually be offered. +### What it is + +`premium_baseline_percent` (median) and `premium_dispersion_percent` (population +standard deviation) of the node's pricing premium/discount versus market rate. + +### Source + +The `premium` tag, a signed integer percentage (negative = discount, positive = +markup). Both fields need at least 2 qualifying orders with a valid `premium` value; +otherwise not applicable. + +### How to read it + +The baseline is the premium to expect on a typical trade. The dispersion tells you how +reliable that expectation is — high dispersion means premiums swing widely between +orders. diff --git a/book/src/metrics/disputes-bond-policy.md b/book/src/metrics/disputes-bond-policy.md index 7a7bb9d..79bb637 100644 --- a/book/src/metrics/disputes-bond-policy.md +++ b/book/src/metrics/disputes-bond-policy.md @@ -2,52 +2,43 @@ ## Dispute signals -A dispute means a trade broke down badly enough that a third party had to step in and -resolve it. This section reports how many disputes a node has had, how they were -resolved, and how that compares to the node's trade volume — because a handful of -disputes on a node with thousands of trades reads very differently from the same -handful on a node with only a dozen. - -The source is the dispute event, kind `38386`, scoped to the node. Like orders, a -dispute is republished every time its status changes, so `mostro-score` deduplicates by -the dispute's `d` tag and keeps only its most recent state. That final state's `s` tag -is then classified into one of three buckets: `resolved` (`settled`, -`seller-refunded`, or `released` — the dispute concluded), `active` (`initiated` or -`in-progress` — it's still open), or `unknown` (any other value, or a missing one). A -dispute lands in `unknown` rather than being dropped, because the event itself proves a -real dispute happened, even if its final outcome can't be classified from the tag. - -The report also computes disputes per 100 successful trades, a normalized rate that -lets you compare nodes of very different sizes on equal footing. This rate is not -applicable only when the node has zero successful trades at all — there's no -denominator to divide by. A node with disputes but zero trades in the denominator -(possible if every trade failed or was canceled) reports the rate as not applicable -too, for the same reason. A node with trades but *zero* disputes reports the rate as a -real `0.0`, which is a meaningful, favorable number, not a placeholder for missing data. - -**How to read it:** look at the rate, not the raw count, when comparing nodes. A high -rate is a real warning sign; a low one, especially alongside a long trade history, is a -positive signal. `unknown`-status disputes are worth a second look on their own — they -mean something happened that the node's own data doesn't fully explain. +### What it is + +How many disputes the node has had, how they resolved, and disputes per 100 successful +trades — a normalized rate for comparing nodes of different sizes. + +### Source + +Dispute events (kind `38386`), deduplicated by `d` tag to each dispute's latest state. +The final `s` tag classifies it: `resolved` (`settled`/`seller-refunded`/`released`), +`active` (`initiated`/`in-progress`), or `unknown` (anything else, or missing) — still +counted once toward the total, since a real dispute event exists regardless of status. + +### Not-applicable rule + +`disputes_per_100_trades` is not applicable only when successful trades are zero — +there's no denominator. Zero disputes with trades present is a real `0.0`, not +not-applicable. + +### How to read it + +Compare the rate, not the raw count, across nodes. `unknown`-status disputes are worth +a second look — something happened that the node's own data doesn't fully explain. ## Bond policy -Some Mostro nodes require traders to lock a small bond before entering a trade, as a -deterrent against bad-faith behavior. Bond policy reports whether this node does: -`enabled`, `disabled`, or `unknown`. - -The source is the instance-status event, kind `38385`, which a Mostro node republishes -periodically with its own operational settings, including a `bond_enabled` tag. -`mostro-score` selects the node's single most recent instance-status event and reads -that tag directly: `true` maps to `enabled`, `false` maps to `disabled`. Anything -else — a missing instance-status event entirely, or a `bond_enabled` value that isn't -recognizably `true`/`false` — maps to `unknown`. `unknown` is deliberately never -collapsed into `disabled`: not knowing whether a bond is required is a different, more -uncertain situation than confirming one isn't, and the report is written to keep that -distinction visible rather than picking a side. - -**How to read it:** this metric is descriptive, not a verdict. The report will never -tell you `enabled` is safer than `disabled` or vice versa, because that depends on -context this tool doesn't have — a bond requirement raises the cost of trading but also -raises the cost of bad-faith behavior on both sides. Treat it as one more fact to weigh -alongside the node's dispute history and trade record, not a pass/fail check. +### What it is + +Whether the node requires traders to lock a bond before a trade: `enabled`, +`disabled`, or `unknown`. + +### Source + +The node's most recent instance-status event (kind `38385`), reading its +`bond_enabled` tag. `true`/`false` map directly; a missing event or an unparseable +value maps to `unknown` — never collapsed into `disabled`. + +### How to read it + +Descriptive, not a verdict: the report never implies which status is safer. Weigh it +alongside dispute history and trade record, not as a pass/fail check. diff --git a/book/src/metrics/longevity-liveness.md b/book/src/metrics/longevity-liveness.md index cff5969..08e8db2 100644 --- a/book/src/metrics/longevity-liveness.md +++ b/book/src/metrics/longevity-liveness.md @@ -2,73 +2,62 @@ ## Longevity -Longevity answers a simple question: how long has this node actually been running? -It's reported as two numbers, `first_seen_at` (a date) and `days_active` (a count), -and both exist to give a trader a sense of track record before they trust a node with -a trade. - -The primary source is the dev-fee-payment event, kind `8383` with `z=dev-fee-payment` -and `y=mostro`. Every time a Mostro instance completes a trade, it pays a small -development fee, and that payment is published as a Nostr event carrying a timestamp. -`mostro-score` looks at every dev-fee event the node has ever published and takes the -oldest one — that timestamp becomes `first_seen_at`, and `days_active` is simply the -number of days between it and now. This is the most reliable anchor available, because -dev-fee payments are tied to the protocol's own fee-splitting logic, not to anything the -node operator controls or could omit. - -Not every node has a dev-fee history to draw on — a very new node, or one running a -build that predates the dev-fee mechanism, might have none. When that happens, -`mostro-score` falls back to the node's own order history: it looks at the oldest -qualifying successful order (see [Trade size and consistency](trade-size-consistency.md) -for what "qualifying" means) and measures from there to now instead. This fallback -deliberately measures from the order to *now*, not from the first order to the *last* -order, because the latter would freeze at whatever the gap between two trades happened -to be — a node with exactly one successful trade would always show zero days active, -which misrepresents a node that traded once and then kept running. - -If neither a dev-fee event nor a qualifying successful order exists at all, there's -nothing to measure from, and both fields print as not applicable (`N/A` in -console/plain text, `null` in JSON). That's not an error; it just means the node has no -publicly visible trading history yet. - -**How to read it:** a longer `days_active` generally means more chances for the node's -behavior to have been tested by real trades, but it says nothing on its own about -whether those trades went well — pair it with the [dispute signals](disputes-bond-policy.md) -and [cumulative performance](trade-size-consistency.md) before drawing a conclusion. +### What it is + +How long the node has been operating: `first_seen_at` (a date) and `days_active` (a +day count). + +### Source + +The oldest dev-fee-payment event (kind `8383`, `z=dev-fee-payment`, `y=mostro`) — a +small fee every Mostro instance pays each time it completes a trade. When the node has +none, longevity falls back to its oldest qualifying successful order instead (see +[Trade size and consistency](trade-size-consistency.md#what-counts-as-a-qualifying-order)), +measured to *now*, not to the node's last order — otherwise a node with exactly one +trade would always read zero days active. + +If neither exists, both fields are not applicable: the node has no publicly visible +history yet, not an error. + +### How to read it + +A longer `days_active` means more chances for the node's behavior to have been tested, +but says nothing about how those trades went. Pair it with +[dispute signals](disputes-bond-policy.md) and +[cumulative performance](trade-size-consistency.md#cumulative-performance). ## Liveness -Where longevity looks at the whole lifetime, liveness looks at whether the node is -*currently* active. It reports the timestamp of the node's last successful trade, how -many days have passed since then, and how many successful trades happened in the last -7, 30, and 90 days. +### What it is + +Whether the node is *currently* active: last successful trade, days since it, and +rolling 7/30/90-day successful-trade counts. -The source is the same order events (kind `38383`) used everywhere else in the report, -filtered down to the ones whose final, deduplicated status is `success`. A node with -zero successful orders reports every liveness field as not applicable — there's no last -trade to measure from, and the three rolling counts are all zero by definition rather -than missing. +### Source -**How to read it:** liveness is the most direct signal of whether a node is still being -actively used right now. A node with strong historical longevity but no successful -trades in the last 90 days may have gone quiet, been abandoned, or simply be in a slow -period — the report doesn't guess which, it just gives you the raw numbers to judge for -yourself. +The same order events (kind `38383`) used throughout the report, filtered to `success` +status. Zero successful orders reports every field as not applicable. + +### How to read it + +Strong longevity with no recent trades may mean the node has gone quiet, been +abandoned, or is in a slow period — liveness doesn't say which, only that current +activity is low. ## Activity consistency -Activity consistency measures how evenly a node's trading is spread out, rather than -clustered in a burst and then silent. It's reported as two numbers: how many distinct -calendar days had at least one successful trade in the last 30 days, and the longest -stretch of consecutive inactive days within that same window. - -The window is exactly 30 UTC calendar days, ending on and including today — not a -rolling 30×86400-second cutoff. That distinction matters at the edges: measuring by raw -seconds instead of calendar days can silently include an extra day whenever the current -moment isn't exactly at midnight UTC, so `mostro-score` aligns both ends of the window -to day boundaries first. - -**How to read it:** a node active on most of the last 30 days, with a short maximum -gap, is trading steadily. A node with only one or two active days and a gap of 28 days -either trades in occasional bursts or has mostly gone quiet — again, the numbers don't -label which one it is, but they give you enough to notice the pattern. +### What it is + +How evenly trading is spread out: active days in the last 30, and the longest gap of +consecutive inactive days in that same window. + +### Source + +Order timestamps, counted over a fixed window of exactly 30 UTC calendar days ending +today — aligned to day boundaries, not a rolling 30×86400-second cutoff, which would +include an extra day whenever "now" isn't exactly at midnight UTC. + +### How to read it + +Many active days with a short max gap means steady trading. Few active days with a long +gap means bursty or mostly-quiet trading. diff --git a/book/src/metrics/trade-size-consistency.md b/book/src/metrics/trade-size-consistency.md index 7e0d653..401fea0 100644 --- a/book/src/metrics/trade-size-consistency.md +++ b/book/src/metrics/trade-size-consistency.md @@ -2,58 +2,50 @@ ## What counts as a qualifying order -Both metrics on this page draw from the same underlying set: order events, kind -`38383`, scoped to the queried node as author with `z=order` and `y=mostro`. Mostro -republishes an order event every time its status changes, so the same order can appear -on a relay several times over its lifetime. `mostro-score` deduplicates by the order's -`d` tag, keeping only each order's most recent published state, and then counts an -order as "qualifying" only if that final state's `s` tag reads `success`. An order that -was created, matched, and then canceled never contributes to these numbers — only -trades that actually completed do. +Order events (kind `38383`, `z=order`, `y=mostro`) republish on every status change. +`mostro-score` deduplicates by the order's `d` tag, keeps only the most recent +published state, and counts an order as qualifying only if that final state's `s` tag +reads `success`. A canceled order never contributes to these metrics. ## Cumulative performance -This is the simplest metric in the report: how many trades has the node completed, -ever, and how much sats volume did they move in total. It has no time window and no -"N/A" case — a node with zero successful trades reports `0` for both, which is a real, -meaningful answer (this node has no completed trade history), not a missing value. +### What it is -**How to read it:** cumulative performance is a floor, not a full picture. A node could -have moved a large volume years ago and gone dormant since — pair this with -[liveness](longevity-liveness.md) to see whether that volume reflects an active node or -a historical one. +Total successful trades and total sats volume, over the node's full history. + +### Source + +Every qualifying successful order, summed. + +### How to read it + +A floor, not the full picture: a node could have moved volume years ago and gone +dormant since. Pair it with [liveness](longevity-liveness.md#liveness). ## Trade size -Trade size describes the shape of the node's individual trades: the smallest and -largest amounts (in sats), the mean and median, the standard deviation, and the -coefficient of variation — the standard deviation divided by the median, which -collapses "how spread out are the trade sizes" into a single, scale-independent number. -A coefficient of variation near zero means the node's trades tend to be similar in -size; a high one means trade sizes swing widely, from tiny to very large, on the same -node. - -The amount comes from the `amt` tag on each qualifying successful order, parsed as an -integer number of sats. Not every order publishes a parseable `amt` — when one doesn't, -that order still counts toward [cumulative performance](#cumulative-performance) and -[liveness](longevity-liveness.md), but it's simply excluded from this specific -calculation, since there's nothing valid to average in. - -When there isn't enough data, the fields report their absence rather than a misleading -number. With zero qualifying orders carrying a parseable `amt`, every field here is not -applicable. The coefficient of variation has an even stricter rule on top of that: it's -not applicable whenever fewer than two orders exist (variation needs at least two -points to mean anything) or whenever the median trade size is exactly zero, since -dividing by a zero median is mathematically undefined no matter how many samples you -have. - -One deliberate precision detail: when the number of qualifying orders is even, the -median is the average of the two middle values, and that average is reported exactly -as computed, including a fractional sats value like `0.5` — it is never rounded down to -a whole number, since doing so would also quietly corrupt the coefficient of variation -that's computed from it. - -**How to read it:** a low coefficient of variation suggests a node that handles -similarly-sized trades consistently. A very high one isn't necessarily bad — it might -just mean the node serves both small retail trades and large ones — but it's worth -knowing before you send a trade far outside what the node normally handles. +### What it is + +The shape of individual trades: min, max, mean, median, standard deviation (sats), and +the coefficient of variation (std dev ÷ median) — a single, scale-independent number +for how spread out trade sizes are. + +### Source + +The `amt` tag on each qualifying successful order. An order with no parseable `amt` +still counts toward cumulative performance and liveness, just not toward this +calculation. + +### Not-applicable rules + +- Every field: not applicable with zero qualifying orders carrying a parseable `amt`. +- Coefficient of variation, additionally: not applicable with fewer than 2 orders, or + when the median is exactly `0` (dividing by a zero median is undefined). +- The median over an even-sized set is a genuine fraction (e.g. `0.5`), never truncated + to an integer. + +### How to read it + +A low coefficient of variation means consistent trade sizes. A high one isn't +necessarily bad — it may just mean the node serves both small and large trades — but +it's worth knowing before trading far outside the node's typical size.