* Fix time for candle * Fix out ouf range * Fix pnl, fix custom money management * Clean a bit
175 lines
6.8 KiB
C#
175 lines
6.8 KiB
C#
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).ToList();
|
|
var signals = signalOnCandles.Where(s => s.SignalType == SignalType.Signal).ToList();
|
|
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))
|
|
.ToList();
|
|
|
|
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, decimal leverage)
|
|
{
|
|
var orders = new List<Tuple<decimal, decimal>>
|
|
{
|
|
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
|
|
new Tuple<decimal, decimal>(-quantity, price)
|
|
};
|
|
|
|
|
|
var pnl = new ProfitAndLoss(orders, position.OriginDirection);
|
|
// Apply leverage on the realized pnl
|
|
pnl.Realized = pnl.Realized * leverage;
|
|
return pnl;
|
|
}
|
|
} |