diff --git a/Directory.Build.props b/Directory.Build.props index 3e313696..681f6bc6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,5 @@ - net8.0 preview $(NoWarn);NU1903;NU5104;NETSDK1057 enable diff --git a/QuanTAlib.sln b/QuanTAlib.sln index a05d420b..583ac393 100644 --- a/QuanTAlib.sln +++ b/QuanTAlib.sln @@ -1,21 +1,16 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "quantalib", "lib\quantalib.csproj", "{F455234B-2A3C-140A-17C3-683D7820A733}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{3A8DF596-E814-FECC-DD4B-D8EF8AAC1A0D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreTypes", "examples\CoreTypes\CoreTypes.csproj", "{8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuanTAlib.Tests", "lib\QuanTAlib.Tests.csproj", "{953F0406-DD9B-406E-993D-6D988D5F5423}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "quantower", "quantower", "{6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuanTAlib.Tests", "tests\QuanTAlib.Tests\QuanTAlib.Tests.csproj", "{43CA2584-D4AD-4082-AFF4-68B3D1239221}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "feeds", "feeds", "{2B942E44-74DA-CD21-D337-7A5E9D347C1B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GbmExample", "examples\feeds\GbmExample.csproj", "{B27145AF-B255-4D6E-827B-3512952FB29C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Averages", "quantower\Averages.csproj", "{D8F03B19-F99F-475F-8951-85C9D2258B73}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,51 +34,37 @@ Global {F455234B-2A3C-140A-17C3-683D7820A733}.Release|x64.Build.0 = Release|Any CPU {F455234B-2A3C-140A-17C3-683D7820A733}.Release|x86.ActiveCfg = Release|Any CPU {F455234B-2A3C-140A-17C3-683D7820A733}.Release|x86.Build.0 = Release|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|x64.ActiveCfg = Debug|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|x64.Build.0 = Debug|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|x86.ActiveCfg = Debug|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|x86.Build.0 = Debug|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|Any CPU.Build.0 = Release|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|x64.ActiveCfg = Release|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|x64.Build.0 = Release|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|x86.ActiveCfg = Release|Any CPU - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|x86.Build.0 = Release|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|x64.ActiveCfg = Debug|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|x64.Build.0 = Debug|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|x86.ActiveCfg = Debug|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|x86.Build.0 = Debug|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|Any CPU.Build.0 = Release|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|x64.ActiveCfg = Release|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|x64.Build.0 = Release|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|x86.ActiveCfg = Release|Any CPU - {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|x86.Build.0 = Release|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|x64.ActiveCfg = Debug|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|x64.Build.0 = Debug|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|x86.ActiveCfg = Debug|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|x86.Build.0 = Debug|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|Any CPU.Build.0 = Release|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|x64.ActiveCfg = Release|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|x64.Build.0 = Release|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|x86.ActiveCfg = Release|Any CPU - {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|x86.Build.0 = Release|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Debug|Any CPU.Build.0 = Debug|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Debug|x64.ActiveCfg = Debug|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Debug|x64.Build.0 = Debug|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Debug|x86.ActiveCfg = Debug|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Debug|x86.Build.0 = Debug|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Release|Any CPU.ActiveCfg = Release|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Release|Any CPU.Build.0 = Release|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Release|x64.ActiveCfg = Release|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Release|x64.Build.0 = Release|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Release|x86.ActiveCfg = Release|Any CPU + {953F0406-DD9B-406E-993D-6D988D5F5423}.Release|x86.Build.0 = Release|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Debug|x64.ActiveCfg = Debug|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Debug|x64.Build.0 = Debug|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Debug|x86.ActiveCfg = Debug|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Debug|x86.Build.0 = Debug|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Release|Any CPU.Build.0 = Release|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Release|x64.ActiveCfg = Release|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Release|x64.Build.0 = Release|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Release|x86.ActiveCfg = Release|Any CPU + {D8F03B19-F99F-475F-8951-85C9D2258B73}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F} - {43CA2584-D4AD-4082-AFF4-68B3D1239221} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - {2B942E44-74DA-CD21-D337-7A5E9D347C1B} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F} - {B27145AF-B255-4D6E-827B-3512952FB29C} = {2B942E44-74DA-CD21-D337-7A5E9D347C1B} + {953F0406-DD9B-406E-993D-6D988D5F5423} = {3A8DF596-E814-FECC-DD4B-D8EF8AAC1A0D} + {D8F03B19-F99F-475F-8951-85C9D2258B73} = {6CF592EE-4302-E72F-3CB4-AB1D314DD5A8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E6DB434C-508E-4231-B8A6-5EDD7FF87E22} diff --git a/examples/CoreTypes/CoreTypes.csproj b/examples/CoreTypes/CoreTypes.csproj deleted file mode 100644 index 89091b79..00000000 --- a/examples/CoreTypes/CoreTypes.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - Exe - net10.0 - enable - enable - - - diff --git a/examples/CoreTypes/Program.cs b/examples/CoreTypes/Program.cs deleted file mode 100644 index 29be166e..00000000 --- a/examples/CoreTypes/Program.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using QuanTAlib; - -namespace CoreTypesExample -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("QuanTAlib Core Types Example"); - Console.WriteLine("============================"); - - // 1. TValue Example - Console.WriteLine("\n1. TValue Usage"); - long now = DateTime.UtcNow.Ticks; - var val1 = new TValue(now, 100.5); - Console.WriteLine($"Created TValue: Time={val1.AsDateTime}, Value={val1.Value}"); - - // 2. TSeries Example (Streaming) - Console.WriteLine("\n2. TSeries Usage (Streaming)"); - var series = new TSeries(); - - // Add new values - series.Add(now, 10.0, isNew: true); - Console.WriteLine($"Added 10.0 (New): Count={series.Count}, Last={series.Last.Value}"); - - // Update last value (streaming update) - series.Add(now, 11.0, isNew: false); - Console.WriteLine($"Updated to 11.0 (Update): Count={series.Count}, Last={series.Last.Value}"); - - // Add another new value - series.Add(now + TimeSpan.TicksPerMinute, 12.0, isNew: true); - Console.WriteLine($"Added 12.0 (New): Count={series.Count}, Last={series.Last.Value}"); - - // 3. TBar Example - Console.WriteLine("\n3. TBar Usage"); - var bar1 = new TBar(now, 100, 105, 95, 102, 1000); - Console.WriteLine($"Created TBar: {bar1}"); - Console.WriteLine($"Computed HL2: {bar1.HL2}"); - Console.WriteLine($"TValue Access: O={bar1.O}, H={bar1.H}, L={bar1.L}, C={bar1.C}, V={bar1.V}"); - - // 4. TBarSeries Example - Console.WriteLine("\n4. TBarSeries Usage"); - var bars = new TBarSeries(); - - // Add a bar - bars.Add(bar1, isNew: true); - Console.WriteLine($"Added Bar 1: Count={bars.Count}, Close={bars.Last.Close}"); - - // Update the bar (e.g. price changed within the same minute) - var bar1Update = new TBar(now, 100, 106, 95, 104, 1500); - bars.Add(bar1Update, isNew: false); - Console.WriteLine($"Updated Bar 1: Count={bars.Count}, Close={bars.Last.Close}, High={bars.Last.High}"); - - // Accessing Views (Zero-Copy) - Console.WriteLine("\n5. TBarSeries Views (Zero-Copy)"); - Console.WriteLine($"Bars Count: {bars.Count}"); - Console.WriteLine($"Close Series Count: {bars.Close.Count}"); - Console.WriteLine($"Close Series Last: {bars.Close.Last.Value}"); - - // Verify view updates automatically - Console.WriteLine("Adding new bar..."); - bars.Add(now + TimeSpan.TicksPerMinute, 104, 108, 103, 107, 2000, isNew: true); - Console.WriteLine($"Bars Count: {bars.Count}"); - Console.WriteLine($"Close Series Count: {bars.Close.Count} (Should match Bars Count)"); - Console.WriteLine($"Close Series Last: {bars.Close.Last.Value} (Should be 107)"); - Console.WriteLine($"Alias Access: C.Last={bars.C.Last.Value}"); - Console.WriteLine($"Direct Last Access: LastClose={bars.LastClose}, LastTime={bars.LastTime}"); - - // 6. GBM Generator Example - Console.WriteLine("\n6. GBM Generator Usage"); - var gbm = new GBM(startPrice: 100.0); - - // 6a. Batch generation - long startTime = DateTime.UtcNow.Ticks; - var interval = TimeSpan.FromMinutes(1); - var randomBars = gbm.Fetch(5, startTime, interval); - Console.WriteLine($"Generated {randomBars.Count} bars in batch:"); - for (int i = 0; i < randomBars.Count; i++) - Console.WriteLine($" Bar {i}: C={randomBars[i].Close:F2}"); - - // 6b. Individual streaming bars - Console.WriteLine("\n6b. Streaming individual bars:"); - var streamBar = gbm.Next(isNew: true); - Console.WriteLine($" New bar: C={streamBar.Close:F2}"); - - // Simulate 3 intra-bar updates - for (int i = 0; i < 3; i++) - { - streamBar = gbm.Next(isNew: false); - Console.WriteLine($" Update {i + 1}: C={streamBar.Close:F2}, H={streamBar.High:F2}, L={streamBar.Low:F2}"); - } - - // Finalize and start new bar - streamBar = gbm.Next(isNew: true); - Console.WriteLine($" New bar: C={streamBar.Close:F2}"); - - // 6c. Manual streaming into TBarSeries - Console.WriteLine("\n6c. Manual streaming into TBarSeries:"); - var streamSeries = new TBarSeries(); - var bar = gbm.Next(isNew: true); - streamSeries.Add(bar, isNew: true); - Console.WriteLine($" Added bar: Count={streamSeries.Count}, C={streamSeries.LastClose:F2}"); - - for (int i = 0; i < 2; i++) - { - bar = gbm.Next(isNew: false); - streamSeries.Add(bar, isNew: false); - Console.WriteLine($" Update {i + 1}: Count={streamSeries.Count}, C={streamSeries.LastClose:F2}"); - } - - bar = gbm.Next(isNew: true); - streamSeries.Add(bar, isNew: true); - Console.WriteLine($" Added bar: Count={streamSeries.Count}, C={streamSeries.LastClose:F2}"); - - Console.WriteLine("\nExample complete."); - } - } -} diff --git a/examples/feeds/CsvFeedExample.cs b/examples/feeds/CsvFeedExample.cs deleted file mode 100644 index 6379cc87..00000000 --- a/examples/feeds/CsvFeedExample.cs +++ /dev/null @@ -1,47 +0,0 @@ -using QuanTAlib; - -// Example: Loading and streaming IBM daily data from CSV -var csvPath = "daily_IBM.csv"; - -// Create feed from CSV file -var feed = new CsvFeed(csvPath); - -Console.WriteLine("=== CSV Feed Example: IBM Daily Data ===\n"); - -// Example 1: Stream through first 5 bars -Console.WriteLine("1. Streaming first 5 bars:"); -for (int i = 0; i < 5; i++) -{ - var bar = feed.Next(isNew: true); - Console.WriteLine($" {bar.AsDateTime:yyyy-MM-dd}: O={bar.Open:F2}, H={bar.High:F2}, L={bar.Low:F2}, C={bar.Close:F2}, V={bar.Volume:F0}"); -} - -// Example 2: Fetch a specific date range -Console.WriteLine("\n2. Fetching 10 bars starting from July 2025:"); -var startTime = new DateTime(2025, 7, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; -var series = feed.Fetch(10, startTime, TimeSpan.FromDays(1)); -Console.WriteLine($" Retrieved {series.Count} bars"); -foreach (var bar in series) -{ - Console.WriteLine($" {bar.AsDateTime:yyyy-MM-dd}: Close={bar.Close:F2}"); -} - -// Example 3: Demonstrate isNew parameter -Console.WriteLine("\n3. Demonstrating isNew parameter (new bar vs update):"); -var bar1 = feed.Next(isNew: true); -Console.WriteLine($" New bar: {bar1.AsDateTime:yyyy-MM-dd} Close={bar1.Close:F2}"); - -var bar1Update = feed.Next(isNew: false); -Console.WriteLine($" Update (same bar): {bar1Update.AsDateTime:yyyy-MM-dd} Close={bar1Update.Close:F2}"); - -var bar2 = feed.Next(isNew: true); -Console.WriteLine($" Next bar: {bar2.AsDateTime:yyyy-MM-dd} Close={bar2.Close:F2}"); - -// Example 4: Working with TBarSeries views -Console.WriteLine("\n4. Accessing OHLCV components via TSeries:"); -var batch = feed.Fetch(5, startTime, TimeSpan.FromDays(1)); -Console.WriteLine($" Close prices: [{string.Join(", ", batch.Close.Take(5).Select(c => c.Value.ToString("F2")))}]"); -Console.WriteLine($" High prices: [{string.Join(", ", batch.High.Take(5).Select(h => h.Value.ToString("F2")))}]"); -Console.WriteLine($" Volumes: [{string.Join(", ", batch.Volume.Take(5).Select(v => v.Value.ToString("F0")))}]"); - -Console.WriteLine("\n=== Example Complete ==="); diff --git a/examples/feeds/CsvFeedExample.csproj b/examples/feeds/CsvFeedExample.csproj deleted file mode 100644 index 9b84565c..00000000 --- a/examples/feeds/CsvFeedExample.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - Exe - net10.0 - enable - enable - - - - - - - - - - - - - PreserveNewest - - - - diff --git a/examples/feeds/GbmExample.cs b/examples/feeds/GbmExample.cs deleted file mode 100644 index e55fdb5e..00000000 --- a/examples/feeds/GbmExample.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using QuanTAlib; - -namespace FeedsExample; - -class GbmExample -{ - static void Main(string[] args) - { - Console.WriteLine("QuanTAlib GBM Feed Example"); - Console.WriteLine("===========================\n"); - - // Create GBM generator - var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2); - var series = new TBarSeries(); - - // 1. Batch generation: 20 bars with 1-hour interval, starting 24 hours ago - Console.WriteLine("1. Batch Generation (20 bars, 1-hour interval)"); - Console.WriteLine("------------------------------------------------"); - - long startTime = DateTime.UtcNow.AddHours(-24).Ticks; - var interval = TimeSpan.FromHours(1); - var batchBars = gbm.Fetch(20, startTime, interval); - - // Add batch to series - for (int i = 0; i < batchBars.Count; i++) - { - series.Add(batchBars[i], isNew: true); - } - - Console.WriteLine($"Generated {batchBars.Count} bars"); - Console.WriteLine($"First bar: Time={new DateTime(batchBars[0].Time):yyyy-MM-dd HH:mm:ss}, Close={batchBars[0].Close:F2}"); - Console.WriteLine($"Last bar: Time={new DateTime(batchBars[19].Time):yyyy-MM-dd HH:mm:ss}, Close={batchBars[19].Close:F2}"); - Console.WriteLine($"Series has {series.Count} bars\n"); - - // 2. Streaming: Add 4 more bars (1 new + 3 intra-bar updates each) - Console.WriteLine("2. Streaming Generation (4 new bars with intra-bar updates)"); - Console.WriteLine("------------------------------------------------------------"); - - for (int barNum = 1; barNum <= 4; barNum++) - { - Console.WriteLine($"\nBar #{barNum + 20}:"); - - // New bar - var bar = gbm.Next(isNew: true); - series.Add(bar, isNew: true); - Console.WriteLine($" New: Time={new DateTime(bar.Time):HH:mm:ss}, O={bar.Open:F2}, H={bar.High:F2}, L={bar.Low:F2}, C={bar.Close:F2}"); - - // Three intra-bar updates - for (int update = 1; update <= 3; update++) - { - bar = gbm.Next(isNew: false); - series.Add(bar, isNew: false); - Console.WriteLine($" Update {update}: Time={new DateTime(bar.Time):HH:mm:ss}, O={bar.Open:F2}, H={bar.High:F2}, L={bar.Low:F2}, C={bar.Close:F2}"); - } - } - - // 3. Summary - Console.WriteLine("\n3. Final Summary"); - Console.WriteLine("----------------"); - Console.WriteLine($"Total bars in series: {series.Count}"); - Console.WriteLine($"Expected: 24 bars (20 batch + 4 streaming)"); - Console.WriteLine($"\nFirst bar: Time={series[0].AsDateTime:yyyy-MM-dd HH:mm:ss}, Close={series[0].Close:F2}"); - Console.WriteLine($"Last bar: Time={series.Last.AsDateTime:yyyy-MM-dd HH:mm:ss}, Close={series.Last.Close:F2}"); - Console.WriteLine($"\nPrice change: {series.Last.Close - series[0].Close:F2} ({(series.Last.Close / series[0].Close - 1) * 100:F2}%)"); - - // Statistics using SIMD - var closeValues = series.Close.Values; - Console.WriteLine($"\nStatistics (Close prices):"); - Console.WriteLine($" Average: {closeValues.AverageSIMD():F2}"); - Console.WriteLine($" Min: {closeValues.MinSIMD():F2}"); - Console.WriteLine($" Max: {closeValues.MaxSIMD():F2}"); - Console.WriteLine($" StdDev: {closeValues.StdDevSIMD():F2}"); - - Console.WriteLine("\nExample complete."); - } -} diff --git a/examples/feeds/GbmExample.csproj b/examples/feeds/GbmExample.csproj deleted file mode 100644 index 357ed889..00000000 --- a/examples/feeds/GbmExample.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net10.0 - enable - enable - 73a533c9-b5d2-4762-9ac9-5017fcffa8d1 - - - - - - - - - - - diff --git a/examples/feeds/daily_IBM.csv b/examples/feeds/daily_IBM.csv deleted file mode 100644 index 111591d2..00000000 --- a/examples/feeds/daily_IBM.csv +++ /dev/null @@ -1,101 +0,0 @@ -timestamp,open,high,low,close,volume -2025-11-25,304.1250,306.0000,297.0600,304.4800,2825322 -2025-11-24,299.1800,307.1800,297.5100,304.1200,6050640 -2025-11-21,293.4800,300.4800,291.8900,297.4400,5710903 -2025-11-20,294.6400,300.7100,290.1600,290.4000,5597028 -2025-11-19,290.5000,291.1099,288.0700,288.5300,3595912 -2025-11-18,297.0000,297.0000,289.9200,289.9500,4861928 -2025-11-17,305.5900,306.0000,296.5100,297.1700,3909741 -2025-11-14,300.0000,307.7200,297.5900,305.6900,3592455 -2025-11-13,312.2900,314.6000,303.6800,304.8600,5310150 -2025-11-12,319.8900,324.9000,314.5324,314.9800,6042686 -2025-11-11,309.0000,317.9100,308.4300,313.7200,4381913 -2025-11-10,306.8200,309.9400,304.2300,309.1300,2975188 -2025-11-07,309.6800,310.0000,302.6301,306.3800,5070773 -2025-11-06,306.7500,315.4400,301.0900,312.4200,6818521 -2025-11-05,301.3800,307.2000,299.7100,306.7700,4633195 -2025-11-04,300.0000,303.1700,296.0000,300.8500,5677330 -2025-11-03,308.0000,312.1411,304.2300,304.7300,4957958 -2025-10-31,312.0000,313.5000,301.6300,307.4100,7697499 -2025-10-30,306.6500,313.7500,305.0200,310.0600,4694275 -2025-10-29,312.7900,314.3300,307.5200,308.2100,4135948 -2025-10-28,312.6000,319.3500,311.4100,312.5700,6044770 -2025-10-27,307.8000,313.5000,302.8800,313.0900,9868151 -2025-10-24,283.7700,310.7500,282.2100,307.4600,16914243 -2025-10-23,264.9500,285.5791,263.5623,285.0000,16676394 -2025-10-22,281.9900,289.1700,281.3500,287.5100,10538480 -2025-10-21,283.3100,285.3100,281.6000,282.0500,4080981 -2025-10-20,281.2500,285.5000,280.9600,283.6500,3494336 -2025-10-17,276.1500,283.4000,275.3500,281.2800,5309565 -2025-10-16,281.1100,282.5600,275.6000,275.9700,2956923 -2025-10-15,278.3800,285.4500,277.0000,280.7500,3346753 -2025-10-14,275.5200,277.5300,272.5469,276.1500,3058149 -2025-10-13,279.7900,282.4399,274.6400,277.2200,4333836 -2025-10-10,288.9700,290.3850,277.5000,277.8200,4508506 -2025-10-09,289.8200,290.1300,283.3200,288.2300,4912375 -2025-10-08,294.1600,294.2000,286.4730,289.4600,5297030 -2025-10-07,295.5500,301.0425,293.2850,293.8700,7190126 -2025-10-06,288.6100,291.4500,287.8000,289.4200,2881947 -2025-10-03,287.5000,293.3200,287.3000,288.3700,4375082 -2025-10-02,285.7900,288.5400,282.7900,286.7200,3814232 -2025-10-01,280.2000,286.5900,280.1500,286.4900,4381338 -2025-09-30,280.8800,286.0250,280.5200,282.1600,5926924 -2025-09-29,286.0000,286.0000,279.6600,279.8000,6022125 -2025-09-26,280.5100,288.8500,280.1100,284.3100,9063938 -2025-09-25,272.9350,284.2300,271.1480,281.4400,11506192 -2025-09-24,272.6200,273.6499,267.3000,267.5300,3159924 -2025-09-23,272.7000,273.2962,269.2650,272.2400,5394121 -2025-09-22,266.6200,272.3100,266.0000,271.3700,5030540 -2025-09-19,266.0500,267.8700,263.6400,266.4000,9858112 -2025-09-18,258.8600,265.2300,256.8004,265.0000,4988421 -2025-09-17,257.4950,260.9644,257.0100,259.0800,3974785 -2025-09-16,256.2600,258.0000,254.4100,257.5200,2719918 -2025-09-15,254.0200,259.0500,254.0000,256.2400,4028365 -2025-09-12,256.9500,257.2500,252.4250,253.4400,3433300 -2025-09-11,257.5600,258.5450,255.6550,257.0100,3576048 -2025-09-10,259.6500,260.0800,254.5600,256.8800,5185420 -2025-09-09,256.1200,260.6600,254.8800,259.1100,4931105 -2025-09-08,248.6300,257.1500,247.0200,256.0900,6940270 -2025-09-05,248.2300,249.0300,245.4500,248.5300,3147478 -2025-09-04,245.4200,249.2800,242.8500,247.1800,4765087 -2025-09-03,240.0200,244.2500,239.4100,244.1000,3156289 -2025-09-02,240.9000,241.5500,238.2500,241.5000,3469501 -2025-08-29,245.2300,245.4599,241.7200,243.4900,2967558 -2025-08-28,245.4300,245.8800,243.3600,245.7300,2820817 -2025-08-27,242.8700,245.9600,242.0000,244.8400,3698372 -2025-08-26,241.0200,244.9800,240.3800,242.6300,5386582 -2025-08-25,242.5650,242.5650,239.4300,239.4300,3513327 -2025-08-22,240.7400,243.6800,240.2200,242.0900,3134882 -2025-08-21,242.2100,242.5000,238.6500,239.4000,2991902 -2025-08-20,242.1100,242.8800,240.3400,242.5500,3240064 -2025-08-19,240.0000,242.8300,239.4900,241.2800,3328305 -2025-08-18,239.5700,241.4200,239.1158,239.4500,3569594 -2025-08-15,237.6100,240.6200,236.7700,239.7200,4344322 -2025-08-14,238.2500,239.0000,235.6200,237.1100,4556725 -2025-08-13,236.2000,240.8411,236.2000,240.0700,5663562 -2025-08-12,236.5300,237.9600,233.3600,234.7700,8800597 -2025-08-11,242.2400,243.1500,234.7000,236.3000,9381960 -2025-08-08,248.8800,249.4800,241.6500,242.2700,6828390 -2025-08-07,252.8100,255.0000,248.8750,250.1600,6251285 -2025-08-06,251.5300,254.3200,249.2800,252.2800,3692105 -2025-08-05,252.0000,252.8000,248.9950,250.6700,5823016 -2025-08-04,251.0500,252.0800,248.1100,251.9800,5280588 -2025-08-01,251.4050,251.4791,245.6100,250.0500,9683404 -2025-07-31,259.5700,259.9900,252.2200,253.1500,6739092 -2025-07-30,261.6000,262.0000,258.9000,260.2600,3718290 -2025-07-29,264.3000,265.7999,261.0200,262.4100,4627265 -2025-07-28,260.3000,264.0000,259.6100,263.2100,5192516 -2025-07-25,260.0200,260.8000,256.3500,259.7200,7758653 -2025-07-24,261.2500,262.0486,252.7500,260.5100,22647720 -2025-07-23,284.3000,288.0800,281.4400,282.0100,8105906 -2025-07-22,284.7400,284.8800,281.2500,281.9600,4824219 -2025-07-21,286.2900,287.7300,284.3800,284.7100,3051791 -2025-07-18,283.3800,287.1600,282.2200,285.8700,4478165 -2025-07-17,281.5000,283.4566,280.9000,282.0000,3337168 -2025-07-16,282.7500,283.8700,279.8700,281.9200,2804831 -2025-07-15,283.7700,284.1550,280.7301,282.7000,2864106 -2025-07-14,282.8300,284.9250,281.7100,283.7900,2857401 -2025-07-11,285.0100,287.4300,282.9200,283.5900,3790679 -2025-07-10,288.9000,288.9000,282.2100,287.4300,3489068 -2025-07-09,291.3900,291.6000,288.6300,290.1400,2971309 -2025-07-08,293.1000,295.6100,289.4900,290.4200,2925329 diff --git a/lib/Directory.Build.props b/lib/Directory.Build.props new file mode 100644 index 00000000..75143476 --- /dev/null +++ b/lib/Directory.Build.props @@ -0,0 +1,13 @@ + + + + + true + + + + obj\tests\ + bin\tests\ + bin\tests\$(Configuration)\ + + diff --git a/tests/QuanTAlib.Tests/QuanTAlib.Tests.csproj b/lib/QuanTAlib.Tests.csproj similarity index 79% rename from tests/QuanTAlib.Tests/QuanTAlib.Tests.csproj rename to lib/QuanTAlib.Tests.csproj index 738f6fee..0959c97e 100644 --- a/tests/QuanTAlib.Tests/QuanTAlib.Tests.csproj +++ b/lib/QuanTAlib.Tests.csproj @@ -1,7 +1,8 @@ - + - net10.0;net8.0 + net10.0 + enable enable false @@ -9,6 +10,7 @@ false false $(NoWarn);CS8892 + false @@ -27,15 +29,15 @@ - + - + - + PreserveNewest daily_IBM.csv diff --git a/lib/trends_IIR/ema/Ema.Notebook.dib b/lib/averages/ema/Ema.Notebook.dib similarity index 100% rename from lib/trends_IIR/ema/Ema.Notebook.dib rename to lib/averages/ema/Ema.Notebook.dib diff --git a/lib/averages/ema/Ema.Quantower.cs b/lib/averages/ema/Ema.Quantower.cs new file mode 100644 index 00000000..5e93fc0a --- /dev/null +++ b/lib/averages/ema/Ema.Quantower.cs @@ -0,0 +1,58 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class EmaIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)] + public int Period { get; set; } = 10; + + [IndicatorExtensions.DataSourceInput] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Ema? ma; + protected LineSeries? Series; + protected string? SourceName; + + public int MinHistoryDepths => Period; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public override string ShortName => $"EMA {Period}:{SourceName}"; + + public EmaIndicator() + { + OnBackGround = true; + SeparateWindow = false; + SourceName = Source.ToString(); + Name = "EMA - Exponential Moving Average"; + Description = "Exponential Moving Average"; + Series = new(name: $"EMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); + AddLineSeries(Series); + } + + protected override void OnInit() + { + ma = new Ema(Period); + SourceName = Source.ToString(); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar; + TValue result = ma!.Update(input, isNew); + Series!.SetValue(result.Value); + Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + } + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, Series!, 0, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/lib/trends_IIR/ema/Ema.Tests.cs b/lib/averages/ema/Ema.Tests.cs similarity index 100% rename from lib/trends_IIR/ema/Ema.Tests.cs rename to lib/averages/ema/Ema.Tests.cs diff --git a/lib/trends_IIR/ema/Ema.Validation.Tests.cs b/lib/averages/ema/Ema.Validation.Tests.cs similarity index 100% rename from lib/trends_IIR/ema/Ema.Validation.Tests.cs rename to lib/averages/ema/Ema.Validation.Tests.cs diff --git a/lib/trends_IIR/ema/Ema.cs b/lib/averages/ema/Ema.cs similarity index 100% rename from lib/trends_IIR/ema/Ema.cs rename to lib/averages/ema/Ema.cs diff --git a/lib/trends_IIR/ema/Ema.md b/lib/averages/ema/Ema.md similarity index 100% rename from lib/trends_IIR/ema/Ema.md rename to lib/averages/ema/Ema.md diff --git a/lib/trends_IIR/ema/EmaVector.Tests.cs b/lib/averages/ema/EmaVector.Tests.cs similarity index 100% rename from lib/trends_IIR/ema/EmaVector.Tests.cs rename to lib/averages/ema/EmaVector.Tests.cs diff --git a/lib/trends_IIR/ema/EmaVector.cs b/lib/averages/ema/EmaVector.cs similarity index 100% rename from lib/trends_IIR/ema/EmaVector.cs rename to lib/averages/ema/EmaVector.cs diff --git a/lib/core/simd/SimdExtensions.Notebook.dib b/lib/core/simd/SimdExtensions.Notebook.dib new file mode 100644 index 00000000..f8812b2c --- /dev/null +++ b/lib/core/simd/SimdExtensions.Notebook.dib @@ -0,0 +1,91 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"name":"csharp","languageName":"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"}]}} + +#!csharp + +// Reference the library +#r "..\..\bin\QuanTAlib.dll" + +using System; +using System.Linq; +using System.Numerics; +using QuanTAlib; + +// 1. Check Hardware Support +Console.WriteLine($"SIMD Hardware Acceleration: {Vector.IsHardwareAccelerated}"); +Console.WriteLine($"Vector Count: {Vector.Count}"); + +#!csharp + +// 2. Basic Operations +// Demonstrate Sum, Min, Max, Average using SIMD extensions + +// Define data locally in this cell +double[] data = new double[1000]; +for (int i = 0; i < data.Length; i++) data[i] = i; + +// We use explicit static method calls with .AsSpan() to ensure correct overload resolution +// and avoid creating top-level ReadOnlySpan variables (which causes CS8345). + +double sum = SimdExtensions.SumSIMD(data.AsSpan()); +double minVal = SimdExtensions.MinSIMD(data.AsSpan()); +double maxVal = SimdExtensions.MaxSIMD(data.AsSpan()); +double avg = SimdExtensions.AverageSIMD(data.AsSpan()); + +Console.WriteLine($"Sum: {sum}"); +Console.WriteLine($"Min: {minVal}"); +Console.WriteLine($"Max: {maxVal}"); +Console.WriteLine($"Average: {avg}"); + +#!csharp + +// 3. Advanced Statistics + +double[] dataStats = new double[1000]; +for (int i = 0; i < dataStats.Length; i++) dataStats[i] = i; + +double variance = SimdExtensions.VarianceSIMD(dataStats.AsSpan()); +double stdDev = SimdExtensions.StdDevSIMD(dataStats.AsSpan()); + +Console.WriteLine($"Variance: {variance:F4}"); +Console.WriteLine($"Standard Deviation: {stdDev:F4}"); + +#!csharp + +// 4. Combined Operations + +double[] dataComb = new double[1000]; +for (int i = 0; i < dataComb.Length; i++) dataComb[i] = i; + +var (min, max) = SimdExtensions.MinMaxSIMD(dataComb.AsSpan()); +Console.WriteLine($"Min: {min}, Max: {max}"); + +#!csharp + +// 5. Performance Comparison (Simple Benchmark) + +int size = 1_000_000; +double[] largeData = new double[size]; +Random rnd = new Random(42); +for (int i = 0; i < size; i++) largeData[i] = rnd.NextDouble(); + +// Warmup +SimdExtensions.SumSIMD(largeData.AsSpan()); + +// Measure SIMD +long start = DateTime.UtcNow.Ticks; +double sumSimd = SimdExtensions.SumSIMD(largeData.AsSpan()); +long end = DateTime.UtcNow.Ticks; +double simdTime = (end - start) / 10000.0; // ms + +// Measure Scalar (LINQ Sum as proxy for scalar loop) +start = DateTime.UtcNow.Ticks; +double sumScalar = largeData.Sum(); +end = DateTime.UtcNow.Ticks; +double scalarTime = (end - start) / 10000.0; // ms + +Console.WriteLine($"Array Size: {size:N0}"); +Console.WriteLine($"SIMD Time: {simdTime:F4} ms"); +Console.WriteLine($"Scalar Time: {scalarTime:F4} ms"); +Console.WriteLine($"Speedup: {scalarTime / simdTime:F2}x"); diff --git a/lib/core/simd/SimdExtensions.md b/lib/core/simd/SimdExtensions.md new file mode 100644 index 00000000..5b22c8b7 --- /dev/null +++ b/lib/core/simd/SimdExtensions.md @@ -0,0 +1,43 @@ +# SimdExtensions Class + +`SimdExtensions` provides high-performance, SIMD-accelerated extension methods for `ReadOnlySpan`. It leverages .NET's `Vector` to achieve 4-8x speedups on supported hardware (AVX2, AVX-512) while automatically falling back to scalar implementations on older hardware. + +## Key Features + +- **Hardware Acceleration**: Uses CPU vector registers to process multiple elements in parallel. +- **Automatic Fallback**: Gracefully handles non-SIMD hardware or small arrays. +- **Zero-Allocation**: Operates directly on spans without creating new arrays. +- **Aggressive Inlining**: Methods are marked for inlining to minimize call overhead. + +## Available Methods + +| Method | Description | +|--------|-------------| +| `SumSIMD()` | Calculates the sum of elements. | +| `MinSIMD()` | Finds the minimum value. | +| `MaxSIMD()` | Finds the maximum value. | +| `MinMaxSIMD()` | Finds both min and max in a single pass (more efficient than separate calls). | +| `AverageSIMD()` | Calculates the arithmetic mean. | +| `VarianceSIMD()` | Calculates the sample variance. | +| `StdDevSIMD()` | Calculates the sample standard deviation. | + +## Performance + +On modern CPUs (e.g., Intel Core i7/i9, AMD Ryzen), these methods typically outperform standard LINQ or scalar loops by a factor of 4 to 8 for large arrays. + +## Usage + +```csharp +using QuanTAlib; + +double[] data = { 1.0, 2.0, 3.0, 4.0, 5.0, ... }; +ReadOnlySpan span = data; + +// Calculate sum +double sum = span.SumSIMD(); + +// Calculate min and max in one pass +var (min, max) = span.MinMaxSIMD(); + +// Calculate standard deviation +double stdDev = span.StdDevSIMD(); diff --git a/lib/core/tbar/TBar.Notebook.dib b/lib/core/tbar/TBar.Notebook.dib new file mode 100644 index 00000000..7e00ac54 --- /dev/null +++ b/lib/core/tbar/TBar.Notebook.dib @@ -0,0 +1,74 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"}]}} + +#!csharp + +// Reference the library +#r "..\..\bin\QuanTAlib.dll" + +using QuanTAlib; + +// 1. Creating a TBar +// TBar represents a single OHLCV bar (Open, High, Low, Close, Volume) +// It is an immutable struct optimized for memory and performance + +long now = DateTime.UtcNow.Ticks; +var bar = new TBar(now, 100.0, 105.0, 95.0, 102.0, 1000.0); + +Console.WriteLine($"Created TBar: {bar}"); +Console.WriteLine($"Time: {bar.AsDateTime}"); +Console.WriteLine($"Open: {bar.Open}"); +Console.WriteLine($"High: {bar.High}"); +Console.WriteLine($"Low: {bar.Low}"); +Console.WriteLine($"Close: {bar.Close}"); +Console.WriteLine($"Volume: {bar.Volume}"); + +#!csharp + +// 2. Computed Properties +// TBar provides on-demand calculation of common price averages +// These are calculated when accessed, saving storage space + +Console.WriteLine($"HL2 (High+Low)/2: {bar.HL2}"); +Console.WriteLine($"OC2 (Open+Close)/2: {bar.OC2}"); +Console.WriteLine($"OHL3 (Open+High+Low)/3: {bar.OHL3}"); +Console.WriteLine($"HLC3 (High+Low+Close)/3: {bar.HLC3}"); +Console.WriteLine($"OHLC4 (Open+High+Low+Close)/4: {bar.OHLC4}"); +Console.WriteLine($"HLCC4 (High+Low+Close+Close)/4: {bar.HLCC4}"); + +#!csharp + +// 3. TValue Accessors +// You can efficiently access individual components as TValue (Time-Value pair) +// This is useful when you need to treat a specific price component as a time series point + +Console.WriteLine($"Open TValue: {bar.O}"); +Console.WriteLine($"High TValue: {bar.H}"); +Console.WriteLine($"Low TValue: {bar.L}"); +Console.WriteLine($"Close TValue: {bar.C}"); +Console.WriteLine($"Volume TValue: {bar.V}"); + +#!csharp + +// 4. Implicit Conversions +// TBar supports implicit conversions to double (Close price), TValue (Close), and DateTime + +double closePrice = bar; +TValue value = bar; +DateTime dt = bar; + +Console.WriteLine($"Implicit double (Close): {closePrice}"); +Console.WriteLine($"Implicit TValue (Close): {value}"); +Console.WriteLine($"Implicit DateTime: {dt}"); + +#!csharp + +// 5. Equality and Immutability +// Being a struct, TBar has value semantics + +var bar2 = new TBar(now, 100.0, 105.0, 95.0, 102.0, 1000.0); +var bar3 = new TBar(now, 101.0, 106.0, 96.0, 103.0, 1100.0); + +Console.WriteLine($"bar equals bar2? {bar == bar2}"); // True, same values +Console.WriteLine($"bar equals bar3? {bar == bar3}"); // False, different values diff --git a/lib/core/tbar/TBar.md b/lib/core/tbar/TBar.md new file mode 100644 index 00000000..3fcde644 --- /dev/null +++ b/lib/core/tbar/TBar.md @@ -0,0 +1,68 @@ +# TBar Struct + +`TBar` is a lightweight, immutable struct representing a single OHLCV (Open, High, Low, Close, Volume) bar. It is designed for high-performance financial data processing with minimal memory overhead. + +## Key Features + +- **Memory Efficient**: Pure data type occupying exactly 48 bytes (1 `long` + 5 `double`s). +- **Immutable**: Thread-safe by design. +- **Zero-Copy Conversions**: Efficiently converts to `TValue` for individual price components (Open, High, Low, Close, Volume). +- **Computed Properties**: Provides on-demand calculation of common price averages (HL2, HLC3, etc.) without storage overhead. +- **SIMD Compatible**: Layout is optimized for potential vectorization in collection types. + +## Structure Definition + +```csharp +public readonly struct TBar : IEquatable +{ + public readonly long Time; // Unix ticks + public readonly double Open; + public readonly double High; + public readonly double Low; + public readonly double Close; + public readonly double Volume; +} +``` + +## Properties + +| Property | Type | Description | +|----------|------|-------------| +| `Time` | `long` | Timestamp in ticks. | +| `Open` | `double` | Opening price. | +| `High` | `double` | Highest price. | +| `Low` | `double` | Lowest price. | +| `Close` | `double` | Closing price. | +| `Volume` | `double` | Traded volume. | +| `AsDateTime` | `DateTime` | `Time` converted to UTC DateTime. | + +### Computed Averages +These properties are calculated on the fly: +- `HL2`: (High + Low) / 2 +- `OC2`: (Open + Close) / 2 +- `OHL3`: (Open + High + Low) / 3 +- `HLC3`: (High + Low + Close) / 3 +- `OHLC4`: (Open + High + Low + Close) / 4 +- `HLCC4`: (High + Low + Close + Close) / 4 + +### TValue Accessors +Efficiently access components as `TValue` (Time-Value pair): +- `O`: (Time, Open) +- `H`: (Time, High) +- `L`: (Time, Low) +- `C`: (Time, Close) +- `V`: (Time, Volume) + +## Usage + +### Creating a TBar +```csharp +long now = DateTime.UtcNow.Ticks; +var bar = new TBar(now, 100.0, 105.0, 95.0, 102.0, 1000.0); +``` + +### Implicit Conversions +```csharp +double closePrice = bar; // Implicitly converts to Close price +TValue value = bar; // Implicitly converts to (Time, Close) +DateTime dt = bar; // Implicitly converts to DateTime diff --git a/lib/core/tbarseries/TBarSeries.Notebook.dib b/lib/core/tbarseries/TBarSeries.Notebook.dib new file mode 100644 index 00000000..81eb7899 --- /dev/null +++ b/lib/core/tbarseries/TBarSeries.Notebook.dib @@ -0,0 +1,82 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"}]}} + +#!csharp + +// Reference the library +#r "..\..\bin\QuanTAlib.dll" + +using QuanTAlib; + +// 1. Creating a TBarSeries +// TBarSeries is a collection of bars stored in Structure of Arrays (SoA) format +// This layout is optimized for performance and SIMD operations + +var bars = new TBarSeries(); +long now = DateTime.UtcNow.Ticks; + +// Add a new bar +var bar1 = new TBar(now, 100, 105, 95, 102, 1000); +bars.Add(bar1, isNew: true); + +Console.WriteLine($"Added Bar 1: Count={bars.Count}"); +Console.WriteLine($"Last Close: {bars.Last.Close}"); + +#!csharp + +// 2. Streaming Updates +// TBarSeries supports updating the last bar in place +// This is crucial for real-time feeds where the current bar changes until it closes + +// Update the bar (e.g. price changed within the same minute) +var bar1Update = new TBar(now, 100, 106, 95, 104, 1500); +bars.Add(bar1Update, isNew: false); + +Console.WriteLine($"Updated Bar 1: Count={bars.Count} (Count should not increase)"); +Console.WriteLine($"Last Close: {bars.Last.Close}"); +Console.WriteLine($"Last High: {bars.Last.High}"); + +#!csharp + +// 3. Zero-Copy Views +// You can access individual components (Open, High, Low, Close, Volume) as TSeries +// These views share the underlying memory, so no copying is involved + +Console.WriteLine($"Bars Count: {bars.Count}"); +Console.WriteLine($"Close Series Count: {bars.Close.Count}"); +Console.WriteLine($"Close Series Last: {bars.Close.Last.Value}"); + +// Verify view updates automatically +Console.WriteLine("\nAdding new bar..."); +bars.Add(now + TimeSpan.TicksPerMinute, 104, 108, 103, 107, 2000, isNew: true); + +Console.WriteLine($"Bars Count: {bars.Count}"); +Console.WriteLine($"Close Series Count: {bars.Close.Count} (Should match Bars Count)"); +Console.WriteLine($"Close Series Last: {bars.Close.Last.Value} (Should be 107)"); + +#!csharp + +// 4. Aliases and Direct Access +// TBarSeries provides short aliases (O, H, L, C, V) and direct access properties + +Console.WriteLine($"Alias Access (C.Last): {bars.C.Last.Value}"); +Console.WriteLine($"Direct Last Access (LastClose): {bars.LastClose}"); +Console.WriteLine($"Direct Last Time (LastTime): {new DateTime(bars.LastTime)}"); + +#!csharp + +// 5. Iteration +// You can iterate over the bars or individual series + +Console.WriteLine("\nIterating over bars:"); +foreach (var bar in bars) +{ + Console.WriteLine($" {bar}"); +} + +Console.WriteLine("\nIterating over Close prices:"); +for (int i = 0; i < bars.Count; i++) +{ + Console.WriteLine($" Bar {i}: Close={bars.Close[i].Value}"); +} diff --git a/lib/core/tbarseries/TBarSeries.md b/lib/core/tbarseries/TBarSeries.md new file mode 100644 index 00000000..9c873419 --- /dev/null +++ b/lib/core/tbarseries/TBarSeries.md @@ -0,0 +1,70 @@ +# TBarSeries Class + +`TBarSeries` is a high-performance collection of OHLCV bars implemented using a Structure of Arrays (SoA) layout. This design optimizes memory access patterns and enables efficient SIMD operations while providing convenient object-oriented views. + +## Key Features + +- **Structure of Arrays (SoA)**: Stores Time, Open, High, Low, Close, and Volume in separate contiguous arrays rather than an array of structs. This improves cache locality for operations that only need specific components (e.g., calculating SMA on Close prices). +- **Zero-Copy Views**: Exposes `TSeries` properties (`Open`, `High`, `Low`, `Close`, `Volume`) that view the underlying data without copying. +- **Streaming Support**: Efficiently handles real-time data updates with `Add(bar, isNew: false)`. +- **Memory Efficient**: Minimizes object overhead by using shared internal lists. + +## Class Definition + +```csharp +public class TBarSeries : IReadOnlyList +{ + // Views + public TSeries Open { get; } + public TSeries High { get; } + public TSeries Low { get; } + public TSeries Close { get; } + public TSeries Volume { get; } + + // Aliases + public TSeries O => Open; + public TSeries H => High; + public TSeries L => Low; + public TSeries C => Close; + public TSeries V => Volume; +} +``` + +## Core Methods + +| Method | Description | +|--------|-------------| +| `Add(TBar bar, bool isNew = true)` | Adds a new bar or updates the last one. | +| `Add(DateTime time, double o, double h, double l, double c, double v, bool isNew)` | Adds raw values directly. | +| `Count` | Returns the number of bars. | +| `Last` | Returns the most recent `TBar`. | + +## Usage + +### Creating and Populating +```csharp +var bars = new TBarSeries(); + +// Add a new bar +long now = DateTime.UtcNow.Ticks; +bars.Add(new TBar(now, 100, 105, 95, 102, 1000), isNew: true); + +// Update the last bar (e.g., real-time feed update) +bars.Add(new TBar(now, 100, 106, 95, 104, 1500), isNew: false); +``` + +### Accessing Data +```csharp +// Access entire bar +TBar lastBar = bars.Last; + +// Access specific component series (Zero-Copy) +TSeries closes = bars.Close; +double lastClose = closes.Last.Value; + +// Access via indexer +TBar firstBar = bars[0]; +``` + +### Performance Note +Because `TBarSeries` uses SoA layout, iterating over a single component (like `Close` prices) is extremely cache-efficient. The CPU prefetcher can load contiguous doubles without loading the interleaved Open, High, Low, or Volume data. diff --git a/lib/core/tseries/TSeries.Notebook.dib b/lib/core/tseries/TSeries.Notebook.dib new file mode 100644 index 00000000..8d83bb75 --- /dev/null +++ b/lib/core/tseries/TSeries.Notebook.dib @@ -0,0 +1,84 @@ +#!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 + +# TSeries Examples + +This notebook demonstrates the usage of `TSeries`, the high-performance time series container in QuanTAlib. + +For detailed documentation, see [TSeries.md](TSeries.md). + +#!csharp + +// Reference the library +#r "..\..\bin\QuanTAlib.dll" + +using System; +using QuanTAlib; + +#!markdown + +## Creating and Adding Data + +`TSeries` supports adding data via `DateTime` or `ticks`. + +#!csharp + +var series = new TSeries(); +var now = DateTime.UtcNow; + +// Add new values +series.Add(now, 10.0); +series.Add(now.AddMinutes(1), 11.0); +series.Add(now.AddMinutes(2), 12.0); + +Console.WriteLine($"Count: {series.Count}"); +Console.WriteLine($"Last Value: {series.Last.Value}"); + +#!markdown + +## Streaming Updates (`isNew`) + +In real-time scenarios, you often receive updates for the *current* bar before it closes. `TSeries` handles this via the `isNew` parameter. + +#!csharp + +var streamSeries = new TSeries(); +long t = DateTime.UtcNow.Ticks; + +// 1. New Bar +streamSeries.Add(t, 100.0, isNew: true); +Console.WriteLine($"New Bar: Count={streamSeries.Count}, Last={streamSeries.Last.Value}"); + +// 2. Update Current Bar (Price moves to 101.0) +streamSeries.Add(t, 101.0, isNew: false); +Console.WriteLine($"Update: Count={streamSeries.Count}, Last={streamSeries.Last.Value}"); + +// 3. Update Current Bar (Price moves to 100.5) +streamSeries.Add(t, 100.5, isNew: false); +Console.WriteLine($"Update: Count={streamSeries.Count}, Last={streamSeries.Last.Value}"); + +// 4. New Bar (Next minute) +streamSeries.Add(t + TimeSpan.TicksPerMinute, 102.0, isNew: true); +Console.WriteLine($"New Bar: Count={streamSeries.Count}, Last={streamSeries.Last.Value}"); + +#!markdown + +## Zero-Copy Access (Spans) + +You can access the underlying data arrays directly as `ReadOnlySpan` for high-performance processing. + +#!csharp + +// Access Values as Span +Console.WriteLine("Values in Span:"); +foreach (var v in series.Values) +{ + Console.Write($"{v} "); +} +Console.WriteLine(); + +// Access Times as Span +Console.WriteLine($"First Time: {new DateTime(series.Times[0])}"); diff --git a/lib/core/tseries/TSeries.md b/lib/core/tseries/TSeries.md new file mode 100644 index 00000000..bba3c546 --- /dev/null +++ b/lib/core/tseries/TSeries.md @@ -0,0 +1,56 @@ +# TSeries: Time Series Data + +## Overview + +`TSeries` is a high-performance container for time-series data. Unlike a standard `List`, it uses a **Structure of Arrays (SoA)** layout internally. This means it stores timestamps and values in separate contiguous arrays (`List` and `List`). + +This layout is critical for performance because it allows: +1. **SIMD Optimization**: The `Values` property returns a `ReadOnlySpan` that can be directly processed by CPU vector instructions (AVX/SSE). +2. **Cache Locality**: Iterating over values doesn't load timestamps into the CPU cache, and vice versa. + +## Structure + +```csharp +public class TSeries : IReadOnlyList +{ + // Internal SoA storage + protected readonly List _t; + protected readonly List _v; + + // Public accessors + public ReadOnlySpan Values => ...; // Zero-copy access + public ReadOnlySpan Times => ...; // Zero-copy access + + public TValue Last { get; } + public int Count { get; } +} +``` + +## Key Features + +* **SoA Layout**: Optimized for numerical computing and SIMD. +* **Zero-Copy Access**: `Values` and `Times` properties expose internal storage as Spans without copying. +* **Streaming Support**: The `Add` method supports `isNew` parameter to handle intra-bar updates (replacing the last value instead of appending). +* **Event Publishing**: Optional `Pub` event for reactive pipelines. + +## Usage + +### Creating and Adding Data +```csharp +var series = new TSeries(); +series.Add(DateTime.Now, 100.0); // isNew=true by default +``` + +### Streaming Updates +```csharp +// New bar +series.Add(time, 100.0, isNew: true); + +// Update current bar (e.g. price change within same minute) +series.Add(time, 101.0, isNew: false); +``` + +### SIMD Processing +```csharp +// Calculate average using SIMD +double avg = series.Values.AverageSIMD(); diff --git a/lib/core/tvalue/TValue.Notebook.dib b/lib/core/tvalue/TValue.Notebook.dib new file mode 100644 index 00000000..91f94608 --- /dev/null +++ b/lib/core/tvalue/TValue.Notebook.dib @@ -0,0 +1,69 @@ +#!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 + +# TValue Examples + +This notebook demonstrates the usage of `TValue`, the fundamental data structure in QuanTAlib. + +For detailed documentation, see [TValue.md](TValue.md). + +#!csharp + +// Reference the library +#r "..\..\bin\QuanTAlib.dll" + +using System; +using QuanTAlib; + +#!markdown + +## Creating TValue + +You can create a `TValue` using `DateTime` or `ticks`. + +#!csharp + +// Using DateTime +var now = DateTime.UtcNow; +var val1 = new TValue(now, 100.5); +Console.WriteLine($"Created TValue: Time={val1.AsDateTime}, Value={val1.Value}"); + +// Using Ticks +long ticks = now.AddMinutes(1).Ticks; +var val2 = new TValue(ticks, 101.0); +Console.WriteLine($"Created TValue: Time={val2.AsDateTime}, Value={val2.Value}"); + +#!markdown + +## Implicit Conversions + +`TValue` supports implicit conversions to `double` and `DateTime` for convenience. + +#!csharp + +double d = val1; // Implicitly gets Value +DateTime t = val1; // Implicitly gets Time (as DateTime) + +Console.WriteLine($"Double: {d}"); +Console.WriteLine($"DateTime: {t}"); + +// Arithmetic operations using implicit conversion +double result = val1 + 5.0; +Console.WriteLine($"Result (100.5 + 5.0): {result}"); + +#!markdown + +## Immutability + +`TValue` is immutable. You cannot change its properties after creation. + +#!csharp + +// val1.Value = 200; // Error: Property or indexer 'TValue.Value' cannot be assigned to -- it is read only + +// To "change" a value, create a new instance +var val3 = new TValue(val1.Time, 200.0); +Console.WriteLine($"New TValue: {val3.Value}"); diff --git a/lib/core/tvalue/TValue.md b/lib/core/tvalue/TValue.md new file mode 100644 index 00000000..41ac7ba6 --- /dev/null +++ b/lib/core/tvalue/TValue.md @@ -0,0 +1,37 @@ +# TValue: Time-Value Pair + +## Overview + +`TValue` is the fundamental building block of QuanTAlib. It represents a single data point in a time series, consisting of a timestamp and a double-precision floating-point value. + +It is implemented as a lightweight `readonly struct` to ensure immutability and high performance (stack allocation, no GC overhead). + +## Structure + +```csharp +public readonly struct TValue +{ + public readonly long Time; // Ticks (UTC) + public readonly double Value; // Data value + public readonly bool IsNew; // Metadata for streaming (optional usage) +} +``` + +## Key Features + +* **Lightweight**: 24 bytes (long + double + bool + padding). +* **Immutable**: Thread-safe by design. +* **Implicit Conversions**: Can be implicitly converted to `double` (returns Value) and `DateTime` (returns Time). +* **Performance**: Designed for high-frequency trading and large dataset processing. + +## Usage + +`TValue` is used throughout the library for: +* Input to indicators (`Update(TValue)`). +* Output from indicators (`Value` property). +* Elements in `TSeries`. + +## Constructors + +* `new TValue(long time, double value, bool isNew = true)` +* `new TValue(DateTime time, double value, bool isNew = true)` diff --git a/lib/feeds/IFeed.md b/lib/feeds/IFeed.md new file mode 100644 index 00000000..c00ec34e --- /dev/null +++ b/lib/feeds/IFeed.md @@ -0,0 +1,45 @@ +# IFeed Interface + +`IFeed` defines the standard contract for all data feeds in QuanTAlib, ensuring consistent behavior across different data sources (synthetic, file-based, or live API). + +## Key Concepts + +- **Bidirectional Control**: The `Next(ref bool isNew)` method allows the consumer to request a new bar (`isNew = true`) or an update to the current bar (`isNew = false`). +- **Streaming**: Designed for bar-by-bar processing, simulating real-time data flow. +- **Batching**: Supports fetching historical data ranges via `Fetch()`. + +## Interface Definition + +```csharp +public interface IFeed +{ + /// + /// Gets the next bar with full control over new/update state. + /// + TBar Next(ref bool isNew); + + /// + /// Convenience overload for simple next-bar requests. + /// + TBar Next(bool isNew = true); + + /// + /// Retrieves a batch of historical bars. + /// + TBarSeries Fetch(int count, long startTime, TimeSpan interval); +} +``` + +## Implementation Guidelines + +When implementing `IFeed`: + +1. **State Management**: Maintain the current position in the data source. +2. **End of Data**: When data is exhausted, `Next` should return the last valid bar and set `isNew` to `false`. +3. **Intra-bar Updates**: If the source supports it (e.g., live ticks), `Next(isNew: false)` should return the updated state of the current bar. If not supported (e.g., CSV), it should return the current bar unchanged. +4. **Thread Safety**: Implementations are generally not required to be thread-safe unless specified. + +## Implementations + +- **`GBM`**: Geometric Brownian Motion generator (Synthetic). +- **`CsvFeed`**: Reads OHLCV data from CSV files (Historical). diff --git a/lib/feeds/csv/CsvFeed.Notebook.dib b/lib/feeds/csv/CsvFeed.Notebook.dib new file mode 100644 index 00000000..459afe1e --- /dev/null +++ b/lib/feeds/csv/CsvFeed.Notebook.dib @@ -0,0 +1,68 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"name":"csharp","languageName":"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"}]}} + +#!csharp + +// Reference the library +#r "..\..\bin\QuanTAlib.dll" + +using QuanTAlib; +using System.IO; + +// 1. Setup: Use existing CSV file +// CsvFeed expects a CSV with header: timestamp,open,high,low,close,volume +// Timestamp format: YYYY-MM-DD + +string csvPath = "daily_IBM.csv"; +Console.WriteLine($"Using CSV file: {csvPath}"); + +#!csharp + +// 2. Initialize CsvFeed +// The feed loads the data and prepares it for streaming + +var feed = new CsvFeed(csvPath); +Console.WriteLine("CsvFeed initialized."); + +#!csharp + +// 3. Streaming Data +// Simulate processing historical data bar by bar + +Console.WriteLine("\nStreaming data (first 5 bars):"); +int count = 0; +bool isNew = true; + +// Get first bar +var bar = feed.Next(isNew: true); + +while (isNew && count < 5) +{ + count++; + Console.WriteLine($" Bar {count}: {bar}"); + + // Get next bar + bar = feed.Next(ref isNew); +} + +Console.WriteLine($"Streamed {count} bars."); + +#!csharp + +// 4. Batch Fetching +// Retrieve a specific range of data + +Console.WriteLine("\nBatch fetching:"); +// Using a date range present in daily_IBM.csv (July 2025) +long startTime = new DateTime(2025, 7, 8).Ticks; +var interval = TimeSpan.FromDays(1); + +// Fetch 3 bars starting from July 8th, 2025 +var batch = feed.Fetch(5, startTime, interval); + +Console.WriteLine($"Fetched {batch.Count} bars:"); +foreach (var b in batch) +{ + Console.WriteLine($" {b}"); +} diff --git a/lib/feeds/csv/CsvFeed.md b/lib/feeds/csv/CsvFeed.md new file mode 100644 index 00000000..db5e10c7 --- /dev/null +++ b/lib/feeds/csv/CsvFeed.md @@ -0,0 +1,68 @@ +# CsvFeed Class + +`CsvFeed` is a file-based feed implementation that loads historical OHLCV data from CSV files. It supports both streaming access (simulating real-time playback) and batch retrieval. + +## Key Features + +- **Historical Data Loading**: Reads standard OHLCV CSV files. +- **Chronological Ordering**: Automatically reverses data if needed (assumes newest-first in file, provides oldest-first). +- **Streaming Interface**: Implements `IFeed` for consistent usage with other feed types. +- **Batch Retrieval**: Supports fetching specific time ranges via `Fetch()`. + +## CSV Format Requirements + +The file must have a header row and follow this column order: +`timestamp, open, high, low, close, volume` + +- **Timestamp**: `YYYY-MM-DD` (assumed UTC midnight) +- **Prices/Volume**: Numeric values + +Example: +```csv +Date,Open,High,Low,Close,Volume +2024-01-01,100.0,105.0,99.0,102.5,10000 +2024-01-02,102.5,103.0,101.0,101.5,8500 +``` + +## Class Definition + +```csharp +public class CsvFeed : IFeed +{ + public CsvFeed(string filePath); + public TBar Next(bool isNew = true); + public TBarSeries Fetch(int count, long startTime, TimeSpan interval); +} +``` + +## Usage + +### 1. Loading Data +```csharp +var feed = new CsvFeed("path/to/data.csv"); +``` + +### 2. Streaming Data (Simulation) +```csharp +// Get first bar +var bar = feed.Next(isNew: true); + +// Loop through all data +while (true) +{ + // Process bar... + Console.WriteLine(bar); + + // Get next bar + bool isNew = true; + bar = feed.Next(ref isNew); + + // Stop if no more new data + if (!isNew) break; +} +``` + +### 3. Fetching a Batch +```csharp +long startTime = new DateTime(2024, 1, 1).Ticks; +var batch = feed.Fetch(10, startTime, TimeSpan.FromDays(1)); diff --git a/lib/feeds/gbm/GBM.Notebook.dib b/lib/feeds/gbm/GBM.Notebook.dib new file mode 100644 index 00000000..3f689e80 --- /dev/null +++ b/lib/feeds/gbm/GBM.Notebook.dib @@ -0,0 +1,69 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"name":"csharp","languageName":"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"}]}} + +#!csharp + +// Reference the library +#r "..\..\bin\QuanTAlib.dll" + +using QuanTAlib; + +// 1. Initialize GBM Generator +// GBM simulates price movements using Geometric Brownian Motion +// Parameters: Start Price, Drift (mu), Volatility (sigma) + +var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2); +Console.WriteLine("GBM Generator initialized (Start=100, Drift=5%, Vol=20%)"); + +#!csharp + +// 2. Batch Generation +// Generate a sequence of bars at once +// Useful for backtesting or initializing indicators + +long startTime = DateTime.UtcNow.Ticks; +var interval = TimeSpan.FromMinutes(1); + +var history = gbm.Fetch(10, startTime, interval); + +Console.WriteLine($"Generated {history.Count} bars:"); +for (int i = 0; i < history.Count; i++) +{ + Console.WriteLine($" Bar {i}: Time={history[i].AsDateTime:HH:mm}, Close={history[i].Close:F2}"); +} + +#!csharp + +// 3. Streaming Generation +// Simulate real-time data feed bar by bar + +Console.WriteLine("\nStreaming new bars:"); +for (int i = 0; i < 3; i++) +{ + var bar = gbm.Next(isNew: true); + Console.WriteLine($" New Bar: {bar.Close:F2}"); +} + +#!csharp + +// 4. Intra-bar Updates +// Simulate real-time price ticks within a single bar +// The High/Low will expand, and Close will update + +Console.WriteLine("\nSimulating intra-bar updates:"); + +// Start a new bar +var liveBar = gbm.Next(isNew: true); +Console.WriteLine($" Open: {liveBar.Open:F2}, Close: {liveBar.Close:F2}"); + +// Simulate 5 ticks +for (int i = 1; i <= 5; i++) +{ + liveBar = gbm.Next(isNew: false); + Console.WriteLine($" Tick {i}: Close={liveBar.Close:F2}, High={liveBar.High:F2}, Low={liveBar.Low:F2}"); +} + +// Finalize bar +liveBar = gbm.Next(isNew: true); +Console.WriteLine($" Finalized Previous, Started New: {liveBar.Open:F2}"); diff --git a/lib/feeds/gbm/GBM.md b/lib/feeds/gbm/GBM.md new file mode 100644 index 00000000..d53f787c --- /dev/null +++ b/lib/feeds/gbm/GBM.md @@ -0,0 +1,67 @@ +# GBM Class + +`GBM` (Geometric Brownian Motion) is a synthetic data generator that simulates realistic financial price movements. It is useful for testing indicators, strategies, and system performance without relying on external data files. + +## Key Features + +- **Geometric Brownian Motion**: Uses the standard mathematical model for asset price dynamics. +- **Configurable Parameters**: Control drift (trend) and volatility (noise). +- **Stateless Design**: Minimal memory footprint; only maintains state needed for continuity. +- **Dual Modes**: Supports both streaming (bar-by-bar) and batch generation. +- **Intra-bar Updates**: Can simulate real-time price updates within a single bar. + +## Mathematical Model + +The price evolution follows the stochastic differential equation: + +$$ dS_t = \mu S_t dt + \sigma S_t dW_t $$ + +Where: +- $S_t$: Asset price at time $t$ +- $\mu$: Drift (expected return) +- $\sigma$: Volatility (standard deviation of returns) +- $W_t$: Wiener process (Brownian motion) + +## Class Definition + +```csharp +public class GBM : IFeed +{ + public GBM(double startPrice = 100.0, double mu = 0.05, double sigma = 0.2, TimeSpan? defaultTimeframe = null); + + public TBar Next(bool isNew = true); + public TBarSeries Fetch(int count, long startTime, TimeSpan interval); +} +``` + +## Usage + +### 1. Initialization +```csharp +// Default: Start at 100, 5% drift, 20% volatility +var gbm = new GBM(); + +// Custom: Start at 50, 10% drift, 50% volatility +var volatileGbm = new GBM(startPrice: 50.0, mu: 0.10, sigma: 0.50); +``` + +### 2. Streaming Generation +```csharp +// Generate a new bar +var bar = gbm.Next(isNew: true); + +// Simulate intra-bar updates (e.g., real-time ticks) +for (int i = 0; i < 5; i++) +{ + var updatedBar = gbm.Next(isNew: false); + Console.WriteLine($"Update: {updatedBar.Close}"); +} +``` + +### 3. Batch Generation +```csharp +long startTime = DateTime.UtcNow.Ticks; +var interval = TimeSpan.FromMinutes(1); + +// Generate 1000 bars +var history = gbm.Fetch(1000, startTime, interval); diff --git a/lib/quantalib.csproj b/lib/quantalib.csproj index 8b8a1057..c14dccb7 100644 --- a/lib/quantalib.csproj +++ b/lib/quantalib.csproj @@ -1,6 +1,6 @@  - net8.0;net10.0 + net8.0;net9.0;net10.0 QuanTAlib Library of TA Calculations, Charts and Strategies for Quantower Quantitative Technical Analysis Library in C# for Quantower @@ -8,7 +8,6 @@ https://github.com/mihakralj/QuanTAlib Miha Kralj Miha Kralj - readme.md QuanTAlib QuanTAlib true @@ -34,12 +33,18 @@ - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - diff --git a/quantower/Averages.csproj b/quantower/Averages.csproj new file mode 100644 index 00000000..853eeee8 --- /dev/null +++ b/quantower/Averages.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + Averages + Indicator + bin\$(Configuration)\ + false + false + + + + + + + + + + + + ..\.github\TradingPlatform.BusinessLayer.dll + + + TradingPlatform.BusinessLayer.xml + + + + + + + + diff --git a/quantower/IndicatorExtensions.cs b/quantower/IndicatorExtensions.cs new file mode 100644 index 00000000..3f4c13b4 --- /dev/null +++ b/quantower/IndicatorExtensions.cs @@ -0,0 +1,237 @@ +using TradingPlatform.BusinessLayer; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace QuanTAlib; + +public enum SourceType +{ + Open, High, Low, Close, HL2, OC2, OHL3, HLC3, OHLC4, HLCC4 +} + +public enum MaType +{ + Alma, Dema, Dsma, Dwma, Ema, Epma, Frama, Fwma, Gma, Hma, Hwma, Jma, Kama, Maaf, Mgdi, MMa, Pwma, Rema, Rma, Sinema, Sma, Smma, T3, Tema, Trima, Vidya, Wma, Zlema +} + +public static class IndicatorExtensions +{ + public static readonly Color Averages = Color.FromArgb(255, 255, 128); // #FFFF80 - Yellow + public static readonly Color Volume = Color.FromArgb(128, 255, 128); // #80FF80 - Green + public static readonly Color Volatility = Color.FromArgb(255, 128, 128); // #FF8080 - Red + public static readonly Color Statistics = Color.FromArgb(128, 128, 255); // #8080FF - Blue + public static readonly Color Oscillators = Color.FromArgb(255, 128, 255); // #FF80FF - Magenta + public static readonly Color Momentum = Color.FromArgb(128, 255, 255); // #80FFFF - Cyan + public static readonly Color Experiments = Color.FromArgb(255, 165, 0); // #FFA500 - Orange + + [AttributeUsage(AttributeTargets.Property)] + public class DataSourceInputAttribute : InputParameterAttribute + { + public DataSourceInputAttribute(string label = "Data source", int sortIndex = 20) + : base(label, sortIndex, variants: new object[] + { + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + }) + { } + } + + public static TValue GetInputValue(this Indicator indicator, UpdateArgs args, SourceType source) + { + var historicalData = indicator.HistoricalData; + TBar bar = new TBar( + time: historicalData.Time(), + open: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Open], + high: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.High], + low: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Low], + close: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Close], + volume: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Volume] + ); + + double price = source switch + { + SourceType.Open => bar.Open, + SourceType.High => bar.High, + SourceType.Low => bar.Low, + SourceType.Close => bar.Close, + SourceType.HL2 => bar.HL2, + SourceType.OC2 => bar.OC2, + SourceType.OHL3 => bar.OHL3, + SourceType.HLC3 => bar.HLC3, + SourceType.OHLC4 => bar.OHLC4, + SourceType.HLCC4 => bar.HLCC4, + _ => bar.Close + }; + + return new TValue(bar.Time, price); + } + + public static TBar GetInputBar(this Indicator indicator, UpdateArgs args) + { + var historicalData = indicator.HistoricalData; + return new TBar( + time: historicalData.Time(), + open: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Open], + high: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.High], + low: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Low], + close: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Close], + volume: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Volume] + ); + } + +#pragma warning disable CA1416 // Validate platform compatibility + public static void PaintHLine(this Indicator indicator, PaintChartEventArgs args, double value, Pen pen) + { + if (indicator.CurrentChart == null) + return; + + Graphics gr = args.Graphics; + var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + + gr.SetClip(clientRect); + int leftX = clientRect.Left; + int rightX = clientRect.Right; + int Y = (int)converter.GetChartY(value); + + using (pen) + { + gr.DrawLine(pen, new Point(leftX, Y), new Point(rightX, Y)); + } + } + + public static void PaintSmoothCurve(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true, double tension = 0.2) + { + if (!series.Visible || indicator.CurrentChart == null) + return; + + Graphics gr = args.Graphics; + gr.SmoothingMode = SmoothingMode.AntiAlias; + var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + + gr.SetClip(clientRect); + DateTime leftTime = new[] { converter.GetTime(clientRect.Left), indicator.HistoricalData.Time(indicator!.Count - 1) }.Max(); + DateTime rightTime = new[] { converter.GetTime(clientRect.Right), indicator.HistoricalData.Time(0) }.Min(); + + int leftIndex = (int)indicator.HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; + int rightIndex = (int)indicator.HistoricalData.GetIndexByTime(rightTime.Ticks); + + List allPoints = new List(); + + for (int i = rightIndex; i < leftIndex; i++) + { + int barX = (int)converter.GetChartX(indicator.HistoricalData.Time(i)); + int barY = (int)converter.GetChartY(series[i]); + int halfBarWidth = indicator.CurrentChart.BarsWidth / 2; + Point point = new Point(barX + halfBarWidth, barY); + allPoints.Add(point); + } + + if (allPoints.Count > 1) + { + if (allPoints.Count < 2) return; + + using (Pen defaultPen = new(series.Color, series.Width) { DashStyle = ConvertLineStyleToDashStyle(series.Style) }) + using (Pen coldPen = new(series.Color, series.Width) { DashStyle = DashStyle.Dot }) + { + int hotCount = indicator.Count - warmupPeriod - rightIndex; + + // Draw the hot part + if (hotCount > 0) + { + var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray(); + gr.DrawCurve(defaultPen, hotPoints, 0, hotPoints.Length - 1, (float)tension); + } + + // Draw the cold part + if (showColdValues && hotCount < allPoints.Count) + { + var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray(); + gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)tension); + } + } + } + } + + public static void PaintHistogram(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true) + { + if (!series.Visible || indicator.CurrentChart == null) + return; + + Graphics gr = args.Graphics; + gr.SmoothingMode = SmoothingMode.AntiAlias; + var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + + gr.SetClip(clientRect); + DateTime leftTime = new[] { converter.GetTime(clientRect.Left), indicator.HistoricalData.Time(indicator!.Count - 1) }.Max(); + DateTime rightTime = new[] { converter.GetTime(clientRect.Right), indicator.HistoricalData.Time(0) }.Min(); + + int leftIndex = (int)indicator.HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; + int rightIndex = (int)indicator.HistoricalData.GetIndexByTime(rightTime.Ticks); + + for (int i = rightIndex; i < leftIndex; i++) + { + int barX = (int)converter.GetChartX(indicator.HistoricalData.Time(i)); + int barY = (int)converter.GetChartY(series[i]); + int barY0 = (int)converter.GetChartY(0); + int HistBarWidth = indicator.CurrentChart.BarsWidth - 2; + + if (series[i] > 0) + { + using (Brush hist = new SolidBrush(Color.FromArgb(150, 0, 255, 0))) + { + gr.FillRectangle(hist, barX, barY, HistBarWidth, Math.Abs(barY - barY0)); + } + } + else + { + using (Brush hist = new SolidBrush(Color.FromArgb(150, 255, 0, 0))) + { + gr.FillRectangle(hist, barX, barY0, HistBarWidth, Math.Abs(barY0 - barY)); + } + } + } + } + + public static void DrawText(this Indicator indicator, PaintChartEventArgs args, string text) + { + if (indicator.CurrentChart == null) + return; + + Graphics gr = args.Graphics; + var clientRect = indicator.CurrentChart.MainWindow.ClientRectangle; + Font font = new Font("Inter", 8); + SizeF textSize = gr.MeasureString(text, font); + RectangleF textRect = new RectangleF(clientRect.Left + 5, + clientRect.Bottom - textSize.Height - 10, + textSize.Width + 10, textSize.Height + 10); + + gr.FillRectangle(Brushes.DarkBlue, textRect); + gr.DrawString(text, font, Brushes.White, new PointF(textRect.X + 6, textRect.Y + 5)); + } + + private static DashStyle ConvertLineStyleToDashStyle(LineStyle lineStyle) + { + return lineStyle switch + { + LineStyle.Solid => DashStyle.Solid, + LineStyle.Dash => DashStyle.Dash, + LineStyle.Dot => DashStyle.Dot, + LineStyle.DashDot => DashStyle.DashDot, + _ => DashStyle.Solid, + }; + } +}