Trading bot grain (#33)

* Trading bot Grain

* Fix a bit more of the trading bot

* Advance on the tradingbot grain

* Fix build

* Fix db script

* Fix user login

* Fix a bit backtest

* Fix cooldown and backtest

* start fixing bot start

* Fix startup

* Setup local db

* Fix build and update candles and scenario

* Add bot registry

* Add reminder

* Updateing the grains

* fix bootstraping

* Save stats on tick

* Save bot data every tick

* Fix serialization

* fix save bot stats

* Fix get candles

* use dict instead of list for position

* Switch hashset to dict

* Fix a bit

* Fix bot launch and bot view

* add migrations

* Remove the tolist

* Add agent grain

* Save agent summary

* clean

* Add save bot

* Update get bots

* Add get bots

* Fix stop/restart

* fix Update config

* Update scanner table on new backtest saved

* Fix backtestRowDetails.tsx

* Fix agentIndex

* Update agentIndex

* Fix more things

* Update user cache

* Fix

* Fix account load/start/restart/run
This commit is contained in:
Oda
2025-08-04 23:07:06 +02:00
committed by GitHub
parent cd378587aa
commit 082ae8714b
215 changed files with 9562 additions and 14028 deletions

View File

@@ -3,10 +3,9 @@ using System.ComponentModel.DataAnnotations;
using Exilion.TradingAtomics;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Indicators;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Domain.Backtests;
@@ -14,16 +13,15 @@ public class Backtest
{
public Backtest(
TradingBotConfig config,
List<Position> positions,
List<LightSignal> signals,
List<Candle> candles = null)
Dictionary<Guid, Position> positions,
Dictionary<string, LightSignal> signals,
HashSet<Candle> candles = null)
{
Config = config;
Positions = positions;
Signals = signals;
Candles = candles != null ? candles : new List<Candle>();
Candles = candles != null ? candles : new HashSet<Candle>();
WalletBalances = new List<KeyValuePair<DateTime, decimal>>();
IndicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
// Initialize start and end dates if candles are provided
if (candles != null && candles.Count > 0)
@@ -44,16 +42,15 @@ public class Backtest
[Required] public decimal GrowthPercentage { get; set; }
[Required] public decimal HodlPercentage { get; set; }
[Required] public TradingBotConfig Config { get; }
[Required] public List<Position> Positions { get; }
[Required] public List<LightSignal> Signals { get; }
[Required] public List<Candle> Candles { get; set; }
[Required] public Dictionary<Guid, Position> Positions { get; }
[Required] public Dictionary<string, LightSignal> Signals { get; }
[Required] public HashSet<Candle> Candles { get; set; }
[Required] public DateTime StartDate { get; set; }
[Required] public DateTime EndDate { get; set; }
[Required] public PerformanceMetrics Statistics { get; set; }
[Required] public decimal Fees { get; set; }
[Required] public List<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; }
[Required] public User User { get; set; }
[Required] public Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
[Required] public double Score { get; set; }
public string RequestId { get; set; }
public object? Metadata { get; set; }

View File

@@ -3,123 +3,22 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Bots
{
/// <summary>
/// A bot define what code should be run.
/// To run a code you have to herit from this class and implement the Run() method
/// </summary>
public abstract class Bot : IBot
public class Bot
{
public int ExecutionCount;
public string Identifier { get; set; }
public string Name { get; set; }
public int Interval { get; set; }
public BotStatus Status { get; set; }
public User User { get; set; }
public Guid Identifier { get; set; }
public string Name { get; set; }
public Ticker Ticker { get; set; }
public BotStatus Status { get; set; }
public DateTime StartupTime { get; set; }
public DateTime CreateDate { get; set; }
/// <summary>
/// The time when the bot was first started (creation date)
/// </summary>
public DateTime StartupTime { get; protected set; }
/// <summary>
/// The time when the bot was created
/// </summary>
public DateTime CreateDate { get; protected set; }
private CancellationTokenSource CancellationToken { get; set; }
public Bot(string name)
{
Identifier = $"{name}-{DateTime.Now:yyyyMMdd-hhmm}-{Guid.NewGuid()}";
Name = name;
Status = BotStatus.Down;
CancellationToken = new CancellationTokenSource();
ExecutionCount = 0;
Interval = 3000;
CreateDate = DateTime.UtcNow; // Set the creation time when the bot is instantiated
StartupTime = DateTime.UtcNow; // Set the startup time to creation date initially
}
public virtual void Start()
{
Status = BotStatus.Up;
// StartupTime remains unchanged on first start (it's already set to creation date)
}
public async Task InitWorker(Func<Task> action)
{
try
{
await Task.Run(async () =>
{
while (Status == BotStatus.Up && !CancellationToken.IsCancellationRequested)
{
try
{
await action();
ExecutionCount++;
if (CancellationToken.IsCancellationRequested)
break;
}
catch (TaskCanceledException) when (CancellationToken.IsCancellationRequested)
{
// Graceful shutdown when cancellation is requested
break;
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
Console.WriteLine(ex.Message);
}
finally
{
await Task.Delay(Interval, CancellationToken.Token);
}
}
}, CancellationToken.Token);
}
catch (TaskCanceledException ex)
{
Console.WriteLine($"Bot was cancelled: {ex.Message}");
}
}
public void Stop()
{
Status = BotStatus.Down;
_ = Task.Run(async () => await SaveBackup());
// CancellationToken.Cancel();
}
public void Restart()
{
Status = BotStatus.Up;
StartupTime = DateTime.UtcNow; // Update the startup time when the bot is restarted
}
public string GetStatus()
{
return Status.ToString();
}
public string GetName()
{
return Name;
}
/// <summary>
/// Gets the total runtime of the bot since it was started
/// </summary>
/// <returns>TimeSpan representing the runtime, or TimeSpan.Zero if the bot is not running</returns>
public TimeSpan GetRuntime()
{
if (Status != BotStatus.Up)
return TimeSpan.Zero;
return DateTime.UtcNow - StartupTime;
}
public abstract Task SaveBackup();
public abstract void LoadBackup(BotBackup backup);
public int TradeWins { get; set; }
public int TradeLosses { get; set; }
public decimal Pnl { get; set; }
public decimal Roi { get; set; }
public decimal Volume { get; set; }
public decimal Fees { get; set; }
}
}

View File

@@ -1,51 +0,0 @@
using Managing.Domain.Users;
using Newtonsoft.Json;
using Orleans;
using static Managing.Common.Enums;
namespace Managing.Domain.Bots;
[GenerateSerializer]
public class BotBackup
{
[Id(0)]
public string Identifier { get; set; }
[Id(1)]
public User User { get; set; }
[Id(2)]
public TradingBotBackup Data { get; set; }
[Id(3)]
public BotStatus LastStatus { get; set; }
[Id(4)]
public DateTime CreateDate { get; set; }
/// <summary>
/// Serializes the TradingBotBackup data to JSON string
/// </summary>
/// <returns>JSON string representation of the data</returns>
public string SerializeData()
{
if (Data == null) return null;
return JsonConvert.SerializeObject(Data);
}
/// <summary>
/// Deserializes JSON string to TradingBotBackup data
/// </summary>
/// <param name="jsonData">JSON string to deserialize</param>
public void DeserializeData(string jsonData)
{
if (string.IsNullOrEmpty(jsonData))
{
Data = null;
return;
}
Data = JsonConvert.DeserializeObject<TradingBotBackup>(jsonData);
}
}

View File

@@ -1,30 +0,0 @@
using Managing.Domain.Users;
namespace Managing.Domain.Bots
{
public interface IBot
{
User User { get; set; }
string Name { get; set; }
void Start();
void Stop();
void Restart();
string GetStatus();
string GetName();
/// <summary>
/// Gets the total runtime of the bot since it was started
/// </summary>
/// <returns>TimeSpan representing the runtime, or TimeSpan.Zero if the bot is not running</returns>
TimeSpan GetRuntime();
/// <summary>
/// The time when the bot was first started (creation date)
/// </summary>
DateTime StartupTime { get; }
string Identifier { get; set; }
Task SaveBackup();
void LoadBackup(BotBackup backup);
}
}

View File

@@ -1,3 +1,4 @@
using Managing.Domain.Indicators;
using Managing.Domain.Trades;
using Orleans;
@@ -19,10 +20,10 @@ public class TradingBotBackup
public HashSet<LightSignal> Signals { get; set; }
/// <summary>
/// Runtime state: Open and closed positions for the bot
/// Runtime state: Open and closed positions for the bot, keyed by position identifier
/// </summary>
[Id(2)]
public List<Position> Positions { get; set; }
public Dictionary<Guid, Position> Positions { get; set; }
/// <summary>
/// Runtime state: Historical wallet balances over time
@@ -41,4 +42,4 @@ public class TradingBotBackup
/// </summary>
[Id(5)]
public DateTime CreateDate { get; set; }
}
}

View File

@@ -32,18 +32,42 @@ public static class CandleExtensions
public static int GetIntervalFromTimeframe(Timeframe timeframe)
{
var seconds = timeframe switch
{
Timeframe.OneDay => 86400,
Timeframe.FourHour => 14400,
Timeframe.OneHour => 3600,
Timeframe.ThirtyMinutes => 1800,
Timeframe.FifteenMinutes => 900,
Timeframe.FiveMinutes => 300,
_ => 300,
};
// Run every 1/5th of the candle duration
return seconds / 5 * 1000; // milliseconds
var seconds = GetBaseIntervalInSeconds(timeframe);
// Run every 1/5th of the candle duration
return seconds / 5 * 1000; // milliseconds
}
/// <summary>
/// Gets the interval in minutes for the given timeframe.
/// This is useful for cooldown period calculations.
/// </summary>
/// <param name="timeframe">The timeframe to get the interval for</param>
/// <returns>The interval in minutes</returns>
public static double GetIntervalInMinutes(Timeframe timeframe)
{
var seconds = GetBaseIntervalInSeconds(timeframe);
// Run every 1/5th of the candle duration
return (seconds / 5.0) / 60.0; // minutes
}
/// <summary>
/// Gets the base interval in seconds for the given timeframe.
/// This is the core interval logic that can be used for various calculations.
/// </summary>
/// <param name="timeframe">The timeframe to get the base interval for</param>
/// <returns>The base interval in seconds</returns>
public static int GetBaseIntervalInSeconds(Timeframe timeframe)
{
return timeframe switch
{
Timeframe.OneDay => 86400,
Timeframe.FourHour => 14400,
Timeframe.OneHour => 3600,
Timeframe.ThirtyMinutes => 1800,
Timeframe.FifteenMinutes => 900,
Timeframe.FiveMinutes => 300,
_ => 300,
};
}
public static int GetUnixInterval(this Timeframe timeframe)

View File

@@ -4,9 +4,9 @@ using Skender.Stock.Indicators;
namespace Managing.Domain.Strategies.Base;
public abstract class EmaBaseIndicator : Indicator
public abstract class EmaBaseIndicatorBase : IndicatorBase
{
protected EmaBaseIndicator(string name, Enums.IndicatorType type) : base(name, type)
protected EmaBaseIndicatorBase(string name, Enums.IndicatorType type) : base(name, type)
{
}

View File

@@ -1,5 +1,6 @@
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;
@@ -7,7 +8,7 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Context;
public class StDevContext : Indicator
public class StDevContext : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
@@ -17,17 +18,17 @@ public class StDevContext : Indicator
Period = period;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= Period)
if (candles.Count <= Period)
{
return null;
}
try
{
var stDev = Candles.GetStdDev(Period.Value).ToList();
var stDevCandles = MapStDev(stDev, Candles.TakeLast(Period.Value));
var stDev = candles.GetStdDev(Period.Value).ToList();
var stDevCandles = MapStDev(stDev, candles.TakeLast(Period.Value));
if (stDev.Count == 0)
return null;
@@ -73,11 +74,11 @@ public class StDevContext : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
var test = new IndicatorsResultBase()
{
StdDev = Candles.GetStdDev(Period.Value).ToList()
StdDev = candles.GetStdDev(Period.Value).ToList()
};
return test;

View File

@@ -1,5 +1,5 @@
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Candles;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Strategies.Base;
using static Managing.Common.Enums;
@@ -14,11 +14,12 @@ namespace Managing.Domain.Strategies
int? FastPeriods { get; set; }
int? SlowPeriods { get; set; }
int? SignalPeriods { get; set; }
FixedSizeQueue<Candle> Candles { get; set; }
double? Multiplier { get; set; }
int? StochPeriods { get; set; }
int? SmoothPeriods { get; set; }
int? CyclePeriods { get; set; }
List<LightSignal> Run();
IndicatorsResultBase GetIndicatorValues();
void UpdateCandles(HashSet<Candle> newCandles);
string GetName();
List<LightSignal> Run(HashSet<Candle> candles);
IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles);
}
}

View File

@@ -0,0 +1,54 @@
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Domain.Strategies
{
public class IndicatorBase : IIndicator
{
public IndicatorBase(string name, IndicatorType type)
{
Name = name;
Type = type;
SignalType = ScenarioHelpers.GetSignalType(type);
}
public string Name { get; set; }
public IndicatorType Type { get; set; }
public SignalType SignalType { get; set; }
public int MinimumHistory { get; set; }
public int? Period { get; set; }
public int? FastPeriods { get; set; }
public int? SlowPeriods { get; set; }
public int? SignalPeriods { get; set; }
public double? Multiplier { get; set; }
public int? SmoothPeriods { get; set; }
public int? StochPeriods { get; set; }
public int? CyclePeriods { get; set; }
public User User { get; set; }
public virtual List<LightSignal> Run(HashSet<Candle> candles)
{
throw new NotImplementedException();
}
public virtual IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
throw new NotImplementedException();
}
}
}

View File

@@ -45,29 +45,35 @@ public class LightIndicator
/// <summary>
/// Converts a full Indicator to a LightIndicator
/// </summary>
public static LightIndicator FromIndicator(Indicator indicator)
public static LightIndicator BaseToLight(IndicatorBase indicatorBase)
{
return new LightIndicator(indicator.Name, indicator.Type)
return new LightIndicator(indicatorBase.Name, indicatorBase.Type)
{
SignalType = indicator.SignalType,
MinimumHistory = indicator.MinimumHistory,
Period = indicator.Period,
FastPeriods = indicator.FastPeriods,
SlowPeriods = indicator.SlowPeriods,
SignalPeriods = indicator.SignalPeriods,
Multiplier = indicator.Multiplier,
SmoothPeriods = indicator.SmoothPeriods,
StochPeriods = indicator.StochPeriods,
CyclePeriods = indicator.CyclePeriods
SignalType = indicatorBase.SignalType,
MinimumHistory = indicatorBase.MinimumHistory,
Period = indicatorBase.Period,
FastPeriods = indicatorBase.FastPeriods,
SlowPeriods = indicatorBase.SlowPeriods,
SignalPeriods = indicatorBase.SignalPeriods,
Multiplier = indicatorBase.Multiplier,
SmoothPeriods = indicatorBase.SmoothPeriods,
StochPeriods = indicatorBase.StochPeriods,
CyclePeriods = indicatorBase.CyclePeriods
};
}
/// <summary>
/// Converts a LightIndicator back to a full Indicator
/// </summary>
public Indicator ToIndicator()
public IIndicator ToInterface()
{
return new Indicator(Name, Type)
// Use the factory method to create the correct indicator type
return ScenarioHelpers.BuildIndicator(this);
}
public IndicatorBase LightToBase()
{
var baseIndicator = new IndicatorBase(Name, Type)
{
SignalType = SignalType,
MinimumHistory = MinimumHistory,
@@ -80,5 +86,7 @@ public class LightIndicator
StochPeriods = StochPeriods,
CyclePeriods = CyclePeriods
};
return baseIndicator;
}
}

View File

@@ -0,0 +1,71 @@
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Managing.Domain.Candles;
using Orleans;
using static Managing.Common.Enums;
namespace Managing.Domain.Indicators;
[GenerateSerializer]
public class LightSignal
{
public LightSignal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date,
TradingExchanges exchange, IndicatorType indicatorType, SignalType signalType, string indicatorName)
{
Direction = direction;
Confidence = confidence;
// Cast to base Candle type to avoid Orleans serialization issues with nested types
Candle = candle as Candle ?? new Candle
{
Exchange = candle.Exchange,
Ticker = candle.Ticker,
OpenTime = candle.OpenTime,
Date = candle.Date,
Open = candle.Open,
Close = candle.Close,
High = candle.High,
Low = candle.Low,
Timeframe = candle.Timeframe,
Volume = candle.Volume
};
Date = date;
Ticker = ticker;
Exchange = exchange;
Status = SignalStatus.WaitingForPosition;
IndicatorType = indicatorType;
IndicatorName = indicatorName;
SignalType = signalType;
Identifier =
$"{indicatorName}-{indicatorType}-{direction}-{ticker}-{Candle?.Close.ToString(CultureInfo.InvariantCulture)}-{date:yyyyMMdd-HHmmss}";
}
[Id(0)] [Required] public SignalStatus Status { get; set; }
[Id(1)] [Required] public TradeDirection Direction { get; }
[Id(2)] [Required] public Confidence Confidence { get; set; }
[Id(3)] [Required] public Timeframe Timeframe { get; }
[Id(4)] [Required] public DateTime Date { get; private set; }
[Id(5)] [Required] public Candle Candle { get; }
[Id(6)] [Required] public string Identifier { get; }
[Id(7)] [Required] public Ticker Ticker { get; }
[Id(8)] [Required] public TradingExchanges Exchange { get; set; }
[Id(9)] [Required] public IndicatorType IndicatorType { get; set; }
[Id(10)] [Required] public SignalType SignalType { get; set; }
[Id(11)] [Required] public string IndicatorName { get; set; }
public void SetConfidence(Confidence confidence)
{
Confidence = confidence;
}
}

View File

@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Users;
using static Managing.Common.Enums;

View File

@@ -1,5 +1,6 @@
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;
@@ -7,11 +8,11 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Signals;
public class ChandelierExitIndicator : Indicator
public class ChandelierExitIndicatorBase : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
public ChandelierExitIndicator(string name, int period, double multiplier) : base(name,
public ChandelierExitIndicatorBase(string name, int period, double multiplier) : base(name,
IndicatorType.ChandelierExit)
{
Signals = new List<LightSignal>();
@@ -20,17 +21,17 @@ public class ChandelierExitIndicator : Indicator
MinimumHistory = 1 + Period.Value;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= MinimumHistory)
if (candles.Count <= MinimumHistory)
{
return null;
}
try
{
GetSignals(ChandelierType.Long);
GetSignals(ChandelierType.Short);
GetSignals(ChandelierType.Long, candles);
GetSignals(ChandelierType.Short, candles);
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
}
@@ -40,20 +41,20 @@ public class ChandelierExitIndicator : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
ChandelierLong = Candles.GetChandelier(Period.Value, Multiplier.Value, ChandelierType.Long).ToList(),
ChandelierShort = Candles.GetChandelier(Period.Value, Multiplier.Value, ChandelierType.Short).ToList()
ChandelierLong = candles.GetChandelier(Period.Value, Multiplier.Value, ChandelierType.Long).ToList(),
ChandelierShort = candles.GetChandelier(Period.Value, Multiplier.Value, ChandelierType.Short).ToList()
};
}
private void GetSignals(ChandelierType chandelierType)
private void GetSignals(ChandelierType chandelierType, HashSet<Candle> candles)
{
var chandelier = Candles.GetChandelier(Period.Value, Multiplier.Value, chandelierType)
var chandelier = candles.GetChandelier(Period.Value, Multiplier.Value, chandelierType)
.Where(s => s.ChandelierExit.HasValue).ToList();
var chandelierCandle = MapChandelierToCandle(chandelier, Candles.TakeLast(MinimumHistory));
var chandelierCandle = MapChandelierToCandle(chandelier, candles.TakeLast(MinimumHistory));
var previousCandle = chandelierCandle[0];
foreach (var currentCandle in chandelierCandle.Skip(1))

View File

@@ -1,5 +1,6 @@
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;
@@ -7,11 +8,12 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Signals;
public class DualEmaCrossIndicator : EmaBaseIndicator
public class DualEmaCrossIndicatorBase : EmaBaseIndicatorBase
{
public List<LightSignal> Signals { get; set; }
public DualEmaCrossIndicator(string name, int fastPeriod, int slowPeriod) : base(name, IndicatorType.DualEmaCross)
public DualEmaCrossIndicatorBase(string name, int fastPeriod, int slowPeriod) : base(name,
IndicatorType.DualEmaCross)
{
Signals = new List<LightSignal>();
FastPeriods = fastPeriod;
@@ -19,28 +21,28 @@ public class DualEmaCrossIndicator : EmaBaseIndicator
MinimumHistory = Math.Max(fastPeriod, slowPeriod) * 2;
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
FastEma = Candles.GetEma(FastPeriods.Value).ToList(),
SlowEma = Candles.GetEma(SlowPeriods.Value).ToList()
FastEma = candles.GetEma(FastPeriods.Value).ToList(),
SlowEma = candles.GetEma(SlowPeriods.Value).ToList()
};
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= MinimumHistory)
if (candles.Count <= MinimumHistory)
{
return null;
}
try
{
var fastEma = Candles.GetEma(FastPeriods.Value).ToList();
var slowEma = Candles.GetEma(SlowPeriods.Value).ToList();
var fastEma = candles.GetEma(FastPeriods.Value).ToList();
var slowEma = candles.GetEma(SlowPeriods.Value).ToList();
var dualEmaCandles = MapDualEmaToCandle(fastEma, slowEma, Candles.TakeLast(MinimumHistory));
var dualEmaCandles = MapDualEmaToCandle(fastEma, slowEma, candles.TakeLast(MinimumHistory));
if (dualEmaCandles.Count < 2)
return null;

View File

@@ -1,4 +1,6 @@
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;
@@ -6,7 +8,7 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Signals;
public class EmaCrossIndicator : EmaBaseIndicator
public class EmaCrossIndicator : EmaBaseIndicatorBase
{
public List<LightSignal> Signals { get; set; }
@@ -16,25 +18,25 @@ public class EmaCrossIndicator : EmaBaseIndicator
Period = period;
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
Ema = Candles.GetEma(Period.Value).ToList()
Ema = candles.GetEma(Period.Value).ToList()
};
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= Period)
if (candles.Count <= Period)
{
return null;
}
try
{
var ema = Candles.GetEma(Period.Value).ToList();
var emaCandles = MapEmaToCandle(ema, Candles.TakeLast(Period.Value));
var ema = candles.GetEma(Period.Value).ToList();
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value));
if (ema.Count == 0)
return null;

View File

@@ -0,0 +1,79 @@
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 EmaCrossIndicatorBase : EmaBaseIndicatorBase
{
public List<LightSignal> Signals { get; set; }
public EmaCrossIndicatorBase(string name, int period) : base(name, IndicatorType.EmaCross)
{
Signals = new List<LightSignal>();
Period = period;
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
Ema = candles.GetEma(Period.Value).ToList()
};
}
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (candles.Count <= Period)
{
return null;
}
try
{
var ema = candles.GetEma(Period.Value).ToList();
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value).ToHashSet());
if (ema.Count == 0)
return null;
var previousCandle = emaCandles[0];
foreach (var currentCandle in emaCandles.Skip(1))
{
if (previousCandle.Close > (decimal)currentCandle.Ema &&
currentCandle.Close < (decimal)currentCandle.Ema)
{
AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium);
}
if (previousCandle.Close < (decimal)currentCandle.Ema &&
currentCandle.Close > (decimal)currentCandle.Ema)
{
AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
}
previousCandle = currentCandle;
}
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
}
catch (RuleException)
{
return null;
}
}
private void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence)
{
var signal = new LightSignal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);
if (!Signals.Any(s => s.Identifier == signal.Identifier))
{
Signals.AddItem(signal);
}
}
}

View File

@@ -1,5 +1,6 @@
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;
@@ -14,7 +15,7 @@ namespace Managing.Domain.Strategies.Signals;
/// 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 : Indicator
public class LaggingSTC : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
@@ -27,17 +28,17 @@ public class LaggingSTC : Indicator
CyclePeriods = cyclePeriods;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= 2 * (SlowPeriods + CyclePeriods))
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));
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;
@@ -89,9 +90,9 @@ public class LaggingSTC : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
var stc = Candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
var stc = candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
return new IndicatorsResultBase
{
Stc = stc

View File

@@ -1,5 +1,6 @@
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;
@@ -7,11 +8,11 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Signals;
public class MacdCrossIndicator : Indicator
public class MacdCrossIndicatorBase : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
public MacdCrossIndicator(string name, int fastPeriods, int slowPeriods, int signalPeriods) :
public MacdCrossIndicatorBase(string name, int fastPeriods, int slowPeriods, int signalPeriods) :
base(name, IndicatorType.MacdCross)
{
Signals = new List<LightSignal>();
@@ -20,17 +21,17 @@ public class MacdCrossIndicator : Indicator
SignalPeriods = signalPeriods;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= 2 * (SlowPeriods + SignalPeriods))
if (candles.Count <= 2 * (SlowPeriods + SignalPeriods))
{
return null;
}
try
{
var macd = Candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList();
var macdCandle = MapMacdToCandle(macd, Candles.TakeLast(SignalPeriods.Value));
var macd = candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList();
var macdCandle = MapMacdToCandle(macd, candles.TakeLast(SignalPeriods.Value));
if (macd.Count == 0)
return null;
@@ -67,11 +68,11 @@ public class MacdCrossIndicator : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
Macd = Candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList()
Macd = candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList()
};
}

View File

@@ -1,4 +1,5 @@
using Managing.Core;
using Managing.Domain.Indicators;
using Managing.Domain.Shared.Rules;
using Managing.Domain.Strategies.Base;
using Skender.Stock.Indicators;
@@ -7,11 +8,11 @@ using Candle = Managing.Domain.Candles.Candle;
namespace Managing.Domain.Strategies.Signals;
public class RsiDivergenceConfirmIndicator : Indicator
public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
public RsiDivergenceConfirmIndicator(string name, int period) : base(name, IndicatorType.RsiDivergenceConfirm)
public RsiDivergenceConfirmIndicatorBase(string name, int period) : base(name, IndicatorType.RsiDivergenceConfirm)
{
Period = period;
Signals = new List<LightSignal>();
@@ -21,25 +22,25 @@ public class RsiDivergenceConfirmIndicator : Indicator
/// Get RSI signals
/// </summary>
/// <returns></returns>
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= Period)
if (candles.Count <= Period)
{
return null;
}
var ticker = Candles.First().Ticker;
var ticker = candles.First().Ticker;
try
{
var rsiResult = Candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
var candlesRsi = MapRsiToCandle(rsiResult, Candles.TakeLast(10 * Period.Value));
var rsiResult = candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));
if (candlesRsi.Count(c => c.Rsi > 0) == 0)
return null;
GetLongSignals(candlesRsi);
GetShortSignals(candlesRsi);
GetLongSignals(candlesRsi, candles);
GetShortSignals(candlesRsi, candles);
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
}
@@ -49,15 +50,15 @@ public class RsiDivergenceConfirmIndicator : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
Rsi = Candles.GetRsi(Period.Value).ToList()
Rsi = candles.GetRsi(Period.Value).ToList()
};
}
private void GetLongSignals(List<CandleRsi> candlesRsi)
private void GetLongSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
{
// Set the low and high for first candle
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
@@ -124,13 +125,13 @@ public class RsiDivergenceConfirmIndicator : Indicator
highRsi.AddItem(currentCandle);
}
CheckIfConfimation(currentCandle, TradeDirection.Long);
CheckIfConfimation(currentCandle, TradeDirection.Long, candles);
previousCandle = currentCandle;
}
}
private void GetShortSignals(List<CandleRsi> candlesRsi)
private void GetShortSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
{
// Set the low and high for first candle
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
@@ -198,15 +199,15 @@ public class RsiDivergenceConfirmIndicator : Indicator
lowRsi.AddItem(currentCandle);
}
CheckIfConfimation(currentCandle, TradeDirection.Short);
CheckIfConfimation(currentCandle, TradeDirection.Short, candles);
previousCandle = currentCandle;
}
}
private void CheckIfConfimation(CandleRsi currentCandle, TradeDirection direction)
private void CheckIfConfimation(CandleRsi currentCandle, TradeDirection direction, HashSet<Candle> candles)
{
var lastCandleOnPeriod = Candles.TakeLast(Period.Value).ToList();
var lastCandleOnPeriod = candles.TakeLast(Period.Value).ToList();
var signalsOnPeriod = Signals.Where(s => s.Date >= lastCandleOnPeriod[0].Date
&& s.Date < currentCandle.Date
&& s.Direction == direction

View File

@@ -1,4 +1,5 @@
using Managing.Core;
using Managing.Domain.Indicators;
using Managing.Domain.Shared.Rules;
using Managing.Domain.Strategies.Base;
using Skender.Stock.Indicators;
@@ -7,14 +8,14 @@ using Candle = Managing.Domain.Candles.Candle;
namespace Managing.Domain.Strategies.Signals;
public class RsiDivergenceIndicator : Indicator
public class RsiDivergenceIndicatorBase : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
public TradeDirection Direction { get; set; }
private const int UpperBand = 70;
private const int LowerBand = 30;
public RsiDivergenceIndicator(string name, int period) : base(name, IndicatorType.RsiDivergence)
public RsiDivergenceIndicatorBase(string name, int period) : base(name, IndicatorType.RsiDivergence)
{
Period = period;
Signals = new List<LightSignal>();
@@ -24,25 +25,25 @@ public class RsiDivergenceIndicator : Indicator
/// Get RSI signals
/// </summary>
/// <returns></returns>
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (!Period.HasValue || Candles.Count <= Period)
if (!Period.HasValue || candles.Count <= Period)
{
return null;
}
var ticker = Candles.First().Ticker;
var ticker = candles.First().Ticker;
try
{
var rsiResult = Candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
var candlesRsi = MapRsiToCandle(rsiResult, Candles.TakeLast(10 * Period.Value));
var rsiResult = candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));
if (candlesRsi.Count(c => c.Rsi > 0) == 0)
return null;
GetLongSignals(candlesRsi);
GetShortSignals(candlesRsi);
GetLongSignals(candlesRsi, candles);
GetShortSignals(candlesRsi, candles);
return Signals;
}
@@ -52,15 +53,15 @@ public class RsiDivergenceIndicator : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
Rsi = Candles.GetRsi(Period.Value).ToList()
Rsi = candles.GetRsi(Period.Value).ToList()
};
}
private void GetLongSignals(List<CandleRsi> candlesRsi)
private void GetLongSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
{
// Set the low and high for first candle
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
@@ -100,7 +101,7 @@ public class RsiDivergenceIndicator : Indicator
// Price go down but RSI go up
if (currentCandle.Close < lowPrices.TakeLast(Period.Value).Min(p => p.Close))
{
AddSignal(currentCandle, TradeDirection.Long);
AddSignal(currentCandle, TradeDirection.Long, candles);
}
}
else
@@ -131,7 +132,7 @@ public class RsiDivergenceIndicator : Indicator
}
}
private void GetShortSignals(List<CandleRsi> candlesRsi)
private void GetShortSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
{
// Set the low and high for first candle
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
@@ -173,7 +174,7 @@ public class RsiDivergenceIndicator : Indicator
// Price go up but RSI go down
if (currentCandle.Close > highPrices.TakeLast(Period.Value).Max(p => p.Close))
{
AddSignal(currentCandle, TradeDirection.Short);
AddSignal(currentCandle, TradeDirection.Short, candles);
}
}
else
@@ -203,14 +204,14 @@ public class RsiDivergenceIndicator : Indicator
}
}
private void AddSignal(CandleRsi candleSignal, TradeDirection direction)
private void AddSignal(CandleRsi candleSignal, TradeDirection direction, HashSet<Candle> candles)
{
var signal = new LightSignal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, Confidence.Low,
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);
if (Signals.Count(s => s.Identifier == signal.Identifier) < 1)
{
var lastCandleOnPeriod = Candles.TakeLast(Period.Value).ToList();
var lastCandleOnPeriod = candles.TakeLast(Period.Value).ToList();
var signalsOnPeriod = Signals.Where(s => s.Date >= lastCandleOnPeriod[0].Date).ToList();
if (signalsOnPeriod.Count == 1)

View File

@@ -1,5 +1,6 @@
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;
@@ -7,11 +8,12 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Signals;
public class StcIndicator : Indicator
public class StcIndicatorBase : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
public StcIndicator(string name, int cyclePeriods, int fastPeriods, int slowPeriods) : base(name, IndicatorType.Stc)
public StcIndicatorBase(string name, int cyclePeriods, int fastPeriods, int slowPeriods) : base(name,
IndicatorType.Stc)
{
Signals = new List<LightSignal>();
FastPeriods = fastPeriods;
@@ -19,9 +21,9 @@ public class StcIndicator : Indicator
CyclePeriods = cyclePeriods;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= 2 * (SlowPeriods + CyclePeriods))
if (candles.Count <= 2 * (SlowPeriods + CyclePeriods))
{
return null;
}
@@ -30,10 +32,10 @@ public class StcIndicator : Indicator
{
if (FastPeriods != null)
{
var stc = Candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
var stc = candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
if (CyclePeriods != null)
{
var stcCandles = MapStcToCandle(stc, Candles.TakeLast(CyclePeriods.Value));
var stcCandles = MapStcToCandle(stc, candles.TakeLast(CyclePeriods.Value));
if (stc.Count == 0)
return null;
@@ -64,11 +66,11 @@ public class StcIndicator : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
if (FastPeriods != null && SlowPeriods != null)
{
var stc = Candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
var stc = candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
return new IndicatorsResultBase
{
Stc = stc

View File

@@ -1,5 +1,6 @@
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;
@@ -7,7 +8,7 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Signals;
public class SuperTrendCrossEma : Indicator
public class SuperTrendCrossEma : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
@@ -19,7 +20,7 @@ public class SuperTrendCrossEma : Indicator
MinimumHistory = 100 + Period.Value;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
// Validate sufficient historical data for all indicators
const int emaPeriod = 50;
@@ -27,7 +28,7 @@ public class SuperTrendCrossEma : Indicator
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)
if (candles.Count < minimumRequiredHistory)
{
return null;
}
@@ -35,20 +36,20 @@ public class SuperTrendCrossEma : Indicator
try
{
// 1. Calculate indicators
var superTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value)
var superTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value)
.Where(s => s.SuperTrend.HasValue)
.ToList();
var ema50 = Candles.GetEma(emaPeriod)
var ema50 = candles.GetEma(emaPeriod)
.Where(e => e.Ema.HasValue)
.ToList();
var adxResults = Candles.GetAdx(adxPeriod)
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));
var superTrendCandles = MapSuperTrendToCandle(superTrend, candles.TakeLast(minimumRequiredHistory));
if (superTrendCandles.Count == 0)
return null;
@@ -157,11 +158,11 @@ public class SuperTrendCrossEma : Indicator
return superTrends;
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
SuperTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue)
SuperTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue)
.ToList()
};
}

View File

@@ -1,5 +1,6 @@
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;
@@ -7,11 +8,11 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Signals;
public class SuperTrendIndicator : Indicator
public class SuperTrendIndicatorBase : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
public SuperTrendIndicator(string name, int period, double multiplier) : base(name, IndicatorType.SuperTrend)
public SuperTrendIndicatorBase(string name, int period, double multiplier) : base(name, IndicatorType.SuperTrend)
{
Signals = new List<LightSignal>();
Period = period;
@@ -19,18 +20,17 @@ public class SuperTrendIndicator : Indicator
MinimumHistory = 100 + Period.Value;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= MinimumHistory)
if (candles.Count <= MinimumHistory)
{
return null;
}
try
{
var superTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue)
.ToList();
var superTrendCandle = MapSuperTrendToCandle(superTrend, Candles.TakeLast(MinimumHistory));
var superTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue);
var superTrendCandle = MapSuperTrendToCandle(superTrend, candles.TakeLast(MinimumHistory));
if (superTrendCandle.Count == 0)
return null;
@@ -70,16 +70,17 @@ public class SuperTrendIndicator : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
SuperTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue)
SuperTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue)
.ToList()
};
}
private List<CandleSuperTrend> MapSuperTrendToCandle(List<SuperTrendResult> superTrend, IEnumerable<Candle> candles)
private List<CandleSuperTrend> MapSuperTrendToCandle(IEnumerable<SuperTrendResult> superTrend,
IEnumerable<Candle> candles)
{
var superTrends = new List<CandleSuperTrend>();
foreach (var candle in candles)

View File

@@ -1,4 +1,5 @@
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Shared.Rules;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Strategies.Rules;
@@ -6,9 +7,9 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Signals
{
public class ThreeWhiteSoldiersIndicator : Indicator
public class ThreeWhiteSoldiersIndicatorBase : IndicatorBase
{
public ThreeWhiteSoldiersIndicator(string name, int period)
public ThreeWhiteSoldiersIndicatorBase(string name, int period)
: base(name, IndicatorType.ThreeWhiteSoldiers)
{
Period = period;
@@ -16,18 +17,18 @@ namespace Managing.Domain.Strategies.Signals
public TradeDirection Direction { get; }
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
var signals = new List<LightSignal>();
if (Candles.Count <= 3)
if (candles.Count <= 3)
{
return null;
}
try
{
var lastFourCandles = Candles.TakeLast(4);
var lastFourCandles = candles.TakeLast(4);
Candle previousCandles = null;
foreach (var currentCandle in lastFourCandles)
@@ -52,7 +53,7 @@ namespace Managing.Domain.Strategies.Signals
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
throw new NotImplementedException();
}

View File

@@ -1,4 +1,6 @@
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;
@@ -6,27 +8,27 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Trends;
public class EmaTrendIndicator : EmaBaseIndicator
public class EmaTrendIndicatorBase : EmaBaseIndicatorBase
{
public List<LightSignal> Signals { get; set; }
public EmaTrendIndicator(string name, int period) : base(name, IndicatorType.EmaTrend)
public EmaTrendIndicatorBase(string name, int period) : base(name, IndicatorType.EmaTrend)
{
Signals = new List<LightSignal>();
Period = period;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= 2 * Period)
if (candles.Count <= 2 * Period)
{
return null;
}
try
{
var ema = Candles.GetEma(Period.Value).ToList();
var emaCandles = MapEmaToCandle(ema, Candles.TakeLast(Period.Value));
var ema = candles.GetEma(Period.Value).ToList();
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value));
if (ema.Count == 0)
return null;
@@ -54,11 +56,11 @@ public class EmaTrendIndicator : EmaBaseIndicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
Ema = Candles.GetEma(Period.Value).ToList()
Ema = candles.GetEma(Period.Value).ToList()
};
}

View File

@@ -1,5 +1,6 @@
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;
@@ -7,11 +8,11 @@ using static Managing.Common.Enums;
namespace Managing.Domain.Strategies.Trends;
public class StochRsiTrendIndicator : Indicator
public class StochRsiTrendIndicatorBase : IndicatorBase
{
public List<LightSignal> Signals { get; set; }
public StochRsiTrendIndicator(
public StochRsiTrendIndicatorBase(
string name,
int period,
int stochPeriod,
@@ -25,21 +26,21 @@ public class StochRsiTrendIndicator : Indicator
Period = period;
}
public override List<LightSignal> Run()
public override List<LightSignal> Run(HashSet<Candle> candles)
{
if (Candles.Count <= 10 * Period + 50)
if (candles.Count <= 10 * Period + 50)
{
return null;
}
try
{
var stochRsi = Candles
var stochRsi = candles
.GetStochRsi(Period.Value, StochPeriods.Value, SignalPeriods.Value, SmoothPeriods.Value)
.RemoveWarmupPeriods().ToList();
var stochRsiCandles = MapStochRsiToCandle(stochRsi, Candles.TakeLast(Period.Value));
.RemoveWarmupPeriods();
var stochRsiCandles = MapStochRsiToCandle(stochRsi, candles.TakeLast(Period.Value));
if (stochRsi.Count == 0)
if (stochRsi.Count() == 0)
return null;
var previousCandle = stochRsiCandles[0];
@@ -65,16 +66,16 @@ public class StochRsiTrendIndicator : Indicator
}
}
public override IndicatorsResultBase GetIndicatorValues()
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
{
return new IndicatorsResultBase()
{
StochRsi = Candles.GetStochRsi(Period.Value, StochPeriods.Value, SignalPeriods.Value, SmoothPeriods.Value)
StochRsi = candles.GetStochRsi(Period.Value, StochPeriods.Value, SignalPeriods.Value, SmoothPeriods.Value)
.ToList()
};
}
private List<CandleStochRsi> MapStochRsiToCandle(List<StochRsiResult> ema, IEnumerable<Candle> candles)
private List<CandleStochRsi> MapStochRsiToCandle(IEnumerable<StochRsiResult> ema, IEnumerable<Candle> candles)
{
var emaList = new List<CandleStochRsi>();
foreach (var candle in candles)

View File

@@ -30,7 +30,7 @@ public class LightScenario
{
var lightScenario = new LightScenario(scenario.Name, scenario.LoopbackPeriod)
{
Indicators = scenario.Indicators?.Select(LightIndicator.FromIndicator).ToList() ??
Indicators = scenario.Indicators?.Select(LightIndicator.BaseToLight).ToList() ??
new List<LightIndicator>()
};
return lightScenario;
@@ -43,15 +43,8 @@ public class LightScenario
{
var scenario = new Scenario(Name, LoopbackPeriod)
{
Indicators = Indicators?.Select(li => li.ToIndicator()).ToList() ?? new List<Indicator>()
Indicators = Indicators?.Select(li => li.LightToBase()).ToList()
};
return scenario;
}
public void AddIndicator(LightIndicator indicator)
{
if (Indicators == null)
Indicators = new List<LightIndicator>();
Indicators.Add(indicator);
}
}

View File

@@ -10,23 +10,19 @@ namespace Managing.Domain.Scenarios
public Scenario(string name, int? loopbackPeriod = 1)
{
Name = name;
Indicators = new List<Indicator>();
Indicators = new List<IndicatorBase>();
LoopbackPeriod = loopbackPeriod;
}
[Id(0)]
public string Name { get; set; }
[Id(1)]
public List<Indicator> Indicators { get; set; }
[Id(2)]
public int? LoopbackPeriod { get; set; }
[Id(3)]
public User User { get; set; }
[Id(0)] public string Name { get; set; }
public void AddIndicator(Indicator indicator)
[Id(1)] public List<IndicatorBase> Indicators { get; set; }
[Id(2)] public int? LoopbackPeriod { get; set; }
[Id(3)] public User User { get; set; }
public void AddIndicator(IndicatorBase indicator)
{
Indicators.Add(indicator);
}

View File

@@ -1,52 +1,97 @@
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Candles;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Context;
using Managing.Domain.Strategies.Signals;
using Managing.Domain.Strategies.Trends;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Domain.Scenarios;
public static class ScenarioHelpers
{
public static IEnumerable<IIndicator> GetIndicatorsFromScenario(Scenario scenario)
/// <summary>
/// Compares two lists of indicators and returns a list of changes (added, removed, modified).
/// </summary>
/// <param name="oldIndicators">The previous list of indicators</param>
/// <param name="newIndicators">The new list of indicators</param>
/// <returns>A list of change descriptions</returns>
public static List<string> CompareIndicators(List<LightIndicator> oldIndicators, List<LightIndicator> newIndicators)
{
var strategies = new List<IIndicator>();
foreach (var strategy in scenario.Indicators)
var changes = new List<string>();
// Create dictionaries for easier comparison using Type as key
var oldIndicatorDict = oldIndicators.ToDictionary(i => i.Type, i => i);
var newIndicatorDict = newIndicators.ToDictionary(i => i.Type, i => i);
// Find removed indicators
var removedTypes = oldIndicatorDict.Keys.Except(newIndicatorDict.Keys);
foreach (var removedType in removedTypes)
{
var result = BuildIndicator(strategy);
strategies.Add(result);
var indicator = oldIndicatorDict[removedType];
changes.Add($" **Removed Indicator:** {removedType} ({indicator.GetType().Name})");
}
return strategies;
// Find added indicators
var addedTypes = newIndicatorDict.Keys.Except(oldIndicatorDict.Keys);
foreach (var addedType in addedTypes)
{
var indicator = newIndicatorDict[addedType];
changes.Add($" **Added Indicator:** {addedType} ({indicator.GetType().Name})");
}
// Find modified indicators (same type but potentially different configuration)
var commonTypes = oldIndicatorDict.Keys.Intersect(newIndicatorDict.Keys);
foreach (var commonType in commonTypes)
{
var oldIndicator = oldIndicatorDict[commonType];
var newIndicator = newIndicatorDict[commonType];
// Compare indicators by serializing them (simple way to detect configuration changes)
var oldSerialized = JsonConvert.SerializeObject(oldIndicator, Formatting.None);
var newSerialized = JsonConvert.SerializeObject(newIndicator, Formatting.None);
if (oldSerialized != newSerialized)
{
changes.Add($"🔄 **Modified Indicator:** {commonType} ({newIndicator.GetType().Name})");
}
}
// Add summary if there are changes
if (changes.Any())
{
var summary =
$"📊 **Indicator Changes:** {addedTypes.Count()} added, {removedTypes.Count()} removed, {commonTypes.Count(c => JsonConvert.SerializeObject(oldIndicatorDict[c]) != JsonConvert.SerializeObject(newIndicatorDict[c]))} modified";
changes.Insert(0, summary);
}
return changes;
}
public static IIndicator BuildIndicator(Indicator indicator, int size = 600)
public static IIndicator BuildIndicator(LightIndicator indicator)
{
IIndicator result = indicator.Type switch
{
IndicatorType.StDev => new StDevContext(indicator.Name, indicator.Period.Value),
IndicatorType.RsiDivergence => new RsiDivergenceIndicator(indicator.Name,
IndicatorType.RsiDivergence => new RsiDivergenceIndicatorBase(indicator.Name,
indicator.Period.Value),
IndicatorType.RsiDivergenceConfirm => new RsiDivergenceConfirmIndicator(indicator.Name,
IndicatorType.RsiDivergenceConfirm => new RsiDivergenceConfirmIndicatorBase(indicator.Name,
indicator.Period.Value),
IndicatorType.MacdCross => new MacdCrossIndicator(indicator.Name,
IndicatorType.MacdCross => new MacdCrossIndicatorBase(indicator.Name,
indicator.FastPeriods.Value, indicator.SlowPeriods.Value, indicator.SignalPeriods.Value),
IndicatorType.EmaCross => new EmaCrossIndicator(indicator.Name, indicator.Period.Value),
IndicatorType.DualEmaCross => new DualEmaCrossIndicator(indicator.Name,
IndicatorType.EmaCross => new EmaCrossIndicatorBase(indicator.Name, indicator.Period.Value),
IndicatorType.DualEmaCross => new DualEmaCrossIndicatorBase(indicator.Name,
indicator.FastPeriods.Value, indicator.SlowPeriods.Value),
IndicatorType.ThreeWhiteSoldiers => new ThreeWhiteSoldiersIndicator(indicator.Name,
IndicatorType.ThreeWhiteSoldiers => new ThreeWhiteSoldiersIndicatorBase(indicator.Name,
indicator.Period.Value),
IndicatorType.SuperTrend => new SuperTrendIndicator(indicator.Name,
IndicatorType.SuperTrend => new SuperTrendIndicatorBase(indicator.Name,
indicator.Period.Value, indicator.Multiplier.Value),
IndicatorType.ChandelierExit => new ChandelierExitIndicator(indicator.Name,
IndicatorType.ChandelierExit => new ChandelierExitIndicatorBase(indicator.Name,
indicator.Period.Value, indicator.Multiplier.Value),
IndicatorType.EmaTrend => new EmaTrendIndicator(indicator.Name, indicator.Period.Value),
IndicatorType.StochRsiTrend => new StochRsiTrendIndicator(indicator.Name,
IndicatorType.EmaTrend => new EmaTrendIndicatorBase(indicator.Name, indicator.Period.Value),
IndicatorType.StochRsiTrend => new StochRsiTrendIndicatorBase(indicator.Name,
indicator.Period.Value, indicator.StochPeriods.Value, indicator.SignalPeriods.Value,
indicator.SmoothPeriods.Value),
IndicatorType.Stc => new StcIndicator(indicator.Name, indicator.CyclePeriods.Value,
IndicatorType.Stc => new StcIndicatorBase(indicator.Name, indicator.CyclePeriods.Value,
indicator.FastPeriods.Value, indicator.SlowPeriods.Value),
IndicatorType.LaggingStc => new LaggingSTC(indicator.Name, indicator.CyclePeriods.Value,
indicator.FastPeriods.Value, indicator.SlowPeriods.Value),
@@ -55,11 +100,10 @@ public static class ScenarioHelpers
_ => throw new NotImplementedException(),
};
result.Candles = new FixedSizeQueue<Candle>(size);
return result;
}
public static Indicator BuildIndicator(
public static IIndicator BuildIndicator(
IndicatorType type,
string name,
int? period = null,
@@ -71,7 +115,7 @@ public static class ScenarioHelpers
int? smoothPeriods = null,
int? cyclePeriods = null)
{
var indicator = new Indicator(name, type);
IIndicator indicator = null;
switch (type)
{

View File

@@ -1,6 +1,8 @@
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
@@ -49,28 +51,28 @@ public static class TradingBox
{
private static readonly IndicatorComboConfig _defaultConfig = new();
public static LightSignal GetSignal(HashSet<Candle> newCandles, HashSet<IIndicator> strategies,
HashSet<LightSignal> previousSignal, int? loopbackPeriod = 1)
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario scenario,
Dictionary<string, LightSignal> previousSignal, int? loopbackPeriod = 1)
{
return GetSignal(newCandles, strategies, previousSignal, _defaultConfig, loopbackPeriod);
return GetSignal(newCandles, scenario, previousSignal, _defaultConfig, loopbackPeriod);
}
public static LightSignal GetSignal(HashSet<Candle> newCandles, HashSet<IIndicator> strategies,
HashSet<LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1)
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario lightScenario,
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1)
{
var signalOnCandles = new List<LightSignal>();
var limitedCandles = newCandles.ToList().TakeLast(600).ToList();
foreach (var strategy in strategies)
foreach (var indicator in lightScenario.Indicators)
{
strategy.UpdateCandles(limitedCandles.ToHashSet());
var signals = strategy.Run();
IIndicator indicatorInstance = indicator.ToInterface();
var signals = indicatorInstance.Run(newCandles);
if (signals == null || signals.Count == 0)
if (signals == null || signals.Count() == 0)
{
// For trend and context strategies, lack of signal might be meaningful
// Signal strategies are expected to be sparse, so we continue
if (strategy.SignalType == SignalType.Signal)
if (indicator.SignalType == SignalType.Signal)
{
continue;
}
@@ -96,10 +98,10 @@ public static class TradingBox
foreach (var signal in signals.Where(s => s.Date >= loopbackStartDate))
{
var hasExistingSignal = previousSignal.Any(s => s.Identifier == signal.Identifier);
var hasExistingSignal = previousSignal.ContainsKey(signal.Identifier);
if (!hasExistingSignal)
{
bool shouldAdd = previousSignal.Count == 0 || previousSignal.Last().Date < signal.Date;
bool shouldAdd = previousSignal.Count == 0 || previousSignal.Values.Last().Date < signal.Date;
if (shouldAdd)
{
signalOnCandles.Add(signal);
@@ -122,22 +124,22 @@ public static class TradingBox
}
var data = newCandles.First();
return ComputeSignals(strategies, latestSignalsPerIndicator, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
return ComputeSignals(lightScenario, latestSignalsPerIndicator, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
data.Timeframe, config);
}
public static LightSignal ComputeSignals(HashSet<IIndicator> strategies, HashSet<LightSignal> signalOnCandles,
public static LightSignal ComputeSignals(LightScenario scenario, HashSet<LightSignal> signalOnCandles,
Ticker ticker,
Timeframe timeframe)
{
return ComputeSignals(strategies, signalOnCandles, ticker, timeframe, _defaultConfig);
return ComputeSignals(scenario, signalOnCandles, ticker, timeframe, _defaultConfig);
}
public static LightSignal ComputeSignals(HashSet<IIndicator> strategies, HashSet<LightSignal> signalOnCandles,
public static LightSignal ComputeSignals(LightScenario scenario, HashSet<LightSignal> signalOnCandles,
Ticker ticker,
Timeframe timeframe, IndicatorComboConfig config)
{
if (strategies.Count == 1)
if (scenario.Indicators.Count == 1)
{
// Only one strategy, return the single signal
return signalOnCandles.Single();
@@ -146,7 +148,7 @@ public static class TradingBox
signalOnCandles = signalOnCandles.OrderBy(s => s.Date).ToHashSet();
// Check if all strategies produced signals - this is required for composite signals
var strategyNames = strategies.Select(s => s.Name).ToHashSet();
var strategyNames = scenario.Indicators.Select(s => s.Name).ToHashSet();
var signalIndicatorNames = signalOnCandles.Select(s => s.IndicatorName).ToHashSet();
if (!strategyNames.SetEquals(signalIndicatorNames))
@@ -161,7 +163,7 @@ public static class TradingBox
var contextSignals = signalOnCandles.Where(s => s.SignalType == SignalType.Context).ToList();
// Context validation - evaluates market conditions based on confidence levels
if (!ValidateContextStrategies(strategies, contextSignals, config))
if (!ValidateContextStrategies(scenario, contextSignals, config))
{
return null; // Context strategies are blocking the trade
}
@@ -233,10 +235,10 @@ public static class TradingBox
/// <summary>
/// Validates context strategies based on confidence levels indicating market condition quality
/// </summary>
private static bool ValidateContextStrategies(HashSet<IIndicator> allStrategies, List<LightSignal> contextSignals,
private static bool ValidateContextStrategies(LightScenario scenario, List<LightSignal> contextSignals,
IndicatorComboConfig config)
{
var contextStrategiesCount = allStrategies.Count(s => s.SignalType == SignalType.Context);
var contextStrategiesCount = scenario.Indicators.Count(s => s.SignalType == SignalType.Context);
if (contextStrategiesCount == 0)
{
@@ -453,11 +455,11 @@ public static class TradingBox
/// </summary>
/// <param name="positions">List of positions to analyze</param>
/// <returns>The total volume traded in decimal</returns>
public static decimal GetTotalVolumeTraded(List<Position> positions)
public static decimal GetTotalVolumeTraded(Dictionary<Guid, Position> positions)
{
decimal totalVolume = 0;
foreach (var position in positions)
foreach (var position in positions.Values)
{
// Add entry volume
totalVolume += position.Open.Quantity * position.Open.Price;
@@ -487,12 +489,12 @@ public static class TradingBox
/// </summary>
/// <param name="positions">List of positions to analyze</param>
/// <returns>The volume traded in the last 24 hours in decimal</returns>
public static decimal GetLast24HVolumeTraded(List<Position> positions)
public static decimal GetLast24HVolumeTraded(Dictionary<Guid, Position> positions)
{
decimal last24hVolume = 0;
DateTime cutoff = DateTime.UtcNow.AddHours(-24);
foreach (var position in positions)
foreach (var position in positions.Values)
{
// Check if any part of this position was traded in the last 24 hours
@@ -528,24 +530,20 @@ public static class TradingBox
/// </summary>
/// <param name="positions">List of positions to analyze</param>
/// <returns>A tuple containing (wins, losses)</returns>
public static (int Wins, int Losses) GetWinLossCount(List<Position> positions)
public static (int Wins, int Losses) GetWinLossCount(Dictionary<Guid, Position> positions)
{
int wins = 0;
int losses = 0;
foreach (var position in positions)
foreach (var position in positions.Values)
{
// Only count finished positions
if (position.IsFinished())
if (position.ProfitAndLoss != null && position.ProfitAndLoss.Realized > 0)
{
if (position.ProfitAndLoss != null && position.ProfitAndLoss.Realized > 0)
{
wins++;
}
else
{
losses++;
}
wins++;
}
else
{
losses++;
}
}
@@ -557,13 +555,13 @@ public static class TradingBox
/// </summary>
/// <param name="positions">List of positions to analyze</param>
/// <returns>The ROI for the last 24 hours as a percentage</returns>
public static decimal GetLast24HROI(List<Position> positions)
public static decimal GetLast24HROI(Dictionary<Guid, Position> positions)
{
decimal profitLast24h = 0;
decimal investmentLast24h = 0;
DateTime cutoff = DateTime.UtcNow.AddHours(-24);
foreach (var position in positions)
foreach (var position in positions.Values)
{
// Only count positions that were opened or closed within the last 24 hours
if (position.IsFinished() &&

View File

@@ -1,5 +1,6 @@
using Exilion.TradingAtomics;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Statistics;
using static Managing.Common.Enums;
@@ -7,7 +8,7 @@ namespace Managing.Domain.Shared.Helpers;
public static class TradingHelpers
{
public static decimal GetHodlPercentage(Candles.Candle candle1, Candles.Candle candle2)
public static decimal GetHodlPercentage(Candle candle1, Candle candle2)
{
return candle2.Close * 100 / candle1.Close - 100;
}

View File

@@ -0,0 +1,47 @@
using Managing.Domain.Users;
using Orleans;
namespace Managing.Domain.Statistics;
[GenerateSerializer]
public class AgentSummary
{
[Id(0)]
public int Id { get; set; }
[Id(1)]
public int UserId { get; set; }
[Id(2)]
public string AgentName { get; set; }
[Id(3)]
public decimal TotalPnL { get; set; }
[Id(4)]
public decimal TotalROI { get; set; }
[Id(5)]
public int Wins { get; set; }
[Id(6)]
public int Losses { get; set; }
[Id(7)]
public DateTime? Runtime { get; set; }
[Id(8)]
public DateTime CreatedAt { get; set; }
[Id(9)]
public DateTime UpdatedAt { get; set; }
[Id(10)]
public User User { get; set; }
[Id(11)]
public int ActiveStrategiesCount { get; set; }
[Id(12)]
public decimal TotalVolume { get; set; }
}

View File

@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Managing.Common;
using Managing.Domain.Indicators;
using Managing.Domain.Scenarios;
namespace Managing.Domain.Statistics;

View File

@@ -1,82 +0,0 @@
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Candles;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Domain.Strategies
{
public class Indicator : IIndicator
{
public Indicator(string name, IndicatorType type)
{
Name = name;
Type = type;
SignalType = ScenarioHelpers.GetSignalType(type);
Candles = new FixedSizeQueue<Candle>(500);
}
public string Name { get; set; }
public FixedSizeQueue<Candle> Candles { get; set; }
public IndicatorType Type { get; set; }
public SignalType SignalType { get; set; }
public int MinimumHistory { get; set; }
public int? Period { get; set; }
public int? FastPeriods { get; set; }
public int? SlowPeriods { get; set; }
public int? SignalPeriods { get; set; }
public double? Multiplier { get; set; }
public int? SmoothPeriods { get; set; }
public int? StochPeriods { get; set; }
public int? CyclePeriods { get; set; }
public User User { get; set; }
public virtual List<LightSignal> Run()
{
return new List<LightSignal>();
}
public virtual IndicatorsResultBase GetIndicatorValues()
{
return new IndicatorsResultBase();
}
public void UpdateCandles(HashSet<Candle> newCandles)
{
if (newCandles == null || newCandles.Count == 0)
{
return;
}
lock (Candles)
{
foreach (var item in newCandles.ToList())
{
if (Candles.All(c => c.Date != item.Date))
{
Candles.Enqueue(item);
}
}
}
}
public string GetName()
{
return Name;
}
}
}

View File

@@ -1,76 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Managing.Core;
using Managing.Domain.Candles;
using Orleans;
using static Managing.Common.Enums;
[GenerateSerializer]
public class LightSignal : ValueObject
{
public LightSignal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date,
TradingExchanges exchange, IndicatorType indicatorType, SignalType signalType, string indicatorName)
{
Direction = direction;
Confidence = confidence;
Candle = candle;
Date = date;
Ticker = ticker;
Exchange = exchange;
Status = SignalStatus.WaitingForPosition;
IndicatorType = indicatorType;
IndicatorName = indicatorName;
SignalType = signalType;
Identifier =
$"{indicatorName}-{indicatorType}-{direction}-{ticker}-{candle?.Close.ToString(CultureInfo.InvariantCulture)}-{date:yyyyMMdd-HHmmss}";
}
[Id(0)]
[Required] public SignalStatus Status { get; set; }
[Id(1)]
[Required] public TradeDirection Direction { get; }
[Id(2)]
[Required] public Confidence Confidence { get; set; }
[Id(3)]
[Required] public Timeframe Timeframe { get; }
[Id(4)]
[Required] public DateTime Date { get; private set; }
[Id(5)]
[Required] public Candle Candle { get; }
[Id(6)]
[Required] public string Identifier { get; }
[Id(7)]
[Required] public Ticker Ticker { get; }
[Id(8)]
[Required] public TradingExchanges Exchange { get; set; }
[Id(9)]
[Required] public IndicatorType IndicatorType { get; set; }
[Id(10)]
[Required] public SignalType SignalType { get; set; }
[Id(11)]
[Required] public string IndicatorName { get; set; }
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Direction;
yield return Confidence;
yield return Date;
}
public void SetConfidence(Confidence confidence)
{
Confidence = confidence;
}
}

View File

@@ -9,7 +9,7 @@ namespace Managing.Domain.Trades
[GenerateSerializer]
public class Position
{
public Position(string identifier, string accountName, TradeDirection originDirection, Ticker ticker,
public Position(Guid identifier, string accountName, TradeDirection originDirection, Ticker ticker,
LightMoneyManagement moneyManagement, PositionInitiator initiator, DateTime date, User user)
{
Identifier = identifier;
@@ -23,23 +23,20 @@ namespace Managing.Domain.Trades
User = user;
}
[Id(0)]
[Required] public string AccountName { get; set; }
[Id(1)]
[Required] public DateTime Date { get; set; }
[Id(2)]
[Required] public TradeDirection OriginDirection { get; set; }
[Id(3)]
[Required] public Ticker Ticker { get; set; }
[Id(4)]
[Required] public LightMoneyManagement MoneyManagement { get; set; }
[Id(0)] [Required] public string AccountName { get; set; }
[Id(1)] [Required] public DateTime Date { get; set; }
[Id(2)] [Required] public TradeDirection OriginDirection { get; set; }
[Id(3)] [Required] public Ticker Ticker { get; set; }
[Id(4)] [Required] public LightMoneyManagement MoneyManagement { get; set; }
[Id(5)]
[Required] [JsonPropertyName("Open")] public Trade Open { get; set; }
[Required]
[JsonPropertyName("Open")]
public Trade Open { get; set; }
[Id(6)]
[Required]
@@ -52,25 +49,22 @@ namespace Managing.Domain.Trades
public Trade TakeProfit1 { get; set; }
[Id(8)]
[JsonPropertyName("TakeProfit2")] public Trade TakeProfit2 { get; set; }
[JsonPropertyName("TakeProfit2")]
public Trade TakeProfit2 { get; set; }
[Id(9)]
[JsonPropertyName("ProfitAndLoss")] public ProfitAndLoss ProfitAndLoss { get; set; }
[Id(10)]
[Required] public PositionStatus Status { get; set; }
[Id(11)]
public string SignalIdentifier { get; set; }
[Id(12)]
[Required] public string Identifier { get; set; }
[Id(13)]
[Required] public PositionInitiator Initiator { get; set; }
[Id(14)]
[Required] public User User { get; set; }
[JsonPropertyName("ProfitAndLoss")]
public ProfitAndLoss ProfitAndLoss { get; set; }
[Id(10)] [Required] public PositionStatus Status { get; set; }
[Id(11)] public string SignalIdentifier { get; set; }
[Id(12)] [Required] public Guid Identifier { get; set; }
[Id(13)] [Required] public PositionInitiator Initiator { get; set; }
[Id(14)] [Required] public User User { get; set; }
public bool IsFinished()
{

View File

@@ -7,17 +7,20 @@ namespace Managing.Domain.Users;
public class User
{
[Id(0)]
public int Id { get; set; }
[Id(1)]
public string Name { get; set; }
[Id(1)]
[Id(2)]
public List<Account> Accounts { get; set; }
[Id(2)]
[Id(3)]
public string AgentName { get; set; }
[Id(3)]
[Id(4)]
public string AvatarUrl { get; set; }
[Id(4)]
[Id(5)]
public string TelegramChannel { get; set; }
}