All strategies · Longbow Weekly review page
Version 1.0.1. Frozen 2026-10-19. Owner: Prashant Maheshwari. Status: forward paper run active, no real money, no orders.
Everything in this document describes what the code in research/weekly/ actually does. If the code and this document ever disagree, the code is the specification and this document has a bug: report it.
| Item | Value |
|---|---|
| Idea | Buy the Nifty 500 stocks that have risen the most steadily over the past ~9 months (skipping the last month), but only while the overall market is above its 26-week average. Otherwise hold cash. |
| Universe | Nifty 500 members on the decision date, at least ₹5 crore traded per day, at least 60 weeks of price history |
| Holdings | Up to 15 stocks, equal weight (6.67% each), long only |
| Rebalance | Every 4th week; exits (stock drops out, or market filter off) can happen in any week |
| Decision time | Friday close (or last session of the week) |
| Execution | First session open of the next week |
| Costs assumed | Delivery charges + 0.2% slippage per side; stress at 2x and 3x |
| Paper capital | ₹5,00,000 |
| Development result (2016 to 2025-09) | 24.8% a year, worst fall 16.7%, Sharpe 1.40 (at 2x costs and a 4% a year survivorship haircut). Nifty 500 index: 13.3% and 34.4% |
| Hold-out result (2025-10 to 2026-10-16), opened once | -1.4% (standard costs), worst fall 13.8%. Nifty 500: -3.3% |
| Honest status | Candidate. The hold-out did not confirm a profit. The paper run is the only clean out-of-sample test left. |
data/nse_extra/. Assembled by research/build_panel.py into data/research/panel.parquet (723 symbols, 1999 to the latest session). Quality labels (OK, REVIEW, EXCLUDE) come from quality.csv; the 41 EXCLUDE symbols are dropped.data/reference/nifty500_constituents.csv: dated snapshots of the Nifty 500 list (effective dates, semi-annual). A stock is a member from a snapshot's effective date until the next snapshot. Because only effective dates are used (never announcements), a newly added stock becomes tradable only after it is actually in the index: slightly conservative.data.py)t (end of week t)A stock can be bought or held if all are true:
1. It is a Nifty 500 member on that date.
2. It has a valid weekly close in week t, and a valid open exists in week t+1 (so it can be traded).
3. ADV over the last 4 weeks is at least ₹5 crore a day.
4. It has at least 60 weeks of price history.
For each eligible stock, with weekly closes C:
momentum = C[t-4] / C[t-39] - 1 (return from 39 weeks ago to 4 weeks ago; the latest 4 weeks are skipped)
risk = standard deviation of the weekly returns of the last 26 weeks
score = momentum / risk
Higher is better. Stocks with a missing score are ignored. Ties are broken by array order (practically never matters).
regime = Nifty 500 weekly close > average of the last 26 weekly closes.
* If regime is false at decision week t, no stock is eligible: the target is 100% cash, and every held stock is sold at the next open. This applies in any week, not only on rebalance weeks.
* While cash, the account earns the RBI repo rate minus 1.5% a year, spread weekly. (Only used for the idle share of the account.)
Let j0 be the first decision week. Rebalance weeks are j0, j0+4, j0+8, .... For the paper run the first decision is the week ending Friday 2026-10-23, so rebalance weeks end on 2026-10-23, 11-20, 12-18, 2027-01-15, 02-12, 03-12, 04-09, 05-07, 06-04 and so on (a holiday on Friday only changes the date label, not the week).
orders_next.csv and positions.csv convert to whole shares at the latest raw close (floor(weight x equity / price)).| Item | Buy | Sell |
|---|---|---|
| Brokerage (average assumption) | 0.030% | 0.030% |
| Securities transaction tax | 0.100% | 0.100% |
| Exchange transaction charge | 0.00325% | 0.00325% |
| SEBI turnover fee | 0.0001% | 0.0001% |
| Stamp duty | 0.015% | none |
| Depository (DP) charge | none | ₹15 + 18% GST = ₹17.70 per stock sold |
| Slippage | 0.20% | 0.20% |
Buy side about 0.35% (including slippage); sell side about 0.33% plus ₹17.70. All cost items are multiplied by the cost multiple (1x standard, 2x and 3x stress). These are estimates until reconciled with a contract note; Dhan's actual delivery brokerage may be lower.
No stop-losses (exits are rank-based or filter-based), no volatility scaling, no sector caps, no shorting, no leverage, no options, no intraday trading, no price-impact model, no taxes. Round 2 tested stops and volatility scaling and they added nothing, so they were left out.
| Situation | Behaviour |
|---|---|
| Market holiday on Friday | Week still ends on its last session; decision once the week is over |
| Last weekly bar still in progress | The paper run ignores it until the week is over |
| Stock has no price in the week | Not eligible; if held, sold at the next open it has |
| Stock leaves the Nifty 500 | Not eligible from its effective date; sold at the next open |
| Stock is suspended or delisted | Not handled by the simulation (zero return while no price). In live use, exit manually and record the loss |
| Split, bonus, demerger | Adjusted away in the simulation; in real life the broker adjusts the holding |
| Fewer than 15 eligible stocks | Fewer positions, weight still 6.67% each, rest in cash |
| Market filter flips off then on | All sold at the first open after the flip; new purchases only on the next rebalance week |
| Price circuit limits or very thin trading | Not modelled; see section 9 |
holdout_ledger.json).| Window | CAGR | Worst fall | Sharpe |
|---|---|---|---|
| Full 2016 to 2025-09 | 24.8% | 16.7% | 1.40 |
| Train 2016 to 2021 | 25.3% | 16.7% | 1.44 |
| Validation 2022 to 2025-09 | 23.3% | 15.3% | 1.31 |
| Nifty 500 price index | 13.3% | 34.4% | 0.85 |
| Costs | Haircut | CAGR | Worst fall |
|---|---|---|---|
| 1x | 0% | 32.4% | 13.8% |
| 1x | 4% | 29.0% | 14.1% |
| 2x | 0% | 28.1% | 14.7% |
| 2x | 4% | 24.8% | 16.7% |
| 3x | 4% | 20.5% | 21.9% |
| 3x | 8% | 17.2% | 24.6% |
| 5x | 8% | 8.3% | 38.0% |
| 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 (to Sep) |
|---|---|---|---|---|---|---|---|---|---|
| +8.1% | +24.0% | -10.5% | +1.3% | +32.6% | +128.4% | -9.3% | +81.0% | +28.7% | +0.5% |
Two years (2021, 2023) do most of the work. This is the single most important weakness to keep in mind.
| 2025-10-01 to 2026-10-16 (55 weeks) | Return | Worst fall |
|---|---|---|
| Candidate, standard costs (paper settings) | -1.4% | 13.8% |
| Candidate, 2x costs + 4% haircut | -7.0% | 15.8% |
| Candidate, 3x costs + 8% haircut | -12.5% | 18.4% |
| Nifty 500 price index | -3.3% | 13.1% |
| Random 15 + same filter (40 runs) | -14.1% average (sd 4.5%) |
Market filter was on in 56% of weeks (cash in 44%). The candidate beat 98% of the random runs. Monthly returns at standard costs: Oct 2025 +2.2, Nov +1.1, Dec -1.2, Jan 2026 -6.4, Feb +0.3, Mar +0.3 (index -9.3), Apr +0.4 (index +7.9), May -1.6, Jun +0.3, Jul -4.6, Aug +7.9, Sep +0.4, Oct +0.2.
Correction added 2026-10-20: the hold-out total depends on the rebalance calendar. The strategy rebalances every fourth week, so the result depends on which of four weekly "phases" the cycle starts on. The -1.4% above is for the phase set by a 2016-01 start. Re-running the same rule with the cycle shifted by one, two or three weeks gave +5.3%, +0.6%, +1.5% and -1.4% over the same 55 weeks (standard costs): a range of about 7 points, all better than the index's -3.3%. So "did not confirm a profit" overstated the case: the honest statement is "between -1.4% and +5.3% at standard costs depending on phase, still far from a clear confirmation". Phase luck is a real property of any monthly strategy. A possible future design (a new version, not applied here) is to split the account into four equal sleeves that rebalance in different weeks, which removes the phase choice. The forward paper run uses one fixed phase (first rebalance 2026-10-23).
Reading: the ranking showed skill against random picks and the filter limited the fall, but there was no profit, and 55 weeks cannot separate skill from luck. The hold-out is now spent: it cannot be re-run with changed settings.
Low volatility, trend following, anchored VWAP, EMA support, market structure, fair-value gaps and RSI/MACD had raw returns of 5% to 30% a year but drawdowns of 33% to 53% (or little return) at 2x costs: none passed the drawdown screen. Base breakouts lost money. Index-rebalancing effects were worth roughly 2% to 3% a year at best. Pairs showed no edge after costs.
research/weekly/paper.py, started by research/weekly/nightly_paper.sh (cron, Monday to Friday 19:15 IST), after the 18:45 job has refreshed data/research/panel.parquet. Then the review site is rebuilt.data/paper/weekly_mom/ and on the review page:| File | Meaning |
|---|---|
events.log |
One line per run: data date, week end, equity, positions, filter ON/OFF, number of next orders. "waiting" = no completed week yet |
equity.csv |
Weekly paper account value in ₹ (from 5,00,000), share invested, Nifty 500 level |
trades.csv |
Completed round trips: symbol, entry and exit dates, price return (before charges), approximate ₹ P&L |
positions.csv |
Current holdings, shares, entry date and price, last close, value |
orders_next.csv |
BUY (about 6.7% of equity each) and SELL orders for the next open. Empty on most weeks |
signals_latest.csv |
Top-30 ranking now. Names 1 to 15 are targets; 16 to 22 are kept if held |
research/weekly/paper_spec.json. No parameter may change during the run.Measured on the paper equity curve at standard (1x) costs, versus the Nifty 500 price index and cash (repo minus 1.5%, about 4.5% a year):
| Review | PASS to the next stage | HOLD (keep paper, no money) | STOP (abandon or redesign as a new version) |
|---|---|---|---|
| 26 weeks | Worst fall under 20% and no worse than the index's; return at least equal to cash | Anything between | Worst fall above 25% from peak, or more than 10 points behind the index while the market filter was on for most of the period |
| 52 weeks | Worst fall under 20%; annualised return at least 5 points above cash after costs; at least 4 of the 12 months positive and no single month contributing more than half of the total gain; not more than 5 points behind the index in total | Return positive but below the pass bar | Worst fall above 25% from peak; or more than 10 points behind the Nifty 500 |
Passing both reviews means permission to consider a small live pilot, not an instruction to go live.
Friday or Saturday morning
1. Open the strategy-review page or events.log: check that the latest line shows the data date of this Friday (or the last session) and no "stale" warning.
2. Read orders_next.csv. If empty: nothing to do. If it has SELL or BUY lines: these are the next open's orders.
3. Read signals_latest.csv for context (top 15 are the targets).
Monday, after 9:15 open 4. Paper: nothing to do, the replay fills at the open price automatically next evening. 5. Live pilot (later): place the sells first, then the buys, using limit orders near the open; record price and quantity in a log.
Monday evening
6. Check that positions.csv matches the broker positions (live pilot) and that cash is as expected.
Monthly 7. Compare equity with the index; note months where the filter was off.
Never 8. Change a rule because of one bad or good week. Add a stock by hand. Skip a SELL because "it will recover".
paper_spec.json, version 1.0.1).| Risk | Why it matters here |
|---|---|
| Overfitting / selection bias | About 900 variants were examined; the chosen rule is the best of a plateau, not a pre-registered hypothesis. Reality-check p is 0.047 |
| Dependence on a few strong years | 2021 (+128%) and 2023 (+81%) |
| Market filter lag and whipsaw | It was off in 44% of the hold-out weeks and missed the April 2026 rebound |
| Rebalance-phase luck | A monthly cycle starting on a different week gave hold-out totals from -1.4% to +5.3%; one fixed phase is a draw from that range |
| Survivorship | One third of past index members missing in 2016 |
| Momentum reversals | January and July 2026 in the hold-out |
| Concentration | 15 stocks, 6.7% each: one stock can cost 1% of the account in a single day |
| Liquidity and impact | ₹33,000 tickets are small, but small-cap names can gap and hit circuits |
| Costs and taxes | Turnover about 4.8 times a year; STT on both sides; short-term capital gains tax not modelled |
| Operational | Late data, a missed order, or a stale index series. The paper run warns if data is more than 5 days old |
| Behavioural | Abandoning a plan after a drawdown that the plan expected |
orders_next.csv. Fixed (placeholder repeats the last close) and re-tested before the first live decision: the one-week case now gives 15 BUY orders; a later non-rebalance week gives none.Why weekly and not daily? Weekly bars cut noise, cut turnover, and match how momentum is usually measured. Daily versions were not tested for this strategy.
Why skip the latest 4 weeks? Stocks that have just risen sharply tend to give back part of the move over the next month (short-term reversal). Skipping that month is the standard "12-1" convention (here 9-1).
Why divide by volatility? Two stocks with the same return are not equally good if one got there smoothly and the other lurched. Dividing favours the smooth one. It was better than raw momentum in testing (Round 2).
Why the 26-week market filter? It keeps the account out of broad sell-offs (for example 2020). It costs missed rebounds.
Why 15 stocks? A compromise: enough to diversify, few enough for the ranking to matter; 10 and 20 names gave broadly similar results (10 had larger falls, 20 slightly lower returns).
Why monthly rebalancing? Weekly rebalancing raised turnover by about half (6.0 versus 4.0 times a year in the grid) and did worse after costs.
Will it make money next year? Unknown. The development result was strong; the hold-out year was slightly negative at standard costs and positive relative to the index. Only the paper run can add evidence.
Is this advice? No. It is research and education. Not investment advice; the publisher is not a registered investment adviser or research analyst.
| Path | What |
|---|---|
research/weekly/data.py |
Weekly data builder |
research/weekly/engine.py |
Portfolio simulator and costs |
research/weekly/families.py |
Signal generators for all tested ideas |
research/weekly/run.py, round2.py |
Round 1 and round 2 grids |
research/weekly/final.py |
Detailed checks on the chosen rule |
research/weekly/events_pairs.py |
Index-change event study and pairs |
research/weekly/holdout.py, holdout_ledger.json |
The one-time hold-out run and its log |
research/weekly/paper.py, nightly_paper.sh, paper_spec.json |
Paper run |
research/weekly/results/ |
Result tables (grid, round 2, family summary, hold-out) |
data/paper/weekly_mom/ |
Paper run outputs |
| Review page | https://strategyreview.tradetechanalytics.com/strategies/weekly-nifty500-study.html |
Reproduce the development numbers: python research/weekly/run.py, round2.py, final.py (development data only). Do not run holdout.py again: it refuses to.
What you can check, and where. The review page has an "Execution audit" section and a trade log with these columns: signal date (the Friday close when the ranking was computed), entry fill (the next week's first session), entry price, rank on the signal date, exit signal, exit fill, exit price, and the exit reason. The downloadable fills_*.csv lists every transaction (including top-ups and trims) with signal rank and score.
Checks run automatically on every build (they can be re-run from the CSV files): 1. Every transaction happens after its signal (fill date later than signal date; the typical gap is 3 days, Friday to Monday). 2. Every new purchase is for a stock ranked inside the target list on its signal date (new entries rank 1 to 15; top-ups of names already held can rank lower). 3. Every full exit has a documented reason: rank fell below the keep limit (22), no longer eligible, or the market filter turned off. 4. Every execution price is a real traded price: the stock's open on the fill date lies inside that day's low to high. 5. Never more than 15 names held at once, and entry dates precede exit dates. 6. The trade ledger reconciles to the equity curve: an independent rupee accounting of every fill (cash, positions, costs paid from cash) ends within about 1.4% of the simulator's equity.
Extra proof for the forward paper run.
* One signal snapshot file per decision week (paper_signals/<name>/signals_log/YYYY-MM-DD.csv) is written the evening of the decision and never overwritten. It holds the top-40 ranking with scores, the target weights and the action for each name.
* signals_ledger.jsonl chains each file's SHA-256 hash to the previous one with the time it was generated. The review page has a button that re-hashes every file in your browser and compares the chain.
* The nightly job commits the snapshots to GitHub; GitHub's server time on the commit is an independent timestamp that the signal existed before Monday's open.
* Every night the system re-derives each past decision from the data and compares it with the stored snapshot. Any difference is reported as a failed check.
* Check "each snapshot was written before the session in which it was traded opened" compares the ledger's generation time with 09:15 IST on the fill date.
* Limits. A paper run proves internal consistency and timing; it cannot prove that real orders would have filled at those prices. When trading real money, reconcile these logs with the broker's contract notes. Tests that replay history (not generated live) correctly show the timing check as failed: only live-generated snapshots can pass it.
Fix log. 2026-10-20: the first ledger version used the signal date's own session as a possible fill date for holiday-shortened weeks; fixed so only sessions strictly after the signal count (the audit caught this: it failed the "after its signal" check until fixed).
Research and education only. Hypothetical and back-tested results have inherent limitations and do not guarantee future results. Not investment advice; the publisher is not presented as a SEBI-registered investment adviser or research analyst.