private void BuyAsset(IndexedCandle indexedCandle, decimal premium, IDictionary <IList <Candle>, decimal> assetCashMap, IList <Transaction> transactions) { if (assetCashMap.TryGetValue(indexedCandle.Candles, out decimal cash)) { var nextCandle = indexedCandle.Next; int quantity = Convert.ToInt32(Math.Floor((cash - premium) / nextCandle.Open)); decimal cashOut = nextCandle.Open * quantity + premium; assetCashMap[indexedCandle.Candles] -= cashOut; transactions.Add(new Transaction(indexedCandle.Candles, nextCandle.Index, nextCandle.DateTime, TransactionType.Buy, quantity, cashOut)); OnBought?.Invoke(indexedCandle.Candles, nextCandle.Index, nextCandle.DateTime, nextCandle.Open, quantity, cashOut, assetCashMap[indexedCandle.Candles]); } }
private void SellAsset(IndexedCandle indexedCandle, decimal premium, IDictionary <IList <Candle>, decimal> assetCashMap, IList <Transaction> transactions) { if (assetCashMap.TryGetValue(indexedCandle.Candles, out _)) { var nextCandle = indexedCandle.Next; var lastTransaction = transactions.LastOrDefault(t => t.Candles.Equals(indexedCandle.Candles)); if (lastTransaction.Type == TransactionType.Sell) { return; } decimal cashIn = nextCandle.Open * lastTransaction.Quantity - premium; decimal plRatio = (cashIn - lastTransaction.AbsoluteCashFlow) / lastTransaction.AbsoluteCashFlow; assetCashMap[indexedCandle.Candles] += cashIn; transactions.Add(new Transaction(indexedCandle.Candles, nextCandle.Index, nextCandle.DateTime, TransactionType.Sell, lastTransaction.Quantity, cashIn)); OnSold?.Invoke(indexedCandle.Candles, nextCandle.Index, nextCandle.DateTime, nextCandle.Open, lastTransaction.Quantity, cashIn, assetCashMap[indexedCandle.Candles], plRatio); } }
public Result RunBacktest(decimal principal, decimal premium = 1.0m, DateTime?startTime = null, DateTime?endTime = null) { if (_weightings == null || !_weightings.Any()) { throw new ArgumentException("You should have at least one candle set for calculation"); } // Distribute principal to each candle set decimal totalWeight = _weightings.Sum(w => w.Value); IReadOnlyDictionary <IList <Candle>, decimal> preAssetCashMap = _weightings.ToDictionary(w => w.Key, w => principal * w.Value / totalWeight); var assetCashMap = preAssetCashMap.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); // Init transaction history var transactions = new List <Transaction>(); // Loop with each asset for (int i = 0; i < _weightings.Count; i++) { var asset = assetCashMap.ElementAt(i).Key; var startIndex = asset.FindIndexOrDefault(c => c.DateTime >= (startTime ?? DateTime.MinValue), 0).Value; var endIndex = asset.FindLastIndexOrDefault(c => c.DateTime <= (endTime ?? DateTime.MaxValue), asset.Count - 1).Value; for (int j = startIndex; j <= endIndex; j++) { var indexedCandle = new IndexedCandle(asset, j); var lastTransaction = transactions.LastOrDefault(t => t.Candles.Equals(asset)); if (lastTransaction?.Type != TransactionType.Buy && _buyRule.IsValid(indexedCandle)) { BuyAsset(indexedCandle, premium, assetCashMap, transactions); } else if (lastTransaction?.Type != TransactionType.Sell && _sellRule.IsValid(indexedCandle)) { SellAsset(indexedCandle, premium, assetCashMap, transactions); } } } return(new Result(preAssetCashMap, assetCashMap, transactions)); }
public static bool IsEmaOscBearish(this IndexedCandle ic, int periodCount1, int periodCount2) => ic.Get <ExponentialMovingAverageOscillatorTrend>(periodCount1, periodCount2)[ic.Index] == Trend.Bearish;
public static decimal?PriceChange(this IndexedCandle ic) => ic.Get <ClosePriceChange>()[ic.Index];
public static bool IsSmaBearish(this IndexedCandle ic, int periodCount) => ic.Get <SimpleMovingAverageTrend>(periodCount)[ic.Index] == Trend.Bearish;
public static bool IsAboveSma(this IndexedCandle ic, int periodCount) => ic.Get <IsAboveSimpleMovingAverage>(periodCount)[ic.Index] ?? false;
public static bool IsFullStoOverbought(this IndexedCandle ic, int periodCount, int smaPeriodCountK, int smaPeriodCountD) => ic.Get <StochasticsOvertrade.Full>(periodCount, smaPeriodCountK, smaPeriodCountD)[ic.Index] == Overtrade.SeverelyOverbought;
public static bool IsLowest(this IndexedCandle ic, int periodCount) => ic.Get <IsLowestPrice>(periodCount)[ic.Index] ?? false;
public static bool IsSlowStoOscBearish(this IndexedCandle ic, int periodCount, int smaPeriodCountD) => ic.Get <StochasticsOscillatorTrend.Slow>(periodCount, smaPeriodCountD)[ic.Index] == Trend.Bearish;
public static bool IsFullStoOscBullish(this IndexedCandle ic, int periodCount, int smaPeriodCountK, int smaPeriodCountD) => ic.Get <StochasticsOscillatorTrend.Full>(periodCount, smaPeriodCountK, smaPeriodCountD)[ic.Index] == Trend.Bullish;
public static bool IsFastStoOversold(this IndexedCandle ic, int periodCount, int smaPeriodCount) => ic.Get <StochasticsOvertrade.Fast>(periodCount, smaPeriodCount)[ic.Index].Tick == Overtrade.SeverelyOversold;
public static bool IsFastStoBearishCross(this IndexedCandle ic, int periodCount, int smaPeriodCount) => ic.Get <StochasticsCrossover.Fast>(periodCount, smaPeriodCount)[ic.Index].Tick == Crossover.BearishCrossover;
public static decimal?PricePercentageChange(this IndexedCandle ic) => ic.Get <ClosePricePercentageChange>()[ic.Index].Tick;
public static bool IsFastStoOscBearish(this IndexedCandle ic, int periodCount, int smaPeriodCount) => ic.Get <StochasticsOscillatorTrend.Fast>(periodCount, smaPeriodCount)[ic.Index].Tick == Trend.Bearish;
public static bool IsObvBearish(this IndexedCandle ic) => ic.Get <OnBalanceVolumeTrend>()[ic.Index] == Trend.Bearish;
public static bool IsInBbRange(this IndexedCandle ic, int periodCount, int sdCount) => ic.Get <BollingerBandsInRange>(periodCount, sdCount)[ic.Index] == Overboundary.InRange;
public static bool IsSmaBullishCross(this IndexedCandle ic, int periodCount1, int periodCount2) => ic.Get <SimpleMovingAverageCrossover>(periodCount1, periodCount2)[ic.Index] == Crossover.BullishCrossover;
public static bool IsRsiOversold(this IndexedCandle ic, int periodCount) => ic.Get <RelativeStrengthIndexOvertrade>(periodCount)[ic.Index] == Overtrade.Oversold;
public static bool IsEmaBearishCross(this IndexedCandle ic, int periodCount1, int periodCount2) => ic.Get <ExponentialMovingAverageCrossover>(periodCount1, periodCount2)[ic.Index] == Crossover.BearishCrossover;
public static bool IsSlowStoOversold(this IndexedCandle ic, int periodCount, int smaPeriodCountD) => ic.Get <StochasticsOvertrade.Slow>(periodCount, smaPeriodCountD)[ic.Index] == Overtrade.SeverelyOversold;
public static bool IsMacdBearishCross(this IndexedCandle ic, int emaPeriodCount1, int emaPeriodCount2, int demPeriodCount) => ic.Get <MovingAverageConvergenceDivergenceCrossover>(emaPeriodCount1, emaPeriodCount2, demPeriodCount)[ic.Index] == Crossover.BearishCrossover;
public static bool IsAboveEma(this IndexedCandle ic, int periodCount) => ic.Get <IsAboveExponentialMovingAverage>(periodCount)[ic.Index] ?? false;
public static bool IsFullStoBullishCross(this IndexedCandle ic, int periodCount, int smaPeriodCountK, int smaPeriodCountD) => ic.Get <StochasticsCrossover.Full>(periodCount, smaPeriodCountK, smaPeriodCountD)[ic.Index] == Crossover.BullishCrossover;
public static bool IsSmaOscBullish(this IndexedCandle ic, int periodCount1, int periodCount2) => ic.Get <SimpleMovingAverageOscillatorTrend>(periodCount1, periodCount2)[ic.Index] == Trend.Bullish;
public static bool IsSlowStoBearishCross(this IndexedCandle ic, int periodCount, int smaPeriodCountD) => ic.Get <StochasticsCrossover.Slow>(periodCount, smaPeriodCountD)[ic.Index] == Crossover.BearishCrossover;
public static bool IsEmaBearish(this IndexedCandle ic, int periodCount) => ic.Get <ExponentialMovingAverageTrend>(periodCount)[ic.Index] == Trend.Bearish;
public static bool IsCandlesBearish(this IndexedCandle ic) => ic.Get <ClosePriceChangeTrend>()[ic.Index] == Trend.Bearish;
public static bool IsMacdOscBearish(this IndexedCandle ic, int emaPeriodCount1, int emaPeriodCount2, int demPeriodCount) => ic.Get <MovingAverageConvergenceDivergenceOscillatorTrend>(emaPeriodCount1, emaPeriodCount2, demPeriodCount)[ic.Index] == Trend.Bearish;
public static bool IsAccumDistBearish(this IndexedCandle ic) => ic.Get <AccumulationDistributionLineTrend>()[ic.Index] == Trend.Bearish;