docker files fixes from liaqat

This commit is contained in:
alirehmani
2024-05-03 16:39:25 +05:00
commit 464a8730e8
587 changed files with 44288 additions and 0 deletions

View 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; }
}

View 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; }
}

View 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:#.##}%";
}
}

View 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;
}
}
}

View 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();
}
}

View 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; }
}
}

View 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()
};
}
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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;
}
}

View 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; }
}

View 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; }
}

View 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>

View 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;
}
}
}

View 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);
}
}
}

View 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(),
};
}
}

View 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;
}
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Managing.Domain.Shared.Rules
{
public interface IValidationRule
{
bool IsValid();
string Message { get; }
}
}

View File

@@ -0,0 +1,9 @@
namespace Managing.Domain.Shared.Rules
{
public class RuleException : Exception
{
public RuleException(string message) : base(message)
{
}
}
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}
}

View 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; }
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}

View 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; }
}
}

View 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; }
}
}

View 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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View 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;
}
}
}

View 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; }
}
}

View 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;
}
}
}

View 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; }
}
}

View 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; }
}
}

View 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;
}
}
}

View 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; }
}
}

View 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;
}
}
}
}

View 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; }
}

View File

@@ -0,0 +1,7 @@
namespace Managing.Domain.Trades;
public class OrderBookEntry
{
public decimal Price { get; set; }
public decimal Quantity { get; set; }
}

View 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;
}
}

View File

@@ -0,0 +1,8 @@
namespace Managing.Domain.Trades;
public class Orderbook
{
public List<OrderBookEntry> Bids { get; set; }
public List<OrderBookEntry> Asks { get; set; }
}

View 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
};
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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; }
}

View 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; }
}

View 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();
}

View File

@@ -0,0 +1,7 @@
namespace Managing.Domain.Workflows;
public class FlowParameter
{
public dynamic Value { get; set; }
public string Name { get; set; }
}

View 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);
}

View 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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View 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);
}
}
}