@@ -153,7 +153,7 @@ namespace Managing.Application.Backtesting
|
|||||||
}
|
}
|
||||||
|
|
||||||
bot.Candles = new HashSet<Candle>(candles);
|
bot.Candles = new HashSet<Candle>(candles);
|
||||||
// bot.UpdateStrategiesValues();
|
bot.UpdateStrategiesValues();
|
||||||
|
|
||||||
var strategies = _scenarioService.GetStrategies();
|
var strategies = _scenarioService.GetStrategies();
|
||||||
var strategiesValues = GetStrategiesValues(strategies, candles);
|
var strategiesValues = GetStrategiesValues(strategies, candles);
|
||||||
@@ -176,12 +176,36 @@ namespace Managing.Application.Backtesting
|
|||||||
Statistics = stats,
|
Statistics = stats,
|
||||||
OptimizedMoneyManagement = optimizedMoneyManagement,
|
OptimizedMoneyManagement = optimizedMoneyManagement,
|
||||||
MoneyManagement = moneyManagement,
|
MoneyManagement = moneyManagement,
|
||||||
StrategiesValues = strategiesValues
|
StrategiesValues = AggregateValues(strategiesValues, bot.StrategiesValues)
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Dictionary<StrategyType, StrategiesResultBase> AggregateValues(
|
||||||
|
Dictionary<StrategyType, StrategiesResultBase> strategiesValues,
|
||||||
|
Dictionary<StrategyType, StrategiesResultBase> botStrategiesValues)
|
||||||
|
{
|
||||||
|
// Foreach strategy type, only retrieve the values where the strategy is not present already in the bot
|
||||||
|
// Then, add the values to the bot values
|
||||||
|
|
||||||
|
var result = new Dictionary<StrategyType, StrategiesResultBase>();
|
||||||
|
foreach (var strategy in strategiesValues)
|
||||||
|
{
|
||||||
|
// if (!botStrategiesValues.ContainsKey(strategy.Key))
|
||||||
|
// {
|
||||||
|
// result[strategy.Key] = strategy.Value;
|
||||||
|
// }else
|
||||||
|
// {
|
||||||
|
// result[strategy.Key] = botStrategiesValues[strategy.Key];
|
||||||
|
// }
|
||||||
|
|
||||||
|
result[strategy.Key] = strategy.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private Dictionary<StrategyType, StrategiesResultBase> GetStrategiesValues(IEnumerable<Strategy> strategies,
|
private Dictionary<StrategyType, StrategiesResultBase> GetStrategiesValues(IEnumerable<Strategy> strategies,
|
||||||
List<Candle> candles)
|
List<Candle> candles)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ public static class Enums
|
|||||||
Composite,
|
Composite,
|
||||||
StochRsiTrend,
|
StochRsiTrend,
|
||||||
Stc,
|
Stc,
|
||||||
StDev
|
StDev,
|
||||||
|
LaggingStc,
|
||||||
|
SuperTrendCrossEma
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SignalType
|
public enum SignalType
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ namespace Managing.Domain.Scenarios;
|
|||||||
|
|
||||||
public static class ScenarioHelpers
|
public static class ScenarioHelpers
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public static IEnumerable<IStrategy> GetStrategiesFromScenario(Scenario scenario)
|
public static IEnumerable<IStrategy> GetStrategiesFromScenario(Scenario scenario)
|
||||||
{
|
{
|
||||||
var strategies = new List<IStrategy>();
|
var strategies = new List<IStrategy>();
|
||||||
@@ -45,6 +43,10 @@ public static class ScenarioHelpers
|
|||||||
strategy.SmoothPeriods.Value),
|
strategy.SmoothPeriods.Value),
|
||||||
StrategyType.Stc => new STCStrategy(strategy.Name, strategy.CyclePeriods.Value,
|
StrategyType.Stc => new STCStrategy(strategy.Name, strategy.CyclePeriods.Value,
|
||||||
strategy.FastPeriods.Value, strategy.SlowPeriods.Value),
|
strategy.FastPeriods.Value, strategy.SlowPeriods.Value),
|
||||||
|
StrategyType.LaggingStc => new LaggingSTC(strategy.Name, strategy.CyclePeriods.Value,
|
||||||
|
strategy.FastPeriods.Value, strategy.SlowPeriods.Value),
|
||||||
|
StrategyType.SuperTrendCrossEma => new SuperTrendCrossEma(strategy.Name,
|
||||||
|
strategy.Period.Value, strategy.Multiplier.Value),
|
||||||
_ => throw new NotImplementedException(),
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,6 +103,7 @@ public static class ScenarioHelpers
|
|||||||
case StrategyType.ThreeWhiteSoldiers:
|
case StrategyType.ThreeWhiteSoldiers:
|
||||||
break;
|
break;
|
||||||
case StrategyType.SuperTrend:
|
case StrategyType.SuperTrend:
|
||||||
|
case StrategyType.SuperTrendCrossEma:
|
||||||
case StrategyType.ChandelierExit:
|
case StrategyType.ChandelierExit:
|
||||||
if (!period.HasValue || !multiplier.HasValue)
|
if (!period.HasValue || !multiplier.HasValue)
|
||||||
{
|
{
|
||||||
@@ -132,6 +135,7 @@ public static class ScenarioHelpers
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case StrategyType.Stc:
|
case StrategyType.Stc:
|
||||||
|
case StrategyType.LaggingStc:
|
||||||
if (!fastPeriods.HasValue || !slowPeriods.HasValue || !cyclePeriods.HasValue)
|
if (!fastPeriods.HasValue || !slowPeriods.HasValue || !cyclePeriods.HasValue)
|
||||||
{
|
{
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
@@ -168,6 +172,8 @@ public static class ScenarioHelpers
|
|||||||
StrategyType.StochRsiTrend => SignalType.Trend,
|
StrategyType.StochRsiTrend => SignalType.Trend,
|
||||||
StrategyType.Stc => SignalType.Signal,
|
StrategyType.Stc => SignalType.Signal,
|
||||||
StrategyType.StDev => SignalType.Context,
|
StrategyType.StDev => SignalType.Context,
|
||||||
|
StrategyType.LaggingStc => SignalType.Signal,
|
||||||
|
StrategyType.SuperTrendCrossEma => SignalType.Signal,
|
||||||
_ => throw new NotImplementedException(),
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,27 @@ public static class TradingBox
|
|||||||
|
|
||||||
if (signals == null || signals.Count == 0) continue;
|
if (signals == null || signals.Count == 0) continue;
|
||||||
|
|
||||||
var candleLoopback = limitedCandles.TakeLast(loopbackPeriod ?? 1).ToList();
|
// Ensure limitedCandles is ordered chronologically
|
||||||
|
var orderedCandles = limitedCandles.OrderBy(c => c.Date).ToList();
|
||||||
|
|
||||||
foreach (var signal in signals.Where(s => s.Date >= candleLoopback.FirstOrDefault()?.Date))
|
var loopback = loopbackPeriod ?? 1;
|
||||||
|
var candleLoopback = orderedCandles.TakeLast(loopback).ToList();
|
||||||
|
|
||||||
|
if (!candleLoopback.Any())
|
||||||
{
|
{
|
||||||
if (previousSignal.SingleOrDefault(s => s.Identifier == signal.Identifier) == null)
|
// Handle empty case (e.g., log warning, skip processing)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var loopbackStartDate = candleLoopback.First().Date;
|
||||||
|
|
||||||
|
foreach (var signal in signals.Where(s => s.Date >= loopbackStartDate))
|
||||||
|
{
|
||||||
|
var hasExistingSignal = previousSignal.Any(s => s.Identifier == signal.Identifier);
|
||||||
|
if (!hasExistingSignal)
|
||||||
{
|
{
|
||||||
if (previousSignal.Count == 0 || previousSignal.Last().Date < signal.Date)
|
bool shouldAdd = previousSignal.Count == 0 || previousSignal.Last().Date < signal.Date;
|
||||||
|
if (shouldAdd)
|
||||||
{
|
{
|
||||||
signalOnCandles.Add(signal);
|
signalOnCandles.Add(signal);
|
||||||
}
|
}
|
||||||
|
|||||||
144
src/Managing.Domain/Strategies/LaggingSTC.cs
Normal file
144
src/Managing.Domain/Strategies/LaggingSTC.cs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
using Managing.Core;
|
||||||
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Shared.Rules;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
|
using Skender.Stock.Indicators;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Domain.Strategies;
|
||||||
|
|
||||||
|
///<summary>
|
||||||
|
/// Lagging STC Strategy: Combines Schaff Trend Cycle with volatility-based confirmation.
|
||||||
|
/// Key Features:
|
||||||
|
/// 1. Short signals on STC breakdown from overbught (75+ → ≤75) with recent compressed volatility (min >78)
|
||||||
|
/// 2. Long signals on STC rebound from oversold (25- → ≥25) with recent compressed volatility (max <11)
|
||||||
|
/// 3. Avoids look-ahead bias through proper rolling window implementation
|
||||||
|
/// </summary>
|
||||||
|
public class LaggingSTC : Strategy
|
||||||
|
{
|
||||||
|
public List<Signal> Signals { get; set; }
|
||||||
|
|
||||||
|
public LaggingSTC(string name, int cyclePeriods, int fastPeriods, int slowPeriods) : base(name,
|
||||||
|
StrategyType.LaggingStc)
|
||||||
|
{
|
||||||
|
Signals = new List<Signal>();
|
||||||
|
FastPeriods = fastPeriods;
|
||||||
|
SlowPeriods = slowPeriods;
|
||||||
|
CyclePeriods = cyclePeriods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<Signal> Run()
|
||||||
|
{
|
||||||
|
if (Candles.Count <= 2 * (SlowPeriods + CyclePeriods))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stc = Candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
|
||||||
|
var stcCandles = MapStcToCandle(stc, Candles.TakeLast(CyclePeriods.Value * 3));
|
||||||
|
|
||||||
|
if (stcCandles.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (int i = 1; i < stcCandles.Count; i++)
|
||||||
|
{
|
||||||
|
var currentCandle = stcCandles[i];
|
||||||
|
var previousCandle = stcCandles[i - 1];
|
||||||
|
|
||||||
|
/* VOLATILITY CONFIRMATION WINDOW
|
||||||
|
* - 22-period rolling window (≈1 trading month)
|
||||||
|
* - Ends at previous candle to avoid inclusion of current break
|
||||||
|
* - Dynamic sizing for early dataset cases */
|
||||||
|
// Calculate the lookback window ending at previousCandle (excludes currentCandle)
|
||||||
|
int windowSize = 32;
|
||||||
|
int windowStart = Math.Max(0, i - windowSize); // Ensure no negative indices
|
||||||
|
var lookbackWindow = stcCandles
|
||||||
|
.Skip(windowStart)
|
||||||
|
.Take(i - windowStart) // Take up to previousCandle (i-1)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
double? minStc = lookbackWindow.Min(c => c.Stc);
|
||||||
|
double? maxStc = lookbackWindow.Max(c => c.Stc);
|
||||||
|
|
||||||
|
// Short Signal: Break below 75 with prior min >78
|
||||||
|
if (previousCandle.Stc > 75 && currentCandle.Stc <= 75)
|
||||||
|
{
|
||||||
|
if (minStc > 78)
|
||||||
|
{
|
||||||
|
AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long Signal: Break above 25 with prior max <11
|
||||||
|
if (previousCandle.Stc < 25 && currentCandle.Stc >= 25)
|
||||||
|
{
|
||||||
|
if (maxStc < 11)
|
||||||
|
{
|
||||||
|
AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override StrategiesResultBase GetStrategyValues()
|
||||||
|
{
|
||||||
|
var stc = Candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
|
||||||
|
return new StrategiesResultBase
|
||||||
|
{
|
||||||
|
Stc = stc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CandleSct> MapStcToCandle(List<StcResult> stc, IEnumerable<Candle> candles)
|
||||||
|
{
|
||||||
|
var sctList = new List<CandleSct>();
|
||||||
|
foreach (var candle in candles)
|
||||||
|
{
|
||||||
|
var currentSct = stc.Find(candle.Date);
|
||||||
|
if (currentSct != null)
|
||||||
|
{
|
||||||
|
sctList.Add(new CandleSct()
|
||||||
|
{
|
||||||
|
Close = candle.Close,
|
||||||
|
Open = candle.Open,
|
||||||
|
Date = candle.Date,
|
||||||
|
Ticker = candle.Ticker,
|
||||||
|
Exchange = candle.Exchange,
|
||||||
|
Stc = currentSct.Stc
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sctList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSignal(CandleSct candleSignal, TradeDirection direction, Confidence confidence)
|
||||||
|
{
|
||||||
|
var signal = new Signal(
|
||||||
|
MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker),
|
||||||
|
direction,
|
||||||
|
confidence,
|
||||||
|
candleSignal,
|
||||||
|
candleSignal.Date,
|
||||||
|
candleSignal.Exchange,
|
||||||
|
Type, SignalType);
|
||||||
|
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||||
|
{
|
||||||
|
Signals.AddItem(signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CandleSct : Candle
|
||||||
|
{
|
||||||
|
public double? Stc { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/Managing.Domain/Strategies/SuperTrendCrossEma.cs
Normal file
192
src/Managing.Domain/Strategies/SuperTrendCrossEma.cs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
using Managing.Core;
|
||||||
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Shared.Rules;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
|
using Skender.Stock.Indicators;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Domain.Strategies;
|
||||||
|
|
||||||
|
public class SuperTrendCrossEma : Strategy
|
||||||
|
{
|
||||||
|
public List<Signal> Signals { get; set; }
|
||||||
|
|
||||||
|
public SuperTrendCrossEma(string name, int period, double multiplier) : base(name, StrategyType.SuperTrendCrossEma)
|
||||||
|
{
|
||||||
|
Signals = new List<Signal>();
|
||||||
|
Period = period;
|
||||||
|
Multiplier = multiplier;
|
||||||
|
MinimumHistory = 100 + Period.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<Signal> Run()
|
||||||
|
{
|
||||||
|
// Validate sufficient historical data for all indicators
|
||||||
|
const int emaPeriod = 50;
|
||||||
|
const int adxPeriod = 14; // Standard ADX period
|
||||||
|
const int adxThreshold = 25; // Minimum ADX level to confirm a trend
|
||||||
|
|
||||||
|
int minimumRequiredHistory = Math.Max(Math.Max(emaPeriod, adxPeriod), Period.Value * 2); // Ensure enough data
|
||||||
|
if (Candles.Count < minimumRequiredHistory)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. Calculate indicators
|
||||||
|
var superTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value)
|
||||||
|
.Where(s => s.SuperTrend.HasValue)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var ema50 = Candles.GetEma(emaPeriod)
|
||||||
|
.Where(e => e.Ema.HasValue)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var adxResults = Candles.GetAdx(adxPeriod)
|
||||||
|
.Where(a => a.Adx.HasValue && a.Pdi.HasValue && a.Mdi.HasValue) // Ensure all values exist
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 2. Create merged dataset with price + indicators
|
||||||
|
var superTrendCandles = MapSuperTrendToCandle(superTrend, Candles.TakeLast(minimumRequiredHistory));
|
||||||
|
if (superTrendCandles.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// 3. Add EMA50 and ADX values to the CandleSuperTrend objects
|
||||||
|
foreach (var candle in superTrendCandles)
|
||||||
|
{
|
||||||
|
var emaValue = ema50.Find(e => e.Date == candle.Date)?.Ema;
|
||||||
|
var adxValue = adxResults.Find(a => a.Date == candle.Date);
|
||||||
|
|
||||||
|
if (emaValue.HasValue)
|
||||||
|
candle.Ema50 = emaValue.Value;
|
||||||
|
|
||||||
|
if (adxValue != null)
|
||||||
|
{
|
||||||
|
candle.Adx = (decimal)adxValue.Adx.Value;
|
||||||
|
candle.Pdi = (decimal)adxValue.Pdi.Value;
|
||||||
|
candle.Mdi = (decimal)adxValue.Mdi.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Signal detection logic with ADX filter
|
||||||
|
for (int i = 1; i < superTrendCandles.Count; i++)
|
||||||
|
{
|
||||||
|
var current = superTrendCandles[i];
|
||||||
|
var previous = superTrendCandles[i - 1];
|
||||||
|
|
||||||
|
// Convert SuperTrend to double for comparison
|
||||||
|
double currentSuperTrend = (double)current.SuperTrend;
|
||||||
|
double previousSuperTrend = (double)previous.SuperTrend;
|
||||||
|
|
||||||
|
// Ensure ADX data exists
|
||||||
|
if (current.Adx < adxThreshold) // Only trade when ADX confirms trend strength
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* LONG SIGNAL CONDITIONS:
|
||||||
|
* 1. SuperTrend crosses above EMA50
|
||||||
|
* 2. Price > SuperTrend and > EMA50
|
||||||
|
* 3. Previous state shows SuperTrend < EMA50
|
||||||
|
* 4. ADX > threshold and +DI > -DI (bullish momentum)
|
||||||
|
*/
|
||||||
|
bool longCross = currentSuperTrend > current.Ema50 &&
|
||||||
|
previousSuperTrend < previous.Ema50;
|
||||||
|
|
||||||
|
bool longPricePosition = current.Close > (decimal)currentSuperTrend &&
|
||||||
|
current.Close > (decimal)current.Ema50;
|
||||||
|
|
||||||
|
bool adxBullish = current.Pdi > current.Mdi; // Bullish momentum confirmation
|
||||||
|
|
||||||
|
if (longCross && longPricePosition && adxBullish)
|
||||||
|
{
|
||||||
|
AddSignal(current, TradeDirection.Long, Confidence.Medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SHORT SIGNAL CONDITIONS:
|
||||||
|
* 1. SuperTrend crosses below EMA50
|
||||||
|
* 2. Price < SuperTrend and < EMA50
|
||||||
|
* 3. Previous state shows SuperTrend > EMA50
|
||||||
|
* 4. ADX > threshold and -DI > +DI (bearish momentum)
|
||||||
|
*/
|
||||||
|
bool shortCross = currentSuperTrend < current.Ema50 &&
|
||||||
|
previousSuperTrend > previous.Ema50;
|
||||||
|
|
||||||
|
bool shortPricePosition = current.Close < (decimal)currentSuperTrend &&
|
||||||
|
current.Close < (decimal)current.Ema50;
|
||||||
|
|
||||||
|
bool adxBearish = current.Mdi > current.Pdi; // Bearish momentum confirmation
|
||||||
|
|
||||||
|
if (shortCross && shortPricePosition && adxBearish)
|
||||||
|
{
|
||||||
|
AddSignal(current, TradeDirection.Short, Confidence.Medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Signals.Where(s => s.Confidence != Confidence.None)
|
||||||
|
.OrderBy(s => s.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CandleSuperTrend> MapSuperTrendToCandle(List<SuperTrendResult> superTrend, IEnumerable<Candle> candles)
|
||||||
|
{
|
||||||
|
var superTrends = new List<CandleSuperTrend>();
|
||||||
|
foreach (var candle in candles)
|
||||||
|
{
|
||||||
|
var currentSuperTrend = superTrend.Find(candle.Date);
|
||||||
|
if (currentSuperTrend != null)
|
||||||
|
{
|
||||||
|
superTrends.Add(new CandleSuperTrend()
|
||||||
|
{
|
||||||
|
Close = candle.Close,
|
||||||
|
Open = candle.Open,
|
||||||
|
Date = candle.Date,
|
||||||
|
Ticker = candle.Ticker,
|
||||||
|
Exchange = candle.Exchange,
|
||||||
|
SuperTrend = currentSuperTrend.SuperTrend.Value,
|
||||||
|
LowerBand = currentSuperTrend.LowerBand,
|
||||||
|
UpperBand = currentSuperTrend.UpperBand,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return superTrends;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override StrategiesResultBase GetStrategyValues()
|
||||||
|
{
|
||||||
|
return new StrategiesResultBase()
|
||||||
|
{
|
||||||
|
SuperTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue)
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddSignal(CandleSuperTrend candleSignal, TradeDirection direction, Confidence confidence)
|
||||||
|
{
|
||||||
|
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
|
||||||
|
candleSignal, candleSignal.Date,
|
||||||
|
candleSignal.Exchange, Type, SignalType);
|
||||||
|
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||||
|
{
|
||||||
|
Signals.AddItem(signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class CandleSuperTrend : Candle
|
||||||
|
{
|
||||||
|
public decimal SuperTrend { get; internal set; }
|
||||||
|
public decimal? LowerBand { get; internal set; }
|
||||||
|
public decimal? UpperBand { get; internal set; }
|
||||||
|
public double Ema50 { get; set; }
|
||||||
|
public decimal Adx { get; set; } // ADX value
|
||||||
|
public decimal Pdi { get; set; } // Positive Directional Indicator (+DI)
|
||||||
|
public decimal Mdi { get; set; } // Negative Directional Indicator (-DI)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -394,7 +394,8 @@ public static class MongoMappers
|
|||||||
Multiplier = strategyDto.Multiplier,
|
Multiplier = strategyDto.Multiplier,
|
||||||
SmoothPeriods = strategyDto.SmoothPeriods,
|
SmoothPeriods = strategyDto.SmoothPeriods,
|
||||||
StochPeriods = strategyDto.StochPeriods,
|
StochPeriods = strategyDto.StochPeriods,
|
||||||
CyclePeriods = strategyDto.CyclePeriods
|
CyclePeriods = strategyDto.CyclePeriods,
|
||||||
|
SignalType = strategyDto.SignalType
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,44 +405,52 @@ public static class MongoMappers
|
|||||||
{
|
{
|
||||||
Type = strategy.Type,
|
Type = strategy.Type,
|
||||||
Name = strategy.Name,
|
Name = strategy.Name,
|
||||||
SignalType = strategy.SignalType
|
SignalType = strategy.SignalType,
|
||||||
|
CyclePeriods = strategy.CyclePeriods,
|
||||||
|
FastPeriods = strategy.FastPeriods,
|
||||||
|
Multiplier = strategy.Multiplier,
|
||||||
|
Period = strategy.Period,
|
||||||
|
SignalPeriods = strategy.SignalPeriods,
|
||||||
|
SlowPeriods = strategy.SlowPeriods,
|
||||||
|
SmoothPeriods = strategy.SmoothPeriods,
|
||||||
|
StochPeriods = strategy.StochPeriods
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (strategy.Type)
|
// switch (strategy.Type)
|
||||||
{
|
// {
|
||||||
case StrategyType.RsiDivergenceConfirm:
|
// case StrategyType.RsiDivergenceConfirm:
|
||||||
case StrategyType.RsiDivergence:
|
// case StrategyType.RsiDivergence:
|
||||||
case StrategyType.EmaCross:
|
// case StrategyType.EmaCross:
|
||||||
case StrategyType.EmaTrend:
|
// case StrategyType.EmaTrend:
|
||||||
case StrategyType.StDev:
|
// case StrategyType.StDev:
|
||||||
dto.Period = strategy.Period;
|
// dto.Period = strategy.Period;
|
||||||
break;
|
// break;
|
||||||
case StrategyType.MacdCross:
|
// case StrategyType.MacdCross:
|
||||||
dto.SlowPeriods = strategy.SlowPeriods;
|
// dto.SlowPeriods = strategy.SlowPeriods;
|
||||||
dto.FastPeriods = strategy.FastPeriods;
|
// dto.FastPeriods = strategy.FastPeriods;
|
||||||
dto.SignalPeriods = strategy.SignalPeriods;
|
// dto.SignalPeriods = strategy.SignalPeriods;
|
||||||
break;
|
// break;
|
||||||
case StrategyType.ThreeWhiteSoldiers:
|
// case StrategyType.ThreeWhiteSoldiers:
|
||||||
break;
|
// break;
|
||||||
case StrategyType.ChandelierExit:
|
// case StrategyType.ChandelierExit:
|
||||||
case StrategyType.SuperTrend:
|
// case StrategyType.SuperTrend:
|
||||||
dto.Period = strategy.Period;
|
// dto.Period = strategy.Period;
|
||||||
dto.Multiplier = strategy.Multiplier;
|
// dto.Multiplier = strategy.Multiplier;
|
||||||
break;
|
// break;
|
||||||
case StrategyType.StochRsiTrend:
|
// case StrategyType.StochRsiTrend:
|
||||||
dto.Period = strategy.Period;
|
// dto.Period = strategy.Period;
|
||||||
dto.StochPeriods = strategy.StochPeriods;
|
// dto.StochPeriods = strategy.StochPeriods;
|
||||||
dto.SignalPeriods = strategy.SignalPeriods;
|
// dto.SignalPeriods = strategy.SignalPeriods;
|
||||||
dto.SmoothPeriods = strategy.SmoothPeriods;
|
// dto.SmoothPeriods = strategy.SmoothPeriods;
|
||||||
break;
|
// break;
|
||||||
case StrategyType.Stc:
|
// case StrategyType.Stc:
|
||||||
dto.SlowPeriods = strategy.SlowPeriods;
|
// dto.SlowPeriods = strategy.SlowPeriods;
|
||||||
dto.FastPeriods = strategy.FastPeriods;
|
// dto.FastPeriods = strategy.FastPeriods;
|
||||||
dto.CyclePeriods = strategy.CyclePeriods;
|
// dto.CyclePeriods = strategy.CyclePeriods;
|
||||||
break;
|
// break;
|
||||||
default:
|
// default:
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { TradeChart, CardPositionItem } from '..'
|
import { TradeChart, CardPositionItem } from '..'
|
||||||
import { IBotRowDetails } from '../../../global/type'
|
import { IBotRowDetails } from '../../../global/type'
|
||||||
import { CardPosition } from '../../mollecules'
|
import { CardPosition, CardText } from '../../mollecules'
|
||||||
|
|
||||||
const BacktestRowDetails: React.FC<IBotRowDetails> = ({
|
const BacktestRowDetails: React.FC<IBotRowDetails> = ({
|
||||||
candles,
|
candles,
|
||||||
positions,
|
positions,
|
||||||
walletBalances,
|
walletBalances,
|
||||||
strategiesValues,
|
strategiesValues,
|
||||||
|
signals,
|
||||||
|
optimizedMoneyManagement,
|
||||||
|
statistics
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -27,17 +30,42 @@ const BacktestRowDetails: React.FC<IBotRowDetails> = ({
|
|||||||
})}
|
})}
|
||||||
></CardPosition>
|
></CardPosition>
|
||||||
<CardPositionItem positions={positions}></CardPositionItem>
|
<CardPositionItem positions={positions}></CardPositionItem>
|
||||||
|
<CardText
|
||||||
|
title="Optimized Money Management"
|
||||||
|
content={
|
||||||
|
"SL: " +optimizedMoneyManagement.stopLoss.toFixed(2) + "% TP: " + optimizedMoneyManagement.takeProfit.toFixed(2) + "%"
|
||||||
|
}
|
||||||
|
></CardText>
|
||||||
|
<CardText
|
||||||
|
title="Max Drowdown"
|
||||||
|
content={
|
||||||
|
statistics.maxDrawdown?.toFixed(4).toString() +
|
||||||
|
'$'
|
||||||
|
}
|
||||||
|
></CardText>
|
||||||
|
<CardText
|
||||||
|
title="Sharpe Ratio"
|
||||||
|
content={
|
||||||
|
(statistics.sharpeRatio
|
||||||
|
? statistics.sharpeRatio * 100
|
||||||
|
: 0
|
||||||
|
)
|
||||||
|
.toFixed(4)
|
||||||
|
.toString() + '%'
|
||||||
|
}
|
||||||
|
></CardText>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<figure>
|
<figure>
|
||||||
<TradeChart
|
<TradeChart
|
||||||
width={1400}
|
width={1400}
|
||||||
height={1400}
|
height={1100}
|
||||||
candles={candles}
|
candles={candles}
|
||||||
positions={positions}
|
positions={positions}
|
||||||
walletBalances={walletBalances}
|
walletBalances={walletBalances}
|
||||||
strategiesValues={strategiesValues}
|
strategiesValues={strategiesValues}
|
||||||
signals={[]}
|
signals={signals}
|
||||||
></TradeChart>
|
></TradeChart>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -248,12 +248,16 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching }) => {
|
|||||||
positions={row.original.positions}
|
positions={row.original.positions}
|
||||||
walletBalances={row.original.walletBalances}
|
walletBalances={row.original.walletBalances}
|
||||||
strategiesValues={row.original.strategiesValues}
|
strategiesValues={row.original.strategiesValues}
|
||||||
|
signals={row.original.signals}
|
||||||
|
optimizedMoneyManagement={row.original.optimizedMoneyManagement}
|
||||||
|
statistics={row.original.statistics}
|
||||||
></BacktestRowDetails>
|
></BacktestRowDetails>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-wrap"
|
className="flex flex-wrap"
|
||||||
@@ -261,13 +265,14 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching }) => {
|
|||||||
>
|
>
|
||||||
{isFetching ? (
|
{isFetching ? (
|
||||||
<progress className="progress progress-primary w-56"></progress>
|
<progress className="progress progress-primary w-56"></progress>
|
||||||
) : (
|
) : (<>
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={rows}
|
data={rows}
|
||||||
renderRowSubCompontent={renderRowSubComponent}
|
renderRowSubCompontent={renderRowSubComponent}
|
||||||
showPagination={true}
|
showPagination={true}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ const TradeChart = ({
|
|||||||
bottomLineColor: theme.secondary,
|
bottomLineColor: theme.secondary,
|
||||||
topLineColor: theme.primary,
|
topLineColor: theme.primary,
|
||||||
lineWidth: 1,
|
lineWidth: 1,
|
||||||
|
priceLineVisible: true,
|
||||||
|
crosshairMarkerVisible: true,
|
||||||
} as BaselineSeriesOptions
|
} as BaselineSeriesOptions
|
||||||
|
|
||||||
function buildMarker(
|
function buildMarker(
|
||||||
@@ -406,6 +408,7 @@ const TradeChart = ({
|
|||||||
stcSeries.setData(stcData)
|
stcSeries.setData(stcData)
|
||||||
stcSeries.applyOptions({
|
stcSeries.applyOptions({
|
||||||
...baselineOptions,
|
...baselineOptions,
|
||||||
|
priceLineVisible: true,
|
||||||
priceFormat: {
|
priceFormat: {
|
||||||
minMove: 1,
|
minMove: 1,
|
||||||
precision: 1,
|
precision: 1,
|
||||||
|
|||||||
@@ -2233,6 +2233,8 @@ export enum StrategyType {
|
|||||||
StochRsiTrend = "StochRsiTrend",
|
StochRsiTrend = "StochRsiTrend",
|
||||||
Stc = "Stc",
|
Stc = "Stc",
|
||||||
StDev = "StDev",
|
StDev = "StDev",
|
||||||
|
LaggingStc = "LaggingStc",
|
||||||
|
SuperTrendCrossEma = "SuperTrendCrossEma",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SignalType {
|
export enum SignalType {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
IFlow,
|
IFlow,
|
||||||
KeyValuePairOfDateTimeAndDecimal,
|
KeyValuePairOfDateTimeAndDecimal,
|
||||||
MoneyManagement,
|
MoneyManagement,
|
||||||
|
PerformanceMetrics,
|
||||||
Position,
|
Position,
|
||||||
RiskLevel,
|
RiskLevel,
|
||||||
Scenario,
|
Scenario,
|
||||||
@@ -147,6 +148,9 @@ export type IBotRowDetails = {
|
|||||||
positions: Position[]
|
positions: Position[]
|
||||||
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
|
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
|
||||||
strategiesValues?: { [key in keyof typeof StrategyType]?: StrategiesResultBase; } | null;
|
strategiesValues?: { [key in keyof typeof StrategyType]?: StrategiesResultBase; } | null;
|
||||||
|
signals: Signal[]
|
||||||
|
optimizedMoneyManagement: MoneyManagement
|
||||||
|
statistics: PerformanceMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IBacktestFormInput = {
|
export type IBacktestFormInput = {
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ const StrategyList: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{strategyType == StrategyType.Stc ? (
|
{strategyType == StrategyType.Stc || strategyType == StrategyType.LaggingStc ? (
|
||||||
<>
|
<>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
@@ -278,6 +278,7 @@ const StrategyList: React.FC = () => {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{strategyType == StrategyType.SuperTrend ||
|
{strategyType == StrategyType.SuperTrend ||
|
||||||
|
strategyType == StrategyType.SuperTrendCrossEma ||
|
||||||
strategyType == StrategyType.ChandelierExit ? (
|
strategyType == StrategyType.ChandelierExit ? (
|
||||||
<>
|
<>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
|
|||||||
Reference in New Issue
Block a user