Appearance
Cross-section rank
How do you rank fifty coins each day on a score, and check whether the ranking meant anything? Score every member with request.each, standardise across them with cs.*, and write the ranks to a report. The Screener's IC section then reads how the ranks lined up with the returns that followed.
ameno
indicator("Cross-section rank")
array<string> m = request.universe(50, exclude_tags = "stablecoin")
array<float> mom20 = request.each(m, ta.roc(close, 20))
array<float> vol30 = request.each(m,
ta.stdev(math.log(close / close[1]), 30))
// Momentum per unit of volatility, trimmed, then standardised
array<float> score = cs.zscore(cs.winsor(mom20 / vol30, 0.05))
report.column("rank", m, cs.rank(score))
report.column("score", m, score)
plot(array.size(m), "Members")How it works
ta.roc(close, 20)is each coin's 20-day rate of change, in percent, on its own bars.ta.stdev(math.log(close / close[1]), 30)is its 30-day volatility of daily log returns.mom20 / vol30divides cell by cell, since arithmetic on arrays works on each cell.cs.winsor(..., 0.05)moves the most extreme 5% at each end in to the nearest value kept, so one wild coin doesn't set the scale.cs.zscorestandardises across the coins, andcs.rankturns the scores into percentiles from 0 to 1.report.columnwrites one row per coin per day.
request.each, the cs.* calls and report.column are AmenoScript additions, so the script is AmenoScript and runs leak-safe.
To read the IC, choose the script in the Screener's script picker, press Run, and open the IC section beside Score. It reads the rank correlation between each column and the return that followed at 1, 2, 5, 10, 20 and 60 days. It is one test, in sample: it tells you which idea deserves a real test.
Traps
- A coin with too little history reads na, and
cs.*leaves it out of the mean and the rank. - Volatility of zero gives an infinite score, which
cs.*skips like a na, as na cells says.