- Introduced BollingerBandsVolatilityProtection indicator in GeneticService with configuration settings for period and standard deviation (stdev). - Updated ScenarioHelpers to handle creation and validation of the new indicator type. - Enhanced CustomScenario, backtest, and scenario pages to include BollingerBandsVolatilityProtection in indicator lists and parameter mappings. - Modified API and types to reflect the addition of the new indicator in relevant enums and mappings. - Updated frontend components to support new parameters and visualization for Bollinger Bands.
20 KiB
build-indicator
When to Use
Use this command when you need to:
- Create new technical indicators based on pattern descriptions
- Add signal, trend, or context indicators to the trading system
- Update all related files and configurations automatically
- Follow the established indicator architecture and conventions
Prerequisites
- Clear indicator specification with Type, Label, Core Logic, Triggers, and Parameters
- Understanding of indicator categories (Signal/Trend/Context)
- Access to existing indicator implementations for reference
- Knowledge of the indicator's mathematical calculations
Execution Steps
Step 1: Parse Indicator Specification
Analyze the indicator description and extract:
Required Information:
- Type: Signal/Trend/Context (determines folder location)
- Label: Indicator name (e.g., "Stochastic Filtered")
- Core Logic: Technical description of what the indicator does
- Trigger Conditions: When to generate signals (for Signal indicators)
- Parameters: Configuration values with defaults
- Signal Type: Long/Short for Signal indicators, Confidence levels for Context
Example Format:
Type: Signal
Label: Stochastic Filtered
Core Logic: Generates signals by filtering %K / %D crossovers to occur only within extreme overbought (above 80) or oversold (below 20) zones.
Trigger a Long when → The %K line crosses above the %D line (bullish momentum shift). The crossover occurs in the oversold zone (both %K and %D lines are below 20).
Trigger a Short when → The %K line crosses below the %D line (bearish momentum shift). The crossover occurs in the overbought zone (both %K and %D lines are above 80).
Parameters:
%K Period (default: 14)
%K Slowing (default: 3)
%D Period (default: 3)
Oversold Threshold: 20
Overbought Threshold: 80
Bollinger Bands Example:
Type: Context
Label: Bollinger Bands Volatility Protection
Core Logic: Uses the Bandwidth (distance between Upper and Lower bands) to measure market volatility and apply veto filters during extreme conditions.
Context Confidence Levels: Block signals when bandwidth is extremely high (>0.15) or low (<0.02), validate when normal (0.02-0.15).
Parameters:
Period (default: 20)
StDev (default: 2.0)
Step 2: Determine Implementation Details
Check Existing Indicators:
- Search codebase:
grep -r "Get{IndicatorType}" src/Managing.Domain/Indicators/ - Look for similar Skender.Stock.Indicators usage patterns
- Check if candle mapping logic can be shared with existing indicators
Class Name Convention:
- Signal indicators:
{IndicatorName}.cs(e.g.,StochasticFiltered.cs) - Trend indicators:
{IndicatorName}.cs(e.g.,EmaTrend.cs) - Context indicators:
{IndicatorName}.cs(e.g.,StDev.cs)
Inheritance Strategy:
- Default: Extend
IndicatorBasedirectly - Shared Mapping: Extend from existing shared base class if mappings overlap
- New Shared Base: Create base class only if multiple indicators will share the same mapping
Class Name Pattern:
- For signal/trend indicators: Class name =
{IndicatorName}(inherits fromIndicatorBaseor shared base) - For context indicators: Class name =
{IndicatorName}(inherits fromIndicatorBaseor shared base)
Location:
- Signal →
src/Managing.Domain/Indicators/Signals/ - Trend →
src/Managing.Domain/Indicators/Trends/ - Context →
src/Managing.Domain/Indicators/Context/
Enum Name:
- Convert label to PascalCase:
StochasticFiltered - Add to
IndicatorTypeenum insrc/Managing.Common/Enums.cs
Step 3: Implement Indicator Class
Create the indicator class following the established pattern. Check if other indicators use similar candle mappings - if so, consider creating or extending a base class.
Check for Existing Candle Mappings:
- Search for similar indicator types that might share candle mappings
- If another indicator uses the same Skender.Stock.Indicators result type, consider extending an existing base class or creating a shared base class
- Only create a new base class if no other indicator shares the same candle mapping pattern
Base Structure:
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Shared.Rules;
using Managing.Domain.Strategies.Base;
using Skender.Stock.Indicators;
using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.{TypeFolder};
public class {IndicatorName} : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
public {IndicatorName}(string name, {parameters}) :
base(name, IndicatorType.{EnumName})
{
Signals = new List<LightSignal>();
// Initialize parameters (e.g., Period, Multiplier, StDev)
}
// Implementation methods...
}
For Bollinger Bands (use shared base):
public class {IndicatorName} : BollingerBandsBase
{
public {IndicatorName}(string name, int period, double stdev) :
base(name, IndicatorType.{EnumName}, period, stdev)
{
}
// Only implement ProcessBollingerBandsSignals method
}
Shared Base Class Pattern (use only if mapping is shared): If another indicator uses the same candle result mapping, extend from a shared base class:
public class {SharedBaseName}Base : IndicatorBase
{
// Shared candle mapping logic here
protected List<{CandleResultType}> Map{Indicator}ToCandle(List<{SkenderResult}> results, IEnumerable<Candle> candles)
{
// Shared mapping implementation
}
}
public class {IndicatorName} : {SharedBaseName}Base
{
// Indicator-specific logic only
}
Key Methods to Implement:
Run(HashSet<Candle> candles)- Main calculation logicRun(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)- Optimized versionGetIndicatorValues(HashSet<Candle> candles)- Return calculated values- Private processing methods for signal generation
Signal Generation Pattern:
private void ProcessSignals(List<{Indicator}Result> results, HashSet<Candle> candles)
{
var mappedData = Map{Indicator}ToCandle(results, candles);
if (mappedData.Count == 0) return;
var previousCandle = mappedData[0];
foreach (var currentCandle in mappedData.Skip(1))
{
// Check trigger conditions
if (/* Long condition */)
{
AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
}
if (/* Short condition */)
{
AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium);
}
previousCandle = currentCandle;
}
}
Step 4: Update Configuration Files
Update Enums.cs:
public enum IndicatorType
{
// ... existing indicators
StochasticFiltered,
// ... continue
}
Update IndicatorBase.cs:
- Add any new parameter properties needed (e.g.,
StDevfor Bollinger Bands)
Update LightIndicator.cs:
- Add any new parameter properties with proper Id attributes for Orleans serialization
- Update
LightToBase()method to copy new properties
Update IndicatorRequest.cs:
- Add any new parameter properties to match LightIndicator
Update ScenarioHelpers.cs:
- Add case to
BuildIndicator()method:IndicatorType.{EnumName} => new {IndicatorName}(indicator.Name, {parameters}) - Add case to
GetSignalType()method:IndicatorType.{EnumName} => SignalType.{Type} - Add parameter validation in
BuildIndicator()method switch statement - Add new parameters to
BuildIndicator()method signature if needed - Update
BaseToLight()method to copy all LightIndicator properties
Update BacktestJobService.cs:
- Update LightIndicator creation in bundle job creation to include all new properties
- Ensure all indicator parameters are properly mapped from requests
Update GeneticService.cs:
- Add default values to
DefaultIndicatorValues:[IndicatorType.{EnumName}] = new() { {param_mappings} } - Add parameter ranges to
IndicatorParameterRanges:[IndicatorType.{EnumName}] = new() { {param_ranges} } - Add parameter mapping to
IndicatorParamMapping:[IndicatorType.{EnumName}] = [{param_names}] - Update
TradingBotChromosome.GetSelectedIndicators()to handle new parameters
Update Frontend Files:
CustomScenario.tsx:
- Add new parameters to indicator type definitions
- Update parameter input handling (float vs int parsing)
- Add default values for new parameters
TradeChart.tsx (if applicable):
- Add visualization logic for new indicator bands/lines
- Use appropriate colors and styles for differentiation
Step 5: Test and Validate
Compile Check:
# Backend compilation
dotnet build
# Frontend compilation
cd src/Managing.WebApp && npm run build
Basic Validation:
- Verify indicator appears in GeneticService configurations
- Check that BuildIndicator methods work correctly
- Ensure proper SignalType assignment
- Verify LightIndicator serialization works (Orleans Id attributes)
- Check parameter validation in ScenarioHelpers.BuildIndicator
- Confirm frontend parameter handling works correctly
Integration Test:
- Create a simple backtest using the new indicator
- Verify signals are generated correctly
- Check parameter handling and validation
- Test frontend scenario creation with new parameters
- Verify chart visualization displays correctly (if applicable)
Available Skender.Stock.Indicators
The following indicators are available from the Skender.Stock.Indicators library and can be used as the basis for custom trading indicators:
Trend Indicators
- EMA (Exponential Moving Average):
GetEma(period)- Smooths price data with exponential weighting - SMA (Simple Moving Average):
GetSma(period)- Arithmetic mean of prices over period - WMA (Weighted Moving Average):
GetWma(period)- Weighted average favoring recent prices - HMA (Hull Moving Average):
GetHma(period)- Responsive moving average using WMA - DEMA (Double Exponential Moving Average):
GetDema(period)- Two EMAs for reduced lag - TEMA (Triple Exponential Moving Average):
GetTema(period)- Three EMAs for further lag reduction - VWMA (Volume Weighted Moving Average):
GetVwma(period)- Volume-weighted price average
Momentum Oscillators
- RSI (Relative Strength Index):
GetRsi(period)- Momentum oscillator (0-100) - Stochastic Oscillator:
GetStoch(kPeriod, kSlowing, dPeriod)- %K and %D lines - Stochastic RSI:
GetStochRsi(rsiPeriod, stochPeriod, signalPeriod, smoothPeriod)- Stochastic of RSI - Williams %R:
GetWilliamsR(period)- Momentum oscillator (-100 to 0) - CCI (Commodity Channel Index):
GetCci(period)- Mean deviation from average price - MFI (Money Flow Index):
GetMfi(period)- Volume-weighted RSI - AO (Awesome Oscillator):
GetAo()- MACD of median price - KVO (Klinger Volume Oscillator):
GetKvo(fastPeriod, slowPeriod, signalPeriod)- Volume oscillator
Trend Following
- MACD (Moving Average Convergence Divergence):
GetMacd(fastPeriod, slowPeriod, signalPeriod)- Trend momentum indicator - SuperTrend:
GetSuperTrend(period, multiplier)- ATR-based trailing stop - Chandelier Exit:
GetChandelier(period, multiplier, type)- ATR-based exit levels - Parabolic SAR:
GetParabolicSar(accelerationStep, maxAcceleration)- Trailing stop and reversal - ADX (Average Directional Index):
GetAdx(period)- Trend strength indicator - DMI (Directional Movement Index):
GetDmi(period)- Trend direction and strength - PSAR (Parabolic SAR):
GetPsar(accelerationStep, maxAcceleration)- Dynamic support/resistance
Volatility Indicators
- ATR (Average True Range):
GetAtr(period)- Volatility measurement - Bollinger Bands:
GetBollingerBands(period, standardDeviations)- Price volatility bands - Standard Deviation:
GetStdDev(period)- Statistical volatility measure - TR (True Range):
GetTr()- Maximum price movement range
Volume Indicators
- OBV (On Balance Volume):
GetObv()- Cumulative volume based on price direction - CMF (Chaikin Money Flow):
GetCmf(period)- Volume-weighted price trend - ADL (Accumulation/Distribution Line):
GetAdl()- Volume-based price accumulation - EMV (Ease of Movement):
GetEmv(period)- Price movement relative to volume - NVI (Negative Volume Index):
GetNvi()- Volume-based trend indicator
Cycle Indicators
- STC (Schaff Trend Cycle):
GetStc(cyclePeriod, fastPeriod, slowPeriod)- Cycle oscillator (0-100) - DPO (Detrended Price Oscillator):
GetDpo(period)- Removes trend from price - EPMA (Endpoint Moving Average):
GetEpma(period)- End-point moving average
Support/Resistance
- Pivot Points:
GetPivotPoints(period)- Traditional pivot levels - Fibonacci Retracements:
GetFibonacciRetracements()- Fibonacci ratio levels
Candlestick Patterns
- Doji:
GetDoji()- Doji candlestick patterns - Hammer:
GetHammer()- Hammer patterns - Engulfing:
GetEngulfing()- Bullish/bearish engulfing - Marubozu:
GetMarubozu()- Marubozu patterns - And many more...
Usage Examples
// Basic usage
var ema = candles.GetEma(20).ToList();
var macd = candles.GetMacd(12, 26, 9).ToList();
var rsi = candles.GetRsi(14).ToList();
var stoch = candles.GetStoch(14, 3, 3).ToList();
var superTrend = candles.GetSuperTrend(10, 3.0).ToList();
// Chain indicators (indicator of indicators)
var rsiOfObv = candles.GetObv().GetRsi(14).ToList();
var smaOfRsi = candles.GetRsi(14).GetSma(9).ToList();
For complete documentation and examples, visit: Skender.Stock.Indicators Guide
Finding the Right Method
When implementing a new indicator, search the Skender documentation for your indicator concept:
- Identify the core calculation: What mathematical formula does your indicator use?
- Find the Skender equivalent: Search for methods like
Get{IndicatorName}() - Check parameters: Most indicators follow common patterns:
period: Lookback period (typically 5-300)fastPeriod/slowPeriod: For dual moving averagessignalPeriod: For signal line calculationsmultiplier: ATR multipliers (typically 1.0-5.0)
- Verify result structure: Check what properties the result object contains
Parameter Guidelines
Common Ranges by Indicator Type:
- Moving Averages: Period 5-300 (shorter = responsive, longer = smooth)
- Oscillators: Period 5-50 (RSI: 14, Stoch: 14, CCI: 20)
- Trend Following: Period 10-50, Multiplier 1.0-5.0
- Volatility: Period 5-50, Standard Deviations (StDev) 1.0-3.0 (Bollinger Bands)
- Volume: Period 5-50 (OBV uses no period)
Testing Parameters:
- Start with industry standard defaults
- Test multiple parameter combinations
- Consider timeframe: Shorter timeframes may need smaller periods
Result Object Patterns
Different indicators return different result objects. Common patterns:
Single Value Results:
EmaResult:{ Date, Ema }RsiResult:{ Date, Rsi }AtrResult:{ Date, Atr }ObvResult:{ Date, Obv }
Dual Value Results:
StochResult:{ Date, PercentK, PercentD, Oscillator }MacdResult:{ Date, Macd, Signal, Histogram }StochRsiResult:{ Date, Rsi, StochRsi, Signal }
Triple+ Value Results:
BollingerBandsResult:{ Date, Sma, UpperBand, LowerBand }SuperTrendResult:{ Date, SuperTrend, UpperBand, LowerBand }ChandelierResult:{ Date, ChandelierExit }
Candlestick Results:
CandleResult:{ Date, Price, Match, Candle }(for pattern recognition)
When creating your Candle{Indicator} mapping class, include all relevant result properties plus the base Candle properties (Close, Open, Date, Ticker, Exchange).
Quick Reference - Currently Used Indicators
In This Codebase:
GetEma(period)→EmaResult- Used in EMA Trend, EMA Cross, Dual EMA CrossGetMacd(fast, slow, signal)→MacdResult- Used in MACD CrossGetRsi(period)→RsiResult- Used in RSI Divergence variantsGetStoch(kPeriod, kSlowing, dPeriod)→StochResult- Used in Stochastic FilteredGetStochRsi(rsiPeriod, stochPeriod, signalPeriod, smoothPeriod)→StochRsiResult- Used in Stoch RSI TrendGetSuperTrend(period, multiplier)→SuperTrendResult- Used in SuperTrend, SuperTrend Cross EMAGetStc(cyclePeriod, fastPeriod, slowPeriod)→StcResult- Used in STC, Lagging STCGetStdDev(period)→StdDevResult- Used in StDev ContextGetChandelier(period, multiplier, type)→ChandelierResult- Used in Chandelier ExitGetBollingerBands(period, stdev)→BollingerBandsResult- Used in Bollinger Bands indicatorsGetAdx(period)→AdxResult- Used in SuperTrend Cross EMA
Available But Unused:
GetBollingerBands(period, stdDev)→BollingerBandsResultGetAtr(period)→AtrResultGetObv()→ObvResultGetCci(period)→CciResultGetMfi(period)→MfiResult- And many more... (see full list above)
Common Patterns
Signal Indicator Pattern
- Uses
TradeDirection.Long/ShortwithConfidencelevels - Implements crossover or threshold-based logic
- Returns filtered signals only when conditions are met
Trend Indicator Pattern
- Uses
TradeDirection.Long/Shortfor trend direction - Continuous assessment rather than discrete signals
- Lower confidence levels for trend indicators
Context Indicator Pattern
- Uses
Confidence.None/Low/Medium/Highfor veto power - Acts as filter for other indicators
- No directional signals, only context assessment
Shared Base Class Pattern
When to Use:
- Multiple indicators use the same Skender.Stock.Indicators result type
- Indicators share identical candle mapping logic
- Common signal processing patterns exist
Example:
public abstract class StochasticBase : IndicatorBase
{
protected List<CandleStoch> MapStochToCandle(List<StochResult> stochResults, IEnumerable<Candle> candles)
{
// Shared mapping logic for all Stochastic-based indicators
}
}
public class StochasticFiltered : StochasticBase { /* Specific logic */ }
public class AnotherStochasticIndicator : StochasticBase { /* Specific logic */ }
Bollinger Bands Example (Implemented):
public abstract class BollingerBandsBase : IndicatorBase
{
protected double Stdev { get; set; }
protected BollingerBandsBase(string name, IndicatorType type, int period, double stdev)
: base(name, type)
{
Stdev = stdev;
Period = period;
}
protected virtual IEnumerable<CandleBollingerBands> MapBollingerBandsToCandle(
IEnumerable<BollingerBandsResult> bbResults, IEnumerable<Candle> candles)
{
// Shared Bollinger Bands mapping logic with all properties
// (Sma, UpperBand, LowerBand, PercentB, ZScore, Width)
}
}
public class BollingerBandsPercentBMomentumBreakout : BollingerBandsBase { /* %B momentum logic */ }
public class BollingerBandsVolatilityProtection : BollingerBandsBase { /* Volatility protection logic */ }
When NOT to Use:
- Indicators have different result types (Stoch vs StochRsi)
- Mapping logic differs significantly
- Only one indicator uses a particular pattern
Error Handling
Common Issues:
- Missing parameters in constructor
- Incorrect SignalType assignment
- Wrong folder location (Signals/Trends/Context)
- Missing enum updates
- Parameter range mismatches
Validation Checklist:
- Checked for existing indicators with similar candle mappings
- Used appropriate base class (IndicatorBase or shared base if mappings overlap)
- Constructor parameters match IIndicator interface
- SignalType correctly assigned
- Enum added to IndicatorType
- IndicatorBase.cs properties added if needed
- LightIndicator.cs properties added with proper Id attributes
- IndicatorRequest.cs properties added
- ScenarioHelpers.cs BuildIndicator and BaseToLight methods updated
- BacktestJobService.cs LightIndicator mapping updated
- GeneticService.cs configurations updated (defaults, ranges, mappings)
- Frontend CustomScenario.tsx updated for new parameters
- Frontend TradeChart.tsx updated for visualization if needed
- Compiles without errors (backend and frontend)
- TypeScript types properly aligned