Add Bollinger Bands Volatility Protection indicator support
- 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.
This commit is contained in:
@@ -44,6 +44,17 @@ 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:**
|
||||
@@ -103,13 +114,26 @@ public class {IndicatorName} : IndicatorBase
|
||||
base(name, IndicatorType.{EnumName})
|
||||
{
|
||||
Signals = new List<LightSignal>();
|
||||
// Initialize parameters
|
||||
// Initialize parameters (e.g., Period, Multiplier, StDev)
|
||||
}
|
||||
|
||||
// Implementation methods...
|
||||
}
|
||||
```
|
||||
|
||||
**For Bollinger Bands (use shared base):**
|
||||
```csharp
|
||||
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:
|
||||
|
||||
@@ -174,32 +198,69 @@ public enum IndicatorType
|
||||
}
|
||||
```
|
||||
|
||||
**Update IndicatorBase.cs:**
|
||||
- Add any new parameter properties needed (e.g., `StDev` for 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 parameters to `BuildIndicator()` overload if needed
|
||||
- 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:**
|
||||
```bash
|
||||
# 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
|
||||
|
||||
@@ -298,7 +359,7 @@ When implementing a new indicator, search the [Skender documentation](https://do
|
||||
- **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 1.0-3.0
|
||||
- **Volatility**: Period 5-50, Standard Deviations (StDev) 1.0-3.0 (Bollinger Bands)
|
||||
- **Volume**: Period 5-50 (OBV uses no period)
|
||||
|
||||
**Testing Parameters:**
|
||||
@@ -343,6 +404,7 @@ When creating your `Candle{Indicator}` mapping class, include all relevant resul
|
||||
- `GetStc(cyclePeriod, fastPeriod, slowPeriod)` → `StcResult` - Used in STC, Lagging STC
|
||||
- `GetStdDev(period)` → `StdDevResult` - Used in StDev Context
|
||||
- `GetChandelier(period, multiplier, type)` → `ChandelierResult` - Used in Chandelier Exit
|
||||
- `GetBollingerBands(period, stdev)` → `BollingerBandsResult` - Used in Bollinger Bands indicators
|
||||
- `GetAdx(period)` → `AdxResult` - Used in SuperTrend Cross EMA
|
||||
|
||||
**Available But Unused:**
|
||||
@@ -390,6 +452,31 @@ public class StochasticFiltered : StochasticBase { /* Specific logic */ }
|
||||
public class AnotherStochasticIndicator : StochasticBase { /* Specific logic */ }
|
||||
```
|
||||
|
||||
**Bollinger Bands Example (Implemented):**
|
||||
```csharp
|
||||
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
|
||||
@@ -410,6 +497,13 @@ public class AnotherStochasticIndicator : StochasticBase { /* Specific logic */
|
||||
- [ ] Constructor parameters match IIndicator interface
|
||||
- [ ] SignalType correctly assigned
|
||||
- [ ] Enum added to IndicatorType
|
||||
- [ ] BuildIndicator methods updated
|
||||
- [ ] GeneticService configurations updated
|
||||
- [ ] Compiles without errors
|
||||
- [ ] 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
|
||||
|
||||
@@ -666,6 +666,16 @@ public class BacktestController : BaseController
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize SignalType for all indicators based on their IndicatorType
|
||||
// This ensures the correct SignalType is saved regardless of what the frontend sent
|
||||
if (request.UniversalConfig.Scenario?.Indicators != null)
|
||||
{
|
||||
foreach (var indicator in request.UniversalConfig.Scenario.Indicators)
|
||||
{
|
||||
indicator.SignalType = ScenarioHelpers.GetSignalType(indicator.Type);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the bundle backtest request
|
||||
var bundleRequest = new BundleBacktestRequest
|
||||
{
|
||||
|
||||
@@ -155,16 +155,24 @@ public class JobService
|
||||
{
|
||||
Indicators = sReq.Indicators?.Select(ind => new LightIndicator(ind.Name, ind.Type)
|
||||
{
|
||||
SignalType = ind.SignalType,
|
||||
MinimumHistory = ind.MinimumHistory,
|
||||
Period = ind.Period,
|
||||
FastPeriods = ind.FastPeriods,
|
||||
SlowPeriods = ind.SlowPeriods,
|
||||
SignalPeriods = ind.SignalPeriods,
|
||||
Multiplier = ind.Multiplier,
|
||||
StDev = ind.StDev,
|
||||
SmoothPeriods = ind.SmoothPeriods,
|
||||
StochPeriods = ind.StochPeriods,
|
||||
CyclePeriods = ind.CyclePeriods
|
||||
CyclePeriods = ind.CyclePeriods,
|
||||
KFactor = ind.KFactor,
|
||||
DFactor = ind.DFactor,
|
||||
TenkanPeriods = ind.TenkanPeriods,
|
||||
KijunPeriods = ind.KijunPeriods,
|
||||
SenkouBPeriods = ind.SenkouBPeriods,
|
||||
OffsetPeriods = ind.OffsetPeriods,
|
||||
SenkouOffset = ind.SenkouOffset,
|
||||
ChikouOffset = ind.ChikouOffset
|
||||
}).ToList() ?? new List<LightIndicator>()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,7 +107,12 @@ public class GeneticService : IGeneticService
|
||||
[IndicatorType.BollingerBandsPercentBMomentumBreakout] = new()
|
||||
{
|
||||
["period"] = 20.0,
|
||||
["multiplier"] = 2.0
|
||||
["stdev"] = 2.0
|
||||
},
|
||||
[IndicatorType.BollingerBandsVolatilityProtection] = new()
|
||||
{
|
||||
["period"] = 20.0,
|
||||
["stdev"] = 2.0
|
||||
},
|
||||
[IndicatorType.IchimokuKumoTrend] = new()
|
||||
{
|
||||
@@ -202,7 +207,12 @@ public class GeneticService : IGeneticService
|
||||
[IndicatorType.BollingerBandsPercentBMomentumBreakout] = new()
|
||||
{
|
||||
["period"] = (5.0, 50.0),
|
||||
["multiplier"] = (1.0, 5.0)
|
||||
["stdev"] = (1.0, 5.0)
|
||||
},
|
||||
[IndicatorType.BollingerBandsVolatilityProtection] = new()
|
||||
{
|
||||
["period"] = (10.0, 50.0),
|
||||
["stdev"] = (1.5, 3.0)
|
||||
},
|
||||
[IndicatorType.IchimokuKumoTrend] = new()
|
||||
{
|
||||
@@ -231,7 +241,8 @@ public class GeneticService : IGeneticService
|
||||
[IndicatorType.StochasticCross] = ["stochPeriods", "signalPeriods", "smoothPeriods", "kFactor", "dFactor"],
|
||||
[IndicatorType.Stc] = ["cyclePeriods", "fastPeriods", "slowPeriods"],
|
||||
[IndicatorType.LaggingStc] = ["cyclePeriods", "fastPeriods", "slowPeriods"],
|
||||
[IndicatorType.BollingerBandsPercentBMomentumBreakout] = ["period", "multiplier"],
|
||||
[IndicatorType.BollingerBandsPercentBMomentumBreakout] = ["period", "stdev"],
|
||||
[IndicatorType.BollingerBandsVolatilityProtection] = ["period", "stdev"],
|
||||
[IndicatorType.IchimokuKumoTrend] = ["tenkanPeriods", "kijunPeriods", "senkouBPeriods", "offsetPeriods"]
|
||||
};
|
||||
|
||||
@@ -860,10 +871,10 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
indicator.SignalPeriods = Convert.ToInt32(genes[baseIndex + 3].Value);
|
||||
}
|
||||
|
||||
paramName = GetParameterName(4); // multiplier
|
||||
paramName = GetParameterName(4); // stdev
|
||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||
{
|
||||
indicator.Multiplier = Convert.ToDouble(genes[baseIndex + 4].Value);
|
||||
indicator.StDev = Convert.ToDouble(genes[baseIndex + 4].Value);
|
||||
}
|
||||
|
||||
paramName = GetParameterName(5); // stochPeriods
|
||||
@@ -983,7 +994,7 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
1 => "fastPeriods",
|
||||
2 => "slowPeriods",
|
||||
3 => "signalPeriods",
|
||||
4 => "multiplier",
|
||||
4 => "stdev",
|
||||
5 => "stochPeriods",
|
||||
6 => "smoothPeriods",
|
||||
7 => "cyclePeriods",
|
||||
|
||||
@@ -324,7 +324,6 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
||||
{
|
||||
Indicators = sReq.Indicators?.Select(i => new LightIndicator(i.Name, i.Type)
|
||||
{
|
||||
SignalType = i.SignalType,
|
||||
MinimumHistory = i.MinimumHistory,
|
||||
Period = i.Period,
|
||||
FastPeriods = i.FastPeriods,
|
||||
|
||||
@@ -229,7 +229,6 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
||||
{
|
||||
Indicators = sReq.Indicators?.Select(i => new LightIndicator(i.Name, i.Type)
|
||||
{
|
||||
SignalType = i.SignalType,
|
||||
MinimumHistory = i.MinimumHistory,
|
||||
Period = i.Period,
|
||||
FastPeriods = i.FastPeriods,
|
||||
|
||||
@@ -67,6 +67,7 @@ public static class Enums
|
||||
SuperTrendCrossEma,
|
||||
DualEmaCross,
|
||||
BollingerBandsPercentBMomentumBreakout,
|
||||
BollingerBandsVolatilityProtection,
|
||||
IchimokuKumoTrend
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,11 @@ public class IndicatorRequest
|
||||
/// </summary>
|
||||
public double? Multiplier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Standard deviation parameter for indicators like Bollinger Bands
|
||||
/// </summary>
|
||||
public double? StDev { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Smooth periods parameter
|
||||
/// </summary>
|
||||
@@ -70,4 +75,45 @@ public class IndicatorRequest
|
||||
/// Cycle periods parameter
|
||||
/// </summary>
|
||||
public int? CyclePeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// K factor parameter for Stochastic Cross
|
||||
/// </summary>
|
||||
public double? KFactor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// D factor parameter for Stochastic Cross
|
||||
/// </summary>
|
||||
public double? DFactor { get; set; }
|
||||
|
||||
// Ichimoku-specific parameters
|
||||
/// <summary>
|
||||
/// Tenkan periods for Ichimoku
|
||||
/// </summary>
|
||||
public int? TenkanPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Kijun periods for Ichimoku
|
||||
/// </summary>
|
||||
public int? KijunPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Senkou B periods for Ichimoku
|
||||
/// </summary>
|
||||
public int? SenkouBPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset periods for Ichimoku
|
||||
/// </summary>
|
||||
public int? OffsetPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Senkou offset for Ichimoku
|
||||
/// </summary>
|
||||
public int? SenkouOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Chikou offset for Ichimoku
|
||||
/// </summary>
|
||||
public int? ChikouOffset { get; set; }
|
||||
}
|
||||
162
src/Managing.Domain/Indicators/Base/BollingerBandsBase.cs
Normal file
162
src/Managing.Domain/Indicators/Base/BollingerBandsBase.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Indicators;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies.Base;
|
||||
|
||||
public abstract class BollingerBandsBase : IndicatorBase
|
||||
{
|
||||
public List<LightSignal> Signals { get; set; }
|
||||
|
||||
protected BollingerBandsBase(string name, IndicatorType type, int period, double stdev) : base(name, type)
|
||||
{
|
||||
Signals = new List<LightSignal>();
|
||||
Period = period;
|
||||
StDev = stdev;
|
||||
}
|
||||
|
||||
public override List<LightSignal> Run(HashSet<Candle> candles)
|
||||
{
|
||||
if (candles.Count <= Period)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var bbResults = candles
|
||||
.GetBollingerBands(Period.Value, StDev.Value)
|
||||
.RemoveWarmupPeriods()
|
||||
.ToList();
|
||||
|
||||
if (bbResults.Count == 0)
|
||||
return null;
|
||||
|
||||
ProcessBollingerBandsSignals(bbResults, candles);
|
||||
|
||||
return Signals.Where(s => s.Confidence != Confidence.None).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||
{
|
||||
if (candles.Count <= Period)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Use pre-calculated Bollinger Bands values if available
|
||||
List<BollingerBandsResult> bbResults = null;
|
||||
if (preCalculatedValues?.BollingerBands != null && preCalculatedValues.BollingerBands.Any())
|
||||
{
|
||||
// Filter pre-calculated values to match the candles we're processing
|
||||
bbResults = preCalculatedValues.BollingerBands
|
||||
.Where(bb => bb.UpperBand.HasValue && bb.LowerBand.HasValue && bb.Sma.HasValue &&
|
||||
candles.Any(c => c.Date == bb.Date))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||
if (bbResults == null || !bbResults.Any())
|
||||
{
|
||||
return Run(candles);
|
||||
}
|
||||
|
||||
ProcessBollingerBandsSignals(bbResults, candles);
|
||||
|
||||
return Signals.Where(s => s.Confidence != Confidence.None).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
BollingerBands = candles.GetBollingerBands(Period.Value, StDev.Value)
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method for processing Bollinger Bands signals - implemented by child classes
|
||||
/// </summary>
|
||||
protected abstract void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, HashSet<Candle> candles);
|
||||
|
||||
/// <summary>
|
||||
/// Maps Bollinger Bands results to candle objects with all BollingerBandsResult properties
|
||||
/// </summary>
|
||||
protected virtual IEnumerable<CandleBollingerBands> MapBollingerBandsToCandle(IEnumerable<BollingerBandsResult> bbResults, IEnumerable<Candle> candles)
|
||||
{
|
||||
var bbCandles = new List<CandleBollingerBands>();
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
var currentBB = bbResults.Find(candle.Date);
|
||||
if (currentBB != null && currentBB.Sma.HasValue)
|
||||
{
|
||||
bbCandles.Add(new CandleBollingerBands()
|
||||
{
|
||||
Close = candle.Close,
|
||||
Open = candle.Open,
|
||||
Date = candle.Date,
|
||||
Ticker = candle.Ticker,
|
||||
Exchange = candle.Exchange,
|
||||
Sma = currentBB.Sma,
|
||||
UpperBand = currentBB.UpperBand,
|
||||
LowerBand = currentBB.LowerBand,
|
||||
PercentB = currentBB.PercentB,
|
||||
ZScore = currentBB.ZScore,
|
||||
Width = currentBB.Width
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return bbCandles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared method for adding signals with duplicate prevention
|
||||
/// </summary>
|
||||
protected void AddSignal(Candle candleSignal, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new LightSignal(
|
||||
candleSignal.Ticker,
|
||||
direction,
|
||||
confidence,
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
Type,
|
||||
SignalType,
|
||||
Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base candle class for Bollinger Bands indicators with all BollingerBandsResult properties
|
||||
/// </summary>
|
||||
public class CandleBollingerBands : Candle
|
||||
{
|
||||
public double? Sma { get; internal set; }
|
||||
public double? UpperBand { get; internal set; }
|
||||
public double? LowerBand { get; internal set; }
|
||||
public double? PercentB { get; internal set; }
|
||||
public double? ZScore { get; internal set; }
|
||||
public double? Width { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies.Context;
|
||||
|
||||
public class BollingerBandsVolatilityProtection : BollingerBandsBase
|
||||
{
|
||||
public BollingerBandsVolatilityProtection(string name, int period, double stdev) : base(name, IndicatorType.BollingerBandsVolatilityProtection, period, stdev)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes Bollinger Bands volatility protection signals based on bandwidth analysis.
|
||||
/// This method applies a veto filter during periods of market extremes:
|
||||
/// - Blocks signals when bandwidth is extremely high (dangerous expansion) or extremely low (dead market)
|
||||
/// - Validates signals when bandwidth is normal to low (excluding squeeze extremes)
|
||||
/// Bandwidth = (UpperBand - LowerBand) / Sma represents market volatility
|
||||
/// </summary>
|
||||
/// <param name="bbResults">List of Bollinger Bands calculation results</param>
|
||||
/// <param name="candles">Candles to process</param>
|
||||
protected override void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, HashSet<Candle> candles)
|
||||
{
|
||||
var bbCandles = MapBollingerBandsToCandle(bbResults, candles.TakeLast(Period.Value)).ToList();
|
||||
|
||||
if (bbCandles.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var currentCandle in bbCandles)
|
||||
{
|
||||
var width = currentCandle.Width ?? 0;
|
||||
|
||||
// Determine confidence based on width levels (bandwidth as % of SMA)
|
||||
// Lower width = less volatility = higher confidence for trading
|
||||
// Higher width = more volatility = lower confidence for trading
|
||||
Confidence confidence;
|
||||
|
||||
if (width >= 0.15) // Extremely high volatility - dangerous expansion
|
||||
{
|
||||
// Block all signals during dangerous volatility expansion
|
||||
confidence = Confidence.None;
|
||||
}
|
||||
else if (width <= 0.02) // Extremely low volatility - dead market/squeeze
|
||||
{
|
||||
// Block all signals in dead markets or extreme squeezes
|
||||
confidence = Confidence.None;
|
||||
}
|
||||
else if (width <= 0.05) // Low to normal volatility - good for trading
|
||||
{
|
||||
// Validate signals during low volatility trending conditions
|
||||
confidence = Confidence.High;
|
||||
}
|
||||
else if (width <= 0.10) // Normal volatility - acceptable for trading
|
||||
{
|
||||
// Validate signals during normal volatility conditions
|
||||
confidence = Confidence.Medium;
|
||||
}
|
||||
else // Moderate high volatility (0.10 - 0.15)
|
||||
{
|
||||
// Lower confidence during elevated but not extreme volatility
|
||||
confidence = Confidence.Low;
|
||||
}
|
||||
|
||||
// Context strategies always return TradeDirection.None
|
||||
// The confidence level indicates the quality of market conditions
|
||||
AddSignal(currentCandle, TradeDirection.None, confidence);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,8 @@ namespace Managing.Domain.Strategies
|
||||
|
||||
public double? Multiplier { get; set; }
|
||||
|
||||
public double? StDev { get; set; }
|
||||
|
||||
public int? SmoothPeriods { get; set; }
|
||||
|
||||
public int? StochPeriods { get; set; }
|
||||
|
||||
@@ -36,23 +36,25 @@ public class LightIndicator
|
||||
|
||||
[Id(8)] public double? Multiplier { get; set; }
|
||||
|
||||
[Id(9)] public int? SmoothPeriods { get; set; }
|
||||
[Id(9)] public double? StDev { get; set; }
|
||||
|
||||
[Id(10)] public int? StochPeriods { get; set; }
|
||||
[Id(10)] public int? SmoothPeriods { get; set; }
|
||||
|
||||
[Id(11)] public int? CyclePeriods { get; set; }
|
||||
[Id(11)] public int? StochPeriods { get; set; }
|
||||
|
||||
[Id(12)] public double? KFactor { get; set; }
|
||||
[Id(12)] public int? CyclePeriods { get; set; }
|
||||
|
||||
[Id(13)] public double? DFactor { get; set; }
|
||||
[Id(13)] public double? KFactor { get; set; }
|
||||
|
||||
[Id(14)] public double? DFactor { get; set; }
|
||||
|
||||
// Ichimoku-specific parameters
|
||||
[Id(14)] public int? TenkanPeriods { get; set; }
|
||||
[Id(15)] public int? KijunPeriods { get; set; }
|
||||
[Id(16)] public int? SenkouBPeriods { get; set; }
|
||||
[Id(17)] public int? OffsetPeriods { get; set; }
|
||||
[Id(18)] public int? SenkouOffset { get; set; }
|
||||
[Id(19)] public int? ChikouOffset { get; set; }
|
||||
[Id(15)] public int? TenkanPeriods { get; set; }
|
||||
[Id(16)] public int? KijunPeriods { get; set; }
|
||||
[Id(17)] public int? SenkouBPeriods { get; set; }
|
||||
[Id(18)] public int? OffsetPeriods { get; set; }
|
||||
[Id(19)] public int? SenkouOffset { get; set; }
|
||||
[Id(20)] public int? ChikouOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts a LightIndicator back to a full Indicator
|
||||
@@ -74,6 +76,7 @@ public class LightIndicator
|
||||
SlowPeriods = SlowPeriods,
|
||||
SignalPeriods = SignalPeriods,
|
||||
Multiplier = Multiplier,
|
||||
StDev = StDev,
|
||||
SmoothPeriods = SmoothPeriods,
|
||||
StochPeriods = StochPeriods,
|
||||
CyclePeriods = CyclePeriods,
|
||||
|
||||
@@ -1,88 +1,17 @@
|
||||
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.Signals;
|
||||
|
||||
public class BollingerBandsPercentBMomentumBreakout : IndicatorBase
|
||||
public class BollingerBandsPercentBMomentumBreakout : BollingerBandsBase
|
||||
{
|
||||
public List<LightSignal> Signals { get; set; }
|
||||
|
||||
public BollingerBandsPercentBMomentumBreakout(
|
||||
string name,
|
||||
int period,
|
||||
double stdDev) : base(name, IndicatorType.BollingerBandsPercentBMomentumBreakout)
|
||||
double stdev) : base(name, IndicatorType.BollingerBandsPercentBMomentumBreakout, period, stdev)
|
||||
{
|
||||
Signals = new List<LightSignal>();
|
||||
Period = period;
|
||||
Multiplier = stdDev; // Using Multiplier property for stdDev since it's a double
|
||||
}
|
||||
|
||||
public override List<LightSignal> Run(HashSet<Candle> candles)
|
||||
{
|
||||
if (candles.Count <= 10 * Period.Value + 50)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var bbResults = candles
|
||||
.GetBollingerBands(Period.Value, (double)Multiplier.Value)
|
||||
.RemoveWarmupPeriods()
|
||||
.ToList();
|
||||
|
||||
if (bbResults.Count == 0)
|
||||
return null;
|
||||
|
||||
ProcessBollingerBandsSignals(bbResults, candles);
|
||||
|
||||
return Signals.ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||
{
|
||||
if (candles.Count <= 10 * Period.Value + 50)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Use pre-calculated Bollinger Bands values if available
|
||||
List<BollingerBandsResult> bbResults = null;
|
||||
if (preCalculatedValues?.BollingerBands != null && preCalculatedValues.BollingerBands.Any())
|
||||
{
|
||||
// Filter pre-calculated values to match the candles we're processing
|
||||
bbResults = preCalculatedValues.BollingerBands
|
||||
.Where(bb => bb.UpperBand.HasValue && bb.LowerBand.HasValue && bb.Sma.HasValue &&
|
||||
candles.Any(c => c.Date == bb.Date))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// If no pre-calculated values or they don't match, fall back to regular calculation
|
||||
if (bbResults == null || !bbResults.Any())
|
||||
{
|
||||
return Run(candles);
|
||||
}
|
||||
|
||||
ProcessBollingerBandsSignals(bbResults, candles);
|
||||
|
||||
return Signals.ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -90,9 +19,9 @@ public class BollingerBandsPercentBMomentumBreakout : IndicatorBase
|
||||
/// Long signals: %B crosses above 0.8 after being below (strong upward momentum)
|
||||
/// Short signals: %B crosses below 0.2 after being above (strong downward momentum)
|
||||
/// </summary>
|
||||
private void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, HashSet<Candle> candles)
|
||||
protected override void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, HashSet<Candle> candles)
|
||||
{
|
||||
var bbCandles = MapBollingerBandsToCandle(bbResults, candles.TakeLast(Period.Value));
|
||||
var bbCandles = MapBollingerBandsToCandle(bbResults, candles.TakeLast(Period.Value)).ToList();
|
||||
|
||||
if (bbCandles.Count < 2)
|
||||
return;
|
||||
@@ -116,74 +45,4 @@ public class BollingerBandsPercentBMomentumBreakout : IndicatorBase
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
BollingerBands = candles.GetBollingerBands(Period.Value, (double)Multiplier.Value)
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private List<CandleBollingerBands> MapBollingerBandsToCandle(IEnumerable<BollingerBandsResult> bbResults, IEnumerable<Candle> candles)
|
||||
{
|
||||
var bbCandles = new List<CandleBollingerBands>();
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
var currentBB = bbResults.Find(candle.Date);
|
||||
if (currentBB != null && currentBB.UpperBand.HasValue && currentBB.LowerBand.HasValue && currentBB.Sma.HasValue)
|
||||
{
|
||||
// Calculate %B = (Price - LowerBand) / (UpperBand - LowerBand)
|
||||
var price = (double)candle.Close;
|
||||
var upperBand = (double)currentBB.UpperBand.Value;
|
||||
var lowerBand = (double)currentBB.LowerBand.Value;
|
||||
var percentB = (double)currentBB.PercentB.Value;
|
||||
|
||||
// Avoid division by zero
|
||||
if (upperBand != lowerBand)
|
||||
{
|
||||
bbCandles.Add(new CandleBollingerBands()
|
||||
{
|
||||
Close = candle.Close,
|
||||
Open = candle.Open,
|
||||
Date = candle.Date,
|
||||
Ticker = candle.Ticker,
|
||||
Exchange = candle.Exchange,
|
||||
PercentB = percentB,
|
||||
UpperBand = upperBand,
|
||||
LowerBand = lowerBand,
|
||||
Sma = currentBB.Sma.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bbCandles;
|
||||
}
|
||||
|
||||
private void AddSignal(CandleBollingerBands candleSignal, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new LightSignal(
|
||||
candleSignal.Ticker,
|
||||
direction,
|
||||
confidence,
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
Type,
|
||||
SignalType,
|
||||
Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
private class CandleBollingerBands : Candle
|
||||
{
|
||||
public double PercentB { get; internal set; }
|
||||
public double UpperBand { get; internal set; }
|
||||
public double LowerBand { get; internal set; }
|
||||
public double Sma { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +100,11 @@ public static class ScenarioHelpers
|
||||
indicator.FastPeriods.Value, indicator.SlowPeriods.Value),
|
||||
IndicatorType.SuperTrendCrossEma => new SuperTrendCrossEma(indicator.Name,
|
||||
indicator.Period.Value, indicator.Multiplier.Value),
|
||||
IndicatorType.BollingerBandsPercentBMomentumBreakout => new BollingerBandsPercentBMomentumBreakout(indicator.Name,
|
||||
indicator.Period.Value, indicator.Multiplier.Value),
|
||||
IndicatorType.BollingerBandsPercentBMomentumBreakout => new BollingerBandsPercentBMomentumBreakout(
|
||||
indicator.Name,
|
||||
indicator.Period.Value, indicator.StDev.Value),
|
||||
IndicatorType.BollingerBandsVolatilityProtection => new BollingerBandsVolatilityProtection(indicator.Name,
|
||||
indicator.Period.Value, indicator.StDev.Value),
|
||||
IndicatorType.IchimokuKumoTrend => new IchimokuKumoTrend(indicator.Name,
|
||||
indicator.TenkanPeriods ?? 9,
|
||||
indicator.KijunPeriods ?? 26,
|
||||
@@ -112,6 +115,8 @@ public static class ScenarioHelpers
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
result.SignalType = GetSignalType(indicator.Type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -122,16 +127,24 @@ public static class ScenarioHelpers
|
||||
{
|
||||
return new LightIndicator(indicatorBase.Name, indicatorBase.Type)
|
||||
{
|
||||
SignalType = indicatorBase.SignalType,
|
||||
MinimumHistory = indicatorBase.MinimumHistory,
|
||||
Period = indicatorBase.Period,
|
||||
FastPeriods = indicatorBase.FastPeriods,
|
||||
SlowPeriods = indicatorBase.SlowPeriods,
|
||||
SignalPeriods = indicatorBase.SignalPeriods,
|
||||
Multiplier = indicatorBase.Multiplier,
|
||||
StDev = indicatorBase.StDev,
|
||||
SmoothPeriods = indicatorBase.SmoothPeriods,
|
||||
StochPeriods = indicatorBase.StochPeriods,
|
||||
CyclePeriods = indicatorBase.CyclePeriods
|
||||
CyclePeriods = indicatorBase.CyclePeriods,
|
||||
KFactor = indicatorBase.KFactor,
|
||||
DFactor = indicatorBase.DFactor,
|
||||
TenkanPeriods = indicatorBase.TenkanPeriods,
|
||||
KijunPeriods = indicatorBase.KijunPeriods,
|
||||
SenkouBPeriods = indicatorBase.SenkouBPeriods,
|
||||
OffsetPeriods = indicatorBase.OffsetPeriods,
|
||||
SenkouOffset = indicatorBase.SenkouOffset,
|
||||
ChikouOffset = indicatorBase.ChikouOffset
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,11 +156,18 @@ public static class ScenarioHelpers
|
||||
int? slowPeriods = null,
|
||||
int? signalPeriods = null,
|
||||
double? multiplier = null,
|
||||
double? stdev = null,
|
||||
int? stochPeriods = null,
|
||||
int? smoothPeriods = null,
|
||||
int? cyclePeriods = null,
|
||||
double? kFactor = null,
|
||||
double? dFactor = null)
|
||||
double? dFactor = null,
|
||||
int? tenkanPeriods = null,
|
||||
int? kijunPeriods = null,
|
||||
int? senkouBPeriods = null,
|
||||
int? offsetPeriods = null,
|
||||
int? senkouOffset = null,
|
||||
int? chikouOffset = null)
|
||||
{
|
||||
IIndicator indicator = new IndicatorBase(name, type);
|
||||
|
||||
@@ -249,12 +269,43 @@ public static class ScenarioHelpers
|
||||
{
|
||||
throw new Exception($"kFactor must be greater than 0 for {indicator.Type} strategy type");
|
||||
}
|
||||
|
||||
if (indicator.DFactor <= 0)
|
||||
{
|
||||
throw new Exception($"dFactor must be greater than 0 for {indicator.Type} strategy type");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case IndicatorType.BollingerBandsPercentBMomentumBreakout:
|
||||
case IndicatorType.BollingerBandsVolatilityProtection:
|
||||
if (!period.HasValue || !stdev.HasValue)
|
||||
{
|
||||
throw new Exception($"Missing period or stdev for {indicator.Type} strategy type");
|
||||
}
|
||||
else
|
||||
{
|
||||
((IndicatorBase)indicator).Period = period;
|
||||
((IndicatorBase)indicator).StDev = stdev;
|
||||
}
|
||||
|
||||
break;
|
||||
case IndicatorType.IchimokuKumoTrend:
|
||||
if (!tenkanPeriods.HasValue || !kijunPeriods.HasValue || !senkouBPeriods.HasValue ||
|
||||
!offsetPeriods.HasValue)
|
||||
{
|
||||
throw new Exception($"Missing Ichimoku parameters for {indicator.Type} strategy type");
|
||||
}
|
||||
else
|
||||
{
|
||||
((IndicatorBase)indicator).TenkanPeriods = tenkanPeriods;
|
||||
((IndicatorBase)indicator).KijunPeriods = kijunPeriods;
|
||||
((IndicatorBase)indicator).SenkouBPeriods = senkouBPeriods;
|
||||
((IndicatorBase)indicator).OffsetPeriods = offsetPeriods;
|
||||
((IndicatorBase)indicator).SenkouOffset = senkouOffset;
|
||||
((IndicatorBase)indicator).ChikouOffset = chikouOffset;
|
||||
}
|
||||
|
||||
break;
|
||||
case IndicatorType.Stc:
|
||||
case IndicatorType.LaggingStc:
|
||||
@@ -299,6 +350,7 @@ public static class ScenarioHelpers
|
||||
IndicatorType.LaggingStc => SignalType.Signal,
|
||||
IndicatorType.SuperTrendCrossEma => SignalType.Signal,
|
||||
IndicatorType.BollingerBandsPercentBMomentumBreakout => SignalType.Signal,
|
||||
IndicatorType.BollingerBandsVolatilityProtection => SignalType.Context,
|
||||
IndicatorType.IchimokuKumoTrend => SignalType.Trend,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ public class IndicatorComboConfig
|
||||
/// <summary>
|
||||
/// Minimum confidence level to return a signal (default: Low)
|
||||
/// </summary>
|
||||
public Confidence MinimumConfidence { get; set; } = Confidence.Low;
|
||||
public Confidence MinimumConfidence { get; set; } = Confidence.Medium;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum confidence level required from context strategies (default: Medium)
|
||||
|
||||
@@ -48,7 +48,8 @@ const CustomScenario: React.FC<ICustomScenario> = ({
|
||||
case IndicatorType.SuperTrendCrossEma:
|
||||
case IndicatorType.ChandelierExit:
|
||||
case IndicatorType.BollingerBandsPercentBMomentumBreakout:
|
||||
params = ['period', 'multiplier'];
|
||||
case IndicatorType.BollingerBandsVolatilityProtection:
|
||||
params = ['period', 'stdev'];
|
||||
break;
|
||||
|
||||
case IndicatorType.StochRsiTrend:
|
||||
@@ -147,6 +148,7 @@ const CustomScenario: React.FC<ICustomScenario> = ({
|
||||
slowPeriods: 26,
|
||||
signalPeriods: 9,
|
||||
multiplier: 3.0,
|
||||
stDev: 2.0,
|
||||
stochPeriods: 14,
|
||||
smoothPeriods: 3,
|
||||
cyclePeriods: 10,
|
||||
@@ -284,10 +286,10 @@ const CustomScenario: React.FC<ICustomScenario> = ({
|
||||
<FormInput key={param} label={param.charAt(0).toUpperCase() + param.slice(1)} htmlFor={`${param}-${index}`} inline={false}>
|
||||
<input
|
||||
value={indicator[param as keyof LightIndicator] as number || ''}
|
||||
onChange={(e) => updateIndicator(index, param, param.includes('multiplier') ? parseFloat(e.target.value) : parseInt(e.target.value))}
|
||||
onChange={(e) => updateIndicator(index, param, param.includes('multiplier') || param.includes('stdev') ? parseFloat(e.target.value) : parseInt(e.target.value))}
|
||||
type='number'
|
||||
step={param.includes('multiplier') ? '0.1' : '1'}
|
||||
min={param.includes('multiplier') ? '0.1' : '1'}
|
||||
step={param.includes('multiplier') || param.includes('stdev') ? '0.1' : '1'}
|
||||
min={param.includes('multiplier') || param.includes('stdev') ? '0.1' : '1'}
|
||||
className='input input-bordered w-full'
|
||||
/>
|
||||
</FormInput>
|
||||
|
||||
@@ -39,7 +39,6 @@ import useTheme from '../../../../hooks/useTheme'
|
||||
// }
|
||||
|
||||
|
||||
|
||||
type ITradeChartProps = {
|
||||
candles: Candle[]
|
||||
positions: Position[]
|
||||
@@ -69,18 +68,18 @@ const TradeChart = ({
|
||||
const series1 = useRef<ISeriesApi<'Candlestick'>>()
|
||||
const [timeDiff, setTimeDiff] = useState<number>(0)
|
||||
const [candleCount, setCandleCount] = useState<number>(candles.length)
|
||||
const [chartDimensions, setChartDimensions] = useState({ width: 0, height: 0 })
|
||||
const [chartDimensions, setChartDimensions] = useState({width: 0, height: 0})
|
||||
|
||||
// Get responsive dimensions
|
||||
const getResponsiveDimensions = () => {
|
||||
if (!containerRef.current) return { width: width || 510, height: height || 300 }
|
||||
if (!containerRef.current) return {width: width || 510, height: height || 300}
|
||||
|
||||
const containerWidth = containerRef.current.offsetWidth
|
||||
const containerHeight = containerRef.current.offsetHeight
|
||||
|
||||
// Use provided dimensions if available, otherwise calculate responsive ones
|
||||
if (width && height) {
|
||||
return { width, height }
|
||||
return {width, height}
|
||||
}
|
||||
|
||||
// For responsive mode, calculate based on container
|
||||
@@ -231,7 +230,7 @@ const TradeChart = ({
|
||||
} else {
|
||||
color = negativeColor
|
||||
}
|
||||
}else if (status == PositionStatus.Filled) {
|
||||
} else if (status == PositionStatus.Filled) {
|
||||
color = theme.warning
|
||||
}
|
||||
}
|
||||
@@ -393,8 +392,7 @@ const TradeChart = ({
|
||||
}
|
||||
|
||||
// Price panel
|
||||
if (indicatorsValues?.EmaTrend != null || indicatorsValues?.EmaCross != null)
|
||||
{
|
||||
if (indicatorsValues?.EmaTrend != null || indicatorsValues?.EmaCross != null) {
|
||||
const emaSeries = chart.current.addLineSeries({
|
||||
color: theme.secondary,
|
||||
lineWidth: 1,
|
||||
@@ -417,8 +415,7 @@ const TradeChart = ({
|
||||
}
|
||||
})
|
||||
|
||||
if (emaData != null)
|
||||
{
|
||||
if (emaData != null) {
|
||||
// @ts-ignore
|
||||
emaSeries.setData(emaData)
|
||||
}
|
||||
@@ -568,6 +565,47 @@ const TradeChart = ({
|
||||
lowerBandSeries.setData(lowerBandData)
|
||||
}
|
||||
|
||||
// Display Bollinger Bands on price chart for Volatility Protection
|
||||
if (indicatorsValues?.BollingerBandsVolatilityProtection != null) {
|
||||
const upperBandSeries = chart.current.addLineSeries({
|
||||
color: '#FF6B6B', // Lighter red for volatility protection bands
|
||||
lineWidth: 1,
|
||||
priceLineVisible: false,
|
||||
priceLineWidth: 1,
|
||||
title: 'Volatility Protection Upper Band',
|
||||
pane: 0,
|
||||
lineStyle: LineStyle.Dotted,
|
||||
})
|
||||
|
||||
const upperBandData = indicatorsValues.BollingerBandsVolatilityProtection.bollingerBands?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.upperBand,
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
upperBandSeries.setData(upperBandData)
|
||||
|
||||
const lowerBandSeries = chart.current.addLineSeries({
|
||||
color: '#4ECDC4', // Teal for volatility protection bands
|
||||
lineWidth: 1,
|
||||
priceLineVisible: false,
|
||||
priceLineWidth: 1,
|
||||
title: 'Volatility Protection Lower Band',
|
||||
pane: 0,
|
||||
lineStyle: LineStyle.Dotted,
|
||||
})
|
||||
|
||||
const lowerBandData = indicatorsValues.BollingerBandsVolatilityProtection.bollingerBands?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.lowerBand,
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
lowerBandSeries.setData(lowerBandData)
|
||||
}
|
||||
|
||||
if (markers.length > 0) {
|
||||
series1.current.setMarkers(markers)
|
||||
}
|
||||
@@ -576,8 +614,7 @@ const TradeChart = ({
|
||||
var paneCount = 1
|
||||
|
||||
|
||||
if (indicatorsValues?.RsiDivergence != null || indicatorsValues?.RsiDivergenceConfirm != null)
|
||||
{
|
||||
if (indicatorsValues?.RsiDivergence != null || indicatorsValues?.RsiDivergenceConfirm != null) {
|
||||
const rsiSeries = chart.current.addLineSeries({
|
||||
pane: paneCount,
|
||||
title: 'RSI',
|
||||
@@ -690,7 +727,7 @@ const TradeChart = ({
|
||||
})
|
||||
|
||||
var priceOptions = {
|
||||
scaleMargins:{
|
||||
scaleMargins: {
|
||||
top: 0.7,
|
||||
bottom: 0.02,
|
||||
}
|
||||
@@ -751,7 +788,7 @@ const TradeChart = ({
|
||||
paneCount++
|
||||
}
|
||||
|
||||
if (indicatorsValues?.StochRsiTrend){
|
||||
if (indicatorsValues?.StochRsiTrend) {
|
||||
const stochRsiSeries = chart.current.addLineSeries({
|
||||
...baselineOptions,
|
||||
priceLineVisible: false,
|
||||
@@ -963,9 +1000,9 @@ const TradeChart = ({
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full h-full"
|
||||
style={{ minHeight: height || 250 }}
|
||||
style={{minHeight: height || 250}}
|
||||
>
|
||||
<div ref={chartRef} className="w-full h-full" />
|
||||
<div ref={chartRef} className="w-full h-full"/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -572,9 +572,18 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
slowPeriods: indicator.slowPeriods,
|
||||
signalPeriods: indicator.signalPeriods,
|
||||
multiplier: indicator.multiplier,
|
||||
stDev: indicator.stDev,
|
||||
smoothPeriods: indicator.smoothPeriods,
|
||||
stochPeriods: indicator.stochPeriods,
|
||||
cyclePeriods: indicator.cyclePeriods,
|
||||
kFactor: indicator.kFactor,
|
||||
dFactor: indicator.dFactor,
|
||||
tenkanPeriods: indicator.tenkanPeriods,
|
||||
kijunPeriods: indicator.kijunPeriods,
|
||||
senkouBPeriods: indicator.senkouBPeriods,
|
||||
offsetPeriods: indicator.offsetPeriods,
|
||||
senkouOffset: indicator.senkouOffset,
|
||||
chikouOffset: indicator.chikouOffset,
|
||||
})).filter(indicator => indicator.type) || [] // Only filter out indicators without type
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4960,6 +4960,7 @@ export interface LightIndicator {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
@@ -4991,6 +4992,7 @@ export enum IndicatorType {
|
||||
SuperTrendCrossEma = "SuperTrendCrossEma",
|
||||
DualEmaCross = "DualEmaCross",
|
||||
BollingerBandsPercentBMomentumBreakout = "BollingerBandsPercentBMomentumBreakout",
|
||||
BollingerBandsVolatilityProtection = "BollingerBandsVolatilityProtection",
|
||||
IchimokuKumoTrend = "IchimokuKumoTrend",
|
||||
}
|
||||
|
||||
@@ -5263,9 +5265,18 @@ export interface IndicatorRequest {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
kFactor?: number | null;
|
||||
dFactor?: number | null;
|
||||
tenkanPeriods?: number | null;
|
||||
kijunPeriods?: number | null;
|
||||
senkouBPeriods?: number | null;
|
||||
offsetPeriods?: number | null;
|
||||
senkouOffset?: number | null;
|
||||
chikouOffset?: number | null;
|
||||
}
|
||||
|
||||
export interface MoneyManagementRequest {
|
||||
@@ -5577,6 +5588,7 @@ export interface IndicatorBase {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
|
||||
@@ -426,6 +426,7 @@ export interface LightIndicator {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
@@ -457,6 +458,7 @@ export enum IndicatorType {
|
||||
SuperTrendCrossEma = "SuperTrendCrossEma",
|
||||
DualEmaCross = "DualEmaCross",
|
||||
BollingerBandsPercentBMomentumBreakout = "BollingerBandsPercentBMomentumBreakout",
|
||||
BollingerBandsVolatilityProtection = "BollingerBandsVolatilityProtection",
|
||||
IchimokuKumoTrend = "IchimokuKumoTrend",
|
||||
}
|
||||
|
||||
@@ -729,9 +731,18 @@ export interface IndicatorRequest {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
kFactor?: number | null;
|
||||
dFactor?: number | null;
|
||||
tenkanPeriods?: number | null;
|
||||
kijunPeriods?: number | null;
|
||||
senkouBPeriods?: number | null;
|
||||
offsetPeriods?: number | null;
|
||||
senkouOffset?: number | null;
|
||||
chikouOffset?: number | null;
|
||||
}
|
||||
|
||||
export interface MoneyManagementRequest {
|
||||
@@ -1043,6 +1054,7 @@ export interface IndicatorBase {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
|
||||
@@ -231,9 +231,18 @@ const BundleRequestModal: React.FC<BundleRequestModalProps> = ({
|
||||
slowPeriods: indicator.slowPeriods || 26,
|
||||
signalPeriods: indicator.signalPeriods || 9,
|
||||
multiplier: indicator.multiplier || 3.0,
|
||||
stDev: indicator.stDev || 2.0,
|
||||
stochPeriods: indicator.stochPeriods || 14,
|
||||
smoothPeriods: indicator.smoothPeriods || 3,
|
||||
cyclePeriods: indicator.cyclePeriods || 10
|
||||
cyclePeriods: indicator.cyclePeriods || 10,
|
||||
kFactor: indicator.kFactor || 3.0,
|
||||
dFactor: indicator.dFactor || 3.0,
|
||||
tenkanPeriods: indicator.tenkanPeriods || 9,
|
||||
kijunPeriods: indicator.kijunPeriods || 26,
|
||||
senkouBPeriods: indicator.senkouBPeriods || 52,
|
||||
offsetPeriods: indicator.offsetPeriods || 26,
|
||||
senkouOffset: indicator.senkouOffset || 26,
|
||||
chikouOffset: indicator.chikouOffset || 26
|
||||
})),
|
||||
loopbackPeriod: scenario.loopbackPeriod || 1
|
||||
} : undefined,
|
||||
|
||||
Reference in New Issue
Block a user