#!meta {"kernelInfo":{"defaultKernelName":"csharp","items":[{"name":"csharp"},{"name":"fsharp","languageName":"F#","aliases":["f#","fs"]},{"name":"html","languageName":"HTML"},{"name":"http","languageName":"HTTP"},{"name":"javascript","languageName":"JavaScript","aliases":["js"]},{"name":"mermaid","languageName":"Mermaid"},{"name":"pwsh","languageName":"PowerShell","aliases":["powershell"]},{"name":"value"}]}} #!markdown # Triangular Moving Average (TRIMA) Examples This is a **.NET Interactive** notebook. To run it, you need the [Polyglot Notebooks](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) extension installed in VS Code. The **Triangular Moving Average (TRIMA)** is a weighted moving average where the weights increase linearly to the middle of the period and then decrease. It is equivalent to a double-smoothed SMA (SMA of an SMA). **Key characteristics:** - Triangular weighting (emphasis on middle values) - Smoother than SMA - Higher lag than SMA - O(1) update complexity #!csharp // Reference the library #r "..\..\bin\Debug\net10.0\QuanTAlib.dll" using System; using System.Linq; using QuanTAlib; // Helper to print TSeries void PrintSeries(TSeries series, int count = 5) { Console.WriteLine($"Series Length: {series.Count}"); foreach (var item in series.Take(count)) { Console.WriteLine($"Time: {item.Time:HH:mm:ss}, Value: {item.Value:F2}"); } if (series.Count > count) Console.WriteLine("..."); } #!markdown ## 1. Manual Data: Batch vs. Streaming We'll start with a small, manually created dataset. #!csharp // Create a small manual dataset var manualData = new TSeries(); manualData.Add(DateTime.Now, 100.0); manualData.Add(DateTime.Now.AddMinutes(1), 102.0); manualData.Add(DateTime.Now.AddMinutes(2), 101.0); manualData.Add(DateTime.Now.AddMinutes(3), 103.0); manualData.Add(DateTime.Now.AddMinutes(4), 105.0); Console.WriteLine("--- Input Data ---"); PrintSeries(manualData, 5); // Batch Calculation Console.WriteLine("\n--- Batch TRIMA (Period 3) ---"); var trimaBatch = new Trima(3); var resultBatch = trimaBatch.Update(manualData); PrintSeries(resultBatch, 5); #!markdown ### Streaming Processing #!csharp Console.WriteLine("\n--- Streaming TRIMA (Period 3) ---"); var trimaStream = new Trima(3); foreach (var item in manualData) { var result = trimaStream.Update(item); Console.WriteLine($"Time: {item.Time:HH:mm:ss}, Input: {item.Value:F2}, TRIMA: {result.Value:F2}, IsHot: {trimaStream.IsHot}"); } // Verify that the last values match var batchLast = resultBatch.Last().Value; var streamLast = trimaStream.Value.Value; Console.WriteLine($"\nMatch: {Math.Abs(batchLast - streamLast) < 1e-10} (Batch: {batchLast:F2}, Stream: {streamLast:F2})"); #!markdown ## 2. Large Dataset: Geometric Brownian Motion (GBM) We'll generate a larger dataset (1000 bars) using a Geometric Brownian Motion generator to simulate realistic market data. #!csharp // Generate 1000 bars of data var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2); var gbmData = gbm.Fetch(1000, DateTime.Now.Ticks, TimeSpan.FromMinutes(1)); var closeSeries = gbmData.Close; Console.WriteLine($"Generated {closeSeries.Count} bars of GBM data."); #!markdown ### Batch vs. Streaming Performance on Large Data #!csharp // Batch var trimaLargeBatch = new Trima(20); var batchLargeResult = trimaLargeBatch.Update(closeSeries); Console.WriteLine($"Batch Last Value: {batchLargeResult.Last().Value:F2}"); // Streaming var trimaLargeStream = new Trima(20); TValue lastStreamVal = default; foreach(var item in closeSeries) { lastStreamVal = trimaLargeStream.Update(item); } Console.WriteLine($"Streaming Last Value: {lastStreamVal.Value:F2}"); // Verify match Console.WriteLine($"Match: {Math.Abs(batchLargeResult.Last().Value - lastStreamVal.Value) < 1e-10}"); #!markdown ## 3. TRIMA vs SMA Comparison TRIMA is smoother than SMA but has more lag. Let's compare them on a volatile dataset. #!csharp Console.WriteLine("\n--- TRIMA vs SMA Comparison (Period 10) ---"); var compareData = new TSeries(); var baseTime = DateTime.Now; for (int i = 0; i < 20; i++) { // Create data with a sudden spike at position 10 double value = (i == 10) ? 150.0 : 100.0; compareData.Add(baseTime.AddMinutes(i), value); } var trimaCompare = new Trima(10); var smaCompare = new Sma(10); Console.WriteLine("Position | Input | TRIMA | SMA | Difference"); Console.WriteLine("---------+--------+---------+---------+-----------"); for (int i = 0; i < compareData.Count; i++) { var trimaVal = trimaCompare.Update(compareData[i]); var smaVal = smaCompare.Update(compareData[i]); var input = compareData[i].Value; var diff = trimaVal.Value - smaVal.Value; Console.WriteLine($" {i,2} | {input,6:F0} | {trimaVal.Value,7:F2} | {smaVal.Value,7:F2} | {diff,+9:F2}"); } Console.WriteLine("\nNote how TRIMA reacts more gradually to the spike compared to SMA.");