Skip to content

Commit 6a499fe

Browse files
committed
docs: multi-timeframes strategies
1 parent 237ee5f commit 6a499fe

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default defineConfig({
3333
{ text: 'Placing Orders', link: '/placing-orders' },
3434
{ text: 'Position Sizing', link: '/position-sizing' },
3535
{ text: 'Plotting & Visualization', link: '/plotting' },
36+
{ text: 'Multi-Timeframe Strategies', link: '/multi-timeframe-strategies' },
3637
]
3738
},
3839
{

multi-timeframe-strategies.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Multi-Timeframe Strategies
2+
3+
Analyzing a single timeframe can give you entry signals, but professional strategies often gain a significant edge by consulting higher timeframes to determine the overall market trend. This is known as Multi-Timeframe (MTF) Analysis.
4+
5+
Stochastix has first-class support for MTF analysis, allowing your strategy to seamlessly access data from different candle sizes within a single `onBar()` execution.
6+
7+
::: warning Data must be downloaded
8+
Before running an MTF strategy, you **must** download the historical data for **every timeframe** you intend to use. If a secondary timeframe's data file is missing, the backtest will fail.
9+
10+
Please refer to the [**Downloading Market Data**](/data-downloading) guide for instructions.
11+
:::
12+
13+
## 1. Requesting Timeframe Data
14+
15+
The first step is to inform the backtesting engine which timeframes your strategy requires. This is done using the `requiredMarketData` parameter in the `#[AsStrategy]` attribute.
16+
17+
Let's design a simple strategy that trades on a 1-hour (`H1`) chart but uses the 4-hour (`H4`) chart to confirm the trend.
18+
19+
```php
20+
use Stochastix\Domain\Common\Enum\TimeframeEnum;
21+
use Stochastix\Domain\Strategy\Attribute\AsStrategy;
22+
23+
#[AsStrategy(
24+
alias: 'mtf_rsi_trend',
25+
name: 'Multi-Timeframe RSI Trend',
26+
// The primary timeframe for the strategy
27+
timeframe: TimeframeEnum::H1,
28+
// An array of all timeframes the strategy needs access to.
29+
requiredMarketData: [
30+
TimeframeEnum::H1,
31+
TimeframeEnum::H4,
32+
]
33+
)]
34+
class MtfRsiStrategy extends AbstractStrategy
35+
{
36+
// ...
37+
}
38+
```
39+
40+
## 2. Accessing Timeframe Data in `onBar`
41+
42+
When you request secondary data, the `onBar()` method receives a `MultiTimeframeOhlcvSeries` object as its `$bars` parameter. This special object provides access to all requested timeframes.
43+
44+
### Accessing the Primary Timeframe
45+
46+
You access the primary timeframe's data (in this case, `H1`) directly via the object's properties:
47+
48+
```php
49+
// Get the most recent close price from the primary H1 series
50+
$h1_close = $bars->close[0];
51+
52+
// Get the previous open price from the primary H1 series
53+
$h1_previous_open = $bars->open[1];
54+
```
55+
56+
### Accessing a Secondary Timeframe
57+
58+
You access a secondary timeframe's data by treating the `$bars` object like an array, using the `TimeframeEnum` case as the key. This returns a complete `OhlcvSeries` object for that timeframe, or `null` if the data isn't available yet.
59+
60+
```php
61+
public function onBar(MultiTimeframeOhlcvSeries $bars): void
62+
{
63+
// Access the entire 4-hour OhlcvSeries object
64+
$h4_bars = $bars[TimeframeEnum::H4];
65+
66+
// It's crucial to check if the data exists, as the higher timeframe
67+
// may not have a bar corresponding to the current primary bar.
68+
if ($h4_bars === null) {
69+
return;
70+
}
71+
72+
// Now you can access the H4 data just like a normal series.
73+
// The engine ensures the data is correctly aligned in time.
74+
$h4_close = $h4_bars->close[0];
75+
$h4_high = $h4_bars->high[0];
76+
77+
// ... your logic using both H1 and H4 data
78+
}
79+
```
80+
81+
## 3. A Complete Example
82+
83+
Let's build a full strategy that uses our custom `RSIMovingAverage` indicator on the 1H chart and a simple SMA on the 4H chart as a trend filter.
84+
85+
**The Logic:**
86+
87+
* **Primary Timeframe (1H):** We will use the `RSIMovingAverage` indicator to find entry signals.
88+
* **Secondary Timeframe (4H):** We will use a 50-period SMA to determine the long-term trend.
89+
* **Entry Condition:** We will only enter a `long` position if the 1H RSI crosses above its moving average **AND** the current 1H price is above the 4H SMA(50).
90+
* **Exit Condition:** We will exit when the 1H RSI crosses back below its moving average.
91+
92+
Here is the full strategy code:
93+
94+
```php
95+
<?php
96+
97+
namespace App\Strategy;
98+
99+
use App\Indicator\RSIMovingAverage;
100+
use Stochastix\Domain\Common\Enum\DirectionEnum;
101+
use Stochastix\Domain\Common\Enum\TimeframeEnum;
102+
use Stochastix\Domain\Common\Enum\TALibFunctionEnum;
103+
use Stochastix\Domain\Common\Model\MultiTimeframeOhlcvSeries;
104+
use Stochastix\Domain\Indicator\Model\TALibIndicator;
105+
use Stochastix\Domain\Order\Enum\OrderTypeEnum;
106+
use Stochastix\Domain\Strategy\AbstractStrategy;
107+
use Stochastix\Domain\Strategy\Attribute\AsStrategy;
108+
use Stochastix\Domain\Strategy\Attribute\Input;
109+
110+
#[AsStrategy(
111+
alias: 'mtf_rsi_trend',
112+
name: 'Multi-Timeframe RSI Trend',
113+
timeframe: TimeframeEnum::H1,
114+
requiredMarketData: [TimeframeEnum::H1, TimeframeEnum::H4]
115+
)]
116+
class MtfRsiStrategy extends AbstractStrategy
117+
{
118+
#[Input(description: 'RSI Period on the primary timeframe')]
119+
private int $rsiPeriod = 14;
120+
121+
#[Input(description: 'MA Period for the RSI line')]
122+
private int $rsiMaPeriod = 9;
123+
124+
#[Input(description: 'SMA Period for the higher timeframe trend filter')]
125+
private int $trendSmaPeriod = 50;
126+
127+
protected function defineIndicators(): void
128+
{
129+
// Indicator for our primary (H1) timeframe
130+
$this->addIndicator(
131+
'h1_rsi_ma',
132+
new RSIMovingAverage($this->rsiPeriod, $this->rsiMaPeriod)
133+
);
134+
135+
// Indicator for our secondary (H4) timeframe trend filter
136+
$this->addIndicator(
137+
'h4_trend_sma',
138+
new TALibIndicator(
139+
TALibFunctionEnum::Sma,
140+
['timePeriod' => $this->trendSmaPeriod],
141+
sourceTimeframe: TimeframeEnum::H4 // Tell the indicator to use H4 data
142+
)
143+
);
144+
}
145+
146+
public function onBar(MultiTimeframeOhlcvSeries $bars): void
147+
{
148+
// --- 1. Get indicator values ---
149+
$h1Rsi = $this->getIndicatorSeries('h1_rsi_ma', 'rsi');
150+
$h1RsiMa = $this->getIndicatorSeries('h1_rsi_ma', 'rsi_ma');
151+
$h4Sma = $this->getIndicatorSeries('h4_trend_sma');
152+
153+
// --- 2. Check for missing data ---
154+
// The higher timeframe data or indicators might not have values yet.
155+
if ($h1Rsi[0] === null || $h1RsiMa[0] === null || $h4Sma[0] === null) {
156+
return;
157+
}
158+
159+
// --- 3. Define the conditions ---
160+
$isBullishTrend = $bars->close[0] > $h4Sma[0];
161+
$isEntrySignal = $h1Rsi->crossesOver($h1RsiMa);
162+
$isExitSignal = $h1Rsi->crossesUnder($h1RsiMa);
163+
164+
// --- 4. Execute trading logic ---
165+
if (!$this->isInPosition()) {
166+
// Only enter long if our H1 signal occurs during a H4 uptrend.
167+
if ($isEntrySignal && $isBullishTrend) {
168+
$this->entry(DirectionEnum::Long, OrderTypeEnum::Market, '0.1');
169+
}
170+
} else {
171+
if ($isExitSignal) {
172+
$this->exit('0.1');
173+
}
174+
}
175+
}
176+
}
177+
```

0 commit comments

Comments
 (0)