feat: add Emmanuel MTF v1 strategy seed
Multi-timeframe SMA pullback strategy: 4h trend context (20/200 SMA stack) + 15m entry timing (pullback to 20 SMA with bounce evidence). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
29
assets/strategy/emmanuel-mtf/readme.md
Normal file
29
assets/strategy/emmanuel-mtf/readme.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Emmanuel MTF — Multi-Timeframe SMA Pullback Strategy
|
||||
|
||||
Inspired by [Emmanuel Malyarovich's video](https://www.youtube.com/watch?v=0aCaq-_N3nI) on trading with the 20 and 200 period simple moving averages.
|
||||
|
||||
## Core idea
|
||||
|
||||
Emmanuel's approach uses only two indicators — the 20 SMA and the 200 SMA — applied across multiple timeframes. The higher timeframe establishes trend direction; the lower timeframe times entries at pullbacks to the 20 SMA.
|
||||
|
||||
From the video: *"I like to look for potential entries whenever prices are close to or near the 20 MA"* during an uptrend where the *"20 MA is under price, trending higher."*
|
||||
|
||||
## How it works
|
||||
|
||||
**4h (trend context):** Confirms a bullish regime — close above the 200 SMA, 20 SMA stacked above the 200 and rising, price above the 20 SMA but not overextended from it.
|
||||
|
||||
**15m (entry timing):** Waits for price to retrace to the 15m 20 SMA and bounce. Requires the MA to be rising, price to be in a tight proximity zone just above it, evidence of an actual pullback (low touched the MA recently), and evidence of a prior impulsive move (recent high was meaningfully above the MA — avoids first-touch traps after exhaustion moves).
|
||||
|
||||
**Exits:** Trailing exit on a cross below the 15m 20 SMA, a structural exit if the 4h MAs unstack, and a hard stop at 2% below the 15m 20 SMA.
|
||||
|
||||
## Adaptation notes
|
||||
|
||||
Emmanuel trades US equities intraday, often using gap-up/gap-down catalysts and order flow as additional context. This strategy adapts his SMA logic for crypto (BTC), which trades 24/7 without gaps. The multi-timeframe structure, proximity-based entries, and extension filters are faithful to his described method; the discretionary elements (candle structure, level 2 order flow, gap dynamics) are not expressible in the current DSL.
|
||||
|
||||
## Data requirements
|
||||
|
||||
Requires backfilled candle data for both **15m** and **4h** intervals across the backtest window.
|
||||
|
||||
## Lineage
|
||||
|
||||
This strategy evolved through eight single-timeframe iterations (emmanuel-ma v1–v8) before the DSL gained multi-timeframe support. Key learnings from that process: strict cross_over triggers are too rare on higher timeframes; proximity-and-rising entries generate more trades with comparable edge; exit timeframe must match entry timeframe to avoid whipsaw; and the strategy is consistently green but low-conviction without the multi-timeframe layering Emmanuel describes.
|
||||
329
assets/strategy/emmanuel-mtf/v1.json
Normal file
329
assets/strategy/emmanuel-mtf/v1.json
Normal file
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"type": "rule_based",
|
||||
"candle_interval": "15m",
|
||||
"rules": [
|
||||
{
|
||||
"comment": "ENTRY: Emmanuel MTF pullback long v1. 4h trend context (20 SMA rising under price, close > 200 SMA) + 15m entry (price retraces to 20 SMA and bounces). Faithful to transcript: higher TF establishes trend, lower TF times entry at the 20 MA.",
|
||||
"when": {
|
||||
"kind": "all_of",
|
||||
"conditions": [
|
||||
{
|
||||
"comment": "Gate: only enter from flat",
|
||||
"kind": "position",
|
||||
"state": "flat"
|
||||
},
|
||||
{
|
||||
"comment": "4h trend: close is above the 200 SMA. Emmanuel uses 200 as support/resistance context.",
|
||||
"kind": "compare",
|
||||
"left": { "kind": "field", "field": "close", "timeframe": "4h" },
|
||||
"op": ">",
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 200,
|
||||
"timeframe": "4h"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "4h trend: 20 SMA is above 200 SMA (MAs stacked bullish).",
|
||||
"kind": "compare",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20,
|
||||
"timeframe": "4h"
|
||||
},
|
||||
"op": ">",
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 200,
|
||||
"timeframe": "4h"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "4h trend: 20 SMA is rising — current > 5 bars ago. Emmanuel: 'under price, trending higher'.",
|
||||
"kind": "compare",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20,
|
||||
"timeframe": "4h"
|
||||
},
|
||||
"op": ">",
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20,
|
||||
"offset": 5,
|
||||
"timeframe": "4h"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "4h trend: price is above the 4h 20 SMA. Emmanuel: 'I want to see the 20 MA under price'.",
|
||||
"kind": "compare",
|
||||
"left": { "kind": "field", "field": "close", "timeframe": "4h" },
|
||||
"op": ">",
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20,
|
||||
"timeframe": "4h"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "4h: not extended — close < 5% above the 4h 20 SMA. Emmanuel: 'I don't want to be entering when prices are super far away from the 20 MA'.",
|
||||
"kind": "compare",
|
||||
"left": {
|
||||
"kind": "bin_op",
|
||||
"op": "sub",
|
||||
"left": { "kind": "field", "field": "close", "timeframe": "4h" },
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20,
|
||||
"timeframe": "4h"
|
||||
}
|
||||
},
|
||||
"op": "<",
|
||||
"right": {
|
||||
"kind": "bin_op",
|
||||
"op": "mul",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20,
|
||||
"timeframe": "4h"
|
||||
},
|
||||
"right": { "kind": "literal", "value": "0.05" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "15m entry: 20 SMA is rising on the entry timeframe — momentum pointing up on the lower TF too.",
|
||||
"kind": "compare",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
},
|
||||
"op": ">",
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20,
|
||||
"offset": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "15m entry: close is above the 15m 20 SMA (price is on the right side of the MA).",
|
||||
"kind": "compare",
|
||||
"left": { "kind": "field", "field": "close" },
|
||||
"op": ">=",
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "15m proximity: close is within 0.5% above the 15m 20 SMA. Tighter than 4h because 15m candles are smaller.",
|
||||
"kind": "compare",
|
||||
"left": {
|
||||
"kind": "bin_op",
|
||||
"op": "sub",
|
||||
"left": { "kind": "field", "field": "close" },
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
}
|
||||
},
|
||||
"op": "<",
|
||||
"right": {
|
||||
"kind": "bin_op",
|
||||
"op": "mul",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
},
|
||||
"right": { "kind": "literal", "value": "0.005" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "15m rising: close > close 3 bars ago. Price is moving up off the MA, not still falling.",
|
||||
"kind": "compare",
|
||||
"left": { "kind": "field", "field": "close" },
|
||||
"op": ">",
|
||||
"right": { "kind": "field", "field": "close", "offset": 3 }
|
||||
},
|
||||
{
|
||||
"comment": "15m pullback evidence: low of last 8 bars touched within 0.3% of the 15m 20 SMA. Confirms price actually retraced to the MA.",
|
||||
"kind": "compare",
|
||||
"left": {
|
||||
"kind": "unary_op",
|
||||
"op": "abs",
|
||||
"operand": {
|
||||
"kind": "bin_op",
|
||||
"op": "sub",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "lowest",
|
||||
"field": "low",
|
||||
"period": 8
|
||||
},
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
"op": "<",
|
||||
"right": {
|
||||
"kind": "bin_op",
|
||||
"op": "mul",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
},
|
||||
"right": { "kind": "literal", "value": "0.003" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "15m not-first-touch after extension: highest high in last 20 bars was > 1.5% above the 15m 20 SMA. Emmanuel: 'always pass on the first touch after a huge parabolic run' — we require evidence of a prior move.",
|
||||
"kind": "compare",
|
||||
"left": {
|
||||
"kind": "bin_op",
|
||||
"op": "sub",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "highest",
|
||||
"field": "high",
|
||||
"period": 20
|
||||
},
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
}
|
||||
},
|
||||
"op": ">",
|
||||
"right": {
|
||||
"kind": "bin_op",
|
||||
"op": "mul",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
},
|
||||
"right": { "kind": "literal", "value": "0.015" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "side": "buy", "quantity": "0.001" }
|
||||
},
|
||||
{
|
||||
"comment": "EXIT 1 (trend exit): close crosses below the 15m 20 SMA. Emmanuel trails his exits against the 20 MA on the entry timeframe.",
|
||||
"when": {
|
||||
"kind": "all_of",
|
||||
"conditions": [
|
||||
{ "kind": "position", "state": "long" },
|
||||
{
|
||||
"kind": "cross_under",
|
||||
"left": { "kind": "field", "field": "close" },
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "side": "sell", "quantity": "0.001" }
|
||||
},
|
||||
{
|
||||
"comment": "EXIT 2 (higher TF structure lost): 4h 20 SMA crosses below 4h 200 SMA. Macro trend has broken.",
|
||||
"when": {
|
||||
"kind": "all_of",
|
||||
"conditions": [
|
||||
{ "kind": "position", "state": "long" },
|
||||
{
|
||||
"kind": "cross_under",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20,
|
||||
"timeframe": "4h"
|
||||
},
|
||||
"right": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 200,
|
||||
"timeframe": "4h"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "side": "sell", "quantity": "0.001" }
|
||||
},
|
||||
{
|
||||
"comment": "EXIT 3 (hard stop): close is more than 2% below the 15m 20 SMA. Tighter than 4h version — on 15m timeframe, 2% is a significant breakdown.",
|
||||
"when": {
|
||||
"kind": "all_of",
|
||||
"conditions": [
|
||||
{ "kind": "position", "state": "long" },
|
||||
{
|
||||
"kind": "compare",
|
||||
"left": {
|
||||
"kind": "bin_op",
|
||||
"op": "sub",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
},
|
||||
"right": { "kind": "field", "field": "close" }
|
||||
},
|
||||
"op": ">",
|
||||
"right": {
|
||||
"kind": "bin_op",
|
||||
"op": "mul",
|
||||
"left": {
|
||||
"kind": "func",
|
||||
"name": "sma",
|
||||
"field": "close",
|
||||
"period": 20
|
||||
},
|
||||
"right": { "kind": "literal", "value": "0.02" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "side": "sell", "quantity": "0.001" }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -26,6 +26,7 @@ pub fn strategies() -> Vec<StrategySeed> {
|
||||
emmanuel_ma_v6(),
|
||||
emmanuel_ma_v7(),
|
||||
emmanuel_ma_v8(),
|
||||
emmanuel_mtf_v1(),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -983,6 +984,41 @@ the pullback). Exit unchanged from v7.
|
||||
}
|
||||
}
|
||||
|
||||
fn emmanuel_mtf_v1() -> StrategySeed {
|
||||
StrategySeed {
|
||||
name: "Emmanuel MTF",
|
||||
version: 1,
|
||||
description: "\
|
||||
## Emmanuel MTF — Multi-Timeframe SMA Pullback Strategy
|
||||
|
||||
Inspired by Emmanuel Malyarovich's approach using only two indicators — the 20 SMA \
|
||||
and 200 SMA — applied across multiple timeframes. The higher timeframe establishes \
|
||||
trend direction; the lower timeframe times entries at pullbacks to the 20 SMA.
|
||||
|
||||
**4h (trend context):** Confirms a bullish regime — close above the 200 SMA, 20 SMA \
|
||||
stacked above the 200 and rising, price above the 20 SMA but not overextended from it.
|
||||
|
||||
**15m (entry timing):** Waits for price to retrace to the 15m 20 SMA and bounce. \
|
||||
Requires the MA to be rising, price to be in a tight proximity zone just above it, \
|
||||
evidence of an actual pullback (low touched the MA recently), and evidence of a prior \
|
||||
impulsive move (recent high was meaningfully above the MA — avoids first-touch traps \
|
||||
after exhaustion moves).
|
||||
|
||||
**Entry**: 11-condition all_of — 4h trend stack (close > 200 SMA, 20 SMA > 200 SMA, \
|
||||
rising 20 SMA, price > 20 SMA, not overextended < 5%) + 15m entry (rising 20 SMA, \
|
||||
close ≥ 20 SMA, proximity within 0.5%, price rising, pullback touch within 0.3%, \
|
||||
prior impulsive move > 1.5%).
|
||||
|
||||
**Exit 1** (trailing): close crosses below the 15m 20 SMA.
|
||||
**Exit 2** (structural): 4h 20 SMA crosses below 4h 200 SMA (macro trend broken).
|
||||
**Exit 3** (hard stop): close drops more than 2% below the 15m 20 SMA.",
|
||||
config: serde_json::from_str(
|
||||
include_str!("../../../../assets/strategy/emmanuel-mtf/v1.json"),
|
||||
)
|
||||
.expect("assets/strategy/emmanuel-mtf/v1.json is valid JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
fn emmanuel_ma_v7() -> StrategySeed {
|
||||
StrategySeed {
|
||||
name: "Emmanuel MA",
|
||||
|
||||
Reference in New Issue
Block a user