tripolskypetr/trading-entries
TradingView Analyst Grading via Risk-Management Grid Sweep This dataset answers a single question: does an analyst's call differ from the market spread — and if so, at what cost. The cost here is the investor's risk management: how deep a stop they will tolerate, how many days their money stays frozen, and at what percentage they lock in profit. The basis is TradingView Ideas posts on crypto carrying a LONG / SHORT direction. Every post is swept across 21,280 points of a… See the full description on the dataset page: https://huggingface.co/datasets/tripolskypetr/trading-entries.
TradingView Analyst Grading via Risk-Management Grid Sweep
This dataset answers a single question: does an analyst's call differ from the market spread — and if so, at what cost. The cost here is the investor's risk management: how deep a stop they will tolerate, how many days their money stays frozen, and at what percentage they lock in profit.
The basis is TradingView Ideas posts on crypto carrying a LONG / SHORT direction. Every post is swept across 21,280 points of a risk-management grid, and for each point the outcome for the money is computed. The very same set of calls yields anywhere from −445 % to +201 % total PnL depending on exit parameters alone — an analyst's quality is inseparable from the risk regime they are read in.
Where this sits in the pipeline. This is the author-selection stage, not a finished strategy. Its output is a ranking — which authors are worth following, and inside which risk corridor — to be handed to a full backtest in `backtest-kit`, whose Simulator entity produced these artifacts. Every author is graded in isolation here (one slot per author, no shared capital), which is what makes the numbers comparable across authors; turning a whitelist into a portfolio — shared exposure, risk validation across positions, real order execution — is the engine's job, not the dataset's. The reference implementation of that second stage ships as a strategy example in backtest-kit.
What makes a call "good" or "bad"
The single grading metric is profit-before-stop, judged by the chronology of a real trade inside the hold window:
A timeout and a data cut-off are both losses, not exclusions from the statistics. A candle touching the stop and a fixation level simultaneously is resolved in favour of the stop: for a long, the falling price crosses the lower level first.
The grid
{
hardStopPercent: [1, 1.5, … 10], // 19 values
trailingTakePercent: [0.5, 1, … 5], // 10 values
holdMinutes: [1 … 14 days], // 14 values
profitLockPercent: [1, 1.5, 2, 2.5, 3, 3.5, 4, 5], // 8 values
}
// 19 × 10 × 14 × 8 = 21,280 pointsThe grid is the measuring instrument. It answers questions a single backtest cannot pose:
- How wide a stop the author's calls need to survive. If positive PnL only begins at
hardStopPercent ≥ 7, the author enters too early and the market routinely pierces their "entry point" by 7 %. - How long the money stays frozen.
avgHoldMinutes,p95,p99per point. If PnL only appears at 11–14 days of holding, the return was paid for with dead capital. - Whether the signal differs from the spread. PnL is computed net of costs (fee ×2 + slippage on both legs). Points where the result drowns in costs are calls at the noise level.
Layout
content/<month>/
assets/tv-ideas.normalize.jsonl # input: TradingView posts
index.<symbol>.mjs # one-symbol run
data/<SYMBOL>/
result.json # per-symbol summary
result_reports.jsonl # 21,280 points × metrics + trades
result_best.jsonl # winners by 4 criteria
result_tracks.jsonl # (rule × author): hitRate67 months (jan_2021 … jul_2026), 11 symbols, 2,503 files, 63 GB.
result_reports.jsonl — the core
One line per grid point (~2 GB per symbol-month).
{
"point": {"hardStopPercent": 7.5, "trailingTakePercent": 1.5,
"holdMinutes": 15840, "profitLockPercent": 2},
"skippedBusy": 71,
"totalPnlPercent": 146.16, "avgPnlPercent": 0.40, "winRate": 0.70,
"profitFactor": 1.58, "maxSeriesDrawdownPercent": 87.62,
"calmarRatio": 24.35, "recoveryFactor": 1.67,
"avgHoldMinutes": 4620.18, "p95HoldMinutes": 15840, "p99HoldMinutes": 15840,
"sharpe": 1.86, "sortino": 3.46,
"exitReasons": {"hard_stop": 14, "trailing_take": 69, "profit_lock": 228,
"time_expired": 37, "data_truncated": 16},
"tradesList": [ /* ideaId, author, direction, exitReason, pnlPercent, … */ ]
}tradesList gives attribution down to the individual post: who wrote it, when the entry happened, why the exit fired, how much was lost or made.
result_tracks.jsonl — author track records
One line per (rule × author), where the rule is the point's own four levels. Exactly 21,280 × author_count lines (5,000,800 for jul_2026/BTCUSDT).
{"holdMinutes": 1440, "profitLockPercent": 1, "hardStopPercent": 1,
"trailingTakePercent": 0.5, "author": "BitCoinGuide",
"ideas": 8, "hits": 6, "hitRate": 0.75}This is raw material with no thresholds and no ban list: the engine trades every author and merely reports the track. Who counts as noise is the consumer's call.
result_best.jsonl
Winners by four criteria: sharpe, sortino, pnl, recovery. Different criteria pick different points — the Sharpe optimum and the PnL optimum are not the same thing.
What the data shows
On jul_2026/BTCUSDT (739 posts → 435 directional after deduplication):
grid points: 21,280
losing points: 13,398 (63 %)
sharpe: −6.19 … +1.86
totalPnl: −445 % … +201 %
winRate: 0.12 … 0.89Exit reasons across the whole grid:
63 % of configurations are unprofitable on the very same set of calls. The worst point (stop 1 %, trailing 0.5 %, lock 1 %, 2 days) yields −166 % and a Sharpe of −6.19; the best (stop 7.5 %, trailing 1.5 %, lock 2 %, 11 days) yields +146 % and a Sharpe of +1.86. That is the measurement: an analyst is not "profitable" or "unprofitable" in a vacuum — they are profitable within a specific risk corridor, and the dataset shows which one.
Dependence on the hold window for the same symbol:
hitRate plateaus by day 7. Holding longer does not improve the call — it only freezes the money.
Run methodology
- Filtering and deduplication. Ideas are selected by symbol,
NEUTRALis dropped, then anti-flood: at most one post perauthor + directionper 8 hours. A repeated opinion is not new evidence — it is discarded entirely and does not inflate the author's track. Example: 1,077 ideas in the file → 739 for BTCUSDT → 449 directional → 435. - Idea profile. One asynchronous forward pass over 1-minute candles from the minute after publication, capped by the grid's longest hold (14 days). MFE / MAE, whale-shakeout depth and the median move are computed. Candles are never re-iterated per grid point — the outcomes of all 21,280 points are derived from the profile arithmetically.
- Per-author slot. Each author owns a single slot: while their position is open, their new posts are absorbed (
skippedBusy,absorbedIdeas). Authors never collide — another author's position does not occupy the slot. The slot is held until the trade's actual exit, not for the fullholdMinutes: that axis is only the upper bound, and ahard_stop,profit_lockortrailing_takereleases the slot earlier. SoskippedBusyis driven by all four levels of the point at once, not byholdMinutesalone — onjul_2026/BTCUSDTthe 20160-minute step (stop 4 %) absorbs 43 ideas with a longest actual hold of 13,298 minutes, while the 15840-minute step (stop 7.5 %) absorbs 71. - Honesty contracts. Entry at the
openof the next minute; exits against candle wicks (high/low), neverclose; the stop wins an ambiguous candle; trailing and lock arm only from previous-candle peaks; fee and slippage on both legs. - Invariants. Every point is checked: PnL not below the hard-stop floor,
trailing_takecannot lock a loss,profit_lockcannot fill below its own level, exit not before entry. A violation raises — it never silently corrupts the data.
Sharpe and Sortino are time-based: computed over daily equity increments on a window shared by every grid point, idle days included. As a result the same PnL harvested through rare chunky exits scores worse than the same PnL through frequent short trades — frozen capital is penalised.
Limitations
These matter — read before use.
- Author tracks are a whole-month summary, not a point-in-time signal. Every individual trade is strictly forward-looking: the profile is built from the minute after publication onward, and a hit is decided by the chronology from entry. Nothing in
result_reports.jsonlpeeks into its own future. Butresult_tracks.jsonlaggregates an author'shitRateover all of their ideas in the month at once, later posts included. Using thathitRateas an entry filter on trades from the same month injects lookahead that is not present in the data. The engine deliberately ships the raw track and applies no ban list — every author is traded (364 trades + 71skippedBusy= all 435 directional ideas, 235 of 235 authors). Rolling-window discipline is the consumer's job. - The grid's last steps are biased. A profile requires 14 days of forward candles; near the edge of history there is not enough data. On
jul_2026/BTCUSDT,truncatedCount= 199 of 435 (46 %), and the observation total drops on the 12–14-day steps (80,040 → 76,560). ThehitRatedecline at 14 days is the data edge, not deteriorating calls. Do not compare the 14-day step against the short ones directly. - A month bounds the ideas, not the candles — so consecutive months overlap. Every idea gets its full forward horizon regardless of frame boundaries: a post from the 30th is simulated on candles well into the next month and is still booked in the month it opened in. On
jun_2026/BTCUSDTat the winning point, 106 of 253 trades (42 %) exit in July, the last one on Jul 7 from an entry on Jun 30 18:40. There are no cut-off artifacts at month edges — but the runs are not disjoint periods. Do not sum monthly PnL as independent intervals: the first ~14 days of every month are covered twice, once by the previous month's open positions and once by its own ideas. The daily-bucket window for Sharpe/Sortino likewise extends past the month end (it runs to the lastoutcomeKnownAt). - `Infinity` → `null` in JSON.
sortino,profitFactor,calmarRatioandrecoveryFactorare infinite on loss-free series, andJSON.stringifywritesnull. Read that as "no denominator", not as a missing value. - `hitRate` requires a sample-size filter. Values of 0.00 and 1.00 in
tracksare typically authors with one or two ideas. Without a threshold onideasthe ranking is meaningless. - The per-author slot is a grading device, not a portfolio model. Each author is simulated in isolation with a single slot, so what you read is "following one guru's signals," not a real book. A live investor holds positions from several authors at once, and this dataset deliberately does not model that: authors never interact, there is no shared capital, no global exposure cap, no cross-author netting. Isolation is what makes the per-author numbers comparable — the moment authors shared a pool, an author's
hitRatewould depend on who else happened to post that week. Portfolio construction is the next stage, not this one (see below). - Symbol coverage is uneven (67 months for BTCUSDT down to 3 for PUMPUSDT). Aggregating across all symbols unweighted skews towards BTC.
- The simulator picks candidates; it does not replace the engine. Parameters found via the grid must be validated by a real backtest.
Usage
from datasets import load_dataset
# risk-management grid (default)
reports = load_dataset("tripolskypetr/trading-entries", "reports", split="train")
# author track records
tracks = load_dataset("tripolskypetr/trading-entries", "tracks", split="train")
# source posts
ideas = load_dataset("tripolskypetr/trading-entries", "ideas", split="train")result_reports.jsonl runs about 2 GB per symbol-month because of tradesList. To analyse the grid itself, strip the field with a stream:
jq -c 'del(.tradesList)' result_reports.jsonl > grid.jsonlReproducing a run takes backtest-kit + ccxt; the entry point is content/<month>/index.<symbol>.mjs.
Walk-forward: pin one month's winner, re-run it on the next
The limitation above says an author's hitRate must not be applied inside the month it was measured on. Here is how to avoid that: take the grid point and the author from month N, freeze the grid to that single point, and feed the ideas of month N+1. Because gridAxes merges per-axis and a single-value list freezes an axis, a four-single-value override collapses the 21,280-point sweep into exactly one out-of-sample evaluation.
import { addExchangeSchema, addSimulatorSchema, Simulator } from "backtest-kit";
import { readFileSync } from "fs";
import { singleshot } from "functools-kit";
import ccxt from "ccxt";
const SYMBOL = "BTCUSDT";
// ── TRAIN (jun_2026): winner by sharpe + the author to test ───────────────
// taken from content/jun_2026/data/BTCUSDT/result_best.jsonl
const TRAINED_POINT = {
hardStopPercent: 8.5,
trailingTakePercent: 2,
holdMinutes: 20160, // 14 days
profitLockPercent: 5,
};
// from content/jun_2026/data/BTCUSDT/result_tracks.jsonl at that same point
const AUTHOR = "TradingShot"; // train: ideas 15, hits 14, hitRate 0.93
const getExchange = singleshot(async () => {
const exchange = new ccxt.binance({
options: { defaultType: "spot", adjustForTimeDifference: true },
enableRateLimit: true,
timeout: 15000,
});
await exchange.loadMarkets();
return exchange;
});
addExchangeSchema({
exchangeName: "ccxt_cached",
getCandles: async (symbol, interval, since, limit) => {
const exchange = await getExchange();
const candles = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
return candles.map(([timestamp, open, high, low, close, volume]) => ({
timestamp, open, high, low, close, volume,
}));
},
});
// Freezing every axis to one value turns the sweep into a single point:
// the trade rule is now fixed by the TRAINING month and cannot be re-fitted.
addSimulatorSchema({
simulatorName: "walk_forward",
exchangeName: "ccxt_cached",
gridAxes: {
hardStopPercent: [TRAINED_POINT.hardStopPercent],
trailingTakePercent: [TRAINED_POINT.trailingTakePercent],
holdMinutes: [TRAINED_POINT.holdMinutes],
profitLockPercent: [TRAINED_POINT.profitLockPercent],
},
});
// ── TEST (jul_2026): the NEXT month's ideas, unseen by the training ───────
const ideas = readFileSync("./content/jul_2026/assets/tv-ideas.normalize.jsonl", "utf-8")
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line))
// swap the sample: keep only the author selected on the training month
.filter((idea) => idea.author === AUTHOR);
const result = await Simulator.run({
symbol: SYMBOL,
simulatorName: "walk_forward",
ideas,
});
// exactly one point in the grid -> exactly one report
const [report] = result.reports.reports;
const [track] = result.reports.tracks;
console.log("out-of-sample:", {
author: AUTHOR,
point: report.point,
hitRate: track.hitRate, // compare against the training hitRate
ideas: track.ideas,
totalPnlPercent: report.totalPnlPercent,
winRate: report.winRate,
exitReasons: report.exitReasons,
});To reproduce the numbers without running anything, the same comparison is already in the dataset — read the identical point out of both months' result_tracks.jsonl:
POINT='.hardStopPercent==8.5 and .trailingTakePercent==2
and .holdMinutes==20160 and .profitLockPercent==5'
for M in jun_2026 jul_2026; do
echo -n "$M: "
jq -c "select($POINT) | select(.author==\"TradingShot\")" \
content/$M/data/BTCUSDT/result_tracks.jsonl
doneWhat that point actually shows for the winner of jun_2026 — and why the limitation matters:
The best in-sample author is the one that fell apart hardest. Selecting authors by a hitRate measured on the same month would have picked TradingShot — which is precisely the lookahead the dataset refuses to bake in.
License
MIT. The source posts belong to their authors on TradingView; only metadata (id, author, direction, title, link) and derived metrics are stored here.
