Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ jobs:
"assert baseline_control_frames.__module__ == "
"'nano.library.contribution'; "
"catalog = load_catalog(); "
"assert catalog['strategyCount'] == 53; "
"assert catalog['strategyCount'] == 55; "
"assert catalog_diagnostics() == ()"
),
],
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ The same corpus is browsable at **[aethersystems.net/nano](https://aethersystems

| momentum | mean_reversion | trend | volatility | volume | risk | event_volatility | watchdog |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 6 rules | 6 rules | 7 rules | 4 rules | 4 rules | 7 rules | 11 rules | 8 rules |
| 6 rules | 7 rules | 8 rules | 4 rules | 4 rules | 7 rules | 11 rules | 8 rules |

That table holds **two corpora**. A **baseline rule** compares a host-supplied named signal with a literal and compiles to byte-stable v0.1.0 IR. A **v1 rule** declares its own `input`s and lets Nano compute the indicators, so the host contract is OHLCV rather than a vocabulary of agreed indicator names. `tests/test_library.py` derives every count above from the directory, so the table cannot go stale silently.

Expand Down
2 changes: 1 addition & 1 deletion nano/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
artifact cannot change behavior because a transitive dependency did.
"""

__version__ = "1.0.11"
__version__ = "1.0.12"
8 changes: 4 additions & 4 deletions nano/library/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The library holds two kinds of entry, and the difference is worth understanding

| | **Baseline entries** | **v1 entries** |
| --- | --- | --- |
| Count | 41 | 12 |
| Count | 41 | 14 |
| Source shape | one `every`, one `if`, AND-chained comparisons of a named signal against a literal | `param`/`input`/`let` declarations, arithmetic, `else`, offsets |
| Where the numbers come from | the **host** computes every indicator and injects it as a named series | the host supplies **OHLCV**; Nano computes the indicators |
| Compiled IR | `0.1.0`, byte-stable | `1.0.0`, carries `sourceHash` and `moduleHash` |
Expand All @@ -27,7 +27,7 @@ The practical difference for a host is the size of the contract. A baseline entr

## Learn, compare, contribute

The current library contains 53 strategies across eight categories — seven trading, one for deterministic watchdog controls — in two corpora: 41 baseline and 12 v1. Browse an entry to see the source, its expected IR, and the data contract it assumes. When you are ready, a well-documented strategy pair is the most direct contribution to Nano.
The current library contains 55 strategies across eight categories — seven trading, one for deterministic watchdog controls — in two corpora: 41 baseline and 14 v1. Browse an entry to see the source, its expected IR, and the data contract it assumes. When you are ready, a well-documented strategy pair is the most direct contribution to Nano.

[Walk through a first contribution →](../../docs/first-contribution.md) · [Add a strategy →](../../CONTRIBUTING.md#add-a-strategy) · [Open a proposal →](https://github.com/AetherAI3/Nano/issues/new?template=strategy-library.yml)

Expand All @@ -44,8 +44,8 @@ python scripts/check_contribution.py --write nano/library/<category>/<name>.nano
| Category | Strategies |
| --- | --- |
| `momentum/` | `rsi_oversold_reversal`, `stochastic_oversold`, `williams_r_reversal`, `roc_momentum`, `absolute_momentum_filter`†, `stochastic_reclaim`† |
| `mean_reversion/` | `bollinger_band_touch`, `zscore_reversion`, `cci_extreme`, `bollinger_lower_reclaim`†, `zscore_fade_trend_filtered`†, `opening_gap_fade`† |
| `trend/` | `golden_cross`, `macd_histogram_flip`, `donchian_breakout`, `supertrend_flip_long`, `ema_pullback_continuation`†, `macd_zero_line_reclaim`†, `donchian_high_breakout`† |
| `mean_reversion/` | `bollinger_band_touch`, `zscore_reversion`, `cci_extreme`, `bollinger_lower_reclaim`†, `zscore_fade_trend_filtered`†, `opening_gap_fade`†, `volatility_percentile_reclaim`† |
| `trend/` | `golden_cross`, `macd_histogram_flip`, `donchian_breakout`, `supertrend_flip_long`, `ema_pullback_continuation`†, `macd_zero_line_reclaim`†, `donchian_high_breakout`†, `volume_confirmed_channel_breakout`† |
| `volatility/` | `atr_volatility_halt`, `bb_squeeze_breakout`, `squeeze_release_expansion`†, `atr_regime_halt`† |
| `volume/` | `volume_spike_confirmation`, `obv_trend`, `volume_climax_reversal`†, `vwap_band_reversion`† |
| `risk/` | `max_drawdown_breaker`, `daily_loss_limit`, `position_concentration_cap`, `correlation_cluster_guard`, `stale_data_halt`, `leverage_ceiling`, `consecutive_loss_circuit` |
Expand Down
76 changes: 72 additions & 4 deletions nano/library/catalog/strategy_metadata_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
"type": "NanoStrategyCatalog",
"schemaVersion": 1,
"metadataVersion": "StrategyMetadataV1",
"strategyCount": 53,
"strategyCount": 55,
"categoryCounts": {
"event_volatility": 11,
"mean_reversion": 6,
"mean_reversion": 7,
"momentum": 6,
"risk": 7,
"trend": 7,
"trend": 8,
"volatility": 4,
"volume": 4,
"watchdog": 8
},
"irMaturityCounts": {
"baseline": 41,
"v1": 12
"v1": 14
},
"strategies": [
{
Expand Down Expand Up @@ -478,6 +478,40 @@
"sourcePath": "library/mean_reversion/opening_gap_fade.nano",
"irPath": "library/mean_reversion/opening_gap_fade_ir.json"
},
{
"metadataVersion": "StrategyMetadataV1",
"id": "mean_reversion/volatility_percentile_reclaim",
"slug": "volatility_percentile_reclaim",
"name": "VolatilityPercentileReclaim",
"category": "mean_reversion",
"irMaturity": "v1",
"irVersion": "1.0.0",
"regime": "range-like or orderly pullback behavior outside an exceptional volatility state. Do NOT use the gate as a claim that low volatility causes reversal; it only separates a familiar reclaim from a distributional shock. INPUTS: high, low, close (series<float>). Bars must be contiguous: ATR resets across gaps, and PERCENTRANK stays absent until a full new history is present.",
"conditions": "prior close at or below its prior lower band, current close above its current lower band, and ATR rank below 0.9 against exactly the preceding hundred ATR values. All three terms have independent mutation controls.",
"invalidation": "another close below the band, or ATR entering the excluded top decile. Either says the excursion is continuing or the volatility regime has changed; the host, not this rule, decides what to do with an existing position.",
"shape": "1d; a two-bar downside excursion and reclaim after enough contiguous history to compare today's range state with the preceding hundred ATR cells.",
"calibratedOn": "liquid US equity index proxies, daily. Bollinger 20/2 and ATR 14 are published conventions; 100 observations and 0.9 define the research regime split. None is a profitability claim, and cadence changes need review.",
"nearestConfused": [
{
"slug": "bollinger_lower_reclaim",
"id": "mean_reversion/bollinger_lower_reclaim",
"distinction": "that rule takes every strict lower-band reclaim. This one abstains when the same price shape occurs at an extreme ATR rank; the percentile gate, not a different band threshold, is the structural hypothesis."
},
{
"slug": "zscore_fade_trend_filtered",
"id": "mean_reversion/zscore_fade_trend_filtered",
"distinction": "that enters while z-score remains deeply negative and uses a long price trend as its regime gate. This waits for a full band reclaim and gates on the instrument's own volatility distribution."
}
],
"provenance": "Bollinger-band conventions and Engle (1982) conditional-volatility research; the regime gate is an original hypothesis, with no source code copied.",
"requiredHostSignals": [
"close",
"high",
"low"
],
"sourcePath": "library/mean_reversion/volatility_percentile_reclaim.nano",
"irPath": "library/mean_reversion/volatility_percentile_reclaim_ir.json"
},
{
"metadataVersion": "StrategyMetadataV1",
"id": "mean_reversion/zscore_fade_trend_filtered",
Expand Down Expand Up @@ -1119,6 +1153,40 @@
"sourcePath": "library/trend/supertrend_flip_long.nano",
"irPath": "library/trend/supertrend_flip_long_ir.json"
},
{
"metadataVersion": "StrategyMetadataV1",
"id": "trend/volume_confirmed_channel_breakout",
"slug": "volume_confirmed_channel_breakout",
"name": "VolumeConfirmedChannelBreakout",
"category": "trend",
"irMaturity": "v1",
"irVersion": "1.0.0",
"regime": "liquid directional expansion after a range or consolidation. Do NOT fire in thin markets, opening auctions, or feeds where volume is synthetic: the participation term is only meaningful when bars measure comparable size. INPUTS: high, close, volume (series<float>). Volume must be real traded size from one consistently defined venue or consolidated feed.",
"conditions": "close above the preceding twenty-bar high and volume at least 1.5 times the preceding twenty-bar average. Price expansion without volume is explicitly a no-fire; high volume without a price break is also a no-fire.",
"invalidation": "a following close back inside the prior channel, especially on equal or greater volume. The rule proposes an entry only; the host owns exits.",
"shape": "1d; a close through a multi-week ceiling with visibly greater participation than the bars that built the ceiling.",
"calibratedOn": "liquid continuously traded instruments, daily. Twenty bars and 1.5 times prior average volume are public conventions, not performance claims. The volume multiple does not travel across venue or session changes.",
"nearestConfused": [
{
"slug": "donchian_high_breakout",
"id": "trend/donchian_high_breakout",
"distinction": "that price-only rule fires on every qualifying channel break and carries a short mirror. This long-only variant requires an independent participation confirmation and abstains on an ordinary-volume break, which is the essential condition pinned by its mutation control."
},
{
"slug": "volume_climax_reversal",
"id": "volume/volume_climax_reversal",
"distinction": "that is a one-bar exhaustion reversal below its trend with a high close-position requirement. This is continuation through a prior price boundary; volume confirms expansion rather than capitulation."
}
],
"provenance": "Donchian channel concepts and Lee-Swaminathan (2000) volume/momentum research; original Nano expression, with no source code copied.",
"requiredHostSignals": [
"close",
"high",
"volume"
],
"sourcePath": "library/trend/volume_confirmed_channel_breakout.nano",
"irPath": "library/trend/volume_confirmed_channel_breakout_ir.json"
},
{
"metadataVersion": "StrategyMetadataV1",
"id": "volatility/atr_regime_halt",
Expand Down
56 changes: 56 additions & 0 deletions nano/library/mean_reversion/volatility_percentile_reclaim.nano
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Volatility-percentile reclaim. The two-bar signal is the same strict
// lower-Bollinger-band excursion and reclaim shape readers already know, but it
// is admitted only when current ATR is below the top decile of its own preceding
// hundred observations. PERCENTRANK excludes the current cell, so an extreme
// current ATR can reach 1.0 and veto the trade without contaminating its baseline.
// SOURCE: Bollinger-band conventions and Engle (1982) conditional-volatility
// research; the regime gate is an original hypothesis, with no source code copied.
// REGIME: range-like or orderly pullback behavior outside an exceptional
// volatility state. Do NOT use the gate as a claim that low volatility causes
// reversal; it only separates a familiar reclaim from a distributional shock.
// INPUTS: high, low, close (series<float>). Bars must be contiguous: ATR resets
// across gaps, and PERCENTRANK stays absent until a full new history is present.
// CONDITIONS: prior close at or below its prior lower band, current close above
// its current lower band, and ATR rank below 0.9 against exactly the preceding
// hundred ATR values. All three terms have independent mutation controls.
// INVALIDATION: another close below the band, or ATR entering the excluded top
// decile. Either says the excursion is continuing or the volatility regime has
// changed; the host, not this rule, decides what to do with an existing position.
// SHAPE: 1d; a two-bar downside excursion and reclaim after enough contiguous
// history to compare today's range state with the preceding hundred ATR cells.
// NOT bollinger_lower_reclaim: that rule takes every strict lower-band reclaim.
// This one abstains when the same price shape occurs at an extreme ATR rank; the
// percentile gate, not a different band threshold, is the structural hypothesis.
// NOT zscore_fade_trend_filtered: that enters while z-score remains deeply
// negative and uses a long price trend as its regime gate. This waits for a full
// band reclaim and gates on the instrument's own volatility distribution.
// CALIBRATED ON: liquid US equity index proxies, daily. Bollinger 20/2 and ATR
// 14 are published conventions; 100 observations and 0.9 define the research
// regime split. None is a profitability claim, and cadence changes need review.
strategy VolatilityPercentileReclaim {

param band_window: int = 20
param band_mult: float = 2.0
param atr_window: int = 14
param rank_window: int = 100
param max_atr_rank: float = 0.9

input high: series<float>
input low: series<float>
input close: series<float>

let lower = BB_LOWER(close, band_window, band_mult)
let atr_now = ATR(high, low, close, atr_window)
let atr_rank = PERCENTRANK(atr_now, rank_window)

every 1d {

if close > lower and close[1] <= lower[1] and atr_rank < max_atr_rank {

buy(SPY, 0.6)

}

}

}
38 changes: 38 additions & 0 deletions nano/library/mean_reversion/volatility_percentile_reclaim_ir.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"type": "Strategy",
"nanoIrVersion": "1.0.0",
"name": "VolatilityPercentileReclaim",
"tier": "nano",
"determinism": {"clock": "injected", "entropy": "injected", "fastmath": false},
"params": [{"name": "band_window", "type": "int", "value": 20}, {"name": "band_mult", "type": "float", "value": 2.0}, {"name": "atr_window", "type": "int", "value": 14}, {"name": "rank_window", "type": "int", "value": 100}, {"name": "max_atr_rank", "type": "float", "value": 0.9}],
"inputs": [{"name": "high", "type": "series<float>"}, {"name": "low", "type": "series<float>"}, {"name": "close", "type": "series<float>"}],
"warmup": 114,
"entries": ["n22"],
"provenance": {"compiler": {"name": "nnc", "version": "1.0.0"}, "sourceHash": "sha256:a59d47f6086accd206b1d991e610fe8df7a85d2680b69f3721b530ee051d2e7c"},
"moduleHash": "sha256:077456625a685f240735382fb89a85bff92775774ebda859c1c6d34f55aee26a",
"effects": ["intent.emit", "log.append"],
"nodes": [
{ "id": "n1", "op": "input.ref", "inputs": [], "attrs": {"name": "close"}, "type": "series<float>" },
{ "id": "n2", "op": "param.ref", "inputs": [], "attrs": {"name": "band_mult"}, "type": "float" },
{ "id": "n3", "op": "indicator", "inputs": ["n1", "n2"], "attrs": {"name": "BB_LOWER", "periods": [20], "lookback": 19, "lifted": false}, "type": "series<float>" },
{ "id": "n4", "op": "let", "inputs": ["n3"], "attrs": {"name": "lower"}, "type": "series<float>" },
{ "id": "n5", "op": "input.ref", "inputs": [], "attrs": {"name": "high"}, "type": "series<float>" },
{ "id": "n6", "op": "input.ref", "inputs": [], "attrs": {"name": "low"}, "type": "series<float>" },
{ "id": "n7", "op": "indicator", "inputs": ["n5", "n6", "n1"], "attrs": {"name": "ATR", "periods": [14], "lookback": 14, "lifted": false}, "type": "series<float>" },
{ "id": "n8", "op": "let", "inputs": ["n7"], "attrs": {"name": "atr_now"}, "type": "series<float>" },
{ "id": "n9", "op": "indicator", "inputs": ["n8"], "attrs": {"name": "PERCENTRANK", "periods": [100], "lookback": 114, "lifted": false}, "type": "series<float>" },
{ "id": "n10", "op": "let", "inputs": ["n9"], "attrs": {"name": "atr_rank"}, "type": "series<float>" },
{ "id": "n11", "op": "schedule", "inputs": [], "attrs": {"interval": "1d"} },
{ "id": "n12", "op": "compare.gt", "inputs": ["n1", "n4"], "type": "series<bool>" },
{ "id": "n13", "op": "series.index", "inputs": ["n1"], "attrs": {"offset": 1}, "type": "series<float>" },
{ "id": "n14", "op": "series.index", "inputs": ["n4"], "attrs": {"offset": 1}, "type": "series<float>" },
{ "id": "n15", "op": "compare.le", "inputs": ["n13", "n14"], "type": "series<bool>" },
{ "id": "n16", "op": "logic.and", "inputs": ["n12", "n15"], "type": "series<bool>" },
{ "id": "n17", "op": "param.ref", "inputs": [], "attrs": {"name": "max_atr_rank"}, "type": "float" },
{ "id": "n18", "op": "compare.lt", "inputs": ["n10", "n17"], "type": "series<bool>" },
{ "id": "n19", "op": "logic.and", "inputs": ["n16", "n18"], "type": "series<bool>" },
{ "id": "n20", "op": "intent.emit", "inputs": [], "attrs": {"action": "BUY", "asset": "SPY", "confidence": 0.6} },
{ "id": "n21", "op": "block", "inputs": ["n20"] },
{ "id": "n22", "op": "rule", "inputs": ["n11", "n19", "n21"] }
]
}
52 changes: 52 additions & 0 deletions nano/library/trend/volume_confirmed_channel_breakout.nano
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Volume-confirmed channel breakout. Price must close above the prior
// twenty-bar high while current volume reaches a multiple of the prior
// twenty-bar volume average. Both reference series are read at [1], so the
// breakout bar cannot raise the boundary or dilute its own participation test.
// SOURCE: Donchian channel concepts and Lee-Swaminathan (2000) volume/momentum
// research; original Nano expression, with no source code copied.
// REGIME: liquid directional expansion after a range or consolidation. Do NOT
// fire in thin markets, opening auctions, or feeds where volume is synthetic:
// the participation term is only meaningful when bars measure comparable size.
// INPUTS: high, close, volume (series<float>). Volume must be real traded size
// from one consistently defined venue or consolidated feed.
// CONDITIONS: close above the preceding twenty-bar high and volume at least
// 1.5 times the preceding twenty-bar average. Price expansion without volume
// is explicitly a no-fire; high volume without a price break is also a no-fire.
// INVALIDATION: a following close back inside the prior channel, especially on
// equal or greater volume. The rule proposes an entry only; the host owns exits.
// SHAPE: 1d; a close through a multi-week ceiling with visibly greater
// participation than the bars that built the ceiling.
// NOT donchian_high_breakout: that price-only rule fires on every qualifying
// channel break and carries a short mirror. This long-only variant requires an
// independent participation confirmation and abstains on an ordinary-volume
// break, which is the essential condition pinned by its mutation control.
// NOT volume_climax_reversal: that is a one-bar exhaustion reversal below its
// trend with a high close-position requirement. This is continuation through a
// prior price boundary; volume confirms expansion rather than capitulation.
// CALIBRATED ON: liquid continuously traded instruments, daily. Twenty bars
// and 1.5 times prior average volume are public conventions, not performance
// claims. The volume multiple does not travel across venue or session changes.
strategy VolumeConfirmedChannelBreakout {

param channel: int = 20
param volume_window: int = 20
param volume_multiple: float = 1.5

input high: series<float>
input close: series<float>
input volume: series<float>

let prior_top = HIGHEST(high, channel)[1]
let prior_volume = SMA(volume, volume_window)[1]

every 1d {

if close > prior_top and volume >= prior_volume * volume_multiple {

buy(ETHUSD, 0.65)

}

}

}
Loading
Loading