docker files fixes from liaqat
This commit is contained in:
19
src/Managing.Domain/Accounts/Account.cs
Normal file
19
src/Managing.Domain/Accounts/Account.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Managing.Domain.Users;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Accounts;
|
||||
|
||||
public class Account
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public TradingExchanges Exchange { get; set; }
|
||||
[Required]
|
||||
public AccountType Type { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Secret { get; set; }
|
||||
public User User { get; set; }
|
||||
public List<Balance> Balances { get; set; }
|
||||
}
|
||||
14
src/Managing.Domain/Accounts/Balance.cs
Normal file
14
src/Managing.Domain/Accounts/Balance.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Managing.Domain.Evm;
|
||||
|
||||
namespace Managing.Domain.Accounts;
|
||||
|
||||
public class Balance
|
||||
{
|
||||
public string TokenImage { get; set; }
|
||||
public string TokenName { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public string TokenAdress { get; set; }
|
||||
public Chain Chain { get; set; }
|
||||
}
|
||||
74
src/Managing.Domain/Backtests/Backtest.cs
Normal file
74
src/Managing.Domain/Backtests/Backtest.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Exilion.TradingAtomics;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Backtests;
|
||||
|
||||
public class Backtest
|
||||
{
|
||||
public Backtest(
|
||||
Ticker ticker,
|
||||
string scenario,
|
||||
List<Position> positions,
|
||||
List<Signal> signals,
|
||||
Timeframe timeframe,
|
||||
List<Candle> candles,
|
||||
BotType botType,
|
||||
string accountName)
|
||||
{
|
||||
Ticker = ticker;
|
||||
Positions = positions;
|
||||
Signals = signals;
|
||||
Timeframe = timeframe;
|
||||
Candles = candles;
|
||||
Scenario = scenario;
|
||||
BotType = botType;
|
||||
AccountName = accountName;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public string Id { get; set; }
|
||||
[Required]
|
||||
public decimal FinalPnl { get; set; }
|
||||
[Required]
|
||||
public int WinRate { get; set; }
|
||||
[Required]
|
||||
public decimal GrowthPercentage { get; set; }
|
||||
[Required]
|
||||
public decimal HodlPercentage { get; set; }
|
||||
[Required]
|
||||
public Ticker Ticker { get; }
|
||||
[Required]
|
||||
public string Scenario { get; set; }
|
||||
[Required]
|
||||
public List<Position> Positions { get; }
|
||||
[Required]
|
||||
public List<Signal> Signals { get; }
|
||||
[Required]
|
||||
public Timeframe Timeframe { get; }
|
||||
[Required]
|
||||
public BotType BotType { get; }
|
||||
[Required]
|
||||
public string AccountName { get; }
|
||||
[Required]
|
||||
public List<Candle> Candles { get; }
|
||||
[Required]
|
||||
public PerformanceMetrics Statistics { get; set; }
|
||||
[Required]
|
||||
public decimal Fees { get; set; }
|
||||
[Required]
|
||||
public List<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; }
|
||||
[Required]
|
||||
public MoneyManagement OptimizedMoneyManagement { get; set; }
|
||||
[Required]
|
||||
public MoneyManagement MoneyManagement { get; set; }
|
||||
|
||||
public string GetStringReport()
|
||||
{
|
||||
return $"{Ticker} | {Timeframe} | Positions: {Positions.Count} | Winrate: {WinRate}% | Pnl: {FinalPnl:#.##}$ | %Pnl: {GrowthPercentage:#.##}% | %Hodl: {HodlPercentage:#.##}%";
|
||||
}
|
||||
}
|
||||
75
src/Managing.Domain/Bots/Bot.cs
Normal file
75
src/Managing.Domain/Bots/Bot.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
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 int ExecutionCount;
|
||||
public string Identifier { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Interval { get; set; }
|
||||
public BotStatus Status { get; 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;
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
Status = BotStatus.Up;
|
||||
}
|
||||
|
||||
public async Task InitWorker(Func<Task> action)
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
while (Status == BotStatus.Up)
|
||||
{
|
||||
try
|
||||
{
|
||||
await action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
ExecutionCount++;
|
||||
await Task.Delay(Interval, CancellationToken.Token);
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
}
|
||||
}, CancellationToken.Token);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Status = BotStatus.Down;
|
||||
//CancellationToken.Cancel();
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
Status = BotStatus.Up;
|
||||
}
|
||||
|
||||
public string GetStatus()
|
||||
{
|
||||
return Status.ToString();
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Managing.Domain/Bots/IBot.cs
Normal file
12
src/Managing.Domain/Bots/IBot.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Managing.Domain.Bots
|
||||
{
|
||||
public interface IBot
|
||||
{
|
||||
string Name { get; set; }
|
||||
void Start();
|
||||
void Stop();
|
||||
void Restart();
|
||||
string GetStatus();
|
||||
string GetName();
|
||||
}
|
||||
}
|
||||
34
src/Managing.Domain/Candles/Candle.cs
Normal file
34
src/Managing.Domain/Candles/Candle.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Managing.Common;
|
||||
using Skender.Stock.Indicators;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Managing.Domain.Candles
|
||||
{
|
||||
public class Candle : IQuote
|
||||
{
|
||||
[Required]
|
||||
public Enums.TradingExchanges Exchange { get; set; }
|
||||
[Required]
|
||||
public string Ticker { get; set; }
|
||||
[Required]
|
||||
public DateTime OpenTime { get; set; }
|
||||
[Required]
|
||||
public DateTime Date { get; set; }
|
||||
[Required]
|
||||
public decimal Open { get; set; }
|
||||
[Required]
|
||||
public decimal Close { get; set; }
|
||||
public decimal Volume { get; }
|
||||
[Required]
|
||||
public decimal High { get; set; }
|
||||
[Required]
|
||||
public decimal Low { get; set; }
|
||||
public decimal BaseVolume { get; set; }
|
||||
public decimal QuoteVolume { get; set; }
|
||||
public int TradeCount { get; set; }
|
||||
public decimal TakerBuyBaseVolume { get; set; }
|
||||
public decimal TakerBuyQuoteVolume { get; set; }
|
||||
[Required]
|
||||
public Enums.Timeframe Timeframe { get; set; }
|
||||
}
|
||||
}
|
||||
71
src/Managing.Domain/Candles/CandleExtensions.cs
Normal file
71
src/Managing.Domain/Candles/CandleExtensions.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Candles;
|
||||
|
||||
public static class CandleExtensions
|
||||
{
|
||||
|
||||
public static Candle SetupClosingCandle(this Candle candle)
|
||||
{
|
||||
|
||||
return candle;
|
||||
}
|
||||
|
||||
public static DateTime GetBotPreloadSinceFromTimeframe(Timeframe timeframe)
|
||||
{
|
||||
return timeframe switch
|
||||
{
|
||||
Timeframe.FiveMinutes => DateTime.UtcNow.AddDays(-1),
|
||||
Timeframe.FifteenMinutes => DateTime.UtcNow.AddDays(-5),
|
||||
Timeframe.ThirtyMinutes => DateTime.UtcNow.AddDays(-10),
|
||||
Timeframe.OneHour => DateTime.UtcNow.AddDays(-30),
|
||||
Timeframe.FourHour => DateTime.UtcNow.AddDays(-60),
|
||||
Timeframe.OneDay => DateTime.UtcNow.AddDays(-360),
|
||||
_ => DateTime.Now.AddHours(-15),
|
||||
};
|
||||
}
|
||||
|
||||
public static DateTime GetPreloadSinceFromTimeframe(Timeframe timeframe)
|
||||
{
|
||||
return DateTime.UtcNow.AddDays(GetMinimalDays(timeframe));
|
||||
}
|
||||
|
||||
public static int GetIntervalFromTimeframe(Timeframe timeframe)
|
||||
{
|
||||
return timeframe switch
|
||||
{
|
||||
Timeframe.OneDay => 3600000, // 1h
|
||||
Timeframe.FiveMinutes => 120000, // 2min
|
||||
Timeframe.FifteenMinutes => 60000, // 1 min
|
||||
Timeframe.OneHour => 900000, // 15min
|
||||
_ => 300000, // 5min
|
||||
};
|
||||
}
|
||||
|
||||
public static int GetUnixInterval(this Timeframe timeframe)
|
||||
{
|
||||
return timeframe switch
|
||||
{
|
||||
Timeframe.OneDay => 86400,
|
||||
Timeframe.FiveMinutes => 300,
|
||||
Timeframe.FifteenMinutes => 900,
|
||||
Timeframe.FourHour => 14400,
|
||||
Timeframe.OneHour => 3600,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
||||
public static double GetMinimalDays(Timeframe timeframe)
|
||||
{
|
||||
return timeframe switch
|
||||
{
|
||||
Timeframe.FiveMinutes => -1,
|
||||
Timeframe.FifteenMinutes => -5,
|
||||
Timeframe.ThirtyMinutes => -10,
|
||||
Timeframe.OneHour => -30,
|
||||
Timeframe.FourHour => -60,
|
||||
Timeframe.OneDay => -360,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
8
src/Managing.Domain/Evm/Chain.cs
Normal file
8
src/Managing.Domain/Evm/Chain.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Managing.Domain.Evm;
|
||||
|
||||
public class Chain
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string RpcUrl { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
12
src/Managing.Domain/Evm/EvmBalance.cs
Normal file
12
src/Managing.Domain/Evm/EvmBalance.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Managing.Domain.Evm;
|
||||
|
||||
public class EvmBalance
|
||||
{
|
||||
public string TokenImage { get; set; }
|
||||
public string TokenName { get; set; }
|
||||
public decimal Balance { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public string TokenAddress { get; set; }
|
||||
public Chain Chain { get; set; }
|
||||
}
|
||||
14
src/Managing.Domain/Evm/Holder.cs
Normal file
14
src/Managing.Domain/Evm/Holder.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Managing.Domain.Evm;
|
||||
|
||||
public class Holder
|
||||
{
|
||||
public Holder(string holderAddress)
|
||||
{
|
||||
HolderAddress = holderAddress;
|
||||
Nfts = new List<Nft>();
|
||||
}
|
||||
|
||||
public string HolderAddress { get; set; }
|
||||
public decimal Yield { get; set; }
|
||||
public List<Nft> Nfts { get; set; }
|
||||
}
|
||||
31
src/Managing.Domain/Evm/HolderExtensions.cs
Normal file
31
src/Managing.Domain/Evm/HolderExtensions.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace Managing.Domain.Evm;
|
||||
|
||||
|
||||
public static class HolderExtensions
|
||||
{
|
||||
public static void AddNft(this Holder holder, Nft nft)
|
||||
{
|
||||
if (holder.Nfts.FirstOrDefault(n => n.TokenId == nft.TokenId) == null)
|
||||
{
|
||||
holder.Nfts.Add(nft);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Holder> GetYields(this List<Holder> holders, decimal yieldAmount)
|
||||
{
|
||||
var nftHeldCount = 0;
|
||||
foreach (var holder in holders)
|
||||
{
|
||||
nftHeldCount += holder.Nfts.Count;
|
||||
}
|
||||
|
||||
var yieldPerNft = yieldAmount / nftHeldCount;
|
||||
|
||||
foreach (var holder in holders)
|
||||
{
|
||||
holder.Yield = holder.Nfts.Count * yieldPerNft;
|
||||
}
|
||||
|
||||
return holders;
|
||||
}
|
||||
}
|
||||
17
src/Managing.Domain/Evm/Nft.cs
Normal file
17
src/Managing.Domain/Evm/Nft.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Managing.Domain.Evm;
|
||||
|
||||
public class Nft
|
||||
{
|
||||
public Nft(string contractAddress, BigInteger tokenId, DateTime blockDate)
|
||||
{
|
||||
ContractAddress = contractAddress;
|
||||
TokenId = tokenId;
|
||||
BlockDate = blockDate;
|
||||
}
|
||||
|
||||
public string ContractAddress { get; set; }
|
||||
public BigInteger TokenId { get; set; }
|
||||
public DateTime BlockDate { get; set; }
|
||||
}
|
||||
9
src/Managing.Domain/Evm/Subgraph.cs
Normal file
9
src/Managing.Domain/Evm/Subgraph.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Managing.Common;
|
||||
|
||||
namespace Managing.Domain.Evm;
|
||||
|
||||
public class Subgraph
|
||||
{
|
||||
public Enums.SubgraphProvider SubgraphProvider { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
19
src/Managing.Domain/Managing.Domain.csproj
Normal file
19
src/Managing.Domain/Managing.Domain.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Exilion.TradingAtomics" Version="1.0.4" />
|
||||
<PackageReference Include="Skender.Stock.Indicators" Version="2.4.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
|
||||
<ProjectReference Include="..\Managing.Core\Managing.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
28
src/Managing.Domain/MoneyManagements/MoneyManagement.cs
Normal file
28
src/Managing.Domain/MoneyManagements/MoneyManagement.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.MoneyManagements
|
||||
{
|
||||
public class MoneyManagement
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public Timeframe Timeframe { get; set; }
|
||||
[Required]
|
||||
public decimal BalanceAtRisk { get; set; }
|
||||
[Required]
|
||||
public decimal StopLoss { get; set; }
|
||||
[Required]
|
||||
public decimal TakeProfit { get; set; }
|
||||
[Required]
|
||||
public decimal Leverage { get; set; }
|
||||
|
||||
public void FormatPercentage()
|
||||
{
|
||||
StopLoss /= 100;
|
||||
TakeProfit /= 100;
|
||||
BalanceAtRisk /= 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Managing.Domain/Scenarios/Scenario.cs
Normal file
20
src/Managing.Domain/Scenarios/Scenario.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Managing.Domain.Strategies;
|
||||
|
||||
namespace Managing.Domain.Scenarios
|
||||
{
|
||||
public class Scenario
|
||||
{
|
||||
public Scenario(string name)
|
||||
{
|
||||
Name = name;
|
||||
Strategies = new List<Strategy>();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public List<Strategy> Strategies { get; set; }
|
||||
public void AddStrategy(Strategy strategy)
|
||||
{
|
||||
Strategies.Add(strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/Managing.Domain/Scenarios/ScenarioHelpers.cs
Normal file
148
src/Managing.Domain/Scenarios/ScenarioHelpers.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using Managing.Application.Strategies;
|
||||
using Managing.Domain.Strategies;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Scenarios;
|
||||
|
||||
public static class ScenarioHelpers
|
||||
{
|
||||
public static IEnumerable<IStrategy> GetStrategiesFromScenario(Scenario scenario)
|
||||
{
|
||||
var strategies = new List<IStrategy>();
|
||||
foreach (var strategy in scenario.Strategies)
|
||||
{
|
||||
IStrategy result = strategy.Type switch
|
||||
{
|
||||
StrategyType.StDev => new StDevContext(strategy.Name, strategy.Timeframe, strategy.Period.Value),
|
||||
StrategyType.RsiDivergence => new RSIDivergenceStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value),
|
||||
StrategyType.RsiDivergenceConfirm => new RSIDivergenceConfirmStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value),
|
||||
StrategyType.MacdCross => new MACDCrossStrategy(strategy.Name, strategy.Timeframe, strategy.FastPeriods.Value, strategy.SlowPeriods.Value, strategy.SignalPeriods.Value),
|
||||
StrategyType.EmaCross => new EmaCrossStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value),
|
||||
StrategyType.ThreeWhiteSoldiers => new ThreeWhiteSoldiersStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value),
|
||||
StrategyType.SuperTrend => new SuperTrendStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value, strategy.Multiplier.Value),
|
||||
StrategyType.ChandelierExit => new ChandelierExitStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value, strategy.Multiplier.Value),
|
||||
StrategyType.EmaTrend => new EmaTrendStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value),
|
||||
StrategyType.StochRsiTrend => new StochRsiTrendStrategy(strategy.Name, strategy.Timeframe, strategy.Period.Value, strategy.StochPeriods.Value, strategy.SignalPeriods.Value, strategy.SmoothPeriods.Value),
|
||||
StrategyType.Stc => new STCStrategy(strategy.Name, strategy.Timeframe, strategy.CyclePeriods.Value, strategy.FastPeriods.Value, strategy.SlowPeriods.Value),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
strategies.Add(result);
|
||||
}
|
||||
|
||||
return strategies;
|
||||
}
|
||||
|
||||
public static Strategy BuildStrategy(
|
||||
StrategyType type,
|
||||
Timeframe timeframe,
|
||||
string name,
|
||||
int? period = null,
|
||||
int? fastPeriods = null,
|
||||
int? slowPeriods = null,
|
||||
int? signalPeriods = null,
|
||||
double? multiplier = null,
|
||||
int? stochPeriods = null,
|
||||
int? smoothPeriods = null,
|
||||
int? cyclePeriods = null)
|
||||
{
|
||||
var strategy = new Strategy(name, timeframe, type);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case StrategyType.RsiDivergence:
|
||||
case StrategyType.RsiDivergenceConfirm:
|
||||
case StrategyType.EmaTrend:
|
||||
case StrategyType.EmaCross:
|
||||
case StrategyType.StDev:
|
||||
if (!period.HasValue)
|
||||
{
|
||||
throw new Exception($"Missing period for {strategy.Type} strategy type");
|
||||
}
|
||||
else
|
||||
{
|
||||
strategy.Period = period.Value;
|
||||
}
|
||||
break;
|
||||
case StrategyType.MacdCross:
|
||||
if (!fastPeriods.HasValue || !slowPeriods.HasValue || !signalPeriods.HasValue)
|
||||
{
|
||||
throw new Exception($"Missing fastPeriods or slowPeriods or signalPeriods, for {strategy.Type} strategy type");
|
||||
}
|
||||
else
|
||||
{
|
||||
strategy.FastPeriods = fastPeriods;
|
||||
strategy.SlowPeriods = slowPeriods;
|
||||
strategy.SignalPeriods = signalPeriods;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case StrategyType.ThreeWhiteSoldiers:
|
||||
break;
|
||||
case StrategyType.SuperTrend:
|
||||
case StrategyType.ChandelierExit:
|
||||
if (!period.HasValue || !multiplier.HasValue)
|
||||
{
|
||||
throw new Exception($"Missing period or multiplier, for {strategy.Type} strategy type");
|
||||
}
|
||||
else
|
||||
{
|
||||
strategy.Period = period;
|
||||
strategy.Multiplier = multiplier;
|
||||
}
|
||||
break;
|
||||
case StrategyType.StochRsiTrend:
|
||||
if (!period.HasValue
|
||||
|| !stochPeriods.HasValue
|
||||
|| !signalPeriods.HasValue
|
||||
|| !smoothPeriods.HasValue)
|
||||
{
|
||||
throw new Exception($"Missing period, stochPeriods, signalPeriods, smoothPeriods for {strategy.Type} strategy type");
|
||||
}
|
||||
else
|
||||
{
|
||||
strategy.Period = period;
|
||||
strategy.StochPeriods = stochPeriods;
|
||||
strategy.SignalPeriods = signalPeriods;
|
||||
strategy.SmoothPeriods = smoothPeriods;
|
||||
}
|
||||
break;
|
||||
case StrategyType.Stc:
|
||||
if (!fastPeriods.HasValue || !slowPeriods.HasValue || !cyclePeriods.HasValue)
|
||||
{
|
||||
throw new Exception($"Missing fastPeriods or slowPeriods or cyclePeriods, for {strategy.Type} strategy type");
|
||||
}
|
||||
else
|
||||
{
|
||||
strategy.FastPeriods = fastPeriods;
|
||||
strategy.SlowPeriods = slowPeriods;
|
||||
strategy.CyclePeriods = cyclePeriods;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public static SignalType GetSignalType(StrategyType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
StrategyType.RsiDivergence => SignalType.Signal,
|
||||
StrategyType.RsiDivergenceConfirm => SignalType.Signal,
|
||||
StrategyType.MacdCross => SignalType.Signal,
|
||||
StrategyType.EmaCross => SignalType.Signal,
|
||||
StrategyType.ThreeWhiteSoldiers => SignalType.Signal,
|
||||
StrategyType.SuperTrend => SignalType.Signal,
|
||||
StrategyType.ChandelierExit => SignalType.Signal,
|
||||
StrategyType.EmaTrend => SignalType.Trend,
|
||||
StrategyType.Composite => SignalType.Signal,
|
||||
StrategyType.StochRsiTrend => SignalType.Trend,
|
||||
StrategyType.Stc => SignalType.Signal,
|
||||
StrategyType.StDev => SignalType.Context,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
52
src/Managing.Domain/Shared/Helpers/RiskHelpers.cs
Normal file
52
src/Managing.Domain/Shared/Helpers/RiskHelpers.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Shared.Helpers
|
||||
{
|
||||
public static class RiskHelpers
|
||||
{
|
||||
public static decimal GetStopLossPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement)
|
||||
{
|
||||
return direction == TradeDirection.Long ?
|
||||
price -= price * moneyManagement.StopLoss :
|
||||
price += price * moneyManagement.StopLoss;
|
||||
}
|
||||
|
||||
public static decimal GetBalanceAtRisk(decimal balance, MoneyManagement moneyManagement)
|
||||
{
|
||||
decimal amountToRisk = balance * moneyManagement.BalanceAtRisk;
|
||||
|
||||
if (amountToRisk <= 1)
|
||||
{
|
||||
throw new Exception("Cannot open trade, not enough balance");
|
||||
}
|
||||
|
||||
return amountToRisk;
|
||||
}
|
||||
|
||||
public static decimal GetTakeProfitPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement, int count = 1)
|
||||
{
|
||||
decimal percentage = moneyManagement.TakeProfit * count;
|
||||
|
||||
price = direction == TradeDirection.Short ? price -= price * percentage : price += price * percentage;
|
||||
return price;
|
||||
}
|
||||
|
||||
public static RiskLevel GetRiskFromConfidence(Confidence confidence)
|
||||
{
|
||||
switch (confidence)
|
||||
{
|
||||
case Confidence.Low:
|
||||
return RiskLevel.Low;
|
||||
case Confidence.Medium:
|
||||
return RiskLevel.Medium;
|
||||
case Confidence.High:
|
||||
return RiskLevel.High;
|
||||
case Confidence.None:
|
||||
break;
|
||||
}
|
||||
|
||||
return RiskLevel.Low;
|
||||
}
|
||||
}
|
||||
}
|
||||
161
src/Managing.Domain/Shared/Helpers/TradingBox.cs
Normal file
161
src/Managing.Domain/Shared/Helpers/TradingBox.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Shared.Helpers;
|
||||
|
||||
public static class TradingBox
|
||||
{
|
||||
public static Signal GetSignal(HashSet<Candle> newCandles, HashSet<IStrategy> strategies, HashSet<Signal> previousSignal)
|
||||
{
|
||||
var signalOnCandles = new HashSet<Signal>();
|
||||
foreach (var strategy in strategies)
|
||||
{
|
||||
strategy.UpdateCandles(newCandles);
|
||||
var signals = strategy.Run();
|
||||
|
||||
if (signals == null || signals.Count == 0) continue;
|
||||
|
||||
foreach (var signal in signals.Where(s => s.Date == newCandles.Last().Date))
|
||||
{
|
||||
if (previousSignal.SingleOrDefault(s => s.Identifier == signal.Identifier) == null)
|
||||
{
|
||||
if (previousSignal.Count == 0 || previousSignal.Last().Date < signal.Date)
|
||||
{
|
||||
signalOnCandles.Add(signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (signalOnCandles.Count != strategies.Count)
|
||||
return null;
|
||||
|
||||
var data = newCandles.First();
|
||||
return ComputeSignals(strategies, signalOnCandles, MiscExtensions.ParseEnum<Ticker>(data.Ticker), data.Timeframe);
|
||||
}
|
||||
|
||||
public static Signal ComputeSignals(HashSet<IStrategy> strategies, HashSet<Signal> signalOnCandles, Ticker ticker, Timeframe timeframe)
|
||||
{
|
||||
Signal signal = null;
|
||||
if (strategies.Count > 1)
|
||||
{
|
||||
var trendSignal = signalOnCandles.Where(s => s.SignalType == SignalType.Trend);
|
||||
var signals = signalOnCandles.Where(s => s.SignalType == SignalType.Signal);
|
||||
var contextStrategiesCount = strategies.Count(s => s.SignalType == SignalType.Context);
|
||||
var validContext = true;
|
||||
|
||||
if (contextStrategiesCount > 0 &&
|
||||
signalOnCandles.Count(s => s.SignalType == SignalType.Context) != contextStrategiesCount)
|
||||
{
|
||||
validContext = false;
|
||||
}
|
||||
|
||||
if (signals.All(s => s.Direction == TradeDirection.Long) && trendSignal.All(t => t.Direction == TradeDirection.Long) && validContext)
|
||||
{
|
||||
signal = new Signal(
|
||||
ticker,
|
||||
TradeDirection.Long,
|
||||
Confidence.High,
|
||||
signals.Last().Candle,
|
||||
signals.Last().Date,
|
||||
signals.Last().Exchange,
|
||||
timeframe,
|
||||
StrategyType.Composite, SignalType.Signal);
|
||||
}
|
||||
else if (signals.All(s => s.Direction == TradeDirection.Short) && trendSignal.All(t => t.Direction == TradeDirection.Short) && validContext)
|
||||
{
|
||||
signal = new Signal(
|
||||
ticker,
|
||||
TradeDirection.Short,
|
||||
Confidence.High,
|
||||
signals.Last().Candle,
|
||||
signals.Last().Date,
|
||||
signals.Last().Exchange,
|
||||
timeframe,
|
||||
StrategyType.Composite, SignalType.Signal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only one strategy, we just add the single signal to the bot
|
||||
signal = signalOnCandles.Single();
|
||||
}
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
public static MoneyManagement GetBestMoneyManagement(List<Candle> candles, List<Position> positions, MoneyManagement originMoneyManagement)
|
||||
{
|
||||
// Foreach positions, identitify the price when the position is open
|
||||
// Then, foreach candles, get the maximum price before the next position
|
||||
// Then, identify the lowest price before the maximum price
|
||||
// Base on that, return the best StopLoss and TakeProfit to use and build a
|
||||
var moneyManagement = new MoneyManagement();
|
||||
var stoplossPercentage = new List<decimal>();
|
||||
var takeProfitsPercentage = new List<decimal>();
|
||||
|
||||
if (positions.Count == 0)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < positions.Count; i++)
|
||||
{
|
||||
var position = positions[i];
|
||||
var nextPosition = i + 1 < positions.Count ? positions[i + 1] : null;
|
||||
var (stopLoss, takeProfit) = GetBestSLTPForPosition(candles, position, nextPosition);
|
||||
|
||||
stoplossPercentage.Add(stopLoss);
|
||||
takeProfitsPercentage.Add(takeProfit);
|
||||
}
|
||||
|
||||
moneyManagement.StopLoss = stoplossPercentage.Average();
|
||||
moneyManagement.TakeProfit = takeProfitsPercentage.Average();
|
||||
moneyManagement.BalanceAtRisk = originMoneyManagement.BalanceAtRisk * 100;
|
||||
moneyManagement.Timeframe = originMoneyManagement.Timeframe;
|
||||
moneyManagement.Leverage = originMoneyManagement.Leverage;
|
||||
moneyManagement.Name = "Optimized";
|
||||
return moneyManagement;
|
||||
}
|
||||
|
||||
public static (decimal Stoploss, decimal TakeProfit) GetBestSLTPForPosition(List<Candle> candles, Position position, Position nextPosition)
|
||||
{
|
||||
var stopLoss = 0M;
|
||||
var takeProfit = 0M;
|
||||
var candlesBeforeNextPosition = candles.Where(c => c.Date >= position.Date && c.Date <= (nextPosition == null ? candles.Last().Date : nextPosition.Date));
|
||||
|
||||
if (position.OriginDirection == TradeDirection.Long)
|
||||
{
|
||||
var maxPrice = candlesBeforeNextPosition.Max(c => c.High);
|
||||
var minPrice = candlesBeforeNextPosition.TakeWhile(c => c.High <= maxPrice).Min(c => c.Low);
|
||||
stopLoss = GetPercentageFromEntry(position.Open.Price, minPrice);
|
||||
takeProfit = GetPercentageFromEntry(position.Open.Price, maxPrice);
|
||||
}
|
||||
else if (position.OriginDirection == TradeDirection.Short)
|
||||
{
|
||||
var minPrice = candlesBeforeNextPosition.Min(c => c.Low);
|
||||
var maxPrice = candlesBeforeNextPosition.TakeWhile(c => c.Low >= minPrice).Max(c => c.High);
|
||||
stopLoss = GetPercentageFromEntry(position.Open.Price, maxPrice);
|
||||
takeProfit = GetPercentageFromEntry(position.Open.Price, minPrice);
|
||||
}
|
||||
|
||||
return (stopLoss, takeProfit);
|
||||
}
|
||||
|
||||
private static decimal GetPercentageFromEntry(decimal entry, decimal price)
|
||||
{
|
||||
return Math.Abs(100 - ((100 * price) / entry));
|
||||
}
|
||||
|
||||
public static ProfitAndLoss GetProfitAndLoss(Position position, decimal quantity, decimal price)
|
||||
{
|
||||
var orders = new List<Tuple<decimal, decimal>>
|
||||
{
|
||||
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
|
||||
new Tuple<decimal, decimal>(-quantity, price)
|
||||
};
|
||||
return new ProfitAndLoss(orders, position.OriginDirection);
|
||||
}
|
||||
}
|
||||
99
src/Managing.Domain/Shared/Helpers/TradingHelpers.cs
Normal file
99
src/Managing.Domain/Shared/Helpers/TradingHelpers.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Exilion.TradingAtomics;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Statistics;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Shared.Helpers;
|
||||
|
||||
public static class TradingHelpers
|
||||
{
|
||||
public static decimal GetHodlPercentage(Candles.Candle candle1, Candles.Candle candle2)
|
||||
{
|
||||
return candle2.Close * 100 / candle1.Close - 100;
|
||||
}
|
||||
|
||||
public static decimal GetGrowthFromInitalBalance(decimal balance, decimal finalPnl)
|
||||
{
|
||||
var growth = balance + finalPnl;
|
||||
|
||||
return growth * 100 / balance - 100;
|
||||
}
|
||||
|
||||
public static PerformanceMetrics GetStatistics(Dictionary<DateTime, decimal> pnls)
|
||||
{
|
||||
var priceSeries = new TimePriceSeries(pnls);
|
||||
|
||||
return priceSeries.CalculatePerformanceMetrics();
|
||||
}
|
||||
|
||||
public static decimal GetFeeAmount(decimal fee, decimal amount)
|
||||
{
|
||||
return fee * amount;
|
||||
}
|
||||
|
||||
public static decimal GetFeeAmount(decimal fee, decimal amount, TradingExchanges exchange)
|
||||
{
|
||||
if (exchange.Equals(TradingExchanges.Evm))
|
||||
return fee;
|
||||
|
||||
return GetFeeAmount(fee, amount);
|
||||
}
|
||||
|
||||
public static bool IsAGoodTrader(Trader trader)
|
||||
{
|
||||
return trader.Winrate > 80
|
||||
&& trader.TradeCount > 8
|
||||
&& trader.AverageWin > Math.Abs(trader.AverageLoss) * 3
|
||||
&& trader.Pnl > 0;
|
||||
}
|
||||
|
||||
public static bool IsABadTrader(Trader trader)
|
||||
{
|
||||
return trader.Winrate < 30
|
||||
&& trader.TradeCount > 8
|
||||
&& trader.AverageWin * 3 < Math.Abs(trader.AverageLoss)
|
||||
&& trader.Pnl < 0;
|
||||
}
|
||||
|
||||
public static List<Trader> FindBadTrader(this List<Trader> traders)
|
||||
{
|
||||
var filteredTrader = new List<Trader>();
|
||||
foreach (var trader in traders)
|
||||
{
|
||||
if (IsABadTrader(trader))
|
||||
{
|
||||
filteredTrader.Add(trader);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTrader;
|
||||
}
|
||||
|
||||
public static List<Trader> FindGoodTrader(this List<Trader> traders)
|
||||
{
|
||||
var filteredTrader = new List<Trader>();
|
||||
foreach (var trader in traders)
|
||||
{
|
||||
if (IsAGoodTrader(trader))
|
||||
{
|
||||
filteredTrader.Add(trader);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTrader;
|
||||
}
|
||||
|
||||
public static List<Trader> MapToTraders(this List<Account> accounts)
|
||||
{
|
||||
var traders = new List<Trader>();
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
traders.Add(new Trader
|
||||
{
|
||||
Address = account.Key
|
||||
});
|
||||
}
|
||||
|
||||
return traders;
|
||||
}
|
||||
}
|
||||
13
src/Managing.Domain/Shared/Rules/Check.cs
Normal file
13
src/Managing.Domain/Shared/Rules/Check.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Managing.Domain.Shared.Rules
|
||||
{
|
||||
public static class Check
|
||||
{
|
||||
public static void That(IValidationRule rule)
|
||||
{
|
||||
if (!rule.IsValid())
|
||||
{
|
||||
throw new RuleException(rule.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Managing.Domain/Shared/Rules/IValidationRules.cs
Normal file
9
src/Managing.Domain/Shared/Rules/IValidationRules.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Managing.Domain.Shared.Rules
|
||||
{
|
||||
public interface IValidationRule
|
||||
{
|
||||
bool IsValid();
|
||||
|
||||
string Message { get; }
|
||||
}
|
||||
}
|
||||
9
src/Managing.Domain/Shared/Rules/RuleException.cs
Normal file
9
src/Managing.Domain/Shared/Rules/RuleException.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Managing.Domain.Shared.Rules
|
||||
{
|
||||
public class RuleException : Exception
|
||||
{
|
||||
public RuleException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Managing.Domain/Statistics/Spotlight.cs
Normal file
38
src/Managing.Domain/Statistics/Spotlight.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Strategies;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
public class SpotlightOverview
|
||||
{
|
||||
[Required]
|
||||
public List<Spotlight> Spotlights { get; set; }
|
||||
[Required]
|
||||
public DateTime DateTime { get; set; }
|
||||
public Guid Identifier { get; set; }
|
||||
public int ScenarioCount { get; set; }
|
||||
}
|
||||
|
||||
public class Spotlight
|
||||
{
|
||||
[Required]
|
||||
public Scenario Scenario { get; set; }
|
||||
[Required]
|
||||
public List<TickerSignal> TickerSignals { get; set; }
|
||||
}
|
||||
|
||||
public class TickerSignal
|
||||
{
|
||||
[Required]
|
||||
public Ticker Ticker { get; set; }
|
||||
[Required]
|
||||
public List<Signal> FiveMinutes { get; set; }
|
||||
[Required]
|
||||
public List<Signal> FifteenMinutes { get; set; }
|
||||
[Required]
|
||||
public List<Signal> OneHour { get; set; }
|
||||
[Required]
|
||||
public List<Signal> FourHour { get; set; }
|
||||
[Required]
|
||||
public List<Signal> OneDay { get; set; }
|
||||
}
|
||||
12
src/Managing.Domain/Statistics/TopVolumeTicker.cs
Normal file
12
src/Managing.Domain/Statistics/TopVolumeTicker.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Statistics;
|
||||
|
||||
public class TopVolumeTicker
|
||||
{
|
||||
public Ticker Ticker { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public decimal Volume { get; set; }
|
||||
public int Rank { get; set; }
|
||||
public TradingExchanges Exchange { get; set; }
|
||||
}
|
||||
12
src/Managing.Domain/Statistics/Trader.cs
Normal file
12
src/Managing.Domain/Statistics/Trader.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Managing.Domain.Statistics;
|
||||
|
||||
public class Trader
|
||||
{
|
||||
public string Address { get; set; }
|
||||
public int Winrate { get; set; }
|
||||
public decimal Pnl { get; set; }
|
||||
public int TradeCount { get; set; }
|
||||
public decimal AverageWin { get; set; }
|
||||
public decimal AverageLoss { get; set; }
|
||||
public decimal Roi { get; set; }
|
||||
}
|
||||
39
src/Managing.Domain/Strategies/Base/EmaBaseStrategy.cs
Normal file
39
src/Managing.Domain/Strategies/Base/EmaBaseStrategy.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Candles;
|
||||
using Skender.Stock.Indicators;
|
||||
|
||||
namespace Managing.Domain.Strategies.Base;
|
||||
|
||||
public abstract class EmaBaseStrategy : Strategy
|
||||
{
|
||||
protected EmaBaseStrategy(string name, Enums.Timeframe timeframe, Enums.StrategyType type) : base(name, timeframe, type)
|
||||
{
|
||||
}
|
||||
|
||||
protected List<CandleEma> MapEmaToCandle(List<EmaResult> ema, IEnumerable<Candle> candles)
|
||||
{
|
||||
var emaList = new List<CandleEma>();
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
var currentEma = ema.Find(candle.Date);
|
||||
if (currentEma != null && currentEma.Ema.HasValue)
|
||||
{
|
||||
emaList.Add(new CandleEma()
|
||||
{
|
||||
Close = candle.Close,
|
||||
Open = candle.Open,
|
||||
Date = candle.Date,
|
||||
Ticker = candle.Ticker,
|
||||
Exchange = candle.Exchange,
|
||||
Ema = currentEma.Ema.Value,
|
||||
});
|
||||
}
|
||||
}
|
||||
return emaList;
|
||||
}
|
||||
|
||||
public class CandleEma : Candle
|
||||
{
|
||||
public double Ema { get; set; }
|
||||
}
|
||||
}
|
||||
115
src/Managing.Domain/Strategies/ChandelierExitStrategy.cs
Normal file
115
src/Managing.Domain/Strategies/ChandelierExitStrategy.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies;
|
||||
|
||||
public class ChandelierExitStrategy : Strategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
public ChandelierExitStrategy(string name, Timeframe timeframe, int period, double multiplier) : base(name, timeframe, StrategyType.ChandelierExit)
|
||||
{
|
||||
Signals = new List<Signal>();
|
||||
Period = period;
|
||||
Multiplier = multiplier;
|
||||
MinimumHistory = 1 + Period.Value;
|
||||
}
|
||||
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
if (Candles.Count <= MinimumHistory)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
GetSignals(ChandelierType.Long);
|
||||
GetSignals(ChandelierType.Short);
|
||||
|
||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetSignals(ChandelierType chandelierType)
|
||||
{
|
||||
var chandelier = Candles.GetChandelier(Period.Value, Multiplier.Value, chandelierType).Where(s => s.ChandelierExit.HasValue).ToList();
|
||||
var chandelierCandle = MapChandelierToCandle(chandelier, Candles.TakeLast(MinimumHistory));
|
||||
var previousCandle = chandelierCandle[0];
|
||||
|
||||
foreach (var currentCandle in chandelierCandle.Skip(1))
|
||||
{
|
||||
// Short
|
||||
if (currentCandle.Close < previousCandle.ChandelierExit &&
|
||||
previousCandle.Close > previousCandle.ChandelierExit &&
|
||||
currentCandle.Close < previousCandle.Open &&
|
||||
chandelierType == ChandelierType.Short)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium);
|
||||
}
|
||||
|
||||
// Long
|
||||
if (currentCandle.Close > previousCandle.ChandelierExit &&
|
||||
previousCandle.Close < previousCandle.ChandelierExit &&
|
||||
currentCandle.Close > currentCandle.Open &&
|
||||
chandelierType == ChandelierType.Long)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium);
|
||||
}
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CandleChandelier> MapChandelierToCandle(List<ChandelierResult> superTrend, IEnumerable<Candle> candles)
|
||||
{
|
||||
var superTrends = new List<CandleChandelier>();
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
var currentChandelier = superTrend.Find(candle.Date);
|
||||
if (currentChandelier != null)
|
||||
{
|
||||
superTrends.Add(new CandleChandelier()
|
||||
{
|
||||
Close = candle.Close,
|
||||
Open = candle.Open,
|
||||
Date = candle.Date,
|
||||
Ticker = candle.Ticker,
|
||||
Exchange = candle.Exchange,
|
||||
ChandelierExit = (decimal)currentChandelier.ChandelierExit.Value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return superTrends;
|
||||
}
|
||||
|
||||
private void AddSignal(CandleChandelier candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(
|
||||
MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker),
|
||||
direction,
|
||||
confidence,
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
timeframe,
|
||||
Type, SignalType);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class CandleChandelier : Candle
|
||||
{
|
||||
public decimal ChandelierExit { get; internal set; }
|
||||
}
|
||||
}
|
||||
69
src/Managing.Domain/Strategies/EmaCrossStrategy.cs
Normal file
69
src/Managing.Domain/Strategies/EmaCrossStrategy.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Strategies;
|
||||
|
||||
public class EmaCrossStrategy : EmaBaseStrategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
|
||||
public EmaCrossStrategy(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.EmaCross)
|
||||
{
|
||||
Signals = new List<Signal>();
|
||||
Period = period;
|
||||
}
|
||||
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
if (Candles.Count <= Period)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var ema = Candles.GetEma(Period.Value).ToList();
|
||||
var emaCandles = MapEmaToCandle(ema, Candles.TakeLast(Period.Value));
|
||||
|
||||
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, Timeframe, TradeDirection.Short, Confidence.Medium);
|
||||
}
|
||||
|
||||
if (previousCandle.Close < (decimal)currentCandle.Ema &&
|
||||
currentCandle.Close > (decimal)currentCandle.Ema)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, 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, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/Managing.Domain/Strategies/EmaTrendStrategy.cs
Normal file
65
src/Managing.Domain/Strategies/EmaTrendStrategy.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Managing.Core;
|
||||
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 EmaTrendStrategy : EmaBaseStrategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
|
||||
public EmaTrendStrategy(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.EmaTrend)
|
||||
{
|
||||
Signals = new List<Signal>();
|
||||
Period = period;
|
||||
}
|
||||
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
if (Candles.Count <= 2 * Period)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var ema = Candles.GetEma(Period.Value).ToList();
|
||||
var emaCandles = MapEmaToCandle(ema, Candles.TakeLast(Period.Value));
|
||||
|
||||
if (ema.Count == 0)
|
||||
return null;
|
||||
|
||||
var previousCandle = emaCandles[0];
|
||||
foreach (var currentCandle in emaCandles.Skip(1))
|
||||
{
|
||||
if (currentCandle.Close > (decimal)currentCandle.Ema)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.None);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.None);
|
||||
}
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
|
||||
return Signals.OrderBy(s => s.Date).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSignal(CandleEma candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Managing.Domain/Strategies/IStrategy.cs
Normal file
20
src/Managing.Domain/Strategies/IStrategy.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Managing.Domain.Candles;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies
|
||||
{
|
||||
public interface IStrategy
|
||||
{
|
||||
string Name { get; set; }
|
||||
StrategyType Type { get; set; }
|
||||
SignalType SignalType { get; set; }
|
||||
int? Period { get; set; }
|
||||
int? FastPeriods { get; set; }
|
||||
int? SlowPeriods { get; set; }
|
||||
int? SignalPeriods { get; set; }
|
||||
|
||||
List<Signal> Run();
|
||||
void UpdateCandles(HashSet<Candle> newCandles);
|
||||
string GetName();
|
||||
}
|
||||
}
|
||||
102
src/Managing.Domain/Strategies/MACDCrossStrategy.cs
Normal file
102
src/Managing.Domain/Strategies/MACDCrossStrategy.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies;
|
||||
|
||||
public class MACDCrossStrategy : Strategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
|
||||
public MACDCrossStrategy(string name, Timeframe timeframe, int fastPeriods, int slowPeriods, int signalPeriods) : base(name, timeframe, StrategyType.MacdCross)
|
||||
{
|
||||
Signals = new List<Signal>();
|
||||
FastPeriods = fastPeriods;
|
||||
SlowPeriods = slowPeriods;
|
||||
SignalPeriods = signalPeriods;
|
||||
}
|
||||
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
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));
|
||||
|
||||
if (macd.Count == 0)
|
||||
return null;
|
||||
|
||||
var previousCandle = macdCandle[0];
|
||||
foreach (var currentCandle in macdCandle.Skip(1))
|
||||
{
|
||||
if (previousCandle.Histogram > 0 && currentCandle.Histogram < 0 && currentCandle.Macd < 0)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium);
|
||||
}
|
||||
|
||||
if (previousCandle.Histogram < 0 && currentCandle.Histogram > 0 && currentCandle.Macd > 0)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium);
|
||||
}
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
|
||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CandleMacd> MapMacdToCandle(List<MacdResult> macd, IEnumerable<Candle> candles)
|
||||
{
|
||||
var macdList = new List<CandleMacd>();
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
var currentMacd = macd.Find(candle.Date);
|
||||
if (currentMacd != null)
|
||||
{
|
||||
macdList.Add(new CandleMacd()
|
||||
{
|
||||
Close = candle.Close,
|
||||
Open = candle.Open,
|
||||
Date = candle.Date,
|
||||
Ticker = candle.Ticker,
|
||||
Exchange = candle.Exchange,
|
||||
FastEma = currentMacd.FastEma.Value,
|
||||
SlowEma = currentMacd.SlowEma.Value,
|
||||
Macd = currentMacd.Macd.Value,
|
||||
Histogram = currentMacd.Histogram.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
return macdList;
|
||||
}
|
||||
|
||||
private void AddSignal(CandleMacd candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
private class CandleMacd : Candle
|
||||
{
|
||||
public double Macd { get; set; }
|
||||
public double Signal { get; set; }
|
||||
public double Histogram { get; set; }
|
||||
public double FastEma { get; set; }
|
||||
public double SlowEma { get; set; }
|
||||
}
|
||||
}
|
||||
252
src/Managing.Domain/Strategies/RSIDivergenceConfirmStrategy.cs
Normal file
252
src/Managing.Domain/Strategies/RSIDivergenceConfirmStrategy.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
using Candle = Managing.Domain.Candles.Candle;
|
||||
|
||||
namespace Managing.Domain.Strategies;
|
||||
|
||||
public class RSIDivergenceConfirmStrategy : Strategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
|
||||
public RSIDivergenceConfirmStrategy(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.RsiDivergenceConfirm)
|
||||
{
|
||||
Period = period;
|
||||
Signals = new List<Signal>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get RSI signals
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
if (Candles.Count <= Period)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (candlesRsi.Count(c => c.Rsi > 0) == 0)
|
||||
return null;
|
||||
|
||||
GetLongSignals(candlesRsi);
|
||||
GetShortSignals(candlesRsi);
|
||||
|
||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetLongSignals(List<CandleRsi> candlesRsi)
|
||||
{
|
||||
// Set the low and high for first candle
|
||||
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
|
||||
var highPrices = new List<CandleRsi>();
|
||||
var lowPrices = new List<CandleRsi>();
|
||||
|
||||
var highRsi = new List<CandleRsi>();
|
||||
var lowRsi = new List<CandleRsi>();
|
||||
|
||||
highPrices.Add(firstCandleRsi);
|
||||
lowPrices.Add(firstCandleRsi);
|
||||
|
||||
highRsi.Add(firstCandleRsi);
|
||||
lowRsi.Add(firstCandleRsi);
|
||||
|
||||
var previousCandle = firstCandleRsi;
|
||||
|
||||
// For a long
|
||||
foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1))
|
||||
{
|
||||
// If price go down
|
||||
if (previousCandle.Close > currentCandle.Close)
|
||||
{
|
||||
// because the last price is upper than the current
|
||||
highPrices.AddItem(previousCandle);
|
||||
|
||||
// Check if rsi is higher than the last lowest
|
||||
if (currentCandle.Rsi > lowRsi.TakeLast(Period.Value).Min(r => r.Rsi))
|
||||
{
|
||||
// If new higher high, we set it
|
||||
if (currentCandle.Rsi > highRsi.Last().Rsi)
|
||||
highRsi.AddItem(currentCandle);
|
||||
|
||||
if (currentCandle.Rsi > lowRsi.Last().Rsi)
|
||||
lowRsi.AddItem(currentCandle);
|
||||
|
||||
// Price go down but RSI go up
|
||||
if (currentCandle.Close < lowPrices.TakeLast(Period.Value).Min(p => p.Close))
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.None);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No divergence, price go down, rsi go down
|
||||
lowRsi.AddItem(currentCandle);
|
||||
}
|
||||
|
||||
lowPrices.AddItem(currentCandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Price go up, so we have to update if price is a new higher high than previous candle
|
||||
// Normally always true
|
||||
if (previousCandle.Close < currentCandle.Close)
|
||||
highPrices.AddItem(currentCandle); //15-15-12-14-17
|
||||
|
||||
// If rsi is lower low or not set
|
||||
if (currentCandle.Rsi < lowRsi.Last().Rsi || lowRsi.Last().Rsi == 0)
|
||||
lowRsi.AddItem(currentCandle);
|
||||
|
||||
// Price going up, so if its a new high we set it
|
||||
if (currentCandle.Rsi > highRsi.Last().Rsi)
|
||||
highRsi.AddItem(currentCandle);
|
||||
}
|
||||
|
||||
CheckIfConfimation(currentCandle, TradeDirection.Long);
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetShortSignals(List<CandleRsi> candlesRsi)
|
||||
{
|
||||
// Set the low and high for first candle
|
||||
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
|
||||
|
||||
var signals = new List<Signal>();
|
||||
var highPrices = new List<CandleRsi>();
|
||||
var lowPrices = new List<CandleRsi>();
|
||||
|
||||
var highRsi = new List<CandleRsi>();
|
||||
var lowRsi = new List<CandleRsi>();
|
||||
|
||||
highPrices.Add(firstCandleRsi);
|
||||
lowPrices.Add(firstCandleRsi);
|
||||
|
||||
highRsi.Add(firstCandleRsi);
|
||||
lowRsi.Add(firstCandleRsi);
|
||||
|
||||
var previousCandle = firstCandleRsi;
|
||||
|
||||
// For a short
|
||||
foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1))
|
||||
{
|
||||
// If price go up
|
||||
if (previousCandle.Close < currentCandle.Close)
|
||||
{
|
||||
// because the last price is lower than the current
|
||||
lowPrices.AddItem(previousCandle);
|
||||
|
||||
// Check if rsi is lower than the last high
|
||||
if (currentCandle.Rsi < highRsi.TakeLast(Period.Value).Max(r => r.Rsi))
|
||||
{
|
||||
// If new lower low, we set it
|
||||
if (currentCandle.Rsi < lowRsi.Last().Rsi)
|
||||
lowRsi.AddItem(currentCandle);
|
||||
|
||||
if (currentCandle.Rsi < highRsi.Last().Rsi)
|
||||
highRsi.AddItem(currentCandle);
|
||||
|
||||
// Price go up but RSI go down
|
||||
if (currentCandle.Close > highPrices.TakeLast(Period.Value).Max(p => p.Close))
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.None);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No divergence, price go up, rsi go up
|
||||
highRsi.AddItem(currentCandle);
|
||||
}
|
||||
|
||||
highPrices.AddItem(currentCandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Price go down, so we have to update if price is a new lower low than previous candle
|
||||
if (previousCandle.Close > currentCandle.Close)
|
||||
lowPrices.AddItem(currentCandle);
|
||||
|
||||
// If rsi is higher high or not set
|
||||
if (currentCandle.Rsi > highRsi.Last().Rsi || highRsi.Last().Rsi == 0)
|
||||
highRsi.AddItem(currentCandle);
|
||||
|
||||
// Price going down, so if its a new low we set it
|
||||
if (currentCandle.Rsi < lowRsi.Last().Rsi)
|
||||
lowRsi.AddItem(currentCandle);
|
||||
}
|
||||
|
||||
CheckIfConfimation(currentCandle, TradeDirection.Short);
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckIfConfimation(CandleRsi currentCandle, TradeDirection direction)
|
||||
{
|
||||
var lastCandleOnPeriod = Candles.TakeLast(Period.Value).ToList();
|
||||
var signalsOnPeriod = Signals.Where(s => s.Date >= lastCandleOnPeriod[0].Date
|
||||
&& s.Date < currentCandle.Date
|
||||
&& s.Direction == direction
|
||||
&& s.Confidence == Confidence.None
|
||||
&& s.Status != SignalStatus.Expired
|
||||
&& s.Status != SignalStatus.PositionOpen).ToList();
|
||||
|
||||
foreach (var signal in signalsOnPeriod)
|
||||
{
|
||||
if (direction == TradeDirection.Short && currentCandle.Close < signal.Candle.Open)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, direction, Confidence.High);
|
||||
Signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
|
||||
}
|
||||
|
||||
if (direction == TradeDirection.Long && currentCandle.Close > signal.Candle.Open)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, direction, Confidence.High);
|
||||
Signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSignal(CandleRsi candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
private List<CandleRsi> MapRsiToCandle(IReadOnlyCollection<RsiResult> rsiResult,
|
||||
IEnumerable<Candle> candles)
|
||||
{
|
||||
|
||||
return candles.Select(c => new CandleRsi()
|
||||
{
|
||||
Close = c.Close,
|
||||
Open = c.Open,
|
||||
Rsi = rsiResult.Find(c.Date).Rsi.GetValueOrDefault(),
|
||||
Date = c.Date,
|
||||
Ticker = c.Ticker,
|
||||
Exchange = c.Exchange
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private class CandleRsi : Candle
|
||||
{
|
||||
public double Rsi { get; set; }
|
||||
}
|
||||
}
|
||||
234
src/Managing.Domain/Strategies/RSIDivergenceStrategy.cs
Normal file
234
src/Managing.Domain/Strategies/RSIDivergenceStrategy.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
using Candle = Managing.Domain.Candles.Candle;
|
||||
|
||||
namespace Managing.Domain.Strategies;
|
||||
|
||||
public class RSIDivergenceStrategy : Strategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
public TradeDirection Direction { get; set; }
|
||||
private const int UpperBand = 70;
|
||||
private const int LowerBand = 30;
|
||||
|
||||
public RSIDivergenceStrategy(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.RsiDivergence)
|
||||
{
|
||||
Period = period;
|
||||
Signals = new List<Signal>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get RSI signals
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
if (!Period.HasValue || Candles.Count <= Period)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (candlesRsi.Count(c => c.Rsi > 0) == 0)
|
||||
return null;
|
||||
|
||||
GetLongSignals(candlesRsi);
|
||||
GetShortSignals(candlesRsi);
|
||||
|
||||
return Signals;
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetLongSignals(List<CandleRsi> candlesRsi)
|
||||
{
|
||||
// Set the low and high for first candle
|
||||
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
|
||||
var highPrices = new List<CandleRsi>();
|
||||
var lowPrices = new List<CandleRsi>();
|
||||
|
||||
var highRsi = new List<CandleRsi>();
|
||||
var lowRsi = new List<CandleRsi>();
|
||||
|
||||
highPrices.Add(firstCandleRsi);
|
||||
lowPrices.Add(firstCandleRsi);
|
||||
|
||||
highRsi.Add(firstCandleRsi);
|
||||
lowRsi.Add(firstCandleRsi);
|
||||
|
||||
var previousCandle = firstCandleRsi;
|
||||
|
||||
// For a long
|
||||
foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1))
|
||||
{
|
||||
// If price go down
|
||||
if (previousCandle.Close > currentCandle.Close)
|
||||
{
|
||||
// because the last price is upper than the current
|
||||
highPrices.AddItem(previousCandle);
|
||||
|
||||
// Check if rsi is higher than the last lowest
|
||||
if (currentCandle.Rsi > lowRsi.TakeLast(Period.Value).Min(r => r.Rsi))
|
||||
{
|
||||
// If new higher high, we set it
|
||||
if (currentCandle.Rsi > highRsi.Last().Rsi)
|
||||
highRsi.AddItem(currentCandle);
|
||||
|
||||
if (currentCandle.Rsi > lowRsi.Last().Rsi)
|
||||
lowRsi.AddItem(currentCandle);
|
||||
|
||||
// Price go down but RSI go up
|
||||
if (currentCandle.Close < lowPrices.TakeLast(Period.Value).Min(p => p.Close))
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Long);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No divergence, price go down, rsi go down
|
||||
lowRsi.AddItem(currentCandle);
|
||||
}
|
||||
|
||||
lowPrices.AddItem(currentCandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Price go up, so we have to update if price is a new higher high than previous candle
|
||||
// Normally always true
|
||||
if (previousCandle.Close < currentCandle.Close)
|
||||
highPrices.AddItem(currentCandle); //15-15-12-14-17
|
||||
|
||||
// If rsi is lower low or not set
|
||||
if (currentCandle.Rsi < lowRsi.Last().Rsi || lowRsi.Last().Rsi == 0)
|
||||
lowRsi.AddItem(currentCandle);
|
||||
|
||||
// Price going up, so if its a new high we set it
|
||||
if (currentCandle.Rsi > highRsi.Last().Rsi)
|
||||
highRsi.AddItem(currentCandle);
|
||||
}
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetShortSignals(List<CandleRsi> candlesRsi)
|
||||
{
|
||||
// Set the low and high for first candle
|
||||
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
|
||||
|
||||
var signals = new List<Signal>();
|
||||
var highPrices = new List<CandleRsi>();
|
||||
var lowPrices = new List<CandleRsi>();
|
||||
|
||||
var highRsi = new List<CandleRsi>();
|
||||
var lowRsi = new List<CandleRsi>();
|
||||
|
||||
highPrices.Add(firstCandleRsi);
|
||||
lowPrices.Add(firstCandleRsi);
|
||||
|
||||
highRsi.Add(firstCandleRsi);
|
||||
lowRsi.Add(firstCandleRsi);
|
||||
|
||||
var previousCandle = firstCandleRsi;
|
||||
|
||||
// For a short
|
||||
foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1))
|
||||
{
|
||||
// If price go up
|
||||
if (previousCandle.Close < currentCandle.Close)
|
||||
{
|
||||
// because the last price is lower than the current
|
||||
lowPrices.AddItem(previousCandle);
|
||||
|
||||
// Check if rsi is lower than the last high
|
||||
if (currentCandle.Rsi < highRsi.TakeLast(Period.Value).Max(r => r.Rsi))
|
||||
{
|
||||
// If new lower low, we set it
|
||||
if (currentCandle.Rsi < lowRsi.Last().Rsi)
|
||||
lowRsi.AddItem(currentCandle);
|
||||
|
||||
if (currentCandle.Rsi < highRsi.Last().Rsi)
|
||||
highRsi.AddItem(currentCandle);
|
||||
|
||||
// Price go up but RSI go down
|
||||
if (currentCandle.Close > highPrices.TakeLast(Period.Value).Max(p => p.Close))
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Short);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No divergence, price go up, rsi go up
|
||||
highRsi.AddItem(currentCandle);
|
||||
}
|
||||
|
||||
highPrices.AddItem(currentCandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Price go down, so we have to update if price is a new lower low than previous candle
|
||||
if (previousCandle.Close > currentCandle.Close)
|
||||
lowPrices.AddItem(currentCandle);
|
||||
|
||||
// If rsi is higher high or not set
|
||||
if (currentCandle.Rsi > highRsi.Last().Rsi || highRsi.Last().Rsi == 0)
|
||||
highRsi.AddItem(currentCandle);
|
||||
|
||||
// Price going down, so if its a new low we set it
|
||||
if (currentCandle.Rsi < lowRsi.Last().Rsi)
|
||||
lowRsi.AddItem(currentCandle);
|
||||
}
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSignal(CandleRsi candleSignal, Timeframe timeframe, TradeDirection direction)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, Confidence.Low, candleSignal, candleSignal.Date, candleSignal.Exchange, timeframe, Type, SignalType);
|
||||
|
||||
if (Signals.Count(s => s.Identifier == signal.Identifier) < 1)
|
||||
{
|
||||
var lastCandleOnPeriod = Candles.TakeLast(Period.Value).ToList();
|
||||
var signalsOnPeriod = Signals.Where(s => s.Date >= lastCandleOnPeriod[0].Date).ToList();
|
||||
|
||||
if (signalsOnPeriod.Count == 1)
|
||||
signal.SetConfidence(Confidence.Medium);
|
||||
|
||||
if (signalsOnPeriod.Count >= 2)
|
||||
signal.SetConfidence(Confidence.High);
|
||||
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
private List<CandleRsi> MapRsiToCandle(IReadOnlyCollection<RsiResult> rsiResult,
|
||||
IEnumerable<Candle> candles)
|
||||
{
|
||||
|
||||
return candles.Select(c => new CandleRsi()
|
||||
{
|
||||
Close = c.Close,
|
||||
Rsi = rsiResult.Find(c.Date).Rsi.GetValueOrDefault(),
|
||||
Date = c.Date,
|
||||
Ticker = c.Ticker,
|
||||
Exchange = c.Exchange
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private class CandleRsi : Candle
|
||||
{
|
||||
public double Rsi { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
|
||||
namespace Managing.Domain.Strategies.Rules
|
||||
{
|
||||
public class CloseHigherThanThePreviousHigh : IValidationRule
|
||||
{
|
||||
private readonly Candle _previousCandles;
|
||||
private readonly Candle _currentCandle;
|
||||
|
||||
public CloseHigherThanThePreviousHigh(Candle previousCandles, Candle currentCandle)
|
||||
{
|
||||
_previousCandles = previousCandles;
|
||||
_currentCandle = currentCandle;
|
||||
}
|
||||
|
||||
public string Message => $"Current candle did close higher than the previous high close candle";
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return _previousCandles != null ? _currentCandle.Close > _previousCandles.High : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
|
||||
namespace Managing.Domain.Strategies.Rules
|
||||
{
|
||||
public class CloseLowerThanThePreviousHigh : IValidationRule
|
||||
{
|
||||
private readonly Candle _previousCandles;
|
||||
private readonly Candle _currentCandle;
|
||||
|
||||
public CloseLowerThanThePreviousHigh(Candle previousCandles, Candle currentCandle)
|
||||
{
|
||||
_previousCandles = previousCandles;
|
||||
_currentCandle = currentCandle;
|
||||
}
|
||||
|
||||
public string Message => $"Current candle did close lower than the previous high close candle";
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return _previousCandles != null ? _currentCandle.Close > _previousCandles.Low : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Managing.Domain/Strategies/Rules/RSIShouldBeBullish.cs
Normal file
21
src/Managing.Domain/Strategies/Rules/RSIShouldBeBullish.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Managing.Domain.Shared.Rules;
|
||||
|
||||
namespace Managing.Domain.Strategies.Rules
|
||||
{
|
||||
public class RSIShouldBeBullish : IValidationRule
|
||||
{
|
||||
private int _rsiValue;
|
||||
|
||||
public RSIShouldBeBullish(int rsiValue)
|
||||
{
|
||||
_rsiValue = rsiValue;
|
||||
}
|
||||
|
||||
public string Message => $"RSI is not bullish, current value : {_rsiValue}";
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return _rsiValue > 70;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/Managing.Domain/Strategies/STCStrategy.cs
Normal file
103
src/Managing.Domain/Strategies/STCStrategy.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies;
|
||||
|
||||
public class STCStrategy : Strategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
|
||||
public STCStrategy(string name, Timeframe timeframe, int cyclePeriods, int fastPeriods, int slowPeriods) : base(name, timeframe, StrategyType.Stc)
|
||||
{
|
||||
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));
|
||||
|
||||
if (stc.Count == 0)
|
||||
return null;
|
||||
|
||||
var previousCandle = stcCandles[0];
|
||||
foreach (var currentCandle in stcCandles.Skip(1))
|
||||
{
|
||||
if (previousCandle.Stc > 75 && currentCandle.Stc <= 75)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium);
|
||||
}
|
||||
|
||||
if (previousCandle.Stc < 25 && currentCandle.Stc >= 25)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium);
|
||||
}
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
|
||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(
|
||||
MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker),
|
||||
direction,
|
||||
confidence,
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
timeframe,
|
||||
Type, SignalType);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
private class CandleSct : Candle
|
||||
{
|
||||
public double? Stc { get; internal set; }
|
||||
}
|
||||
}
|
||||
62
src/Managing.Domain/Strategies/Signal.cs
Normal file
62
src/Managing.Domain/Strategies/Signal.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies
|
||||
{
|
||||
public class Signal : ValueObject
|
||||
{
|
||||
[Required]
|
||||
public SignalStatus Status { get; set; }
|
||||
[Required]
|
||||
public TradeDirection Direction { get; }
|
||||
[Required]
|
||||
public Confidence Confidence { get; private set; }
|
||||
[Required]
|
||||
public Timeframe Timeframe { get; }
|
||||
[Required]
|
||||
public DateTime Date { get; private set; }
|
||||
[Required]
|
||||
public Candle Candle { get; }
|
||||
[Required]
|
||||
public string Identifier { get; }
|
||||
[Required]
|
||||
public Ticker Ticker { get; }
|
||||
[Required]
|
||||
public TradingExchanges Exchange { get; set; }
|
||||
[Required]
|
||||
public StrategyType StrategyType { get; set; }
|
||||
[Required]
|
||||
public SignalType SignalType { get; set; }
|
||||
|
||||
public Signal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date,
|
||||
TradingExchanges exchange, Timeframe timeframe, StrategyType strategyType, SignalType signalType)
|
||||
{
|
||||
Direction = direction;
|
||||
Confidence = confidence;
|
||||
Candle = candle;
|
||||
Date = date;
|
||||
Ticker = ticker;
|
||||
Exchange = exchange;
|
||||
Status = SignalStatus.WaitingForPosition;
|
||||
Timeframe = timeframe;
|
||||
StrategyType = strategyType;
|
||||
|
||||
Identifier = $"{StrategyType}-{direction}-{ticker}-{Timeframe}-{candle?.Close}-{date:yyyyMMdd-HHmmss}";
|
||||
SignalType = signalType;
|
||||
}
|
||||
|
||||
public void SetConfidence(Confidence confidence)
|
||||
{
|
||||
Confidence = confidence;
|
||||
}
|
||||
|
||||
protected override IEnumerable<object> GetEqualityComponents()
|
||||
{
|
||||
yield return Direction;
|
||||
yield return Confidence;
|
||||
yield return Date;
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/Managing.Domain/Strategies/StDevContext.cs
Normal file
102
src/Managing.Domain/Strategies/StDevContext.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies;
|
||||
|
||||
public class StDevContext : Strategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
|
||||
public StDevContext(string name, Timeframe timeframe, int period) : base(name, timeframe, StrategyType.StDev)
|
||||
{
|
||||
Signals = new List<Signal>();
|
||||
Period = period;
|
||||
}
|
||||
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
if (Candles.Count <= Period)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var stDev = Candles.GetStdDev(Period.Value).ToList();
|
||||
var stDevCandles = MapStDev(stDev, Candles.TakeLast(Period.Value));
|
||||
|
||||
if (stDev.Count == 0)
|
||||
return null;
|
||||
|
||||
var lastCandle = stDevCandles.Last();
|
||||
|
||||
if (lastCandle.ZScore is < 1.2 and > (-1.2))
|
||||
{
|
||||
AddSignal(lastCandle, Timeframe, TradeDirection.None, Confidence.Medium);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Bad zscore");
|
||||
}
|
||||
|
||||
return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CandleStDev> MapStDev(List<StdDevResult> stDev, IEnumerable<Candle> candles)
|
||||
{
|
||||
var sctList = new List<CandleStDev>();
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
var currentSct = stDev.Find(candle.Date);
|
||||
if (currentSct != null)
|
||||
{
|
||||
sctList.Add(new CandleStDev()
|
||||
{
|
||||
Close = candle.Close,
|
||||
Open = candle.Open,
|
||||
Date = candle.Date,
|
||||
Ticker = candle.Ticker,
|
||||
Exchange = candle.Exchange,
|
||||
StDev = currentSct.StdDev,
|
||||
ZScore = currentSct.ZScore,
|
||||
StdDevSma = currentSct.StdDevSma,
|
||||
Mean = currentSct.Mean
|
||||
});
|
||||
}
|
||||
}
|
||||
return sctList;
|
||||
}
|
||||
|
||||
private void AddSignal(CandleStDev candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(
|
||||
MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker),
|
||||
direction,
|
||||
confidence,
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
timeframe,
|
||||
Type, SignalType);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
private class CandleStDev : Candle
|
||||
{
|
||||
public double? StDev { get; internal set; }
|
||||
public double? ZScore { get; internal set; }
|
||||
public double? StdDevSma { get; internal set; }
|
||||
public double? Mean { get; internal set; }
|
||||
}
|
||||
}
|
||||
112
src/Managing.Domain/Strategies/StochRsiTrendStrategy.cs
Normal file
112
src/Managing.Domain/Strategies/StochRsiTrendStrategy.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies;
|
||||
|
||||
public class StochRsiTrendStrategy : Strategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
|
||||
public StochRsiTrendStrategy(
|
||||
string name,
|
||||
Timeframe timeframe,
|
||||
int period,
|
||||
int stochPeriod,
|
||||
int signalPeriod,
|
||||
int smoothPeriods) : base(name, timeframe, StrategyType.StochRsiTrend)
|
||||
{
|
||||
Signals = new List<Signal>();
|
||||
StochPeriods = stochPeriod;
|
||||
SignalPeriods = signalPeriod;
|
||||
SmoothPeriods = smoothPeriods;
|
||||
Period = period;
|
||||
}
|
||||
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
if (Candles.Count <= 10 * Period + 50)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var stochRsi = Candles.GetStochRsi(Period.Value, StochPeriods.Value, SignalPeriods.Value, SmoothPeriods.Value).RemoveWarmupPeriods().ToList();
|
||||
var stochRsiCandles = MapStochRsiToCandle(stochRsi, Candles.TakeLast(Period.Value));
|
||||
|
||||
if (stochRsi.Count == 0)
|
||||
return null;
|
||||
|
||||
var previousCandle = stochRsiCandles[0];
|
||||
foreach (var currentCandle in stochRsiCandles.Skip(1))
|
||||
{
|
||||
if (currentCandle.Signal < 20)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.None);
|
||||
}
|
||||
else if (currentCandle.Signal > 80)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.None);
|
||||
}
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
|
||||
return Signals.OrderBy(s => s.Date).ToList();
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CandleStochRsi> MapStochRsiToCandle(List<StochRsiResult> ema, IEnumerable<Candle> candles)
|
||||
{
|
||||
var emaList = new List<CandleStochRsi>();
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
var currentEma = ema.Find(candle.Date);
|
||||
if (currentEma != null)
|
||||
{
|
||||
emaList.Add(new CandleStochRsi()
|
||||
{
|
||||
Close = candle.Close,
|
||||
Open = candle.Open,
|
||||
Date = candle.Date,
|
||||
Ticker = candle.Ticker,
|
||||
Exchange = candle.Exchange,
|
||||
Signal = currentEma.Signal.Value,
|
||||
StochRsi = currentEma.StochRsi.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
return emaList;
|
||||
}
|
||||
|
||||
private void AddSignal(CandleStochRsi candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(
|
||||
MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker),
|
||||
direction,
|
||||
confidence,
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
timeframe,
|
||||
Type,
|
||||
SignalType);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
}
|
||||
}
|
||||
|
||||
private class CandleStochRsi : Candle
|
||||
{
|
||||
public double Signal { get; internal set; }
|
||||
public double StochRsi { get; internal set; }
|
||||
}
|
||||
}
|
||||
58
src/Managing.Domain/Strategies/Strategy.cs
Normal file
58
src/Managing.Domain/Strategies/Strategy.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Core;
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Domain.Scenarios;
|
||||
|
||||
namespace Managing.Domain.Strategies
|
||||
{
|
||||
public class Strategy : IStrategy
|
||||
{
|
||||
public Strategy(string name, Timeframe timeframe, StrategyType type)
|
||||
{
|
||||
Name = name;
|
||||
Timeframe = timeframe;
|
||||
Candles = new List<Candle>();
|
||||
Type = type;
|
||||
SignalType = ScenarioHelpers.GetSignalType(type);
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public Timeframe Timeframe { get; set; }
|
||||
public List<Candle> Candles { get; set; }
|
||||
public StrategyType 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 virtual List<Signal> Run()
|
||||
{
|
||||
return new List<Signal>();
|
||||
}
|
||||
|
||||
public void UpdateCandles(HashSet<Candle> newCandles)
|
||||
{
|
||||
lock (Candles)
|
||||
{
|
||||
foreach (var item in newCandles.ToList())
|
||||
{
|
||||
if (Candles.All(c => c.Date != item.Date))
|
||||
{
|
||||
Candles.AddItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/Managing.Domain/Strategies/SuperTrendStrategy.cs
Normal file
104
src/Managing.Domain/Strategies/SuperTrendStrategy.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Skender.Stock.Indicators;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Strategies;
|
||||
|
||||
public class SuperTrendStrategy : Strategy
|
||||
{
|
||||
public List<Signal> Signals { get; set; }
|
||||
|
||||
public SuperTrendStrategy(string name, Timeframe timeframe, int period, double multiplier) : base(name, timeframe, StrategyType.SuperTrend)
|
||||
{
|
||||
Signals = new List<Signal>();
|
||||
Period = period;
|
||||
Multiplier = multiplier;
|
||||
MinimumHistory = 100 + Period.Value;
|
||||
}
|
||||
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
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));
|
||||
|
||||
if (superTrendCandle.Count == 0)
|
||||
return null;
|
||||
|
||||
var previousCandle = superTrendCandle[0];
|
||||
foreach (var currentCandle in superTrendCandle.Skip(1))
|
||||
{
|
||||
// Short
|
||||
if (currentCandle.Close < previousCandle.SuperTrend && previousCandle.Close > previousCandle.SuperTrend)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Short, Confidence.Medium);
|
||||
}
|
||||
|
||||
// Long
|
||||
if (currentCandle.Close > previousCandle.SuperTrend && previousCandle.Close < previousCandle.SuperTrend)
|
||||
{
|
||||
AddSignal(currentCandle, Timeframe, TradeDirection.Long, Confidence.Medium);
|
||||
}
|
||||
|
||||
previousCandle = currentCandle;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void AddSignal(CandleSuperTrend candleSignal, Timeframe timeframe, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date,
|
||||
candleSignal.Exchange, timeframe, 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; }
|
||||
}
|
||||
}
|
||||
55
src/Managing.Domain/Strategies/ThreeWhiteSoldiersStrategy.cs
Normal file
55
src/Managing.Domain/Strategies/ThreeWhiteSoldiersStrategy.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Rules;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Rules;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Strategies
|
||||
{
|
||||
public class ThreeWhiteSoldiersStrategy : Strategy
|
||||
{
|
||||
public ThreeWhiteSoldiersStrategy(string name, Timeframe timeframe, int period)
|
||||
: base(name, timeframe, StrategyType.ThreeWhiteSoldiers)
|
||||
{
|
||||
Period = period;
|
||||
}
|
||||
|
||||
public TradeDirection Direction { get; }
|
||||
|
||||
public override List<Signal> Run()
|
||||
{
|
||||
var signals = new List<Signal>();
|
||||
|
||||
if (Candles.Count <= 3)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var lastFourCandles = Candles.TakeLast(4);
|
||||
Candle previousCandles = null;
|
||||
|
||||
foreach (var currentCandle in lastFourCandles)
|
||||
{
|
||||
if (Direction == TradeDirection.Long)
|
||||
{
|
||||
Check.That(new CloseHigherThanThePreviousHigh(previousCandles, currentCandle));
|
||||
}
|
||||
else
|
||||
{
|
||||
Check.That(new CloseLowerThanThePreviousHigh(previousCandles, currentCandle));
|
||||
}
|
||||
|
||||
previousCandles = currentCandle;
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
catch (RuleException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/Managing.Domain/Trades/Fee.cs
Normal file
10
src/Managing.Domain/Trades/Fee.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Trades;
|
||||
|
||||
public class Fee
|
||||
{
|
||||
public decimal Cost { get; set; }
|
||||
public TradingExchanges Exchange { get; set; }
|
||||
public DateTime LastUpdate { get; set; }
|
||||
}
|
||||
7
src/Managing.Domain/Trades/OrderBookEntry.cs
Normal file
7
src/Managing.Domain/Trades/OrderBookEntry.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Managing.Domain.Trades;
|
||||
|
||||
public class OrderBookEntry
|
||||
{
|
||||
public decimal Price { get; set; }
|
||||
public decimal Quantity { get; set; }
|
||||
}
|
||||
35
src/Managing.Domain/Trades/OrderBookExtensions.cs
Normal file
35
src/Managing.Domain/Trades/OrderBookExtensions.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Trades;
|
||||
|
||||
public static class OrderBookExtensions
|
||||
{
|
||||
public static decimal GetBestPrice(this Orderbook orderbook, TradeDirection direction, decimal quantity)
|
||||
{
|
||||
var entries = direction == TradeDirection.Long ? orderbook.Asks : orderbook.Bids;
|
||||
var spend = 0m;
|
||||
var entryIndex = 0;
|
||||
var matches = new List<(decimal amount, decimal price, decimal sum)>();
|
||||
|
||||
while (spend < quantity)
|
||||
{
|
||||
if (entries[entryIndex].Quantity > quantity - spend)
|
||||
{
|
||||
var amount = quantity - spend;
|
||||
matches.Add((amount, entries[entryIndex].Price, amount * entries[entryIndex].Price));
|
||||
spend += amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
matches.Add((entries[entryIndex].Quantity, entries[entryIndex].Price, entries[entryIndex].Quantity * entries[entryIndex].Price));
|
||||
spend += entries[entryIndex].Quantity;
|
||||
}
|
||||
|
||||
entryIndex++;
|
||||
}
|
||||
|
||||
var meanPrice = matches.Sum(s => s.sum) / matches.Sum(s => s.amount);
|
||||
|
||||
return meanPrice;
|
||||
}
|
||||
}
|
||||
8
src/Managing.Domain/Trades/Orderbook.cs
Normal file
8
src/Managing.Domain/Trades/Orderbook.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Managing.Domain.Trades;
|
||||
|
||||
public class Orderbook
|
||||
{
|
||||
public List<OrderBookEntry> Bids { get; set; }
|
||||
public List<OrderBookEntry> Asks { get; set; }
|
||||
}
|
||||
|
||||
57
src/Managing.Domain/Trades/Position.cs
Normal file
57
src/Managing.Domain/Trades/Position.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Trades
|
||||
{
|
||||
public class Position
|
||||
{
|
||||
public Position(string accountName, TradeDirection originDirection, Ticker ticker, MoneyManagement moneyManagement, PositionInitiator positionInitiator, DateTime date)
|
||||
{
|
||||
Identifier = Guid.NewGuid().ToString();
|
||||
AccountName = accountName;
|
||||
OriginDirection = originDirection;
|
||||
Ticker = ticker;
|
||||
MoneyManagement = moneyManagement;
|
||||
Initiator = positionInitiator;
|
||||
Date = date;
|
||||
Status = Initiator == PositionInitiator.PaperTrading ? PositionStatus.Filled : PositionStatus.New;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public string AccountName { get; }
|
||||
[Required]
|
||||
public DateTime Date { get; set; }
|
||||
[Required]
|
||||
public TradeDirection OriginDirection { get; }
|
||||
[Required]
|
||||
public Ticker Ticker { get; }
|
||||
[Required]
|
||||
public MoneyManagement MoneyManagement { get; }
|
||||
[Required]
|
||||
public Trade Open { get; set; }
|
||||
[Required]
|
||||
public Trade StopLoss { get; set; }
|
||||
[Required]
|
||||
public Trade TakeProfit1 { get; set; }
|
||||
public Trade TakeProfit2 { get; set; }
|
||||
public ProfitAndLoss ProfitAndLoss { get; set; }
|
||||
[Required]
|
||||
public PositionStatus Status { get; set; }
|
||||
public string SignalIdentifier { get; set; }
|
||||
[Required]
|
||||
public string Identifier { get; set; }
|
||||
[Required]
|
||||
public PositionInitiator Initiator { get; }
|
||||
|
||||
public bool IsFinished()
|
||||
{
|
||||
return Status switch
|
||||
{
|
||||
PositionStatus.Finished => true,
|
||||
PositionStatus.Flipped => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/Managing.Domain/Trades/ProfitAndLoss.cs
Normal file
102
src/Managing.Domain/Trades/ProfitAndLoss.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Trades
|
||||
{
|
||||
public sealed class ProfitAndLoss
|
||||
{
|
||||
public decimal Realized { get; set; }
|
||||
|
||||
public decimal Net { get; set; }
|
||||
|
||||
public decimal AverageOpenPrice { get; private set; }
|
||||
private const decimal _multiplier = 100000;
|
||||
|
||||
public ProfitAndLoss()
|
||||
{
|
||||
}
|
||||
|
||||
public ProfitAndLoss(IEnumerable<Tuple<decimal, decimal>> initial, TradeDirection direction)
|
||||
{
|
||||
decimal buyQuantity = 0, sellQuantity = 0;
|
||||
decimal averageBuyPrice = 0, averageSellPrice = 0;
|
||||
foreach (var fill in initial)
|
||||
{
|
||||
var quantityFilled = fill.Item1;
|
||||
if (fill.Item1 > 0)
|
||||
{
|
||||
buyQuantity += quantityFilled;
|
||||
averageBuyPrice += quantityFilled * fill.Item2;
|
||||
}
|
||||
else if (fill.Item1 < 0)
|
||||
{
|
||||
var absQuantity = Math.Abs(quantityFilled);
|
||||
sellQuantity += absQuantity;
|
||||
averageSellPrice += absQuantity * fill.Item2;
|
||||
}
|
||||
}
|
||||
|
||||
if (buyQuantity > 0)
|
||||
averageBuyPrice /= buyQuantity;
|
||||
if (sellQuantity > 0)
|
||||
averageSellPrice /= sellQuantity;
|
||||
|
||||
//buyQuantity = (buyQuantity / _multiplier);
|
||||
//sellQuantity = (sellQuantity / _multiplier);
|
||||
Net = buyQuantity - sellQuantity;
|
||||
AverageOpenPrice = Net > 0 ? averageBuyPrice : averageSellPrice;
|
||||
|
||||
var absoluteRealized = (averageSellPrice - averageBuyPrice) * Math.Min(buyQuantity, sellQuantity);
|
||||
if (direction == TradeDirection.Long)
|
||||
{
|
||||
Realized = absoluteRealized;
|
||||
} else
|
||||
{
|
||||
Realized = -absoluteRealized;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFill(decimal quantity, decimal price, TradeDirection direction)
|
||||
{
|
||||
if (quantity == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(quantity), "Quantity must be non-zero.");
|
||||
|
||||
if (Math.Sign(Net) != Math.Sign(quantity))
|
||||
{
|
||||
decimal absNet = Math.Abs(Net);
|
||||
decimal absQuantity = Math.Abs(quantity);
|
||||
decimal realizedResult = 0;
|
||||
|
||||
if (absNet == absQuantity) // flat
|
||||
{
|
||||
realizedResult = (price - AverageOpenPrice) * Net;
|
||||
AverageOpenPrice = 0;
|
||||
}
|
||||
else if (absNet > absQuantity) // decrease
|
||||
{
|
||||
realizedResult = (price - AverageOpenPrice) * -quantity;
|
||||
}
|
||||
else // reverse
|
||||
{
|
||||
realizedResult = (price - AverageOpenPrice) * Net;
|
||||
AverageOpenPrice = price;
|
||||
}
|
||||
|
||||
if (direction == TradeDirection.Long)
|
||||
Realized += realizedResult;
|
||||
else
|
||||
Realized += -realizedResult;
|
||||
}
|
||||
else // increase position
|
||||
{
|
||||
AverageOpenPrice = (Net * AverageOpenPrice + quantity * price) / (Net + quantity);
|
||||
}
|
||||
|
||||
Net += quantity;
|
||||
}
|
||||
|
||||
public decimal FloatingForTheoriticalExit(decimal exitPrice)
|
||||
{
|
||||
return (exitPrice - AverageOpenPrice) * Net;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/Managing.Domain/Trades/Trade.cs
Normal file
69
src/Managing.Domain/Trades/Trade.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Trades
|
||||
{
|
||||
public class Trade
|
||||
{
|
||||
public Trade(DateTime date, TradeDirection direction, TradeStatus status, TradeType tradeType, Ticker ticker,
|
||||
decimal quantity, decimal price, decimal? leverage, string exchangeOrderId, string message)
|
||||
{
|
||||
Date = date;
|
||||
Direction = direction;
|
||||
Status = status;
|
||||
TradeType = tradeType;
|
||||
Ticker = ticker;
|
||||
Quantity = quantity;
|
||||
Price = price;
|
||||
Leverage = leverage.GetValueOrDefault();
|
||||
ExchangeOrderId = exchangeOrderId;
|
||||
Message = message;
|
||||
Fee = 0;
|
||||
}
|
||||
|
||||
public decimal Fee { get; set; }
|
||||
[Required]
|
||||
public DateTime Date { get; }
|
||||
[Required]
|
||||
public TradeDirection Direction { get; }
|
||||
[Required]
|
||||
public TradeStatus Status { get; private set; }
|
||||
[Required]
|
||||
public TradeType TradeType { get; }
|
||||
[Required]
|
||||
public Ticker Ticker { get; }
|
||||
[Required]
|
||||
public decimal Quantity { get; set; }
|
||||
[Required]
|
||||
public decimal Price { get; set; }
|
||||
public decimal Leverage { get; }
|
||||
[Required]
|
||||
public string ExchangeOrderId { get; private set; }
|
||||
public string Message { get; private set; }
|
||||
|
||||
public void SetStatus(TradeStatus status)
|
||||
{
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public void SetExchangeOrderId(string exchangeOrderId)
|
||||
{
|
||||
ExchangeOrderId = exchangeOrderId;
|
||||
}
|
||||
|
||||
public void SetMessage(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public void SetQuantity(decimal quantity, int precision)
|
||||
{
|
||||
Quantity = Math.Round(quantity, precision);
|
||||
}
|
||||
|
||||
public void SetPrice(decimal price, int precision)
|
||||
{
|
||||
Price = Math.Round(price, precision);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Managing.Domain/Users/User.cs
Normal file
9
src/Managing.Domain/Users/User.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Managing.Domain.Accounts;
|
||||
|
||||
namespace Managing.Domain.Users;
|
||||
|
||||
public class User
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public List<Account> Accounts { get; set; }
|
||||
}
|
||||
13
src/Managing.Domain/Workers/Worker.cs
Normal file
13
src/Managing.Domain/Workers/Worker.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workers;
|
||||
|
||||
public class Worker
|
||||
{
|
||||
public WorkerType WorkerType { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime? LastRunTime { get; set; }
|
||||
public int ExecutionCount { get; set; }
|
||||
public TimeSpan Delay { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
19
src/Managing.Domain/Workflows/FlowBase.cs
Normal file
19
src/Managing.Domain/Workflows/FlowBase.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows;
|
||||
|
||||
public abstract class FlowBase : IFlow
|
||||
{
|
||||
public abstract Guid Id { get; }
|
||||
public abstract string Name { get; }
|
||||
public abstract FlowType Type { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract List<FlowOutput> AcceptedInputs { get; }
|
||||
public abstract List<IFlow> Children { get; set; }
|
||||
public abstract List<FlowParameter> Parameters { get; set; }
|
||||
public abstract Guid ParentId { get; }
|
||||
public abstract string Output { get; set; }
|
||||
public abstract List<FlowOutput> OutputTypes { get; }
|
||||
public abstract Task Execute(string input);
|
||||
public abstract void MapParameters();
|
||||
}
|
||||
7
src/Managing.Domain/Workflows/FlowParameter.cs
Normal file
7
src/Managing.Domain/Workflows/FlowParameter.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Managing.Domain.Workflows;
|
||||
|
||||
public class FlowParameter
|
||||
{
|
||||
public dynamic Value { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
26
src/Managing.Domain/Workflows/IFlow.cs
Normal file
26
src/Managing.Domain/Workflows/IFlow.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows;
|
||||
|
||||
public interface IFlow
|
||||
{
|
||||
[Required]
|
||||
Guid Id { get; }
|
||||
[Required]
|
||||
string Name { get; }
|
||||
[Required]
|
||||
FlowType Type { get; }
|
||||
[Required]
|
||||
string Description { get; }
|
||||
[Required]
|
||||
List<FlowOutput> AcceptedInputs { get; }
|
||||
List<IFlow> Children { get; set; }
|
||||
[Required]
|
||||
List<FlowParameter> Parameters { get; set; }
|
||||
Guid ParentId { get; }
|
||||
string Output { get; set; }
|
||||
[Required]
|
||||
List<FlowOutput> OutputTypes { get; }
|
||||
Task Execute(string input);
|
||||
}
|
||||
15
src/Managing.Domain/Workflows/Synthetics/SyntheticFlow.cs
Normal file
15
src/Managing.Domain/Workflows/Synthetics/SyntheticFlow.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
public class SyntheticFlow
|
||||
{
|
||||
[Required]
|
||||
public string Id { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
[Required]
|
||||
public FlowType Type { get; set; }
|
||||
[Required]
|
||||
public List<SyntheticFlowParameter> Parameters { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
public class SyntheticFlowParameter
|
||||
{
|
||||
[Required]
|
||||
public string Value { get; set; }
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows.Synthetics;
|
||||
|
||||
public class SyntheticWorkflow
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public WorkflowUsage Usage { get; set; }
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
[Required]
|
||||
public List<SyntheticFlow> Flows { get; set; }
|
||||
}
|
||||
24
src/Managing.Domain/Workflows/Workflow.cs
Normal file
24
src/Managing.Domain/Workflows/Workflow.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Workflows;
|
||||
|
||||
public class Workflow
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public WorkflowUsage Usage { get; set; }
|
||||
[Required]
|
||||
public List<IFlow> Flows { get; set; }
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
|
||||
public async Task Execute()
|
||||
{
|
||||
foreach (var flow in Flows)
|
||||
{
|
||||
await flow.Execute(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user