Methodology: how these numbers are counted

Everything on this page is read straight from the same engine that produces the numbers on the site. If a definition changes, this page changes with it — none of these statements is typed in here.

Methodology version 1.0.0 · Response generated:

89 days · 1405/02/27, 15:13 UTC to 1405/05/23, 18:45 UTC

Which trades enter the record

A trade enters the public record when it has closed (closed_at IS NOT NULL), has a recorded realised ROI (pnl_percent_on_close IS NOT NULL AND <> 0), and was not closed by a risk-engine housekeeping action. A win is a trade whose recorded ROI is strictly greater than zero. Nothing else is filtered — every loss is included.

Definition of a win

A win is a closed gradeable trade whose recorded realised ROI (positions.pnl_percent_on_close) is strictly greater than zero. This deliberately counts a trailing stop that closed in profit as a win and a break-even stop as a loss, because the outcome, not the status token, is what a follower experienced.

Definition of a loss

Any closed gradeable trade with recorded ROI <= 0. All of them are published.

What is excluded, and why

Excluded rows are positions the risk engine flattened for reasons unrelated to the trade thesis (exposure caps, correlation guards, circuit breakers, news blackouts, adaptive throttles). They close at exactly pnl_percent_on_close = 0 because no market outcome was realised. Counting them as wins would inflate the win rate; counting them as losses would deflate it. They are reported as a count so the exclusion is auditable.

Excluded close reasons

  • EXPOSURE_CAP
  • ADMIN_BULK_CLOSE_P10
  • SIGNAL_GUARD
  • CORR_GUARD
  • CIRCUIT_BREAKER
  • CLAUDE_VETO
  • CLAUDE_L5_ABORT
  • TF_HIERARCHY
  • NEWS_BLACKOUT
  • LIQUIDITY
  • MAX_HOLD_MANUAL_EXPOSURE_UNBLOCK
  • ADAPTIVE:* (any adaptive throttle)

339 closed rows are set aside for these reasons, and the count is published here.

How the return is computed

Source column
positions.pnl_percent_on_close
Written by
app/trading/monitor.py via app.trading.utils.roi_percent()
Formula
((exit - entry)/entry for LONG, (entry - exit)/entry for SHORT) * leverage * 100
Unit
percent of margin (leveraged ROI)

Important warning

This number ALREADY includes leverage. Multiplying it by leverage again is the double-count that produced the -192 USD display for a -19.21% trade.

Unleveraged price move

The unleveraged price move. Derived algebraically from the authoritative recorded ROI rather than recomputed from stored exit prices — see known_limitations.

leveraged_roi_pct / leverage

The equity curve — a model, not an account balance

This curve is a model, not an account balance. Assumption: each trade allocates 2% of current equity as margin, trades are taken sequentially with no overlap, and fees and funding are not modelled. The starting value of 100 is an index, not dollars.

equity_n = equity_(n-1) * (1 + allocation_pct/100 * leveraged_roi_pct/100)

allocation_pct is an assumption chosen by whoever called this endpoint, not a fact measured from the data. Every metric listed in sizing_dependent_metrics moves with it and each one restates the value used in its own method string. At a small allocation the compounded curve approaches the arithmetic sum of per-trade ROI and can be positive while a larger allocation is negative, because losses compound. The per-trade record — win rate, profit factor, expectancy, the ledger — does NOT depend on it.

Why no dollar figure is published

positions.quantity, positions.pnl_usdt and positions.exit_price are 100% NULL across all 1,495 rows of positions. Position size was never recorded, so absolute profit in USDT is not computable from this dataset and is never published.

Risk metrics

profit_factor
sum(positive ROI-points) / |sum(negative ROI-points)|
expectancy
mean leveraged ROI per trade
max_drawdown
peak-to-trough of the compounded equity curve, expressed in percent
sharpe
annualised on calendar-daily returns of the compounded curve, sqrt(365), risk-free rate 0, days with no closed trade count as 0%. Requires >= 30 days.
calmar
annualised compounded return / |max drawdown|. Requires >= 30 days of history.
sizing_dependence
profit_factor, expectancy and the win rate are properties of the trade record and do not move with any assumption. max_drawdown, sharpe and calmar are computed on the compounded curve and therefore DO move with allocation_pct, which the caller chooses. Every one of those envelopes restates the allocation_pct it used in its own method string and carries sizing_dependent=true.

Known limitations

We do not hide this list and we do not shorten it. Each entry has its own id, severity and technical explanation.

L1No money figure exists anywhere in the dataset Critical

positions.pnl_usdt, positions.exit_price, positions.quantity and positions.order_id are 100% NULL across all 1,495 rows. Position size was never recorded, so absolute profit in USDT is not computable. No endpoint on this surface returns a USDT figure, and any page that shows one is wrong.

L2Stored exit prices do not reproduce the recorded ROI High

signal_quality_metrics.exit_price is the market price observed when the exit condition was detected. On stop exits the engine deliberately books the ROI at a modelled fill of sl +/- 0.10% instead (app/trading/monitor.py, P7 2026-05-30). Measured live from the current dataset: the two disagree by more than 0.5 ROI-points on 759 of the 1,083 trades that have both, and the disagreement is systematically flattering to the observed series. Aggregates therefore use the recorded ROI only; the observed exit price is published per-trade with a reconciliation flag.

L3Recorded ROI ignores partial take-profit fills High

pnl_percent_on_close records only the final exit leg. Since 2026-07-30 the TP ladder closes positions in parts (partial_close_log). For those trades the recorded number UNDER-states the real result. basis=blended reconstructs sum(close_pct * roi_at_close) + (1 - sum(close_pct)) * recorded. The default basis is 'recorded' — the conservative one, and the one that matches what was announced on Telegram at the time. Live count: 86 of the 1,155 closed gradeable trades have at least one recorded partial fill.

L4ROI is not comparable across time High

Stop distance was tightened from roughly 5-8% to roughly 1.2-1.5% during 2026-08 and average leverage fell from 15.2x to 11.8x. A May trade and an August trade are measured with different rulers. avg_leverage is published per month so the break is visible rather than hidden.

L5Signal-record performance, not audited account statements Medium

These are the outcomes of published signals as recorded by our own monitor. There is no exchange order id, no fill, no fee and no funding cost in the dataset. A follower's real result will differ. What IS verifiable is publication: see row_counts.trades_with_telegram_message — each of those trades carries the id of the message that announced it in the channel before the outcome was known.

L6Timestamps are stored as text in three different shapes Medium

Every timestamp column in this dataset is TEXT, not a timestamptz. Measured 2026-08-15: positions.created_at (1495/1495), activated_at (1382/1382) and closed_at (1494/1494) always carry an explicit UTC offset — '+03:30' from app.trading.utils.now_iso(), or '+00' on the 70 rows written by the 2026-05-30 admin bulk close. partial_close_log.executed_at (125/125) always carries '+03:30'. position_events.occurred_at is the exception: 14,935 of 14,937 rows are NAIVE and are Tehran local time, because db_positions.py writes them with now_sql_naive(). Reading those as UTC shifted the whole event log 3.5 hours forward and produced per-trade pages whose entry came after their own exit; that is fixed, and there is now exactly one conversion function. Everything published is UTC and month buckets are computed in UTC, so the calendar-month boundary sits at 03:30 Tehran, not midnight.

L7Concurrency is not modelled in the equity curve Medium

The compounded curve applies trades sequentially in close order. In reality several positions were open at once, so the real path of a follower's equity would differ. The curve is a model of the trade sequence, not a reconstruction of an account.

L8A closing event can be logged a few seconds after the close Low

position_events rows are written by the monitor loop after it has already stamped positions.closed_at, so a closing event can trail the close slightly. Measured 2026-08-15 across the whole gradeable universe: 14,568 events, none before the position opened, three after it closed and all three within 125 seconds (one position, TYR-260520-IQZ3E1S0). The published allowance is 300 seconds; /trade/{code} ships a timeline_consistency block that lists any event outside it instead of hiding it.

L9Two endpoints can be served from snapshots up to one TTL apart Low

All arithmetic comes from this single module, so no two surfaces can ever apply different DEFINITIONS. The snapshot is a different matter: the trade set is cached per worker process for 60 seconds and the API runs several workers, so /summary and /ledger can be answered from snapshots up to one TTL apart and differ by whatever closed in between. Every payload publishes provenance.data_loaded_at and provenance.cache_ttl_seconds, so the skew is always measurable from the responses themselves; compare those two fields before comparing two numbers.

Timestamp policy

Every timestamp published by this module is ISO-8601 in UTC (+00:00). Source columns are TEXT in mixed formats: positions.created_at/activated_at/closed_at and partial_close_log.executed_at always carry an explicit UTC offset; position_events.occurred_at is written naive in Asia/Tehran local time by app.trading.utils.now_sql_naive(). A naive value is therefore interpreted as Asia/Tehran and converted to UTC. There is exactly one conversion function, so no two timestamps in one payload can be in different zones.

Live row counts

Live row counts
CounterValue
positions_table_rows1,495
closed_gradeable_trades1,155
excluded_housekeeping_rows339
trades_with_telegram_message905
trades_with_observed_exit_price1,083
trades_with_nonreconciling_exit_price759
trades_with_partial_fills86
trades_without_recorded_leverage0
tp_hit_events816
distinct_symbols93

Data range: · 93 symbols

The same data, machine-readable

All of these endpoints are public and unauthenticated. You can reproduce every number on this site yourself.

Every number on this surface is computed by one module, so no two endpoints can apply different definitions of a win, a window or a universe. The data SNAPSHOT is cached per worker process for 60 seconds and the API runs several workers, so two endpoints can be answered from snapshots up to one TTL apart and differ by whatever closed in between. Compare provenance.data_loaded_at on the two responses before comparing their numbers — that field exists so the skew is measurable rather than deniable.