diff --git a/src/Managing.Application/GeneticService.cs b/src/Managing.Application/GeneticService.cs index 8595b335..dc5e2be5 100644 --- a/src/Managing.Application/GeneticService.cs +++ b/src/Managing.Application/GeneticService.cs @@ -108,6 +108,13 @@ public class GeneticService : IGeneticService { ["period"] = 20.0, ["multiplier"] = 2.0 + }, + [IndicatorType.IchimokuKumoTrend] = new() + { + ["tenkanPeriods"] = 9.0, + ["kijunPeriods"] = 26.0, + ["senkouBPeriods"] = 52.0, + ["offsetPeriods"] = 26.0 } }; @@ -196,6 +203,13 @@ public class GeneticService : IGeneticService { ["period"] = (5.0, 50.0), ["multiplier"] = (1.0, 5.0) + }, + [IndicatorType.IchimokuKumoTrend] = new() + { + ["tenkanPeriods"] = (5.0, 20.0), + ["kijunPeriods"] = (10.0, 40.0), + ["senkouBPeriods"] = (20.0, 80.0), + ["offsetPeriods"] = (10.0, 50.0) } }; @@ -217,7 +231,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", "multiplier"], + [IndicatorType.IchimokuKumoTrend] = ["tenkanPeriods", "kijunPeriods", "senkouBPeriods", "offsetPeriods"] }; public GeneticService( diff --git a/src/Managing.Common/Enums.cs b/src/Managing.Common/Enums.cs index 69eb4bf7..0058d88d 100644 --- a/src/Managing.Common/Enums.cs +++ b/src/Managing.Common/Enums.cs @@ -66,7 +66,8 @@ public static class Enums LaggingStc, SuperTrendCrossEma, DualEmaCross, - BollingerBandsPercentBMomentumBreakout + BollingerBandsPercentBMomentumBreakout, + IchimokuKumoTrend } public enum SignalType diff --git a/src/Managing.Domain.IndicatorTests/Managing.Domain.IndicatorTests.csproj b/src/Managing.Domain.IndicatorTests/Managing.Domain.IndicatorTests.csproj index f3a3eb45..fd79991e 100644 --- a/src/Managing.Domain.IndicatorTests/Managing.Domain.IndicatorTests.csproj +++ b/src/Managing.Domain.IndicatorTests/Managing.Domain.IndicatorTests.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Managing.Domain/Indicators/Base/IndicatorsResultBase.cs b/src/Managing.Domain/Indicators/Base/IndicatorsResultBase.cs index 6d7011f5..4057b4c5 100644 --- a/src/Managing.Domain/Indicators/Base/IndicatorsResultBase.cs +++ b/src/Managing.Domain/Indicators/Base/IndicatorsResultBase.cs @@ -17,4 +17,5 @@ public class IndicatorsResultBase public List StdDev { get; set; } public List SuperTrend { get; set; } public List ChandelierLong { get; set; } + public List Ichimoku { get; set; } } \ No newline at end of file diff --git a/src/Managing.Domain/Indicators/IndicatorBase.cs b/src/Managing.Domain/Indicators/IndicatorBase.cs index 1e95c081..5d34db52 100644 --- a/src/Managing.Domain/Indicators/IndicatorBase.cs +++ b/src/Managing.Domain/Indicators/IndicatorBase.cs @@ -43,6 +43,14 @@ namespace Managing.Domain.Strategies public double? DFactor { get; set; } + // Ichimoku-specific parameters + public int? TenkanPeriods { get; set; } + public int? KijunPeriods { get; set; } + public int? SenkouBPeriods { get; set; } + public int? OffsetPeriods { get; set; } + public int? SenkouOffset { get; set; } + public int? ChikouOffset { get; set; } + public User User { get; set; } public virtual List Run(HashSet candles) diff --git a/src/Managing.Domain/Indicators/LightIndicator.cs b/src/Managing.Domain/Indicators/LightIndicator.cs index cd52e1ff..d1794bd1 100644 --- a/src/Managing.Domain/Indicators/LightIndicator.cs +++ b/src/Managing.Domain/Indicators/LightIndicator.cs @@ -46,6 +46,14 @@ public class LightIndicator [Id(13)] 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; } + /// /// Converts a LightIndicator back to a full Indicator /// @@ -70,7 +78,13 @@ public class LightIndicator StochPeriods = StochPeriods, CyclePeriods = CyclePeriods, KFactor = KFactor, - DFactor = DFactor + DFactor = DFactor, + TenkanPeriods = TenkanPeriods, + KijunPeriods = KijunPeriods, + SenkouBPeriods = SenkouBPeriods, + OffsetPeriods = OffsetPeriods, + SenkouOffset = SenkouOffset, + ChikouOffset = ChikouOffset }; return baseIndicator; diff --git a/src/Managing.Domain/Indicators/Trends/IchimokuKumoTrend.cs b/src/Managing.Domain/Indicators/Trends/IchimokuKumoTrend.cs new file mode 100644 index 00000000..e04883de --- /dev/null +++ b/src/Managing.Domain/Indicators/Trends/IchimokuKumoTrend.cs @@ -0,0 +1,291 @@ +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.Trends; + +public class IchimokuKumoTrend : IndicatorBase +{ + public List Signals { get; set; } + + public IchimokuKumoTrend( + string name, + int tenkanPeriods = 9, + int kijunPeriods = 26, + int senkouBPeriods = 52, + int offsetPeriods = 26, + int? senkouOffset = null, + int? chikouOffset = null) : base(name, IndicatorType.IchimokuKumoTrend) + { + Signals = new List(); + TenkanPeriods = tenkanPeriods; // Tenkan-sen periods + KijunPeriods = kijunPeriods; // Kijun-sen periods + SenkouBPeriods = senkouBPeriods; // Senkou Span B periods + OffsetPeriods = offsetPeriods; // Default offset periods + SenkouOffset = senkouOffset; // Separate offset for Senkou span + ChikouOffset = chikouOffset; // Separate offset for Chikou span + } + + public override List Run(HashSet candles) + { + // Need at least the greater of tenkanPeriods, kijunPeriods, senkouBPeriods, and all offset periods + var maxOffset = Math.Max(Math.Max(OffsetPeriods.Value, SenkouOffset ?? OffsetPeriods.Value), ChikouOffset ?? OffsetPeriods.Value); + var minRequired = Math.Max(Math.Max(Math.Max(TenkanPeriods.Value, KijunPeriods.Value), SenkouBPeriods.Value), maxOffset); + if (candles.Count <= minRequired) + { + return null; + } + + try + { + var ichimokuResults = CalculateIchimoku(candles.ToList()); + if (ichimokuResults.Count == 0) + return null; + + ProcessKumoTrendSignals(ichimokuResults, candles); + + return Signals.ToList(); + } + catch (RuleException) + { + return null; + } + } + + public override List Run(HashSet candles, IndicatorsResultBase preCalculatedValues) + { + // Need at least the greater of tenkanPeriods, kijunPeriods, senkouBPeriods, and all offset periods + var maxOffset = Math.Max(Math.Max(OffsetPeriods.Value, SenkouOffset ?? OffsetPeriods.Value), ChikouOffset ?? OffsetPeriods.Value); + var minRequired = Math.Max(Math.Max(Math.Max(TenkanPeriods.Value, KijunPeriods.Value), SenkouBPeriods.Value), maxOffset); + if (candles.Count <= minRequired) + { + return null; + } + + try + { + List ichimokuResults = null; + + // Use pre-calculated Ichimoku values if available + if (preCalculatedValues?.Ichimoku != null && preCalculatedValues.Ichimoku.Any()) + { + // Filter pre-calculated Ichimoku values to match the candles we're processing + ichimokuResults = preCalculatedValues.Ichimoku + .Where(i => i.SenkouSpanA.HasValue && i.SenkouSpanB.HasValue && candles.Any(c => c.Date == i.Date)) + .ToList(); + } + + // If no pre-calculated values or they don't match, fall back to regular calculation + if (ichimokuResults == null || !ichimokuResults.Any()) + { + return Run(candles); + } + + ProcessKumoTrendSignalsFromResults(ichimokuResults, candles); + + return Signals.ToList(); + } + catch (RuleException) + { + return null; + } + } + + private List CalculateIchimoku(List candles) + { + // Use Skender.Stock.Indicators GetIchimoku method with all supported parameters + IEnumerable ichimokuResults; + + // Use the appropriate overload based on which parameters are specified + if (SenkouOffset.HasValue && ChikouOffset.HasValue) + { + // Use separate offsets for Senkou and Chikou spans + ichimokuResults = candles.GetIchimoku( + tenkanPeriods: TenkanPeriods.Value, + kijunPeriods: KijunPeriods.Value, + senkouBPeriods: SenkouBPeriods.Value, + senkouOffset: SenkouOffset.Value, + chikouOffset: ChikouOffset.Value + ); + } + else + { + // Use default offsetPeriods for both Senkou and Chikou spans + ichimokuResults = candles.GetIchimoku( + tenkanPeriods: TenkanPeriods.Value, + kijunPeriods: KijunPeriods.Value, + senkouBPeriods: SenkouBPeriods.Value, + offsetPeriods: OffsetPeriods.Value + ); + } + + var ichimokuList = ichimokuResults.ToList(); + + var candleIchimokuResults = new List(); + + // Map IchimokuResult to CandleIchimoku + foreach (var ichimoku in ichimokuList) + { + // Find the corresponding candle + var candle = candles.FirstOrDefault(c => c.Date == ichimoku.Date); + if (candle == null) continue; + + candleIchimokuResults.Add(new CandleIchimoku() + { + Close = candle.Close, + Open = candle.Open, + Date = candle.Date, + Ticker = candle.Ticker, + Exchange = candle.Exchange, + TenkanSen = ichimoku.TenkanSen ?? 0, + KijunSen = ichimoku.KijunSen ?? 0, + SenkouSpanA = ichimoku.SenkouSpanA ?? 0, + SenkouSpanB = ichimoku.SenkouSpanB ?? 0 + }); + } + + return candleIchimokuResults; + } + + private void ProcessKumoTrendSignals(List ichimokuResults, HashSet candles) + { + var mappedData = ichimokuResults; + + if (mappedData.Count == 0) + return; + + var previousCandle = mappedData[0]; + foreach (var currentCandle in mappedData.Skip(1)) + { + // For trend assessment, check if price is above or below the cloud + // The cloud is formed by Senkou Span A and Senkou Span B + var cloudTop = Math.Max(currentCandle.SenkouSpanA, currentCandle.SenkouSpanB); + var cloudBottom = Math.Min(currentCandle.SenkouSpanA, currentCandle.SenkouSpanB); + + if (currentCandle.Close > cloudTop) + { + AddSignal(currentCandle, TradeDirection.Long, Confidence.None); + } + else if (currentCandle.Close < cloudBottom) + { + AddSignal(currentCandle, TradeDirection.Short, Confidence.None); + } + // If price is within the cloud, no signal (neutral) + + previousCandle = currentCandle; + } + } + + private void ProcessKumoTrendSignalsFromResults(List ichimokuResults, HashSet candles) + { + if (ichimokuResults.Count == 0) + return; + + var previousResult = ichimokuResults[0]; + foreach (var currentResult in ichimokuResults.Skip(1)) + { + // Find the corresponding candle + var candle = candles.FirstOrDefault(c => c.Date == currentResult.Date); + if (candle == null || !currentResult.SenkouSpanA.HasValue || !currentResult.SenkouSpanB.HasValue) + continue; + + // For trend assessment, check if price is above or below the cloud + // The cloud is formed by Senkou Span A and Senkou Span B + var cloudTop = Math.Max(currentResult.SenkouSpanA.Value, currentResult.SenkouSpanB.Value); + var cloudBottom = Math.Min(currentResult.SenkouSpanA.Value, currentResult.SenkouSpanB.Value); + + if (candle.Close > cloudTop) + { + AddSignal(candle, TradeDirection.Long, Confidence.None); + } + else if (candle.Close < cloudBottom) + { + AddSignal(candle, TradeDirection.Short, Confidence.None); + } + // If price is within the cloud, no signal (neutral) + + previousResult = currentResult; + } + } + + public override IndicatorsResultBase GetIndicatorValues(HashSet candles) + { + IEnumerable ichimokuResults; + + // Use the appropriate overload based on which parameters are specified + if (SenkouOffset.HasValue && ChikouOffset.HasValue) + { + // Use separate offsets for Senkou and Chikou spans + ichimokuResults = candles.GetIchimoku( + tenkanPeriods: TenkanPeriods.Value, + kijunPeriods: KijunPeriods.Value, + senkouBPeriods: SenkouBPeriods.Value, + senkouOffset: SenkouOffset.Value, + chikouOffset: ChikouOffset.Value + ); + } + else + { + // Use default offsetPeriods for both Senkou and Chikou spans + ichimokuResults = candles.GetIchimoku( + tenkanPeriods: TenkanPeriods.Value, + kijunPeriods: KijunPeriods.Value, + senkouBPeriods: SenkouBPeriods.Value, + offsetPeriods: OffsetPeriods.Value + ); + } + + return new IndicatorsResultBase() + { + Ichimoku = ichimokuResults.ToList() + }; + } + + private void AddSignal(CandleIchimoku 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 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); + } + } + + private class CandleIchimoku : Candle + { + public decimal TenkanSen { get; set; } + public decimal KijunSen { get; set; } + public decimal SenkouSpanA { get; set; } + public decimal SenkouSpanB { get; set; } + } +} diff --git a/src/Managing.Domain/Scenarios/ScenarioHelpers.cs b/src/Managing.Domain/Scenarios/ScenarioHelpers.cs index 7257efc9..216d9567 100644 --- a/src/Managing.Domain/Scenarios/ScenarioHelpers.cs +++ b/src/Managing.Domain/Scenarios/ScenarioHelpers.cs @@ -102,6 +102,13 @@ public static class ScenarioHelpers indicator.Period.Value, indicator.Multiplier.Value), IndicatorType.BollingerBandsPercentBMomentumBreakout => new BollingerBandsPercentBMomentumBreakout(indicator.Name, indicator.Period.Value, indicator.Multiplier.Value), + IndicatorType.IchimokuKumoTrend => new IchimokuKumoTrend(indicator.Name, + indicator.TenkanPeriods ?? 9, + indicator.KijunPeriods ?? 26, + indicator.SenkouBPeriods ?? 52, + indicator.OffsetPeriods ?? 26, + indicator.SenkouOffset, + indicator.ChikouOffset), _ => throw new NotImplementedException(), }; @@ -292,6 +299,7 @@ public static class ScenarioHelpers IndicatorType.LaggingStc => SignalType.Signal, IndicatorType.SuperTrendCrossEma => SignalType.Signal, IndicatorType.BollingerBandsPercentBMomentumBreakout => SignalType.Signal, + IndicatorType.IchimokuKumoTrend => SignalType.Trend, _ => throw new NotImplementedException(), }; } diff --git a/src/Managing.WebApp/src/components/organism/CustomScenario/CustomScenario.tsx b/src/Managing.WebApp/src/components/organism/CustomScenario/CustomScenario.tsx index 940a494f..36e82086 100644 --- a/src/Managing.WebApp/src/components/organism/CustomScenario/CustomScenario.tsx +++ b/src/Managing.WebApp/src/components/organism/CustomScenario/CustomScenario.tsx @@ -63,7 +63,11 @@ const CustomScenario: React.FC = ({ case IndicatorType.LaggingStc: params = ['cyclePeriods', 'fastPeriods', 'slowPeriods']; break; - + + case IndicatorType.IchimokuKumoTrend: + params = ['tenkanPeriods', 'kijunPeriods', 'senkouBPeriods', 'offsetPeriods']; + break; + case IndicatorType.Composite: params = []; // Composite might not need specific parameters break; @@ -123,6 +127,9 @@ const CustomScenario: React.FC = ({ case IndicatorType.Composite: label = 'Composite'; break; + case IndicatorType.IchimokuKumoTrend: + label = 'Ichimoku Kumo Trend'; + break; default: label = type; break; @@ -142,7 +149,13 @@ const CustomScenario: React.FC = ({ multiplier: 3.0, stochPeriods: 14, smoothPeriods: 3, - cyclePeriods: 10 + cyclePeriods: 10, + tenkanPeriods: 9, + kijunPeriods: 26, + senkouBPeriods: 52, + offsetPeriods: 26, + senkouOffset: 26, + chikouOffset: 26 } setIndicators([...indicators, newIndicator]) } diff --git a/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx b/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx index 782fed73..b71ef52b 100644 --- a/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx +++ b/src/Managing.WebApp/src/components/organism/Trading/TradeChart/TradeChart.tsx @@ -14,14 +14,15 @@ import moment from 'moment' import * as React from 'react' import {useEffect, useRef, useState} from 'react' -import type { +import { Candle, IndicatorsResultBase, IndicatorType, LightSignal, Position, + PositionStatus, + TradeDirection, } from '../../../../generated/ManagingApi' -import {PositionStatus, TradeDirection,} from '../../../../generated/ManagingApi' import useTheme from '../../../../hooks/useTheme' // var customTheme = { @@ -485,6 +486,47 @@ const TradeChart = ({ chandelierExitsShortsSeries.setData(chandelierExitsShorts) } + // Display Ichimoku Cloud (Kumo) + if (indicatorsValues?.[IndicatorType.IchimokuKumoTrend]?.ichimoku != null) { + const senkouSpanASeries = chart.current.addLineSeries({ + color: theme.secondary, + lineWidth: 1, + priceLineVisible: false, + priceLineWidth: 1, + title: 'Senkou Span A', + pane: 0, + lineStyle: LineStyle.Solid, + }) + + const senkouSpanAData = indicatorsValues[IndicatorType.IchimokuKumoTrend].ichimoku?.map((w) => { + return { + time: moment(w.date).unix(), + value: w.senkouSpanA, + } + }) + // @ts-ignore + senkouSpanASeries.setData(senkouSpanAData) + + const senkouSpanBSeries = chart.current.addLineSeries({ + color: theme.accent, + lineWidth: 1, + priceLineVisible: false, + priceLineWidth: 1, + title: 'Senkou Span B', + pane: 0, + lineStyle: LineStyle.Solid, + }) + + const senkouSpanBData = indicatorsValues[IndicatorType.IchimokuKumoTrend].ichimoku?.map((w) => { + return { + time: moment(w.date).unix(), + value: w.senkouSpanB, + } + }) + // @ts-ignore + senkouSpanBSeries.setData(senkouSpanBData) + } + // Display Bollinger Bands on price chart if (indicatorsValues?.BollingerBandsPercentBMomentumBreakout != null) { const upperBandSeries = chart.current.addLineSeries({ diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index abd48d1d..650f5c51 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -4965,6 +4965,12 @@ export interface LightIndicator { 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 enum IndicatorType { @@ -4985,6 +4991,7 @@ export enum IndicatorType { SuperTrendCrossEma = "SuperTrendCrossEma", DualEmaCross = "DualEmaCross", BollingerBandsPercentBMomentumBreakout = "BollingerBandsPercentBMomentumBreakout", + IchimokuKumoTrend = "IchimokuKumoTrend", } export enum SignalType { @@ -5575,6 +5582,12 @@ export interface IndicatorBase { 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; user?: User | null; } @@ -5606,6 +5619,7 @@ export interface IndicatorsResultBase { stdDev?: StdDevResult[] | null; superTrend?: SuperTrendResult[] | null; chandelierLong?: ChandelierResult[] | null; + ichimoku?: IchimokuResult[] | null; } export interface ResultBase { @@ -5682,6 +5696,14 @@ export interface SuperTrendResult extends ResultBase { lowerBand?: number | null; } +export interface IchimokuResult extends ResultBase { + tenkanSen?: number | null; + kijunSen?: number | null; + senkouSpanA?: number | null; + senkouSpanB?: number | null; + chikouSpan?: number | null; +} + export interface GetCandlesWithIndicatorsRequest { ticker?: Ticker; startDate?: Date; diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index e6926e2e..9fd75e92 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -431,6 +431,12 @@ export interface LightIndicator { 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 enum IndicatorType { @@ -451,6 +457,7 @@ export enum IndicatorType { SuperTrendCrossEma = "SuperTrendCrossEma", DualEmaCross = "DualEmaCross", BollingerBandsPercentBMomentumBreakout = "BollingerBandsPercentBMomentumBreakout", + IchimokuKumoTrend = "IchimokuKumoTrend", } export enum SignalType { @@ -1041,6 +1048,12 @@ export interface IndicatorBase { 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; user?: User | null; } @@ -1072,6 +1085,7 @@ export interface IndicatorsResultBase { stdDev?: StdDevResult[] | null; superTrend?: SuperTrendResult[] | null; chandelierLong?: ChandelierResult[] | null; + ichimoku?: IchimokuResult[] | null; } export interface ResultBase { @@ -1148,6 +1162,14 @@ export interface SuperTrendResult extends ResultBase { lowerBand?: number | null; } +export interface IchimokuResult extends ResultBase { + tenkanSen?: number | null; + kijunSen?: number | null; + senkouSpanA?: number | null; + senkouSpanB?: number | null; + chikouSpan?: number | null; +} + export interface GetCandlesWithIndicatorsRequest { ticker?: Ticker; startDate?: Date; diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx index 4a8dd326..d4f11f90 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx @@ -105,6 +105,7 @@ const ALL_INDICATORS = [ IndicatorType.DualEmaCross, IndicatorType.StochasticCross, IndicatorType.BollingerBandsPercentBMomentumBreakout, + IndicatorType.IchimokuKumoTrend, ] // Indicator type to parameter mapping @@ -125,6 +126,7 @@ const INDICATOR_PARAM_MAPPING = { [IndicatorType.StochasticCross]: ['stochPeriods', 'signalPeriods', 'smoothPeriods', 'kFactor', 'dFactor'], [IndicatorType.Stc]: ['cyclePeriods', 'fastPeriods', 'slowPeriods'], [IndicatorType.LaggingStc]: ['cyclePeriods', 'fastPeriods', 'slowPeriods'], + [IndicatorType.IchimokuKumoTrend]: ['tenkanPeriods', 'kijunPeriods', 'senkouBPeriods', 'offsetPeriods', 'senkouOffset', 'chikouOffset'], } // ============================================================================ diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtestGeneticBundle.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtestGeneticBundle.tsx index b3876684..45c4d402 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/backtestGeneticBundle.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/backtestGeneticBundle.tsx @@ -44,6 +44,7 @@ const ALL_INDICATORS = [ IndicatorType.DualEmaCross, IndicatorType.StochasticCross, IndicatorType.BollingerBandsPercentBMomentumBreakout, + IndicatorType.IchimokuKumoTrend, ] // Form Interface diff --git a/src/Managing.WebApp/src/pages/scenarioPage/indicatorList.tsx b/src/Managing.WebApp/src/pages/scenarioPage/indicatorList.tsx index 324251b1..c0600ddb 100644 --- a/src/Managing.WebApp/src/pages/scenarioPage/indicatorList.tsx +++ b/src/Managing.WebApp/src/pages/scenarioPage/indicatorList.tsx @@ -22,6 +22,12 @@ interface IIndicatorFormInput { stochPeriods: number smoothPeriods: number cyclePeriods: number + tenkanPeriods: number + kijunPeriods: number + senkouBPeriods: number + offsetPeriods: number + senkouOffset: number + chikouOffset: number } const IndicatorList: React.FC = () => { @@ -30,7 +36,16 @@ const IndicatorList: React.FC = () => { ) const [indicators, setIndicators] = useState([]) const [showModal, setShowModal] = useState(false) - const { register, handleSubmit } = useForm() + const { register, handleSubmit } = useForm({ + defaultValues: { + tenkanPeriods: 9, + kijunPeriods: 26, + senkouBPeriods: 52, + offsetPeriods: 26, + senkouOffset: 26, + chikouOffset: 26 + } + }) const { apiUrl } = useApiUrlStore() const scenarioClient = new ScenarioClient({}, apiUrl) @@ -47,7 +62,13 @@ const IndicatorList: React.FC = () => { form.multiplier, form.stochPeriods, form.smoothPeriods, - form.cyclePeriods + form.cyclePeriods, + form.tenkanPeriods, + form.kijunPeriods, + form.senkouBPeriods, + form.offsetPeriods, + form.senkouOffset, + form.chikouOffset ) .then((indicator: IndicatorViewModel) => { t.update('success', 'Indicator created') @@ -497,6 +518,102 @@ const IndicatorList: React.FC = () => { ) : null} + + {indicatorType == IndicatorType.IchimokuKumoTrend ? ( + <> +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + ) : null} +
) : null} + + {indicatorType == IndicatorType.IchimokuKumoTrend ? ( + <> +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + ) : null} +