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:
@@ -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,23 +68,23 @@ 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
|
||||
const calculatedWidth = containerWidth > 0 ? containerWidth : 510
|
||||
|
||||
|
||||
// Use different aspect ratios for different screen sizes
|
||||
let aspectRatio = 0.6 // Default ratio (height/width)
|
||||
if (containerWidth < 768) { // Mobile
|
||||
@@ -93,9 +92,9 @@ const TradeChart = ({
|
||||
} else if (containerWidth < 1024) { // Tablet
|
||||
aspectRatio = 0.65
|
||||
}
|
||||
|
||||
|
||||
const calculatedHeight = height || Math.max(250, calculatedWidth * aspectRatio)
|
||||
|
||||
|
||||
return {
|
||||
width: calculatedWidth,
|
||||
height: calculatedHeight
|
||||
@@ -109,7 +108,7 @@ const TradeChart = ({
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
const newDimensions = getResponsiveDimensions()
|
||||
setChartDimensions(newDimensions)
|
||||
|
||||
|
||||
if (chart.current) {
|
||||
chart.current.applyOptions({
|
||||
width: newDimensions.width,
|
||||
@@ -131,7 +130,7 @@ const TradeChart = ({
|
||||
setTimeout(() => {
|
||||
const newDimensions = getResponsiveDimensions()
|
||||
setChartDimensions(newDimensions)
|
||||
|
||||
|
||||
if (chart.current) {
|
||||
chart.current.applyOptions({
|
||||
width: newDimensions.width,
|
||||
@@ -231,7 +230,7 @@ const TradeChart = ({
|
||||
} else {
|
||||
color = negativeColor
|
||||
}
|
||||
}else if (status == PositionStatus.Filled) {
|
||||
} else if (status == PositionStatus.Filled) {
|
||||
color = theme.warning
|
||||
}
|
||||
}
|
||||
@@ -243,7 +242,7 @@ const TradeChart = ({
|
||||
if (chartRef.current && containerRef.current) {
|
||||
const initialDimensions = getResponsiveDimensions()
|
||||
setChartDimensions(initialDimensions)
|
||||
|
||||
|
||||
const lineColor = theme['base-100']
|
||||
chart.current = createChart(chartRef.current, {
|
||||
crosshair: {
|
||||
@@ -349,13 +348,13 @@ const TradeChart = ({
|
||||
// Get the time range of candles
|
||||
const firstCandleTime = moment(candles[0].date).unix()
|
||||
const lastCandleTime = moment(candles[candles.length - 1].date).unix()
|
||||
|
||||
|
||||
// Filter signals that are within the candle range
|
||||
const filteredSignals = signals.filter((s) => {
|
||||
const signalTime = moment(s.date).unix()
|
||||
return signalTime >= firstCandleTime && signalTime <= lastCandleTime
|
||||
})
|
||||
|
||||
|
||||
const signalMarkers = filteredSignals.map((s) =>
|
||||
buildMarker(
|
||||
'circle',
|
||||
@@ -393,9 +392,8 @@ const TradeChart = ({
|
||||
}
|
||||
|
||||
// Price panel
|
||||
if (indicatorsValues?.EmaTrend != null || indicatorsValues?.EmaCross != null)
|
||||
{
|
||||
const emaSeries = chart.current.addLineSeries({
|
||||
if (indicatorsValues?.EmaTrend != null || indicatorsValues?.EmaCross != null) {
|
||||
const emaSeries = chart.current.addLineSeries({
|
||||
color: theme.secondary,
|
||||
lineWidth: 1,
|
||||
priceLineVisible: true,
|
||||
@@ -417,8 +415,7 @@ const TradeChart = ({
|
||||
}
|
||||
})
|
||||
|
||||
if (emaData != null)
|
||||
{
|
||||
if (emaData != null) {
|
||||
// @ts-ignore
|
||||
emaSeries.setData(emaData)
|
||||
}
|
||||
@@ -446,8 +443,8 @@ const TradeChart = ({
|
||||
superTrendSeries.setData(superTrend)
|
||||
}
|
||||
|
||||
// Display chandeliers exits
|
||||
if (indicatorsValues?.ChandelierExit != null) {
|
||||
// Display chandeliers exits
|
||||
if (indicatorsValues?.ChandelierExit != null) {
|
||||
const chandelierExitsLongsSeries = chart.current.addLineSeries({
|
||||
color: theme.info,
|
||||
lineWidth: 1,
|
||||
@@ -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)
|
||||
}
|
||||
@@ -575,9 +613,8 @@ const TradeChart = ({
|
||||
// Indicator panel
|
||||
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',
|
||||
@@ -608,7 +645,7 @@ const TradeChart = ({
|
||||
const stcSeries = chart.current.addBaselineSeries({
|
||||
pane: paneCount,
|
||||
baseValue: {price: 50, type: 'price'},
|
||||
|
||||
|
||||
title: 'STC',
|
||||
})
|
||||
|
||||
@@ -641,7 +678,7 @@ const TradeChart = ({
|
||||
const laggingStcSeries = chart.current.addBaselineSeries({
|
||||
pane: paneCount,
|
||||
baseValue: {price: 50, type: 'price'},
|
||||
|
||||
|
||||
title: 'Lagging STC',
|
||||
})
|
||||
|
||||
@@ -690,7 +727,7 @@ const TradeChart = ({
|
||||
})
|
||||
|
||||
var priceOptions = {
|
||||
scaleMargins:{
|
||||
scaleMargins: {
|
||||
top: 0.7,
|
||||
bottom: 0.02,
|
||||
}
|
||||
@@ -722,7 +759,7 @@ const TradeChart = ({
|
||||
|
||||
macdSeries.priceScale().applyOptions(priceOptions)
|
||||
// @ts-ignore
|
||||
macdSeries.setData(macdData)
|
||||
macdSeries.setData(macdData)
|
||||
|
||||
const signalSeries = chart.current.addLineSeries({
|
||||
color: theme.info,
|
||||
@@ -747,11 +784,11 @@ const TradeChart = ({
|
||||
signalSeries.priceScale().applyOptions(priceOptions)
|
||||
// @ts-ignore
|
||||
signalSeries.setData(signalData)
|
||||
|
||||
|
||||
paneCount++
|
||||
}
|
||||
|
||||
if (indicatorsValues?.StochRsiTrend){
|
||||
if (indicatorsValues?.StochRsiTrend) {
|
||||
const stochRsiSeries = chart.current.addLineSeries({
|
||||
...baselineOptions,
|
||||
priceLineVisible: false,
|
||||
@@ -859,7 +896,7 @@ const TradeChart = ({
|
||||
paneCount++
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Display dual EMA crossover
|
||||
if (indicatorsValues?.DualEmaCross != null) {
|
||||
const fastEmaSeries = chart.current.addLineSeries({
|
||||
@@ -960,12 +997,12 @@ const TradeChart = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
<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