Skip to content

Commit 79cd26c

Browse files
committed
Add custom-indicator skill to skill-templates/indicators/custom-indicator
1 parent 571a975 commit 79cd26c

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

  • skill-templates/indicators/custom-indicator
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
---
2+
name: custom-indicator
3+
description: >
4+
Creates a custom indicator class and integrates it into a QuantConnect algorithm via MCP.
5+
Invoke when the user wants an indicator LEAN does not provide, needs to combine built-in
6+
indicators beyond extension logic, or describes a formula to compute on price/volume data.
7+
Trigger phrases: "create a custom indicator", "implement a custom [name] indicator",
8+
"write an indicator for [formula]", "I need an indicator that LEAN doesn't have",
9+
"add a custom indicator to my algorithm", "combine built-in indicators with custom logic",
10+
"PythonIndicator", "custom indicator class", "write a [name] indicator".
11+
---
12+
13+
# /custom-indicator -- QuantConnect Custom Indicator
14+
15+
Creates a custom indicator class and wires it into an algorithm.
16+
17+
---
18+
19+
## Step 1 -- Gather Indicator Information
20+
21+
Ask the user (sequentially if needed):
22+
23+
1. **Indicator name**: What should the class be called? (e.g., `CustomVolatility`, `LogMomentum`)
24+
25+
2. **Formula / logic**: What does this indicator compute? Describe the math or logic step by step.
26+
27+
3. **Input type**:
28+
- Single price value -> py`IndicatorDataPoint` (use py`input_.value`cs`input.Value`)
29+
- Full OHLCV bar -> py`TradeBar` (use py`input_.close`cs`input.Close`, py`input_.volume`cs`input.Volume`, etc.)
30+
- Quote bar (bid/ask) -> `QuoteBar`
31+
- Default to `TradeBar` when the formula needs more than just a single price.
32+
33+
4. **Period**: Does the indicator need a rolling lookback window? If yes, how many bars?
34+
Use py`RollingWindow[float](period)`cs`RollingWindow<decimal>(period)`.
35+
36+
5. **Internal built-in indicators**: Does the computation internally use any LEAN built-in
37+
indicators (e.g., SMA, EMA, RSI)? If yes, list them. They must be constructed and updated
38+
inside py`update`cs`ComputeNextValue`.
39+
40+
6. **Algorithm scope**:
41+
- Single fixed symbol -> register in py`initialize`cs`Initialize`
42+
- Universe of symbols -> register in py`on_securities_changed`cs`OnSecuritiesChanged`, deregister on removal
43+
44+
7. **Warm-up strategy** (default to manual):
45+
- **Manual** (recommended): loop over py`self.history[TradeBar](symbol, period + 1)`cs`History<TradeBar>(symbol, period + 1, Resolution.Daily)` and call py`indicator.update(bar)`cs`indicator.Update(bar)` before py`self.register_indicator`cs`RegisterIndicator`.
46+
- **Automatic**: set py`self.settings.automatic_indicator_warm_up = True`cs`Settings.AutomaticIndicatorWarmUp = true` in py`initialize`cs`Initialize`.
47+
48+
---
49+
50+
## Step 2 -- Generate the Code
51+
52+
### Style rules
53+
54+
Apply these rules to all generated code:
55+
56+
- **Imports (Python)**: `from AlgorithmImports import *` only. `AlgorithmImports` already re-exports `math`, `numpy`, and all common libraries -- no additional imports needed.
57+
- **Imports (C#)**: Leave all project `using` statements as-is.
58+
- **Comments**: Capital first letter, space after `#` / `//`, ends with a period.
59+
- **Blank lines (Python)**: 2 blank lines before each class, 1 before each method, none inside method bodies.
60+
- **Blank lines (C#)**: 1 blank line between methods, none inside method bodies.
61+
62+
### File naming
63+
64+
| Language | Class name | File name |
65+
|---|---|---|
66+
| Python | `CustomVolatility` | `custom_volatility.py` |
67+
| C# | `CustomVolatility` | `CustomVolatility.cs` |
68+
69+
The indicator class always goes in its own file. Never inline it in `main.py` / `Main.cs`.
70+
71+
### Indicator class template
72+
73+
```python
74+
# region imports
75+
from AlgorithmImports import *
76+
# endregion
77+
78+
79+
class CustomVolatility(PythonIndicator):
80+
81+
def __init__(self, period):
82+
super().__init__()
83+
self.value = 0
84+
self._window = RollingWindow[float](period)
85+
86+
def update(self, input_: BaseData):
87+
price = input_.value
88+
if price <= 0:
89+
return
90+
self._window.add(price)
91+
if self._window.is_ready:
92+
prices = np.array(list(self._window)[::-1])
93+
log_diffs = np.diff(np.log(prices))
94+
self.value = np.std(log_diffs) * math.sqrt(252) * 100.0
95+
return self.is_ready
96+
97+
@property
98+
def is_ready(self) -> bool:
99+
return self._window.is_ready
100+
```
101+
102+
```csharp
103+
public class CustomVolatility : Indicator
104+
{
105+
private readonly RollingWindow<decimal> _window;
106+
107+
public CustomVolatility(int period) : base("CustomVolatility")
108+
{
109+
_window = new RollingWindow<decimal>(period);
110+
WarmUpPeriod = period;
111+
}
112+
113+
public override bool IsReady => _window.IsReady;
114+
115+
protected override decimal ComputeNextValue(IndicatorDataPoint input)
116+
{
117+
_window.Add(input.Value);
118+
if (!IsReady) return 0m;
119+
// Compute from _window here.
120+
return 0m;
121+
}
122+
}
123+
```
124+
125+
For OHLCV input in C#, inherit from `BarIndicator` instead of `Indicator`:
126+
127+
```csharp
128+
public class CustomVolatility : BarIndicator
129+
{
130+
public CustomVolatility(int period) : base("CustomVolatility")
131+
{
132+
WarmUpPeriod = period;
133+
}
134+
135+
public override bool IsReady => true;
136+
137+
protected override decimal ComputeNextValue(IBaseDataBar input)
138+
{
139+
// input.Close, input.Open, input.High, input.Low, input.Volume
140+
return 0m;
141+
}
142+
}
143+
```
144+
145+
Key rules:
146+
147+
- Python: inherit from `PythonIndicator`, not `Indicator`.
148+
- py`self.value`cs`Value` holds the current computed value.
149+
- py`update` must return py`self.is_ready`cs`IsReady` (bool). Omitting the return silently breaks warm-up.
150+
- Guard invalid input (e.g., `price <= 0`) before touching the window.
151+
- `RollingWindow` iterates newest-first in Python; use `[::-1]` to get chronological order for numpy.
152+
- C#: set `WarmUpPeriod` in the constructor so LEAN knows how many bars are needed.
153+
154+
### Algorithm integration -- single symbol
155+
156+
```python
157+
def initialize(self):
158+
symbol = self.add_equity("SPY", Resolution.DAILY).symbol
159+
self._indicator = CustomVolatility(period)
160+
for bar in self.history[TradeBar](symbol, period + 1):
161+
self._indicator.update(bar)
162+
self.register_indicator(symbol, self._indicator)
163+
```
164+
165+
```csharp
166+
private CustomVolatility _indicator;
167+
168+
public override void Initialize()
169+
{
170+
var symbol = AddEquity("SPY", Resolution.Daily).Symbol;
171+
_indicator = new CustomVolatility(period);
172+
foreach (var bar in History<TradeBar>(symbol, period + 1, Resolution.Daily))
173+
_indicator.Update(bar);
174+
RegisterIndicator(symbol, _indicator);
175+
}
176+
```
177+
178+
### Algorithm integration -- universe
179+
180+
```python
181+
def on_securities_changed(self, changes):
182+
for security in changes.added_securities:
183+
security.indicator = CustomVolatility(period)
184+
for bar in self.history[TradeBar](security, period + 1):
185+
security.indicator.update(bar)
186+
self.register_indicator(security, security.indicator)
187+
for security in changes.removed_securities:
188+
self.deregister_indicator(security.indicator)
189+
self.liquidate(security)
190+
```
191+
192+
```csharp
193+
private readonly Dictionary<Symbol, CustomVolatility> _indicators = new();
194+
195+
public override void OnSecuritiesChanged(SecurityChanges changes)
196+
{
197+
foreach (var security in changes.AddedSecurities)
198+
{
199+
var ind = new CustomVolatility(period);
200+
foreach (var bar in History<TradeBar>(security.Symbol, period + 1, Resolution.Daily))
201+
ind.Update(bar);
202+
RegisterIndicator(security.Symbol, ind);
203+
_indicators[security.Symbol] = ind;
204+
}
205+
foreach (var security in changes.RemovedSecurities)
206+
{
207+
if (_indicators.Remove(security.Symbol, out var ind))
208+
DeregisterIndicator(ind);
209+
Liquidate(security.Symbol);
210+
}
211+
}
212+
```
213+
214+
Gate all reads on py`security.indicator.is_ready`cs`_indicators[symbol].IsReady` before accessing the value.
215+
216+
---
217+
218+
## Step 3 -- Write Files via MCP
219+
220+
1. **Indicator file**: `quantconnect:create_file` with the indicator class.
221+
2. **Algorithm file**: `quantconnect:update_file_contents` for `main.py` / `Main.cs`.
222+
3. Python only: add import after `from AlgorithmImports import *`:
223+
```python
224+
from custom_volatility import CustomVolatility
225+
```
226+
C# does not need this -- all project files share the same namespace.
227+
228+
---
229+
230+
## Step 4 -- Compile
231+
232+
1. `quantconnect:create_compile`.
233+
2. Poll `quantconnect:read_compile` until `BuildSuccess` or `BuildError`.
234+
3. On `BuildError`: parse error messages, fix code via MCP, loop back to Step 4.
235+
236+
---
237+
238+
## Step 5 -- Backtest and Verify
239+
240+
1. `quantconnect:create_backtest`.
241+
2. Poll `quantconnect:read_backtest` until complete.
242+
3. Check both conditions:
243+
244+
**Condition A: Indicator produces values**
245+
- Look for runtime errors about indicator not being ready.
246+
- If py`is_ready`cs`IsReady` never becomes true: verify py`history`cs`History` returned at least `period` bars and that py`update`cs`Update` is called on each. Fix the warm-up loop and re-run from Step 4.
247+
248+
**Condition B: Algorithm logic fires**
249+
- If algorithm has entry/exit conditions: confirm at least one order was placed.
250+
- If no orders but no errors: add a log line as the first line of py`on_data`cs`OnData` and re-backtest. Relax the entry condition if needed.
251+
252+
4. Report: compile clean, backtest complete, indicator py`is_ready`cs`IsReady` confirmed, trades placed.

0 commit comments

Comments
 (0)