public static void CorrelateExposureBars(this PortfolioSimulation sim, Correlation c) { double count = c.Component1.LongExposureBars.Count; if (c.Component1.LongExposureBars.Count != count) { throw new Exception($"Mismatched number of bars between {c.Component2.ComponentId} ({count}) and {c.Component2.ComponentId} ({c.Component1.LongExposureBars.Count})"); } double highSum = 0; double lowSum = 0; //double pow = 2; for (int i = 0; i < count; i++) { //highSum += Math.Pow(Math.Abs(c.Component1.LongExposureBars[i].High - c.Component2.LongExposureBars[i].High), pow); //lowSum += Math.Pow(Math.Abs(c.Component1.LongExposureBars[i].Low - c.Component2.LongExposureBars[i].Low), pow); highSum += Math.Abs(c.Component1.LongExposureBars[i].High - c.Component2.LongExposureBars[i].High); lowSum += Math.Abs(c.Component1.LongExposureBars[i].Low - c.Component2.LongExposureBars[i].Low); //weightedSum += Math.Pow(Math.Abs(c.Component1.LongExposureBars[i].Low - c.Component2.LongExposureBars[i].Low), pow); } //c.HighScore = Math.Sqrt(highSum); //c.LowScore = Math.Sqrt(highSum); c.HighScore = highSum / count; c.LowScore = lowSum / count; }
public static void WriteLineVerbosity(this PortfolioSimulation sim, string msg, int verbosityLevel = 1) { if (sim.Options.Verbosity >= verbosityLevel) { WriteLine(msg); } }
public static async Task <PortfolioSimulation> Simulate(Portfolio portfolio, PortfolioAnalysisOptions options, CancellationToken?token = null) { var sim = new PortfolioSimulation(portfolio, options); await new PortfolioSimulator(sim).Simulate(token); return(sim); }
public static Correlation GetCorrelation(this PortfolioSimulation sim, string correlationId) { var chunks = correlationId.Split('|'); var c = new Correlation(sim.Portfolio.FindComponent(chunks[0]), sim.Portfolio.FindComponent(chunks[1])); foreach (var component in c.Components) { if (!component.NormalizationMultiplier.HasValue) { component.UpdateComponentNormalizationMultiplier(sim); } } return(c); }
public PortfolioSimulator(PortfolioSimulation sim) { this.Sim = sim; if (Sim == null) { throw new ArgumentNullException("Sim must be set"); } this.Options = sim.Options; if (sim.Options == null) { throw new ArgumentNullException("Sim.Options must be set"); } //this.log = Log.Get(); this.log = this.GetLogger(); }
private static double NormalizeVolumeSource(PortfolioSimulation sim, PortfolioComponent component, double unnormalizedVolume) { double value = unnormalizedVolume * component.NormalizationMultiplier.Value; #if DEBUG if (sim.Options.VolumeNormalization.MaxSourceValue.HasValue && value > sim.Options.VolumeNormalization.MaxSourceValue.Value) { if (sim.Options.Verbosity >= 6) { Console.WriteLine($"Capping normalized volume of {value} to max: {sim.Options.VolumeNormalization.MaxSourceValue.Value}"); } } #endif value = Math.Min(value, sim.Options.VolumeNormalization.MaxSourceValue.Value); return(value); }
public static void CorrelateTimeInMarket(this PortfolioSimulation sim, Correlation c) { IEnumerable <PortfolioHistoricalTradeVM> AllTrades = c.Components .Select(b => b.Trades.OfType <_HistoricalTrade>().Select(t => new PortfolioHistoricalTradeVM { Trade = t, Component = b })) .SelectMany(v => v); var tradeEvents = AllTrades.Select(t => new TradeEnterExitEvent { Enter = true, Time = t.Trade.EntryTime, Trade = t }) .Concat(AllTrades.Select(t => new TradeEnterExitEvent { Enter = false, Time = t.Trade.ClosingTime, Trade = t })) .OrderBy(e => e.Time); //var TradesByEntryTime = AllTrades // .OrderBy(t => t.Trade.EntryTime) // .ToList(); // TODO: Normalized versions of these that are rated versus the max normalized score (which should be 1) TimeSpan BothLong = TimeSpan.Zero; TimeSpan BothShort = TimeSpan.Zero; TimeSpan OppositeDirection = TimeSpan.Zero; TimeSpan OffsettingNonZero = TimeSpan.Zero; TimeSpan BothFlat = TimeSpan.Zero; TimeSpan OneOpen = TimeSpan.Zero; // Not sure if this is useful yet. double MultipliedByDifference = 0; var openTrades = new List <PortfolioHistoricalTradeVM>(); double netVolume1 = 0; double netVolume2 = 0; double normalizedNetVolume1 = 0; double normalizedNetVolume2 = 0; double delta = normalizedNetVolume2 - normalizedNetVolume1; DateTime simTime = sim.Portfolio.Start.Value; TimeSpan totalTime; foreach (var e in tradeEvents) { #region Time Delta TimeSpan timeDelta; if (sim.Options.StartTime != default && e.Time < sim.Options.StartTime) { timeDelta = e.Time - sim.Options.StartTime; } else if (sim.Options.EndTime != default && e.Time > sim.Options.EndTime) { timeDelta = sim.Options.StartTime - e.Time; } else { timeDelta = e.Time - simTime; } if (timeDelta < TimeSpan.Zero) { continue; // If equal to zero, keep going, since there may be multiple events at the same bar opens. } totalTime += timeDelta; #endregion #region Collect stats from previous time delta #if DEBUG if (sim.Options.Verbosity >= 5) { WriteLine($"1: {normalizedNetVolume1} 2: {normalizedNetVolume2}"); } #endif // TODO NEXT: for the period of time between simTime and trade entry time (timeDelta), multiply this period of time by the volume delta over that time. if (normalizedNetVolume2 > 0 && normalizedNetVolume1 > 0) { BothLong += timeDelta; } else if (normalizedNetVolume2 < 0 && normalizedNetVolume1 < 0) { BothShort += timeDelta; } else if (normalizedNetVolume2 == 0 && normalizedNetVolume1 == 0) { BothFlat += timeDelta; } if (normalizedNetVolume2 < 0 && normalizedNetVolume1 > 0 || normalizedNetVolume2 < 0 && normalizedNetVolume1 > 0) { OppositeDirection += timeDelta; } else if (normalizedNetVolume2 != 0 && normalizedNetVolume1 == 0 || normalizedNetVolume2 == 0 && normalizedNetVolume1 != 0) { OneOpen += timeDelta; } // Another idea: non-zero, offsetting if (normalizedNetVolume2 != 0 && normalizedNetVolume1 == -normalizedNetVolume2) { OffsettingNonZero += timeDelta; } // These two normalized volumes should be normalized to -1 to +1. //MillisecondsMultipliedByDifference += timeDelta.TotalMilliseconds * (normalizedNetVolume2 - normalizedNetVolume1); MultipliedByDifference += timeDelta.TotalMilliseconds * Math.Pow(normalizedNetVolume2 - normalizedNetVolume1, 2); #endregion if (sim.Options.EndTime != default && simTime >= sim.Options.EndTime) { break; } #region Process the trade open/close event if (e.Enter) { sim.WriteLineVerbosity("OPEN trade:" + e.Trade.Trade.NetVolume, 5); openTrades.Add(e.Trade); } else { sim.WriteLineVerbosity("CLOSE trade:" + e.Trade.Trade.NetVolume, 5); if (!openTrades.Remove(e.Trade)) { WriteLine("[correlation] Removing from open trades - NOT FOUND: " + e + $" (entry time: {e.Trade.Trade.EntryTime})"); } } #endregion #region Set up state for the next trade: netVolume1 = 0; netVolume2 = 0; normalizedNetVolume1 = 0; normalizedNetVolume2 = 0; foreach (var t in openTrades) { if (t.Component.ComponentId == c.Component1.ComponentId) { var volumeDelta = t.Trade.TradeType == TradeType.Buy ? t.Trade.Volume : -t.Trade.Volume; netVolume1 += t.Trade.TradeType == TradeType.Buy ? t.Trade.Volume : -t.Trade.Volume; normalizedNetVolume1 += PortfolioNormalization.NormalizeVolume(t, sim, CorrelationNormalizationOptions); normalizedNetVolume1 = netVolume1; } else { #if DEBUG if (t.Component.ComponentId != c.Component2.ComponentId) { throw new UnreachableCodeException(); } #endif netVolume2 += t.Trade.TradeType == TradeType.Buy ? t.Trade.Volume : -t.Trade.Volume; normalizedNetVolume2 += PortfolioNormalization.NormalizeVolume(t, sim, CorrelationNormalizationOptions); normalizedNetVolume2 = netVolume2; } } delta = normalizedNetVolume2 - normalizedNetVolume1; simTime = e.Time; #endregion } //var PositionSimilarityScore = String.Format(sim.Options.NumberFormat, ((BothLong + BothShort) - Opposite).TotalMilliseconds / totalTime.TotalMilliseconds); //var PositioningSimilarityScore = String.Format(sim.Options.NumberFormat, ((BothLong + BothShort + BothFlat) - Opposite).TotalMilliseconds / totalTime.TotalMilliseconds); var SameDir = BothLong + BothShort; var BothOpen = SameDir + OppositeDirection; var BothSamePositioning = BothOpen + BothFlat; WriteLine(new { TotalTime = totalTime, BothFlat, BothLong, BothShort, OppositeDirection, BothOpen, SameDir, //OffsettingNonZero, BothFlatPc = (BothFlat.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), BothLongPc = (BothLong.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), BothShortPc = (BothShort.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), SameDirPc = (SameDir.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), OppositeDirectionPc = (OppositeDirection.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), BothOpenPc = (BothOpen.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), OffsettingNonZeroPc = (OffsettingNonZero.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), //OppositePc = (Opposite.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), SameDirectionSimilarityPc = ((SameDir).TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString() + " / " + (BothOpen.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), OppositeDirectionSimilarityPc = ((OppositeDirection).TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString() + " / " + (BothOpen.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), OpenDirectionNetSimilarityPc = ((SameDir - OppositeDirection).TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString() + " / " + (BothOpen.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), // TODO: same as OpenDirectionNetSimilarityPc but use normalized volumes: //NetAmplifyingOrHedgingPc = ((SameDir - OppositeDirection).TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString() // + " / " + (BothOpen.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), SimilarityWhenSharedDirectionPc = ((BothSamePositioning - OppositeDirection).TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString() + " / " + (BothSamePositioning.TotalMilliseconds / totalTime.TotalMilliseconds).ToPercentString(), DifferenceScore = Math.Sqrt(MultipliedByDifference / totalTime.TotalMilliseconds), }.DumpProperties("Time in market")); }
/// <summary> /// Gets the multiplier by which all trade volumes from this component are multipied before using in portfolio and correlation analysis. /// </summary> public static void UpdateComponentNormalizationMultiplier(this PortfolioComponent component, PortfolioSimulation sim) { double multiplier = 1.0; var minTradeVolumeForSymbol = component.SymbolHandle.GetMinTradeVolumeForSymbol(); if (sim.Options.VolumeNormalization.ReductionMode?.HasFlag(VolumeNormalizationReductionMode.DivideByMinimumsMultipleOfMinimumAllowedTradeSize) == true) { var min = component.MinAbsoluteVolume; if (min > minTradeVolumeForSymbol) { multiplier *= minTradeVolumeForSymbol / min; #if DEBUG if (sim.Options.Verbosity >= 5) { Console.WriteLine($"[VolumeNormalize] component absolute min: {min}, min for symbol {component.BacktestResult.Symbol}: {minTradeVolumeForSymbol}, multiplier: {multiplier}"); } #endif } } if (sim.Options.VolumeNormalization.ReductionMode?.HasFlag(VolumeNormalizationReductionMode.DivideByMinimumAllowedTradeVolume) == true) { multiplier /= minTradeVolumeForSymbol; } component.NormalizationMultiplier = multiplier; }
/// <param name="trade"></param> /// <param name="sim"></param> /// <returns>Returns negative numbers for Sell trades.</returns> public static double NormalizeVolume(PortfolioHistoricalTradeVM trade, PortfolioSimulation sim, VolumeNormalizationOptions normalizationOptions = null) { // Note: Volume is used here instead of NetVolume, to have positive volumes for both buy and sell trades var normalizedTradeVolume = NormalizeVolumeSource(sim, trade.Component, trade.Trade.Volume); #if DEBUG if (sim.Options.Verbosity >= 5) { Console.WriteLine($"[normalize] {trade.Trade.SymbolCode} normalized volume source from ({trade.Trade.TradeType.ToString()}) {trade.Trade.Volume} to {normalizedTradeVolume}"); } #endif if ((normalizationOptions?.MaxMode ?? sim.Options.VolumeNormalization.MaxMode).Value != VolumeNormalizationTargetMode.None) { double max = normalizationOptions?.Max ?? sim.Options.VolumeNormalization.Max ?? trade.Component.MaxAbsoluteVolume; //switch (mode ?? sim.Options.VolumeNormalization.VolumeNormalizationTargetMax) //{ // case PortfolioNormalizationTargetMaxMode.None: // //var componentMaxNormalizedVolume = NormalizeVolumeSource(sim, trade.Component, trade.Component.MaxAbsoluteVolume); // //max = componentMaxNormalizedVolume; // //max = // break; // case PortfolioNormalizationTargetMaxMode.ToConstant: // max = ; // break; // //case PortfolioNormalizationTargetMaxMode. // //case PortfolioNormalizationTargetMaxMode.ScaleToMinTradeVolume: // // max = // // value = // // if (trade.Trade.TradeType == TradeType.Sell) value *= -1; // // break; // //case PortfolioNormalizationTargetMaxMode.ToBacktestMaxTradeSize: // // value = trade.Trade.Volume / trade.Component.MaxAbsoluteVolume; // // if (trade.Trade.TradeType == TradeType.Sell) value *= -1; // // break; // ////case PortfolioNormalizationMode.ToBacktestConfiguredMaxTradeSize: // //// throw new NotImplementedException(); // //// break; // default: // //case PortfolioNormalizationMode.Unspecified: // throw new ArgumentException(nameof(sim.Options.VolumeNormalization.VolumeNormalizationTargetMax)); //} switch (sim.Options.VolumeNormalization.Curve) { case PortfolioNormalizationCurveType.Linear: normalizedTradeVolume = trade.Trade.TradeType == TradeType.Buy ? trade.Trade.Volume : -trade.Trade.Volume; break; case PortfolioNormalizationCurveType.Step: normalizedTradeVolume = normalizedTradeVolume >= sim.Options.VolumeNormalization.StepThreshold ? max : 0; break; case PortfolioNormalizationCurveType.EaseIn: { double normalizedTo1 = normalizedTradeVolume / max; normalizedTradeVolume = max * Math.Pow(normalizedTo1, sim.Options.VolumeNormalization.EasingExponent ?? VolumeNormalizationOptions.DefaultEasingExponent); break; } case PortfolioNormalizationCurveType.EaseOut: { double normalizedTo1 = normalizedTradeVolume / max; normalizedTradeVolume = max * Math.Pow(1 - (1 - normalizedTo1), sim.Options.VolumeNormalization.EasingExponent ?? VolumeNormalizationOptions.DefaultEasingExponent); break; } //case PortfolioNormalizationCurveType.Unspecified: default: break; } } if (trade.Trade.TradeType == TradeType.Sell) { normalizedTradeVolume *= -1; } #if DEBUG if (sim.Options.Verbosity >= 0) { if (trade.Trade.Volume != 1) { Console.WriteLine($"[NORMALIZE] {trade.Trade.SymbolCode} normalized volume from ({trade.Trade.TradeType.ToString()}) {trade.Trade.Volume} to {normalizedTradeVolume} (final)"); } } #endif return(normalizedTradeVolume); }