Signal dataset
Every signal we sent from June 2021 to February 2024, as one file.
33,694 signals across 33 months — entry, close, timestamps and the per-trade result, including the ones that went nowhere. Download it, run your own numbers, argue with ours.
Looking for the results after February 2024? Every signal broadcast since June 2026 is scored hourly on the live performance page, with one page per month; missed signals count against the rate there too.
Download CSV (3.4 MB) Download JSON (8.6 MB)
The live record, since June 2026
Every element signal broadcast since June 2026, one row each, rebuilt from the trade database every day (updated 2026-11-03): 29,430 signals, 28,346 reached their target, 503 missed, 581 still open. It is the same record as the live performance page, under the same rule: a signal that does not reach its target within the maximum hold is missed, with its move at that moment.
Live record, CSV (3.2 MB) Live record, JSON (8.1 MB)
| Column | Meaning |
|---|---|
month | The month the signal was sent, UTC, YYYY-MM. |
symbol, side | Binance USDT-M perpetual pair; LONG or SHORT. |
element, strength | The element name and strength the signal was posted with. |
status | target_hit, missed (the maximum hold ended first) or open (still running). |
entry_price, target_price, target_pct | As posted: the entry, the target and the target's distance in percent. |
close_price, close_time_utc | Where and when it settled: at the target, or at the market when the hold ended. Empty while open. |
entry_time_utc | When the signal was sent, UTC. |
result_pct | The unleveraged move from entry to close, in percent, signed for the side; negative for a missed signal that ended below its entry. Empty while open. |
Corrections: before 31 August 2026 a rounding fault saved some targets on very cheap coins wrongly. Those signals were re-scored from price history against their rebuilt target; 23 from June to August 2026 whose target could not be rebuilt are left out of this file and of every published count. As with the archive, the file says what happened to each signal, never what caused it.
What is in it
One row per signal the bot broadcast, flattened from the 33 monthly order spreadsheets. Those spreadsheets remain the source; this is the same data in a shape you can load in one line. First entry 2021-06-08 00:15:55, last close 2024-03-10 07:41:30 (UTC).
| Column | Meaning |
|---|---|
month | Which monthly report the row came from, YYYY-MM. |
symbol | Binance USDT-M perpetual pair. |
side | long or short. |
status | closed, or open for a signal still running when that month's sheet was cut. For open rows close_price and close_time are empty and pnl_pct is a mark-to-market snapshot at the cut, not a result. |
strength | The signal's strength rating, where the month recorded it. Empty in the earliest months, which predate the rating. |
entry_price, close_price | Reference price when the signal fired, and the price at which the sheet recorded it closed. |
entry_time, close_time | UTC, YYYY-MM-DD HH:MM:SS. Converted from the spreadsheets' Excel serial dates. |
pnl_pct | Unleveraged move from entry to close, in percent, signed for the side. A short that fell 9.45% reads 9.4500. |
pnl_max_pct, pnl_min_pct | The best and worst unleveraged excursion the sheet recorded while the signal was open — useful for anyone studying where a tighter target or stop would have landed. |
What is not in it, and why
The spreadsheets carry a column naming what fired each signal. It is not here. What the model looks at is the product, and the public description stops at "240+ indicators, formulas and components"; a dataset that let a reader reconstruct the trigger rules would give that away row by row. Everything about the outcome of each signal is included. Nothing about the cause is.
How to read it honestly
- Filter on
statusbefore you compute anything. 613 rows are signals that were still open when the month closed. The largest losses in the file are those snapshots, not closed trades; a reader who averages the whole column without noticing is measuring the cut-off date, not the signals. - "Closed" means the target was reached — and only that. Every one of the 33,081 closed rows is positive, because these sheets recorded a close when a signal hit its target and nothing else: a signal that had not got there by the month's cut is the
openrow, mark-to-market. So the archive's accuracy is target-hits over everything, with the misses sitting instatus = open. That is a weaker accounting than the live page uses now, where a signal that does not reach its target in time is scored as Missed; read the two with that difference in mind, and the monthly figures on the reports page as reported in those sheets. - The same symbol and entry time can appear more than once. When more than one signal fired on a pair at the same moment, the sheet kept a row for each and closed each on its own — 3,004 rows in 1,376 such groups, always within one month. Count rows, not entry moments, if you want the figure the reports use.
- Returns are per signal, unleveraged, and unsized. A row is a price move, not an account return. Position sizing, fees, funding and slippage all sit between this file and any real P&L.
- Frequency is not constant. Signal counts swing with the market; a month with more rows is a more volatile month, not a better one.
- This is the archive, not the present. Signals since June 2026 are on the live performance page, rebuilt hourly from the trade database, where signals that miss their target within the maximum hold count against the hit rate.
Use it
Free to download and analyse. If you publish something built on it, a link back to this page is appreciated — and if you find something in it we should know about, tell us: @hafizebot.
To cite or show the dataset, either of these links back here:
HafizeBot (2026). Binance USDT-M futures signal archive, June 2021 – February 2024, 33,694 rows. https://www.hafizebot.com/data
[](https://www.hafizebot.com/performance)
import pandas as pd
df = pd.read_csv("https://www.hafizebot.com/downloads/signals-2021-2024.csv")
closed = df[df["status"] == "closed"]
closed.groupby("month")["pnl_pct"].agg(["count", "mean", "median"])
Past performance does not guarantee future results; nothing here is investment advice. See the risk disclosure below.