From 13d7c1215dd057ca26b88614a46b3fae74f07623 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Sun, 28 Dec 2025 17:44:08 -0800 Subject: [PATCH] Refactor code formatting and improve consistency across various test files - Removed unnecessary blank lines in multiple test files to enhance readability. - Ensured consistent spacing and formatting in the `Trima`, `Usf`, `Vidya`, `Wma`, and `Atr` test classes. - Updated comments for clarity and consistency in the `Atr` and `Adl` classes. - Adjusted project files for better structure and maintainability. --- .coderabbit.yaml | 12 +- .config/dotnet-tools.json | 24 +- .github/TradingPlatform.BusinessLayer.xml | 13170 ++++++++-------- .vscode/extensions.json | 18 +- .vscode/launch.json | 66 +- .vscode/settings.json | 406 +- .vscode/tasks.json | 108 +- Directory.Build.props | 170 +- docs/api.md | 12 +- docs/integration.md | 8 +- lib/Directory.Build.props | 26 +- lib/QuanTAlib.Tests.csproj | 98 +- lib/core/simd/SimdExtensions.Tests.cs | 30 +- lib/core/simd/SimdExtensions.md | 104 +- lib/core/tbar/TBar.Tests.cs | 4 +- lib/core/tbar/TBar.md | 224 +- lib/core/tbarseries/TBarSeries.Tests.cs | 78 +- lib/core/tbarseries/TBarSeries.md | 242 +- lib/core/tseries/ITValuePublisher.cs | 3 +- lib/core/tseries/TSeries.Tests.cs | 4 +- lib/core/tseries/TSeries.md | 228 +- lib/core/tvalue/TValue.Tests.cs | 4 +- lib/core/tvalue/TValue.md | 198 +- lib/core/tvalue/tvalue.cs | 2 +- lib/feeds/IFeed.cs | 70 +- lib/feeds/IFeed.md | 90 +- lib/feeds/csv/CsvFeed.Tests.cs | 592 +- lib/feeds/csv/CsvFeed.cs | 414 +- lib/feeds/csv/CsvFeed.md | 144 +- lib/feeds/gbm/GBM.md | 142 +- lib/feeds/gbm/Gbm.Tests.cs | 562 +- lib/feeds/gbm/gbm.cs | 508 +- lib/momentum/adx/Adx.Tests.cs | 16 +- lib/momentum/adxr/Adxr.Tests.cs | 16 +- lib/momentum/adxr/Adxr.cs | 4 +- lib/momentum/ao/Ao.Tests.cs | 16 +- lib/momentum/apo/Apo.Tests.cs | 16 +- lib/momentum/apo/Apo.cs | 2 +- lib/momentum/aroon/Aroon.Tests.cs | 20 +- lib/momentum/aroon/Aroon.Validation.Tests.cs | 10 +- lib/momentum/aroonosc/AroonOsc.Tests.cs | 20 +- .../aroonosc/AroonOsc.Validation.Tests.cs | 2 +- lib/momentum/bop/Bop.Tests.cs | 10 +- lib/momentum/bop/Bop.Validation.Tests.cs | 8 +- lib/momentum/bop/Bop.cs | 2 +- lib/momentum/cfb/Cfb.Tests.cs | 2 +- lib/momentum/cfb/Cfb.Validation.Tests.cs | 2 +- lib/momentum/cfb/Cfb.cs | 4 +- lib/momentum/dmx/Dmx.Tests.cs | 10 +- lib/momentum/dmx/Dmx.Validation.Tests.cs | 6 +- lib/momentum/macd/Macd.Quantower.Tests.cs | 4 +- lib/momentum/macd/Macd.Tests.cs | 10 +- lib/momentum/macd/Macd.Validation.Tests.cs | 10 +- lib/momentum/macd/Macd.cs | 18 +- lib/momentum/rsi/Rsi.Quantower.Tests.cs | 6 +- lib/momentum/rsi/Rsi.Tests.cs | 6 +- lib/momentum/rsi/Rsi.Validation.Tests.cs | 4 +- lib/momentum/rsi/Rsi.cs | 8 +- lib/momentum/rsx/Rsx.Repro.Tests.cs | 12 +- lib/momentum/rsx/Rsx.Tests.cs | 26 +- lib/momentum/rsx/Rsx.Validation.Tests.cs | 8 +- lib/momentum/vel/Vel.Tests.cs | 8 +- lib/momentum/vel/Vel.Validation.Tests.cs | 4 +- lib/quantalib.csproj | 102 +- lib/statistics/beta/Beta.Tests.cs | 10 +- lib/statistics/beta/Beta.Validation.Tests.cs | 14 +- lib/statistics/beta/Beta.cs | 8 +- .../covariance/Covariance.Quantower.cs | 2 +- .../covariance/Covariance.Simd.Tests.cs | 18 +- lib/statistics/covariance/Covariance.Tests.cs | 36 +- .../covariance/Covariance.Validation.Tests.cs | 16 +- .../linreg/LinReg.Validation.Tests.cs | 4 +- lib/statistics/linreg/LinReg.cs | 4 +- lib/statistics/median/Median.Tests.cs | 18 +- .../median/Median.Validation.Tests.cs | 2 +- lib/statistics/skew/Skew.Tests.cs | 30 +- lib/statistics/skew/Skew.Validation.Tests.cs | 4 +- lib/statistics/skew/Skew.cs | 38 +- lib/statistics/stddev/StdDev.Tests.cs | 10 +- .../stddev/StdDev.Validation.Tests.cs | 16 +- lib/statistics/stddev/StdDev.cs | 18 +- lib/statistics/variance/Variance.Tests.cs | 22 +- .../variance/Variance.Validation.Tests.cs | 18 +- lib/statistics/variance/Variance.cs | 46 +- lib/trends/alma/Alma.Quantower.cs | 2 +- lib/trends/alma/Alma.Tests.cs | 6 +- lib/trends/alma/Alma.cs | 10 +- lib/trends/bessel/Bessel.Quantower.cs | 2 +- lib/trends/bessel/Bessel.Tests.cs | 2 +- lib/trends/bessel/Bessel.cs | 2 +- .../bilateral/Bilateral.Quantower.Tests.cs | 2 +- lib/trends/bilateral/Bilateral.Quantower.cs | 2 +- lib/trends/bilateral/Bilateral.Tests.cs | 42 +- .../bilateral/Bilateral.Validation.Tests.cs | 16 +- lib/trends/bilateral/Bilateral.cs | 44 +- lib/trends/blma/Blma.Quantower.cs | 2 +- lib/trends/blma/Blma.Tests.cs | 56 +- lib/trends/blma/Blma.Validation.Tests.cs | 16 +- lib/trends/blma/Blma.cs | 12 +- lib/trends/butter/Butter.Quantower.cs | 2 +- lib/trends/butter/Butter.Tests.cs | 14 +- lib/trends/butter/Butter.Validation.Tests.cs | 34 +- lib/trends/butter/Butter.cs | 10 +- lib/trends/conv/Conv.Quantower.cs | 2 +- lib/trends/conv/Conv.Tests.cs | 2 +- lib/trends/dema/Dema.Quantower.cs | 2 +- lib/trends/dema/Dema.Tests.cs | 16 +- lib/trends/dema/Dema.Validation.Tests.cs | 8 +- lib/trends/dwma/Dwma.Quantower.cs | 2 +- lib/trends/dwma/Dwma.Tests.cs | 32 +- lib/trends/dwma/Dwma.Validation.Tests.cs | 24 +- lib/trends/ema/Ema.Tests.cs | 1302 +- lib/trends/ema/Ema.Validation.Tests.cs | 640 +- lib/trends/ema/Ema.cs | 4 +- lib/trends/ema/Ema.md | 134 +- lib/trends/hma/Hma.Quantower.cs | 2 +- lib/trends/hma/Hma.Tests.cs | 8 +- lib/trends/htit/Htit.Tests.cs | 14 +- lib/trends/htit/Htit.Validation.Tests.cs | 2 +- lib/trends/htit/Htit.cs | 2 +- lib/trends/jma/Jma.Quantower.cs | 2 +- lib/trends/jma/Jma.Tests.cs | 2 +- lib/trends/jma/Jma.Validation.Tests.cs | 10 +- lib/trends/jma/Jma.ZeroDiv.Tests.cs | 4 +- lib/trends/kama/Kama.Tests.cs | 6 +- lib/trends/kama/Kama.Validation.Tests.cs | 4 +- .../instructions/codacy.instructions.md | 2 +- lib/trends/lsma/Lsma.Tests.cs | 74 +- lib/trends/mama/Mama.Tests.cs | 54 +- lib/trends/mama/Mama.Validation.Tests.cs | 8 +- lib/trends/mama/Mama.md | 2 +- lib/trends/mgdi/Mgdi.Tests.cs | 14 +- lib/trends/mgdi/Mgdi.Validation.Tests.cs | 2 +- lib/trends/pwma/Pwma.Tests.cs | 2 +- lib/trends/rma/Rma.Tests.cs | 2 +- lib/trends/rma/Rma.Validation.Tests.cs | 4 +- lib/trends/sma/Sma.Tests.cs | 22 +- lib/trends/sma/Sma.Tolerance.Tests.cs | 2 +- lib/trends/sma/Sma.ZeroDiv.Tests.cs | 10 +- lib/trends/sma/Sma.cs | 6 +- lib/trends/ssf/Ssf.Tests.cs | 2 +- lib/trends/ssf/Ssf.cs | 10 +- lib/trends/super/Super.Quantower.cs | 2 +- lib/trends/super/Super.Tests.cs | 20 +- lib/trends/t3/T3.Tests.cs | 42 +- lib/trends/t3/T3.cs | 10 +- lib/trends/tema/Tema.Quantower.Tests.cs | 2 +- lib/trends/tema/Tema.Tests.cs | 22 +- lib/trends/tema/Tema.Validation.Tests.cs | 4 +- lib/trends/trima/Trima.Tests.cs | 22 +- lib/trends/trima/Trima.Validation.Tests.cs | 14 +- lib/trends/usf/Usf.Quantower.Tests.cs | 4 +- lib/trends/usf/Usf.Tests.cs | 22 +- lib/trends/usf/Usf.cs | 10 +- lib/trends/vidya/Vidya.Quantower.Tests.cs | 2 +- lib/trends/vidya/Vidya.Tests.cs | 22 +- lib/trends/vidya/Vidya.cs | 6 +- lib/trends/wma/Wma.Coverage.Tests.cs | 18 +- lib/trends/wma/Wma.Quantower.Tests.cs | 2 +- lib/trends/wma/Wma.Tests.cs | 46 +- lib/volatility/atr/Atr.Validation.Tests.cs | 4 +- lib/volatility/atr/Atr.cs | 8 +- lib/volume/adl/Adl.Tests.cs | 26 +- lib/volume/adl/Adl.Validation.Tests.cs | 4 +- lib/volume/adosc/Adosc.Tests.cs | 4 +- omnisharp.json | 38 +- perf/perf.csproj | 2 +- quantower/Directory.Build.props | 2 +- quantower/Trends.csproj | 68 +- 169 files changed, 10815 insertions(+), 10814 deletions(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 37ab21a0..92e57f00 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -5,18 +5,18 @@ reviews: path_filters: # Include C# source files first - "lib/trends/**/*.cs" - + # Exclude test files - "!**/*.Tests.cs" - "!**/*Tests.cs" - "!**/*.Validation.Tests.cs" - + # Exclude Quantower adapters - "!**/*.Quantower.Tests.cs" - "!**/*.Quantower.cs" - "!**/quantower/**" - "!**/docs/*.md" - + # Exclude build/IDE artifacts - "!**/bin/**" - "!**/obj/**" @@ -31,7 +31,7 @@ reviews: - "!**/ilspy/**" - "!**/perf/**" - "!**/Mocks/**" - + # Exclude non-source files - "!**/*.md" - "!**/*.dib" @@ -49,7 +49,7 @@ reviews: - "!**/*.lock" - "!**/*.props" - "!**/*.targets" - + # Exclude binaries - "!**/*.dll" - "!**/*.pdb" @@ -58,7 +58,7 @@ reviews: - "!**/*.so" - "!**/*.dylib" - "!**/*.exe" - + # Exclude data/media files - "!**/*.db" - "!**/*.sqlite" diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 5782c9e6..dfc5d379 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -1,13 +1,13 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "gitversion.tool": { - "version": "6.5.1", - "commands": [ - "dotnet-gitversion" - ], - "rollForward": false - } - } +{ + "version": 1, + "isRoot": true, + "tools": { + "gitversion.tool": { + "version": "6.5.1", + "commands": [ + "dotnet-gitversion" + ], + "rollForward": false + } + } } \ No newline at end of file diff --git a/.github/TradingPlatform.BusinessLayer.xml b/.github/TradingPlatform.BusinessLayer.xml index 12fd45c7..9c8ede7f 100644 --- a/.github/TradingPlatform.BusinessLayer.xml +++ b/.github/TradingPlatform.BusinessLayer.xml @@ -1,6585 +1,6585 @@ - - - - TradingPlatform.BusinessLayer - - - - - Access to the chart panel - - - - - Chart panel unique ID - - - - - Collection of chart windows - - - - - Current X scale value - width of the bar in pixels - - - - - Provides time zone of current chart. - - - - - Provides account of current chart. - - - - - Provides custom sessions of current chart. - - - - - Current tick size of the chart - - - - - Main window of the chart - - - - - Current right offset value - - - - - Collection of chart drawingsCollection - - - - - Force chart refreshing - - - - - The MouseDown event occurs when the mouse button is pressed down - - - - - The MouseUp event occurs when the mouse button is released - - - - - The MouseClick event occurs when the mouse button is clicked - - - - - The MouseMove event occurs when the mouse moving over the chart - - - - - The MouseDown event occurs when the user scrolling mouse wheel - - - - - The MouseDown event occurs when the mouse enter the chart - - - - - The MouseDown event occurs when the mouse leave the chart - - - - - The SettingsChanged event occurs when any settings were changed - - - - - The AccountChanged event occurs when the account was changed - - - - - Access to the chart drawingsCollection collection - - - - - Add chart drawing to the collection - - - - - - Remove specified chart drawing from collection - - - - - - Get chart drawing by ID - - - - - - - Get all chart drawingsCollection assigned to specified symbol - - - - - - The Added events occured, when new chart drawing was added to collection - - - - - The Moved events occured, when chart drawing was moved - - - - - The Removed events occured, when chart drawing was removed from the collection - - - - - The SelectionChanged events occured, when selected chart drawing was changed - - - - - Access to the particular window from chart panel - - - - - Client rectangle of the chart window - - - - - Determines, whether this window is the main window of the chart - - - - - Chart window number - - - - - Special object, allows you to convert values from x/y scale to Time/Price and back - - - - - - - - - - Converter between x/y and Time/Price scales - - - - - Get the DateTime value that is corresponding to specified x coordinate - - - - - - - Get the Price value that is corresponding to specified y coordinate - - - - - - - Get the X coordinate that is corresponding to specified DateTime value - - - - - - - Get the Y coordinate that is corresponding to specified price value - - - - - - - Get the bar index that is corresponding to specified DateTime value - - - - - - - Access to the chart drawing - - - - - Determines, the way how chart drawing was created: manually or programmatically - - - - - Determines, the availability of drawing - only current chart or all charts with same symbol - - - - - The unique ID of the chart drawing - - - - - Determines, whether chart drawing draws above or below the main chart - - - - - Determines, state of the chart drawing: Locked or Unlocked - - - - - Get time and price of the particular point of the chart drawing - - - - - Set time and price value for particular point of the chart drawing - - - - - Gets s list - - - - - Gets symbol types list - - - - - Gets s list - - - - - Gets s list - - - - - Gets Exchanges list - - - - - Gets Orders list - - - - - Gets Order Types list - - - - - Gets Positions list - - - - - Gets Closed Positions list - - - - - Gets Corporate Actions list - - - - - Gets Report Types list - - - - - Gets s list - - - - - Vendor connection respond bundle - - - - - The connection status - - - - - The respond message - - - - - Ping state dictum - - - - - Ping signals connected - - - - - Ping signals disconnected - - - - - The asset formatting description. - - - - - Initializes a new instance of the class. - - The asset id. - The value. - If true, add currency name. - - - - Mediates a history meta data with available data types and intervals on vendor side - - - - - Asset id bearer - - - - - Asset name bearer - - - - - Asset description - - - - - Asset id bearer - - - - - Performs a binding with broker or data provider - - - - - Specifies any operation before breaking a connection with broker or data provider - - - - - Called when platform finished retreiving all required informations from vendor during connecting - - - - - Ping processing before its visualization in the terminal - - - - - Retrieves an information about available accounts. Yon need to specify at least one account - - - - - Confirms allowed nonfixed list by vendor - - - - - Retrieves a collection of available exchange markets from vendor - - - - - Retrieves information about available assets. - - - - - Retrieves information about available symbols. - - - - - Gets an available symbols types from vendor - - - - - Derives a non fixed symbol from vendor - - - - - Derives a non-fixed list of symbols from vendor - - - - - Retrieves allowed/supported order types - - - - - Retrieves an information about opened orders at the time of connection. - - - - - Retrieves an information about positions at the time of connection - - - - - Gets trades history from server for requested time range - - - - - Subscribing to quote data: Level1/Level2/Trade - - - - - Unsubscribing from quote data: Level1/Level2/Trade - - - - - Gets a list of supported historical intervals and data types by vendor - - - - - Prepare and sending order placing request to broker - - - - - Sending order modification request to broker - - - - - Sending order cancellation request to broker - - - - - Sending position closing request to broker - - - - - Gets information about available reports from vendor - - - - - Called when platform need to generate particular report - - - - - Ping respond bundle - - - - - Ping time - - - - - Time spent for single request - - - - - Ping state - - - - - Performs a binding with broker or data provider - - - - - Specifies any operation before breaking a connection with broker or data provider - - - - - Called when platform finished retreiving all required informations from vendor during connecting - - - - - Ping processing before its visualization in the terminal - - - - - Retrieves an information about available accounts. Yon need to specify at least one account - - - - - Retrieves a collection of available exchange markets from vendor - - - - - Retrieves information about available assets. - - - - - Retrieves information about available symbols. - - - - - Gets an available symbols types from vendor - - - - - Derives a non fixed symbol from vendor - - - - - Derives a non-fixed list of symbols from vendor - - - - - Retrieves an information about opened orders at the time of connection. - - - - - Retrieves an information about positions at the time of connection - - - - - Retrieves an information about closed positions at the time of connection - - - - - Subscribing to quote data: Level1/Level2/Trade - - - - - Unsubscribing from quote data: Level1/Level2/Trade - - - - - Gets a list of supported historical intervals and data types by vendor - - - - - Prepare and sending order placing request to broker - - - - - Sending order modification request to broker - - - - - Sending order cancellation request to broker - - - - - Sending position closing request to broker - - - - - Gets information about available reports from vendor - - - - - Called when platform need to generate particular report - - - - - Retrieves allowed/supported order types - - - - - Confirms allowed non fixed list by vendor - - - - - Gets a settings list from a vendor selection of the setup window - - - - - The ID of the order group. This group created when trades done by the MAM account. - - - - - Total quantity of the order - - - - - Gets OrderType - - - - - Gets order price value - - - - - Gets order trigger price value - - - - - Gets order trailing offset value - - - - - Gets orders current status - - - - - Gets orders last update time - - - - - Gets Position Id. - - - - - Gets StopLoss holder for given order - - - - - Gets TakeProfit holder for given order - - - - - Orders Type Id. It is used for the orders type comparing. - - - - - Gets order TIF(Time-In-Force) type - - - - - Gets orders expiration time - - - - - Remaining quantity of the order - - - - - Filled quantity of the order - - - - - Gets open order original status - - - - - Will be triggered on each invocation - - - - - - - - - - - - - - - Get VWAP value - - - - - - - - - - - - - - - - - - - - - - - - - Снапшот для эмулятора - - - - - Текущая версия для сериализации. Если что-то меняем и нужно делать поддержку старого формата - можно использовать версию. - - - - - Version 1.1: изменилась схема работы с коннектами: стали доступны кастомные коннекты - - - - - Defines 'Volume Analysis' calculation result item - - - - - Contains all user's account information - - - - - Gets account unique code. - - - - - Obtaining account name. - - - - - Gets base currency of account. Account CCY is always equal to the server CCY in AlgoStudio - - - - - Gets current balance of the account. - - - - - Gets additional account information - - - - - Will be triggered on each account information updating - - - - - Gets Account name - - - - - - Creates a business object info with an Account data which can be used for the restoring/serialization process. - - - - - - The account operation. - - - - - Gets the name. - - - - - Gets the button text. - - - - - Gets the settings. - - - - - - - The settings. - - - - Builds the confirmation. - - The settings. - A string. - - - - - - The settings. - - - - Compare to. - - The obj. - An int. - - - - The additional info collection. - - - - - Gets the count. - - - - - Gets the items. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The items. - - - - Try get item. - - The api key. - The item. - A bool. - - - - Gets the enumerator. - - ]]> - - - - Tos the string. - - A string. - - - - The comparing type. - - - - - The additional info item formating type. - - - - - The additional info item. - - - - - ключ для апи, обязательно для заполнения и должен быть уникальным - - - - - Gets or Sets the group info. - - - - - Gets or Sets the sort index. - - - - - Gets or Sets the name key. - - - - - Gets or Sets the tool tip key. - - - - - Gets or Sets the data type. - - - - - Gets or Sets the value. - - - - - Gets or Sets a value indicating whether hidden. - - - - - Gets or Sets the formating type. - - - - - используется в связке с AccountAdditionalInfoItemFormatingType.CustomAsset для форматирования в заданном ассете - - - - - Gets or Sets a value indicating whether visible. - - - - - Gets or Sets a value indicating whether is link. - - - - - Gets or Sets the editing info. - - - - - Gets or Sets the formatting description. - - - - - Initializes a new instance of the class. - - - - - - - The item. - - - - - - An object. - - - - To the XML element. - - A XElement. - - - - From the XML element. - - The element. - The deserialization info. - - - - To the string. - - A string. - - - - Defines asset entity - - - - - Asset id bearer - - - - - Asset name bearer - - - - - Asset description - - - - - Defines a number precision of the change value - - - - - Gets precision value - - - - - Gets asset ISO 4217 code - - - - - Creates an Asset instance - - given connection Id - - - - Formats price into precision normalized string - - - - - - - Formats price into concatenated string which contains the precision normalized value and Asset's name - - - - - - - Gets Asset name - - - - - - Uses comparison by Assets names - - - - - - - Unique ID during active session. Don't use for serialization - - - - - Договориль использовать множественное число для типа инструмента - - - - - Завели для индийской интеграции - - - - - Represents information about corporate action. - - - - - Get the date and time when trade was executed - - - - - Will be triggered on corporate action updating - - - - - Base class for all Custom symbols: Synthetics, Custom Formulas, etc. - - - - - alexb: нам достаточно подставлять правильный HistoricalData - он сделает всю работу - - - - - Расчёт Bid/Ask/BidSize/AskSize для синтетика - - - - - Расчёт Last/LastSize для синтетика - - - - - Расчёт Level2 для синтетика - - - - - Расчёт DayBar для синтетика - - - - - Расчёт бара для синтетика - - - - - Increment current position - - - - - Will be triggered on each invocation - - - - - Applies an position modificaion or position changing by accepting a message. - - - - - - Contains all information which belong to the given exchange - - - - - Gets Exchange Id - - - - - Gets Exchange name - - - - - Used for the Exchanges comparing - - - - - Compares Exchnges in order by next - SortIndex, Exchange name, Connection Id, Exchange Id. - - - - - - - Provides possibility to group and sort symbols for each connection - - - - - Gets group Id - - - - - Gets group name - - - - - Gets sort index for comparing process - - - - - Represents trading information about pending order - - - - - The ID of the order group. This group created when trades done by the MAM account. - - - - - Gets Position Id. - - - - - Total quantity of the order - - - - - Filled quantity of the order - - - - - Remaining quantity of the order - - - - - Orders Type Id. It is used for the orders type comparing. - - - - - Gets OrderType - - - - - Gets order price value - - - - - Gets order trigger price value - - - - - Gets order trailing offset value - - - - - Gets orders current status - - - - - Gets open order original status - - - - - Gets order TIF(Time-In-Force) type - - - - - Gets orders expiration time - - - - - Gets orders last update time - - - - - Gets StopLoss holder for given order - - - - - Gets TakeProfit holder for given order - - - - - Will be triggered on each invocation - - - - - Creates Order instance - - - - - - Applies an order modificaion or order changing by accepting a message. - - - - - - Cancels pending order - - - - - - Represents an wrapper - - - - - Represents trading information about related position - - - - - Will be triggered on each and invocation - - - - - Gets position quantity value - - - - - Gets position open order price - - - - - Gets position openning time - - - - - Gets Profit/loss (without swaps or commissions) all calculated based on the current broker's price. For open position it shows the profit/loss you would make if you close the position at the current price. If position closed, this parameter show profit/loss what trader have after closing this position. - - - - - Gets Profit/loss calculated based on the current broker's price. For open position it shows the profit/loss you would make if you close the position at the current price. If position closed, this parameter show profit/loss what trader have after closing this position. - - - - - Gets fee amount for the position. - - - - - Gets PnL swaps - - - - - The market price obtainable from your broker. - - - - - Closes position if quantity is not specified else - uses partial closing operation. - - - - - - - Gets StopLoss order which belongs to the position - - - - - Gets TakeProfit order which belongs to the position - - - - - Returns ticks amount between open and current price. - - - - - Represents a report grid which can hold system reports based on s and s content. - - - - - Columns collection - - - - - Rows collection - - - - - Initializes report grid - - - - - Adds coloring column to the report - - - - - Adds default(non-colored) column to the report - - - - - Exposes report cell decoration - - - - - Cell label - - - - - Cell value - - - - - Exposes report column decoration - - - - - Header of the column - - - - - Column value type - - - - - Exposes report row decoration - - - - - A collection of cells - - - - - Initializes cells in the row - - - - - Adds cells to the row by label and value - - - - - Adds cells to the row by value only - - - - - Defines report request parameters from which can be used in - - - - - Gets report Id - - - - - Gets report Name - - - - - report settings - - - - - Represents a permisions checking tool which use next priority order , , and - - - - - Represent access to symbol information and properties. - - - - - Gets symbol Id - - - - - Gets symbol name - - - - - Gets symbol description - - - - - Gets symbol type - - - - - Gets symbol base Asset - - - - - Gets symbol counter Asset - - - - - Gets Exchange of current symbol - - - - - Gets Exchange id of current symbol - - - - - Returns delay with which quote come in platform. - - - - - Gets symbol additional info - - - - - The highest trade allowed - - - - - The lowest trade allowed - - - - - Gets symbol NettingType - - - - - Gets SymbolGroup - - - - - Gets current SymbolQuotingType - - - - - Amount of base asset for one lot. - - - - - Stores list of symbol ticksizes - - - - - Cached value of TickSize (if 1 level) - - - - - Step of the lot changes - - - - - Step of the notional value changes - - - - - Gets derivative expiration date - - - - - Gets derivative last trading date - - - - - Gets derivative maturity date - - - - - Gets derivative strike price - - - - - Gets derivative option style - - - - - Gets derivative option type - - - - - Gets derivative option serie - - - - - Gets derivative underlier name - - - - - Gets derivative underlier symbol - - - - - Gets derivative underlier symbol id - - - - - Gets Ask price - - - - - Gets Ask size - - - - - Gets Bid price - - - - - Gets Bid size - - - - - Gets quote time - - - - - Gets last price - - - - - Gets last size - - - - - Gets last time - - - - - Gets mark price - - - - - Gets mark size - - - - - Gets open price - - - - - Gets previous close price - - - - - Gets high price - - - - - Gets low price - - - - - Gets volume value - - - - - Gets quote asset volume value - - - - - Gets PrevSettlement value - - - - - Gets ticks amount - - - - - Gets trades amount - - - - - Gets Level2 data - - - - - Gets spread value between Bid and Ask - - - - - Gets percentage value - - - - - Gets change value between Bid/Last and Close price - - - - - Gets percentage value - - - - - Default history type - - - - - List of all available history types - - - - - Gets SymbolVolumeType - - - - - List of all available history types - - - - - Will be triggered when new Level1 quote is comming - - - - - Will be triggered when new Level2 quote is comming - - - - - Will be triggered when new trade quote is comming - - - - - Will be triggered when new correctional quote is comming from the vendor. - - - - - Will be triggered when symbol updated. - - - - - Gets real time calculation setting - - - - - Gets real time calculation setting - - - - - Gets real time calculation setting - - - - - Gets real time calculation setting - - - - - Gets historical data according to period and other parameters - - - - - - - - - Gets historical data according to period and other parameters - - - - - - - - - - Gets historical data according to aggregation and other parameters - - - - - - - - - - Gets historical data according to given history request - - - - - - - Gets historical ticks data according to given parameters - - - - - - - - - Returns rounded to price - - - - - Calculates new price which equal to given price shifted by a number of given ticks - - - - - - - - Calculates ticks between two prices - - - - - - - - Gets cached symbol tick size or retrives it from the list - - - - - - - Gets symbol tick cost retrived from the list by price - - - - - - - Returns VariableTick if it can be retrived from list by price or null - - - - - - - Gets cached tick size if it available, else tries to obtain with Last, Bid, Ask, first element of list otherwise - - - - - - Formats price value to the appropriative string with a counting on tick precision. - - - - - - - Formats price value to the appropriative string with a counting on max tick precision. - - - - - - - Returns string with formatted ticks value - - - - - - - - Comparing by value - - - - - Gets symbol orders types list which are allowed. - - - - - - - Process order placing with given request parameters - - - - - - - An symbol has possibility to obtain the default account - - - - - Represents information about trade. - - - - - Gets the unique identifier of the order initiating the trade. - - - - - Gets a unique identifier of the position, which is related to this trade. - - - - - Get the price where trade was executed - - - - - Get the trade quantity - - - - - Get the date and time when trade was executed - - - - - Get the trade Gross P&L - - - - - Get the trade Net P&L - - - - - Get the fee value that was charged for this trade - - - - - Get the trade order type - - - - - Will be triggered on trade updating - - - - - The symbols list manager. - - - - - Gets the count. - - - - - Gets or Sets the settings. - - - - - Represent access to level2 data. - - - - - Gets Level2 Asks list - - - - - Gets Level2 Bids list - - - - - Gets current Level2 data - - Parameters of DepthOfMarket - - - - - Gets current Level2 data - - Parameters of request for Leve2Item collection - - - - - Leve2 data. Contains Bids and Ask collections - - - - - Represent parameters of DepthOfMarket - - - - - Represent parameters of request for Leve2Item collection - - - - - Aggregation method - - - - - Required amount of level2 - - - - - Use custom tick size - - - - - Calculate cumulative size - - - - - Aggregation method - - - - - Represent access to level2 item. - - - - - Cumulative size - - - - - Imbalance Percent - - - - - Price - - - - - Size - - - - - Time - - - - - MMID - - - - - проверка на вхождение - - - - - Represents information about connection and provides an access to the current trading information(Symbols, Orders, Position, Accounts etc.). - - - - - Provides access to all business objects which are belong to this connection - - - - - Gets connection Id - - - - - Gets connection Name - - - - - Gets connection's vendor name - - - - - Contains list of connection settings. Will be reused on each population time. - - - - - Gets connection's state (Connected/Connecting/Fail etc.) - - - - - Defines connection type - - - - - Will be triggered when changed. - - - - - Will be triggered when changed. - - - - - Represents connection ping time - - - - - Messages count that one is waited to process - - - - - Gets a matched available metadata info with the vendor's side - - - - - Establishes a connection to a specified vendor - - - - - - Closes a connection. - - - - - Только для использования в ConnectionCache - - - - - alexb: "Умный" поиск - разрешаем указывать слова в любом порядке - - - - - Generates a report with given requested parameters - - specifies Report's type - - - - - Gets connection's s list - - - - - Gets connection's symbol types list which are allowed in this connection - - - - - Gets connection's s list - - - - - Gets connection's s list - - - - - Gets all Exchanges which are allowed in this connection - - - - - Gets all Orders which are belong to this connection - - - - - Gets all Order Types which are available on this connection - - - - - Gets all Positions which are belong to this connection - - - - - Gets all Closed Positions which are belong to this connection - - - - - Gets all Corporate actions which are belong to this connection - - - - - Gets all Report Types which are available on this connection - - - - - Gets all Account operations which are available on this connection - - - - - Gets all Tradins signals which are available on this connection - - - - - Server connection status - - - - - Specifies connection using type. - - - - - Specifies how connection was created: by default or by user - - - - - Represents all needed parameters for the connection constructing process. - - - - - Gets a user friendly name of the connection - - - - - Gets connection group - - - - - Gets vendor's name - - - - - Gets connection Id - - - - - Favorites one will be displayed in Control center toolbar - - - - - Gets ConnectionState - - - - - Gets vendor's settings - - - - - Specifies how connection was created: by default or by user - - - - - realization - - - - - Creates connection info instance - - - - - - Creates connection info instance. - - - - - - - - - дефолтовый период пигования - - - - - Таймер, по которому проходит пинг - - - - - Запускаем таймер - - - - - Останавливаем таймер - - - - - Коннекты, которые нужно подключить - - - - - Сколько ждем до следующей попытки - - - - - The main entry point in the API. Core keeps access to all business logic entities and their properties: - connections, accounts, symbols, positions, orders, etc. Some of them can be reached through using managers or directly via specified collections. - You can always access the Core object via static Core.Instance property. - - - - - Gets a singleton instance of . API entry point - - - - - Gets an access to all created connections and manages them - - - - - Gets an access to the system logging mechanism - - - - - Obtains licence rules for the current user - - - - - Gets an access to all available trading data vendors and creates them - - - - - Gets an access to the all available aggregation types - - - - - Access to Volume Analysis calculations - - - - - Gets an access to the all available indicators and creates them - - - - - Gets an access to the all available trading strategies and manages them - - - - - Gets a permissions checking mechanism - - - - - Gets a time based conversion and synchronization mechanism - - - - - Gets SMTP mail service for sending emails - - - - - Represents current trading status - - - - - Will be triggered when changed - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections - - - - - Gets all available s from open connections. Otherwise returns empty list - > - - - - Gets all available s from open connections. Otherwise returns empty list - > - - - - Starts a initialization process which initializes given Managers, and Utils. And provides a subscribing on events. - - - - - Disposes all previously initialized modules and unsubscribe from events - - - - - Will be triggered when new added to the core - - - - - Gets an instance of exist Account or creates a new one with given info parameter - - - - - - - Will be triggered when new added to the core - - - - - Returns all s from open connections which satisfy given request parameters, otherwise returns empty list - - - - - - - Retrieves any by given request parameters. Otherwise returns null - - - Must be specified if open connections total is more than one. Will search only in Synthetic symbols list if id is equal to - - - - - - Gets an instance of exist symbol or creates a new one with given info parameter - - - - - - - Will be triggered when new placed - - - - - Will be triggered when canceled - - - - - Gets instance by given Id string. Otherwise returns null - - - Must be specified if open connections total is more than one - - - - - Gets instance by given Id string. Otherwise returns null - - - Must be specified if open connections total is more than one - - - - - Will be triggered when new opened - - - - - Will be triggered when closed - - - - - Gets instance by given Id string. Otherwise returns null - - - Must be specified if open connections total is more than one - - - - - Gets Profit'n'Loss with given request parameters from open connection. Otherwise returns null - - - - - - - Will be triggered when new added - - - - - Will be triggered when removed - - - - - Will be triggered when new occured - - - - - Gets collection of by given parameters - - - - - - - - Gets collection of by given parameters and callback - - - - - - - - - Will be triggered when new occured - - - - - Will be triggered when new added - - - - - Gets collection of by given parameters - - - - - - - - Places with given request parameters - - - - - - - Places multiple s with given request parameters - - - - - - - - Modifies by given request parameters - - - - - - - Closes with given request parameters - - - - - - - Cancels with given request parameters - - - - - - - Returns with given request parameters from open connection - - - - - - - Will be triggered when new received - - - - - Sends custom request if connection with given Id is open - - - - - - - Subscribe on custom messages - - custom message handler - custom messages Id - - - - Unsubscribe from custom messages - - custom message handler - custom messages Id - - - - Gets all previously configured s - - - - - Gets an access to s and manages them - - - - - Adds if given parameters are valid - - - - - - - Replaces exist list by new list in if given parameters are valid - - - - - - - Removes with a given name - - - - - - Replaces list name by new given name in if given parameters are valid - - - - - - - Represent all available Historical Symbols - - - - - Will be triggered when custom symbol added - - - - - Will be triggered when custom symbol removed - - - - - Will be triggered when custom symbol updated - - - - - Represent all available Synthetic items - - - - - Adds given Synthetic to the list if it does not contain - - - - - - Removes given Synthetic from the list if it exists - - - - - - Will be triggered when created/chenged/removed - - - - - Override to change string representation of aggregation - - - - - Represent access to historical data information and indicators control. - - - - - Gets HistoricalData symbol - - - - - Gets HistoricalData aggregation - - - - - Gets HistoricalData left time boundary - - - - - Gets HistoricalData right time boundary - - - - - Gets HistoricalData items amount - - - - - Retrieves HistoricalData item by indexing offset and direction to find. - - - - - - - - Will be triggered when new historical item created - - - - - Will be triggered when current historical item changed or updated - - - - - Gets access to built-in indicators - - - - - Reloads entire HistoricalData - - - - - Gets index by time with counting on search direction - - - - - Gets array of attached indicators - - - - - Creates indicator by it's name and if it successfully created adds it to the HistoricalData - - - - - - - - Adds indicator to the HistoricalData - - - - - - Removes indicator from the HistoricalData - - - - - - Will be triggered when volume analysis of current historical item changed or updated - - - - - Period that can be used as a basis for history aggregations - - - - - Represents historical data bar item - - - - - Gets bar's right time border - - - - - Defines bar's ticks count - - - - - Defines Open price - - - - - Defines High price - - - - - Defines Low price - - - - - Defines Close price - - - - - Gets Median (High+Low)/2 price - - - - - Gets Typical (High+Low+Close)/3 price - - - - - Gets Weighted (High+Low+Close+Close)/4 price - - - - - Defines ticks amount - - - - - Defines volume value - - - - - Gets price by indexing - - - - - - - Creates HistoryItemBar instance with default OHLC price = - - - - - Comparing by , OHLC, - - - - - - - Creates HistoryItemLast instance - - - - - Represents historical data trade item - - - - - Defines price value - - - - - Defines volume value - - - - - Defines trade operation side as aggressor flag - - - - - Gets price by indexing - - - - - - - Creates HistoryItemLast instance - - - - - Comparing by , OHLC, - - - - - - - Creates HistoryItemLast instance - - - - - Gets price by indexing - - - - - - - Creates HistoryItemLast instance - - - - - Represents historical data tick item - - - - - Defines Bid price - - - - - Defines Bid size - - - - - Defines Ask price - - - - - Defines Ask size - - - - - Gets price by indexing - - - - - - - Creates HistoryItemBar instance with default Ask/AskSize/Bid/BidSize = - - - - - Comparing by , OHLC, , , , - - - - - - - Resolves a history request parameters per symbol - - - - - Represents mechanism for supporting predefined and custom periods - - - - - Gets period multiplier - - - - - Gets base period type - - - - - Gets ticks value as an result of base period multiplicated by - - - - - Creates Period instance with greater than 0 - - - - - - - True if their base paeriods and are respectively equal. - - - - - - - - True if any of their base paeriods and are respectively not equal. - - - - - - - - True if their ticks values are satisfying initial condition - - - - - - - - True if their ticks values are satisfying initial condition - - - - - - - - True if their ticks values are satisfying initial condition - - - - - - - - True if their ticks values are satisfying initial condition - - - - - - - - True if their base paeriods and are respectively equal. - - - - - - - Formats given value to a specific user friendly string - - - - - - - Returns value in ticks according to base period type - - - - - - - Returns shorted string according to base period type - - - - - - - Converts time gap into dates range - - - - - - - Compares by - - - - - - - Serialize into object - - - - - - Deserialize from - - - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Predefined period - - - - - Represent access to DayBar quote, which contains summary information about instrument prices. - - - - - High price - - - - - Open price - - - - - Low price - - - - - Previous Close price - - - - - Ticks value - - - - - Volume value - - - - - Volume value - - - - - Previous settlement price - - - - - Bid price - - - - - Bid size - - - - - Ask price - - - - - Ask size - - - - - Last price - - - - - Last size - - - - - Trades value - - - - - Change value - - - - - Change value in percentage - - - - - - - - - - Represent access to DOM2 quote, which contains Bids and Asks. - - - - - Collection of Asks quotes - - - - - Collection of Bids quotes - - - - - Represent access to trade information. - - - - - Price at which trade occured - - - - - Size of the trade - - - - - Information about operation side of the trade - - - - - Shows the direction of price movement, comparing to previous value. - - - - - Represent access to Level2 quote. - - - - - Price type of Level2 quote: Bid or Ask - - - - - Price of Level2 quote - - - - - Size of Level2 quote - - - - - Unique ID of Level2 quote - - - - - Shows, whether Level2 quote is using only for removing from depth - - - - - Broker identifier that send level2 quote - - - - - specifies the implied quantity associated with the price for the quote. Subtracting this amount from the Size yields the outright quantity for the price level. A value of zero indicates that the implied size is not available/defined or that it is actually zero. - - - - - Time of the quote - - - - - Represent access to quote information. - - - - - Bid price - - - - - Bid size - - - - - Ask price - - - - - Ask size - - - - - Shows the direction of bid price movement, comparing to previous value. - - - - - Shows the direction of ask price movement, comparing to previous value. - - - - - Constants for licence keys - - - - - User licences info store - - - - - Current state of connection to licence server - - - - - Current connected user info - - - - - All active licences that user have - - - - - Check that user have licence - - - - - The loopback http listener without kestrel. - - - - - Initializes a new instance of the class. - - The port. - - - - Dispose - - - - - Wait for callback asynchronously. - - The token. - ]]> - - - - Specifies the reason of price data updating. - - - - - Indicates the processing of new historical bar. - - - - - Indicates the coming of new real-time tick. - - - - - Indicates a start of new real-time bar. - - - - - Specifies the type of events when indicator should recalculate itself. - - - - - Specifies the style of indicator line. - - - - - Moving average mode - - - - - Simple Moving Average - - - - - Exponential Moving Average - - - - - Smoothed Moving Average - - - - - Linearly Weighted Moving Average - - - - - - - - - - Displays HV schedule and percentile value. - - - - - Displays the percentile schedule only. - - - - - Returns an instance of the Exponential Moving Average (EMA) indicator. - EMA provides a weighted price calculation for the last N periods. - - Period of Exponential Moving Average - Sources prices for MA - Calculation type - - - - Gets the Linearly Weighted Moving Average - Linear Weighted Moving Average makes the most recent bar more important unlike SMA. - - Moving average period - Type of the price - - - - - Gets the SMA(Simple Moving Average) indicator. - The 'SMA' indicator provides an average price for the last N periods. - - Period of simple moving average. - Sources prices for MA. - - - - - Returns an instance of the Smoothed Moving Average (SMMA) indicator. - SMMA indicator provides a smoothed average price for the last N periods. - - Moving average period - Type of the price - Calculation type - - - - - Gets the PPMA(Pivot Point Moving Average) indicator. - The 'PPMA' indicator uses the pivot point calculation as the input a simple moving average. - - Period of PPMA indicator - - - - - - Gets the MAS3 (3MASignal) indicator. - The 'MAS3' indicator offers buy and sell signals according to intersections of three moving averages. - - Short moving average period. - Middle moving average period. - Long moving average period. - The count of bars. The trend will be determined on this interval. - - - - - - Gets the specific MA indicator, according to selected 'MaMode'. - - Period of moving average. - Type of price. - MA mode. - Calculation type - - - - - - Gets the BB(Bollinger Bands) indicator. - The 'BB' indicator provides a relative definition of high and low based on standard deviation and a simple moving average. - - Period of MA for envelopes. - Value of confidence interval. - Sources prices for MA. - Type of moving average. - Calculation type - - - - - Gets the Commodity Channel Index. - Measures the position of price in relation to its moving average. - - Period for CCI MA - Sources prices for CCI - MA mode for CCI - Calculation type - - - - - Returns an instance of the Modified Moving Average (MMA) indicator. - MMA comprises a sloping factor to help it overtake with the growing or declining value of the trading price of the currency. - - Period of Modified Moving Average - Sources prices for MA - - - - - Gets the Regression indicator - The Linear Regression Indicator plots the ending value of a Linear Regression Line for a specified number of bars; showing, statistically, where the price is expected to be. - - Moving average period - Type of the price - - - - - Gets the Channel (Price Channel) indicator. - The 'Channel' indicator is based on measurement of min and max prices for the definite number of periods. - - Period of price channel - - - - - - Gets the AFIRMA indicator - Autoregressive finite impulse response moving average. A digital filter accurately shows the price movement as powered with least square method to minimise time lag - - Moving average period - Type of the price - Afirma mode - with least squares method overlapping if true - - - - - Gets the RSI indicator. - Relative Strength Index (RSI) is a momentum oscillator that measures the speed and change of price movements. - - RSI Period - Price Type - RSI Mode (Simple or Exponential) - MA Mode for smooth data - MA period for smooth data - Calculation type - - - - - Returns an instance of the McGinley Dynamic indicator. - McGinley Dynamic avoids of most whipsaws and it rapidly moves up or down according to a quickly changing market. It needs no adjusting because it is dynamic and it adjusts itself. - - Period of exponential moving average - Dynamic tracking factor - Source price type - Calculation type - - - - - Gets the MAE (Moving Average Envelope) indicator. - The 'MAE' indicator demonstrates a range of the prices discrepancy from a Moving Average. - - Period of MA for envelopes. - Sources prices for MA. - Type of moving average. - Upband deviation in %. - Downband deviation in %. - Calculation type - - - - - Returns an instance of the Parabolic Time/Price System (SAR) indicator. - SAR indicator helps to define the direction of the prevailing trend and the moment to close positions opened during the reversal. - - Step of parabolic SAR system - Maximum value for the acceleration factor - - - - - - Gets the AO (Awesome Oscillator) indicator. - The 'AO' indicator determines market momentum. - - - - - - Gets the CMO (Chande Momentum Oscillator) indicator. - The CMO calculates the dividing of difference between the sum of all recent gains and the sum of all recent losses by the sum of all price movement over the period. - - Period of MA for envelopes. - Sources prices for MA. - - - - - - Returns an instance of the ZigZag indicator. - ZigZag is a trend following indicator that is used to predict when a given symbol's momentum is reversing. - - Percent Deviation - - - - - - Gets the Aroon indicator. - Reveals the beginning of a new trend and determines how strong it is - - Aroons period - - - - - - Returns an instance of the Standart Deviation (SD) indicator. - The SD shows the difference of the volatility value from the average one. - - Period of indicator - Sources prices for MA - Type of Moving Average - Calculation type - - - - - Gets the MACD (Moving Average Convergence/Divergence) indicator. - The MACD is a trend-following momentum indicator that shows the relationship between two moving averages of prices. - - Period of fast EMA. - Period of slow EMA. - Period of signal EMA. - Calculation type - - - - - Returns an instance of the Kairi Relative Index (KRI) indicator. - KRI calculates deviation of the current price from its simple moving average as a percent of the moving average. - - - - - - - - Returns an instance of the Bollinger Bands Flat (BBF) indicator. - The BBF provides the same data as BB, but drawn in separate field and easier to recognize whether price is in or out of the band. - - Period - Deviation - Sources prices for MA - Type of Moving Average - Calculation type - - - - - Gets the ROC (Rate of Change) indicator. - The ROC shows the speed at which price is changing. - - Period of momentum. - - - - - - Gets the Momentum indicator. - Momentum compares where the current price is in relation to where the price was in the past. - - Period for Momentum - Sources prices for Momentum - - - - - - Gets the %R Larry Williams. - Uses Stochastic to determine overbought and oversold levels. - - Period for Momentum - - - - - - Returns an instance of the Price Oscillator (PO) indicator. - PO calculates the variation between price moving averages. - - Period of MA1 - Period of MA2 - Sources prices for MA - Type of Moving Average - Calculation type - - - - - Gets the OsMA (Moving Average of Oscillator) indicator. - The OsMA reflects the difference between an oscillator (MACD) and its moving average (signal line). - - Period of fast EMA. - Period of slow EMA. - Period of signal EMA. - Calculation type - - - - - Gets On Balance Volume. - On Balance Volume (OBV) measures buying and selling pressure as a cumulative indicator that adds volume on up days and subtracts volume on down days. - - Sources prices for OBV - - - - - - Returns an instance of the Positive Volume Index (PVI) indicator. - The PVI value changes on the periods in which value of volume has increased in comparison with the previous period. - - - - - - - - Returns an instance of the Volume indicator. - Volume allows to confirm the strength of a trend or to suggest about it's weakness. - - - - - - - Gets the MFI(Money Flow Index) indicator. - The MFI(Money Flow Index) is an oscillator that uses both price and volume to measure buying and selling pressure. - - Period of MFI. - - - - - - Returns an instance of the Acceleration/Deceleration Oscillator (AC). - AC measures the acceleration and deceleration of the current momentum. - - - - - - - Gets the Average True Range (ATR) indicator. - The ATR measures of market volatility. - - - Period of Moving Average. - Type of Moving Average - Calculation type - - - - - Returns an instance of the Kaufman Adaptive Moving Average (KAMA) indicator. - KAMA is an exponential style average with a smoothing that varies according to recent data. - - Period - Fast factor - Slow factor - Sources prices for MA - - - - - - Returns an instance of the Qstick indicator. - The Qstick is a moving average that shows the difference between the prices at which an issue opens and closes. - - - - Calculation type - - - - - Get the Swing Index (SI) indicator. - The SI is used to confirm trend line breakouts on price charts. - - The divider. - - - - - - Get the True Strength Index (TSI) indicator. - - The TSI is a variation of the Relative Strength Indicator which uses a doubly-smoothed - EMA of price momentum to eliminate choppy price changes and spot trend changes. - - - First MA period. - Second MA period. - Calculation type - - - - - Returns an instance of the Average Directional Index (ADX) indicator. - The ADX determines the strength of a prevailing trend. - - Period - Type of Moving Average - Calculation type - - - - - Returns an instance of the Keltner Channel indicator. - Keltner Channels are volatility-based envelopes set above and below an exponential moving average. - - Period of MA for Keltner's Channel - Coefficient of channel's width - Sources prices for MA - Type of Moving Average - Calculation type - - - - - Returns an instance of the Percentage Price Oscillator (PPO). - Percentage Price Oscillator is a momentum indicator. Signal line is EMA of PPO. Formula: (FastEMA-SlowEMA)/SlowEMA. - - Fast EMA Period - Slow EMA Period - Signal EMA Period - Calculation type - - - - - Gets the Stochastic Slow. - Shows the location of the current close relative to the high/low range over a set number of periods (Slow). - - Period - Smoothing - Double smoothing - Moving type - Calculation type - - - - - Gets the Stochastic x Relative Strength Index. - StochRSI is an oscillator that measures the level of RSI relative to its range. - - Period - Smoothing - Double smoothing - - - - - - Gets the Ichimoku. - Enables to quickly discern and filter 'at a glance' the low-probability trading setups from those of higher probability. - - Tenkan Period - Kijun Period - Senkou Span B - - - - - - Gets the Directional Movement Index(DMI) indicator. - The DMI іdentifies whether there is a definable trend in the market. - - Period of Moving Average. - Type of Moving Average. - Calculation type - - - - - Gets the Alligator. - Three moving averages with different colors, periods and calculation methods. - - Type of Jaw Moving Average. - SourcePrice of Jaw Moving Average. - Period of Jaw Moving Average. - Shift of Jaw Moving Average. - Period of Moving Average. - Type of Moving Average. - Period of Moving Average. - Type of Moving Average. - Period of Moving Average. - Type of Moving Average. - Period of Moving Average. - Type of Moving Average. - - - - - - Base class for all indicators. - - - - - Short name of indicator - - - - - Access to current Symbol of indicator - - - - - Amount of items in internal buffers - - - - - Represent access to current used historical data. - - - - - Represent access indicator series - - - - - - - - - - Specified, whether indicator should use main or additional window on the chart - - - - - Specified, whether indicator should draw on chart background by default. - - - - - Specified, whether indicator should participate into price auto scale system. - - - - - Precision amount for formatting price (the count of digits after decimal point); By default = -1, which means to use precision from indicator's symbol - - - - - Indicator's settings - - - - - Represent access to the chart, that created indicator - - - - - Recalculate indicator - - - - - - - - - - - Sets the value of indicator into internal buffer - - Value - Index of indicator line - Offset value - - - - Gets the value of indicator from internal buffer - - Offset value - Index of indicator line - Offset start point - - - - - Set line break point. - - Offset value - Index of indicator line - Offset start point - - - - Remove line break point. - - Offset value - Index of indicator line - Offset start point - - - - Check if the point is a break point. - - Offset value - Index of indicator line - Offset start point - - - - Gets the price from historical data - - - - - - - - Get Bid price - - Offset value - - - - - Get Ask price - - Offset value - - - - - Get Last price - - Offset value - - - - - Get Open price - - Offset value - - - - - Get High price - - Offset value - - - - - Get Low price - - Offset value - - - - - Get Close price - - Offset value - - - - - Get Median price - - Offset value - - - - - Get Typical price - - Offset value - - - - - Get Weighted price - - Offset value - - - - - Get Volume - - Offset value - - - - - Get Volume in quoting asset - - Offset value - - - - - Get Ticks - - Offset value - - - - - Get Open interest - - Offset value - - - - - Get Funding rate - - Offset value - - - - - Get Time - - Offset value - - - - - Formatting price, using precision from assigned symbol or Digits value if specified - - Price value - - - - - Marks cloud begin between two line series with specific color - - First line series index - Second line series index - Cloud color - Offset - - - - Marks cloud end between two line series with specific color - - First line series index - Second line series index - Cloud color - Offset - - - - Using IndicatorLineMarker class you can mark by color or icon any point of your indicator's line. - - - - - Color of the marker - - - - - Icon that will be drawn above the indicator line - - - - - Icon that will be drawn beyond the indicator line - - - - - Constructor for IndicatorLineMarker - - - - - - - - Constructor for IndicatorLineMarker - - - - - Different type of icons for Indicator marker - - - - - Sets the bar number (from the data beginning) from which the drawing of the given indicator line must start; - - - - - Time shift of indicator's line - - - - - Redraws parts of indicator's line within the interval set by offset - - - - - Removes redrawn parts of indicator's line within the interval set by offset - - - - - - - - - - Fully clears markers from line - - - - - Use this attribute to mark input parameters of your script. You will see them in the settings screen on adding - - - - - Displayed name of input parameter - - - - - Sort index for input paramter - - - - - Minimal value for numeric input parameters - - - - - Maximal value for numeric input parameters - - - - - Increment value for numeric input parameters - - - - - Decimal palces for numeric input parameters - - - - - List of predefined values - - - - - Event occurred when write a new log - - - - - Get logs from the strategy for specified date range - - - - - - - - Write log message - - - - - - - The base class for strategies - - - - - Unique ID of the strategy - - - - - The current state of the strategy - - - - - Event occured when strategy write a new log - - - - - Event occured if any of strategy settings was changed - - - - - Run strategy - - - - - Stop strategy - - - - - Remove the strategy - - - - - Get current metrics from the strategy - - - - - - Get logs from the strategy for specified date range - - - - - - - - Write log message - - - - - - - Specifies constants that define which mouse button was pressed. - - - - - No mouse button was pressed. - - - - - The left mouse button was pressed. - - - - - The right mouse button was pressed. - - - - - The middle mouse button was pressed. - - - - - The first XButton was pressed. - - - - - The second XButton was pressed. - - - - - Gets or sets a value indicating whether the event was handled. - true to bypass the control's default handling; otherwise, false to also pass the event along to the default control handler. - - - - - Defines quote parameters for subscribtion - - - - - Symbol Id - - - - - Quote type - - - - - SubscribeQuotesParameters constructor - - - - - Id of the order - - - - - Gets or Sets the groups. - - - - - SettingItem cast type - - - - - SettingItem state - - - - - Specifies the DatePicker time format. - - - - - Date only - - - - - Date and time - - - - - Time only - - - - - Time with seconds - - - - - - - - - - Typecasts setting as TabControl item - - - - - Typecasts setting as GroupBox item - - - - - Typecasts setting as AccountLookup item - - - - - Typecasts setting as Button item - - - - - Typecasts setting as CheckBox item - - - - - Typecasts setting as CheckBox item - - - - - Typecasts setting as Color item - - - - - Typecasts setting as DateTimePicker item - - - - - Typecasts setting as NumericUpDown item - - - - - Typecasts setting as NumericUpDown item - - - - - Typecasts setting as AccountLookup item - - - - - Typecasts setting as Password item - - - - - Typecasts setting as Period item - - - - - Typecasts setting as ComboBox item - - - - - Typecasts setting as TextBox item - - - - - Typecasts setting as SymbolLookup item - - - - - Typecasts setting as TextBox item - - - - - Cancel all pending s - - the name of the initiator of the call (optional) - - - - Cancel all pending s by - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by and - - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by and - - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by , and - - - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by and - - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by , and - - - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by and - - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by , and - - - - - the name of the initiator of the call (optional) - - - - Cancel all pending s by - - - the name of the initiator of the call (optional) - - - - Close all s - - the name of the initiator of the call (optional) - List of - - - - Close all s by - - - the name of the initiator of the call (optional) - List of - - - - Close all s by - - - the name of the initiator of the call (optional) - List of - - - - Close all s by and - - - - the name of the initiator of the call (optional) - List of - - - - Close all s by - - - the name of the initiator of the call (optional) - List of - - - - Close all s by and - - - - the name of the initiator of the call (optional) - List of - - - - Close all positive s - - the name of the initiator of the call (optional) - List of - - - - Close all positive s by - - - the name of the initiator of the call (optional) - List of - - - - Close all negative s - - the name of the initiator of the call (optional) - List of - - - - Close all negative s by - - - the name of the initiator of the call (optional) - List of - - - - Reverse all s - - the name of the initiator of the call (optional) - List of - - - - Reverse all s by - - - the name of the initiator of the call (optional) - List of - - - - Reverse all s by - - - the name of the initiator of the call (optional) - List of - - - - Reverse by and - - - - the name of the initiator of the call (optional) - - - - - Cancel all s and close all s - - the name of the initiator of the call (optional) - - - - Cancel all s and close all s by - - - the name of the initiator of the call (optional) - - - - Cancel all s and close all s by and - - - - the name of the initiator of the call (optional) - - - - Modify SL to breakeven price - - - the name of the initiator of the call (optional) - - - - Modify SL to breakeven price with certain additional offset - - - offset in ticks - the name of the initiator of the call (optional) - - - - Modify SL to breakeven price - - - list of s that will be used for order placing - the name of the initiator of the call (optional) - - - - Adjust SL/TP for given - - - - the name of the initiator of the call (optional) - - - - Adjust stop loss for given - - - list of s - initial stop loss parameters (optional) - - the name of the initiator of the call (optional) - - - - - Adjust take profit for given - - - list of s - initial take profit parameters (optional) - - the name of the initiator of the call (optional) - - - - - The allowed result. - - - - - Gets the status. - - - - - Gets the reason. - - - - - Get the allowed result. - - An AllowedResult. - - - - Gets the not allowed result. - - The reason. - An AllowedResult. - - - - The action wrapper. - - - - - Gets or Sets the items. - - - - - Initializes a new instance of the class. - - - - - Froms the X element. - - The element. - The deserialization info. - - - - Tos the X element. - - A XElement. - - - - The aggressor flag calculator. - - - - - Initializes a new instance of the class. - - - - - Collect bid ask. - - The symbol. - The time ticks. - The bid. - The ask. - - - - Calculate aggressor flag. - - The symbol. - The time ticks. - The last. - An AggressorFlag. - - - - - - - - - Calculate aggressor flag. - - The previous bid. - The previous ask. - The last. - An AggressorFlag. - - - - The alert. - - - - - Gets or Sets the text. - - - - - Gets or Sets the symbol name. - - - - - Gets or Sets the connection name. - - - - - Gets or Sets the action on confirm. - - - - - Gets or Sets the name. - - - - - The alert data. - - - - - The name. - - - - - Gets or Sets the columns. - - - - - Gets or Sets the groups. - - - - - Gets or Sets the actions. - - - - - Gets or Sets a value indicating whether enabled. - - - - - Gets or Sets the table dictionary. - - - - - Gets or Sets a value indicating whether coloring is alert. - - - - - Gets or Sets a value indicating whether filter alert. - - - - - Gets or Sets a value indicating whether search alert. - - - - - Gets or Sets a value indicating whether to remove. - - - - - Gets or Sets the ID. - - - - - Gets or Sets the command sender. - - - - - Gets or Sets a value indicating whether need confirm trading. - - - - - Initializes a new instance of the class. - - - - - To the XML element. - - A XElement. - - - - From the XML element. - - The element. - The deserialization info. - - - - The arbitrage symbol. - - - - - Gets or Sets the index. - - - - - Gets the unique ID. - - - - - Gets the symbol id. - - - - - Gets or Sets a value indicating whether is selected. - - - - - Gets or Sets the symbol. - - - - - Gets or Sets the account. - - - - - Gets or Sets the commission. - - - - - Initializes a new instance of the class. - - - - - Compare to. - - The other. - An int. - - - - Froms the X element. - - The element. - The deserialization info. - - - - Tos the X element. - - A XElement. - - - - The action buffered processor. - - - - - The action buffered processor with priority. - - - - - Initializes a new instance of the class. - - The threads count. - - - - The buffered processor. - - - - - - Gets the state. - - - - - Gets the queue depth. - - - - - - - - - - - - - - - - - The subject. - - - - Wait all messages process. - - The external token. - - - - The buffered processor state. - - - - - - - - - - Агрегація, що буде використовуватись як дефолтна для "TimeFrameScreen". - Повинна відповідати агрегації чарта. - - - - - https://stackoverflow.com/questions/3060381/datetime-addmonths-adding-only-month-not-days - - Проблема: - (29 Feb).AddMonth(1) = 29 March - - - - - Defines advanced trading operation respond bundle - - - - - Respond operation state - - - - - Respond message - - - - - Respond value (optional) - - - - - Details of sub-operations - - - - - Get all available custom resources - - - - - Get custom resource stream by name - - - - - Check whether specified items was hidden by branding specification - - - - - - The assembly loader. - - - - - Load types. - - The folder name. - The target type. - The assembly name filter. - The search option. - If true, load in memory. - If true, allow loading references. - ]]> - - - - Quote type for subscribtion dictum - - - - - Level 1 quote - - - - - Level 2 quote - - - - - Last - - - - - Mark price - - - - - Trading operation state - - - - - Complex trading operation state - - - - - The aggressor flag calculator extensions. - - - - - Collect bid ask. - - The calculator. - The quote. - - - - Calculate aggressor flag. - - The calculator. - The last. - An AggressorFlag. - - - - Gets the price from historical data - - - - - - - - Get Bid price - - Offset value - - - - - Get Ask price - - Offset value - - - - - Get Last price - - Offset value - - - - - Get Open price - - Offset value - - - - - Get High price - - Offset value - - - - - Get Low price - - Offset value - - - - - Get Close price - - Offset value - - - - - Get Median price - - Offset value - - - - - Get Typical price - - Offset value - - - - - Get Weighted price - - Offset value - - - - - Get Volume - - Offset value - - - - - Get Volume in quoting asset - - Offset value - - - - - Get Ticks - - Offset value - - - - - Get Open interest - - Offset value - - - - - Get Funding rate - - Offset value - - - - - Get Time - - Offset value - - - - - Combines intersecting intervals - - - - - Get all settings or particular settings by provided hierarchy path - - - - - Write info log message - - - - - Write trading log message - - - - - Write error log message - - - - - Variable lifetime - - - - - The variable will be removed on exit - - - - - The variable will be written to file - - - - - Global variable entry - - - - - Variable name - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - List<GlobalVariable> global_List=new List<GlobalVariable>(); - - public override void Init() - { - if(GlobalVariablesManager.Count()>0) - { - global_List=GlobalVariablesManager.GetGlobalVariablesList(); - foreach (var el in global_List) - { - Print(el.Name); - Print(el.Value); - } - } - } - } - } - - - - - - - Variable value - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - List<GlobalVariable> global_List=new List<GlobalVariable>(); - public override void Init() - { - if(GlobalVariablesManager.Count()>0) - { - global_List=GlobalVariablesManager.GetGlobalVariablesList(); - foreach (var el in global_List) - { - Print(el.Name); - Print(el.Value); - } - } - } - } - } - - - - - - - Initializes new variable - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - List<GlobalVariable> global_List=new List<GlobalVariable>(); - public override void Init() - { - if(GlobalVariablesManager.Count()>0) - { - global_List=GlobalVariablesManager.GetGlobalVariablesList(); - foreach (var el in global_List) - { - //Simplified way to retrieve global variable value - el.GlobalVariable("new_global_variable_period", period) - - //However, to obtain certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. - } - } - } - } - } - - - - Variable name - Variable value - - - - Sets variable value to a global storage - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - GlobalVariablesManager(){ - base.ProjectName = "GlobalVariablesManager"; - base.Password=GetHashedPassword(ProjectName); - } - - [InputParameter("Period", 0, 1, 9999)] - public int period = 5; - - public override void OnQuote() - { - //Simplified way to store a global variable - - GlobalVariablesManager.SetValue("global_variable_period", period, VariableLifetime.SaveSession); - - //However, to indicate any variable belongs to certain indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. - - GlobalVariablesManager.SetValue("global_variable_period" +Symbols.Current.Name+period+Password, period, VariableLifetime.SaveSession); - } - } - } - - - - Variable name - New value - Variable lifetime - - - - Removes specified variable from global storage - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - public override void Init() - { - //Simplified way to remove a global variable - - GlobalVariablesManager.Remove("global_variable_period"); - - //However, to remove certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. - } - } - } - - - - Variable name - - - - Returns variables' count in global storage - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - public override void Init() - { - if(GlobalVariablesManager.Count()>0) - { - Print("Your session obtains "+GlobalVariablesManager.Count()+" global variables"); - } - } - } - } - - - - - - - - Removes all variables from global storage - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - public override void Init() - { - GlobalVariablesManager.RemoveAll(); - - if(GlobalVariablesManager.Count()==0) - { - Print("Your session does not have any global variables"); - } - } - } - } - - - - - - - Check if variable with specified name exists in global storage - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - public override void Init() - { - //Simplified way to check an existance of a global variable - - if(GlobalVariablesManager.Exists("global_variable_period")) - Print("Your session has this global variable"); - else - GlobalVariablesManager.SetValue("global_variable_period"); - - //However, to obtain certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. - } - } - } - - - - Variable name - - - - - Returns variable value by name - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - public override void Init() - { - //Simplified way to retrieve global variable value - - if(GlobalVariablesManager.Exists("global_variable_period")) - //Always perform a type casting before assigning any variable from global storage - period = (int)GlobalVariablesManager.GetValue("global_variable_period"); - - //However, to obtain certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. - } - } - } - - - - Variable name - Variable value - - - - Performs a variable assigning from a global storage if such name exists in a scope - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - public override void Init() - { - int new_period; - - //Simplified way to retrieve global variable value - - if(GlobalVariablesManager.TryGetValue("global_variable_period")) - Print("New variable is assigned from globals: " + new_period); - if(new_period==period) - Print("Matching, no need to re-assign globals: "); - else - GlobalVariablesManager.SetValue("global_variable_period", period, VariableLifetime.SaveSession); - - //However, to obtain certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. - } - } - } - - - - Variable name - Variable value - True if variable exists - - - - Saves all serializable variables to disk - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - Connection myConnection = Connection.CurrentConnection; - public override void OnQuote() - { - if(myConnection.Status==Disconnected) - GlobalVariablesManager.Flush(); - } - } - } - - - - - - - Returns all global variables as list - - - - - using System; - using System.Text; - using PTLRuntime.NETScript; - - namespace GlobalVariablesManager - { - public class GlobalVariablesManager : NETIndicator - { - List <GlobalVariable>global_List=new List<GlobalVariable>(); - public override void Init() - { - if(GlobalVariablesManager.Count()>0) - global_List=GlobalVariablesManager.GetGlobalVariablesList(); - - foreach (var el in global_List) - { - Print(el.Name); - } - } - } - } - - - - Collection of global variables - - - - Object is non-serializable - - - - - Object too big - - - - - The application logger event. - - - - - Current logging level - - - - - Sets logs with custom messag, logging level, connection name - - - - - Sets logs with exception and custom message (optional), logging level, connection name - - - - - Sets logs with objects inherited from ILoggable interface,logging level, connection name - - - - - Sets a log level in case of deep debug - - - - - Remove a log level if one has not needed already - - - - - Все параметры, которые могут использоваться при отправке email - - - - - Отправит сообщение по E-mail - - - - - - - - - - Чисто, як маркер для парсера xgettext.exe - - - - - - - Check, whether current translation equal to hidden - - - - - - - - - - - - - - - - - - - - - - - Allows to save Symbols into named lists - - - - - from: - http://www.codeguru.com/csharp/csharp/cs_date_time/timeroutines/article.php/c4207/C-SNTP-Client.htm - +++ добавлен диспозе, подправлен ToString(), убран лишний метод. - +++ ReceiveTimeoutбSendTimeout - - NTPClient is a C# class designed to connect to time servers on the Internet. - The implementation of the protocol is based on the RFC 2030. - - Public class members: - - LeapIndicator - Warns of an impending leap second to be inserted/deleted in the last - minute of the current day. (See the _LeapIndicator enum) - - VersionNumber - Version number of the protocol (3 or 4). - - Mode - Returns mode. (See the _Mode enum) - - Stratum - Stratum of the clock. (See the _Stratum enum) - - PollInterval - Maximum interval between successive messages. - - Precision - Precision of the clock. - - RootDelay - Round trip time to the primary reference source. - - RootDispersion - Nominal error relative to the primary reference source. - - ReferenceTimestamp - The time at which the clock was last set or corrected. - - OriginateTimestamp - The time at which the request departed the client for the server. - - ReceiveTimestamp - The time at which the request arrived at the server. - - Transmit Timestamp - The time at which the reply departed the server for client. - - RoundTripDelay - The time between the departure of request and arrival of reply. - - LocalClockOffset - The offset of the local clock relative to the primary reference - source. - - Initialize - Sets up data structure and prepares for connection. - - Connect - Connects to the time server and populates the data structure. - - IsResponseValid - Returns true if received data is valid and if comes from - a NTP-compliant time server. - - ToString - Returns a string representation of the object. - - ----------------------------------------------------------------------------- - Structure of the standard NTP header (as described in RFC 2030) - 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |LI | VN |Mode | Stratum | Poll | Precision | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Root Delay | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Root Dispersion | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Reference Identifier | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | Reference Timestamp (64) | - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | Originate Timestamp (64) | - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | Receive Timestamp (64) | - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | Transmit Timestamp (64) | - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Key Identifier (optional) (32) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - | | - | Message Digest (optional) (128) | - | | - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - ----------------------------------------------------------------------------- - - NTP Timestamp Format (as described in RFC 2030) - 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Seconds | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Seconds Fraction (0-padded) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - - - - - Connect to the time server - - - - - Defines a trading operation respond bundle - - - - - Respond operation state - - - - - Respond message - - - - - Respond order id - - - - - Provides VA calculation parameters - - - - - Provides VA calculation request per - - - - - Summary calculated Volume info - - - - - Volume info for each price - - - - - Fire in case of price level was added or existing was updated - - - - - Represent item with Volume Analysis calculation results - - - - - Volume Analysis calculations - - - - - Calculate volume profile for requested time range - - - - - Calculate volume profile for each bar in History Data - - - - - Calculate volume profile for requested time range - - - - - Calculate volume profile for each bar in History Data - - - - - - - - - - - - - - + + + + TradingPlatform.BusinessLayer + + + + + Access to the chart panel + + + + + Chart panel unique ID + + + + + Collection of chart windows + + + + + Current X scale value - width of the bar in pixels + + + + + Provides time zone of current chart. + + + + + Provides account of current chart. + + + + + Provides custom sessions of current chart. + + + + + Current tick size of the chart + + + + + Main window of the chart + + + + + Current right offset value + + + + + Collection of chart drawingsCollection + + + + + Force chart refreshing + + + + + The MouseDown event occurs when the mouse button is pressed down + + + + + The MouseUp event occurs when the mouse button is released + + + + + The MouseClick event occurs when the mouse button is clicked + + + + + The MouseMove event occurs when the mouse moving over the chart + + + + + The MouseDown event occurs when the user scrolling mouse wheel + + + + + The MouseDown event occurs when the mouse enter the chart + + + + + The MouseDown event occurs when the mouse leave the chart + + + + + The SettingsChanged event occurs when any settings were changed + + + + + The AccountChanged event occurs when the account was changed + + + + + Access to the chart drawingsCollection collection + + + + + Add chart drawing to the collection + + + + + + Remove specified chart drawing from collection + + + + + + Get chart drawing by ID + + + + + + + Get all chart drawingsCollection assigned to specified symbol + + + + + + The Added events occured, when new chart drawing was added to collection + + + + + The Moved events occured, when chart drawing was moved + + + + + The Removed events occured, when chart drawing was removed from the collection + + + + + The SelectionChanged events occured, when selected chart drawing was changed + + + + + Access to the particular window from chart panel + + + + + Client rectangle of the chart window + + + + + Determines, whether this window is the main window of the chart + + + + + Chart window number + + + + + Special object, allows you to convert values from x/y scale to Time/Price and back + + + + + + + + + + Converter between x/y and Time/Price scales + + + + + Get the DateTime value that is corresponding to specified x coordinate + + + + + + + Get the Price value that is corresponding to specified y coordinate + + + + + + + Get the X coordinate that is corresponding to specified DateTime value + + + + + + + Get the Y coordinate that is corresponding to specified price value + + + + + + + Get the bar index that is corresponding to specified DateTime value + + + + + + + Access to the chart drawing + + + + + Determines, the way how chart drawing was created: manually or programmatically + + + + + Determines, the availability of drawing - only current chart or all charts with same symbol + + + + + The unique ID of the chart drawing + + + + + Determines, whether chart drawing draws above or below the main chart + + + + + Determines, state of the chart drawing: Locked or Unlocked + + + + + Get time and price of the particular point of the chart drawing + + + + + Set time and price value for particular point of the chart drawing + + + + + Gets s list + + + + + Gets symbol types list + + + + + Gets s list + + + + + Gets s list + + + + + Gets Exchanges list + + + + + Gets Orders list + + + + + Gets Order Types list + + + + + Gets Positions list + + + + + Gets Closed Positions list + + + + + Gets Corporate Actions list + + + + + Gets Report Types list + + + + + Gets s list + + + + + Vendor connection respond bundle + + + + + The connection status + + + + + The respond message + + + + + Ping state dictum + + + + + Ping signals connected + + + + + Ping signals disconnected + + + + + The asset formatting description. + + + + + Initializes a new instance of the class. + + The asset id. + The value. + If true, add currency name. + + + + Mediates a history meta data with available data types and intervals on vendor side + + + + + Asset id bearer + + + + + Asset name bearer + + + + + Asset description + + + + + Asset id bearer + + + + + Performs a binding with broker or data provider + + + + + Specifies any operation before breaking a connection with broker or data provider + + + + + Called when platform finished retreiving all required informations from vendor during connecting + + + + + Ping processing before its visualization in the terminal + + + + + Retrieves an information about available accounts. Yon need to specify at least one account + + + + + Confirms allowed nonfixed list by vendor + + + + + Retrieves a collection of available exchange markets from vendor + + + + + Retrieves information about available assets. + + + + + Retrieves information about available symbols. + + + + + Gets an available symbols types from vendor + + + + + Derives a non fixed symbol from vendor + + + + + Derives a non-fixed list of symbols from vendor + + + + + Retrieves allowed/supported order types + + + + + Retrieves an information about opened orders at the time of connection. + + + + + Retrieves an information about positions at the time of connection + + + + + Gets trades history from server for requested time range + + + + + Subscribing to quote data: Level1/Level2/Trade + + + + + Unsubscribing from quote data: Level1/Level2/Trade + + + + + Gets a list of supported historical intervals and data types by vendor + + + + + Prepare and sending order placing request to broker + + + + + Sending order modification request to broker + + + + + Sending order cancellation request to broker + + + + + Sending position closing request to broker + + + + + Gets information about available reports from vendor + + + + + Called when platform need to generate particular report + + + + + Ping respond bundle + + + + + Ping time + + + + + Time spent for single request + + + + + Ping state + + + + + Performs a binding with broker or data provider + + + + + Specifies any operation before breaking a connection with broker or data provider + + + + + Called when platform finished retreiving all required informations from vendor during connecting + + + + + Ping processing before its visualization in the terminal + + + + + Retrieves an information about available accounts. Yon need to specify at least one account + + + + + Retrieves a collection of available exchange markets from vendor + + + + + Retrieves information about available assets. + + + + + Retrieves information about available symbols. + + + + + Gets an available symbols types from vendor + + + + + Derives a non fixed symbol from vendor + + + + + Derives a non-fixed list of symbols from vendor + + + + + Retrieves an information about opened orders at the time of connection. + + + + + Retrieves an information about positions at the time of connection + + + + + Retrieves an information about closed positions at the time of connection + + + + + Subscribing to quote data: Level1/Level2/Trade + + + + + Unsubscribing from quote data: Level1/Level2/Trade + + + + + Gets a list of supported historical intervals and data types by vendor + + + + + Prepare and sending order placing request to broker + + + + + Sending order modification request to broker + + + + + Sending order cancellation request to broker + + + + + Sending position closing request to broker + + + + + Gets information about available reports from vendor + + + + + Called when platform need to generate particular report + + + + + Retrieves allowed/supported order types + + + + + Confirms allowed non fixed list by vendor + + + + + Gets a settings list from a vendor selection of the setup window + + + + + The ID of the order group. This group created when trades done by the MAM account. + + + + + Total quantity of the order + + + + + Gets OrderType + + + + + Gets order price value + + + + + Gets order trigger price value + + + + + Gets order trailing offset value + + + + + Gets orders current status + + + + + Gets orders last update time + + + + + Gets Position Id. + + + + + Gets StopLoss holder for given order + + + + + Gets TakeProfit holder for given order + + + + + Orders Type Id. It is used for the orders type comparing. + + + + + Gets order TIF(Time-In-Force) type + + + + + Gets orders expiration time + + + + + Remaining quantity of the order + + + + + Filled quantity of the order + + + + + Gets open order original status + + + + + Will be triggered on each invocation + + + + + + + + + + + + + + + Get VWAP value + + + + + + + + + + + + + + + + + + + + + + + + + Снапшот для эмулятора + + + + + Текущая версия для сериализации. Если что-то меняем и нужно делать поддержку старого формата + можно использовать версию. + + + + + Version 1.1: изменилась схема работы с коннектами: стали доступны кастомные коннекты + + + + + Defines 'Volume Analysis' calculation result item + + + + + Contains all user's account information + + + + + Gets account unique code. + + + + + Obtaining account name. + + + + + Gets base currency of account. Account CCY is always equal to the server CCY in AlgoStudio + + + + + Gets current balance of the account. + + + + + Gets additional account information + + + + + Will be triggered on each account information updating + + + + + Gets Account name + + + + + + Creates a business object info with an Account data which can be used for the restoring/serialization process. + + + + + + The account operation. + + + + + Gets the name. + + + + + Gets the button text. + + + + + Gets the settings. + + + + + + + The settings. + + + + Builds the confirmation. + + The settings. + A string. + + + + + + The settings. + + + + Compare to. + + The obj. + An int. + + + + The additional info collection. + + + + + Gets the count. + + + + + Gets the items. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + The items. + + + + Try get item. + + The api key. + The item. + A bool. + + + + Gets the enumerator. + + ]]> + + + + Tos the string. + + A string. + + + + The comparing type. + + + + + The additional info item formating type. + + + + + The additional info item. + + + + + ключ для апи, обязательно для заполнения и должен быть уникальным + + + + + Gets or Sets the group info. + + + + + Gets or Sets the sort index. + + + + + Gets or Sets the name key. + + + + + Gets or Sets the tool tip key. + + + + + Gets or Sets the data type. + + + + + Gets or Sets the value. + + + + + Gets or Sets a value indicating whether hidden. + + + + + Gets or Sets the formating type. + + + + + используется в связке с AccountAdditionalInfoItemFormatingType.CustomAsset для форматирования в заданном ассете + + + + + Gets or Sets a value indicating whether visible. + + + + + Gets or Sets a value indicating whether is link. + + + + + Gets or Sets the editing info. + + + + + Gets or Sets the formatting description. + + + + + Initializes a new instance of the class. + + + + + + + The item. + + + + + + An object. + + + + To the XML element. + + A XElement. + + + + From the XML element. + + The element. + The deserialization info. + + + + To the string. + + A string. + + + + Defines asset entity + + + + + Asset id bearer + + + + + Asset name bearer + + + + + Asset description + + + + + Defines a number precision of the change value + + + + + Gets precision value + + + + + Gets asset ISO 4217 code + + + + + Creates an Asset instance + + given connection Id + + + + Formats price into precision normalized string + + + + + + + Formats price into concatenated string which contains the precision normalized value and Asset's name + + + + + + + Gets Asset name + + + + + + Uses comparison by Assets names + + + + + + + Unique ID during active session. Don't use for serialization + + + + + Договориль использовать множественное число для типа инструмента + + + + + Завели для индийской интеграции + + + + + Represents information about corporate action. + + + + + Get the date and time when trade was executed + + + + + Will be triggered on corporate action updating + + + + + Base class for all Custom symbols: Synthetics, Custom Formulas, etc. + + + + + alexb: нам достаточно подставлять правильный HistoricalData - он сделает всю работу + + + + + Расчёт Bid/Ask/BidSize/AskSize для синтетика + + + + + Расчёт Last/LastSize для синтетика + + + + + Расчёт Level2 для синтетика + + + + + Расчёт DayBar для синтетика + + + + + Расчёт бара для синтетика + + + + + Increment current position + + + + + Will be triggered on each invocation + + + + + Applies an position modificaion or position changing by accepting a message. + + + + + + Contains all information which belong to the given exchange + + + + + Gets Exchange Id + + + + + Gets Exchange name + + + + + Used for the Exchanges comparing + + + + + Compares Exchnges in order by next - SortIndex, Exchange name, Connection Id, Exchange Id. + + + + + + + Provides possibility to group and sort symbols for each connection + + + + + Gets group Id + + + + + Gets group name + + + + + Gets sort index for comparing process + + + + + Represents trading information about pending order + + + + + The ID of the order group. This group created when trades done by the MAM account. + + + + + Gets Position Id. + + + + + Total quantity of the order + + + + + Filled quantity of the order + + + + + Remaining quantity of the order + + + + + Orders Type Id. It is used for the orders type comparing. + + + + + Gets OrderType + + + + + Gets order price value + + + + + Gets order trigger price value + + + + + Gets order trailing offset value + + + + + Gets orders current status + + + + + Gets open order original status + + + + + Gets order TIF(Time-In-Force) type + + + + + Gets orders expiration time + + + + + Gets orders last update time + + + + + Gets StopLoss holder for given order + + + + + Gets TakeProfit holder for given order + + + + + Will be triggered on each invocation + + + + + Creates Order instance + + + + + + Applies an order modificaion or order changing by accepting a message. + + + + + + Cancels pending order + + + + + + Represents an wrapper + + + + + Represents trading information about related position + + + + + Will be triggered on each and invocation + + + + + Gets position quantity value + + + + + Gets position open order price + + + + + Gets position openning time + + + + + Gets Profit/loss (without swaps or commissions) all calculated based on the current broker's price. For open position it shows the profit/loss you would make if you close the position at the current price. If position closed, this parameter show profit/loss what trader have after closing this position. + + + + + Gets Profit/loss calculated based on the current broker's price. For open position it shows the profit/loss you would make if you close the position at the current price. If position closed, this parameter show profit/loss what trader have after closing this position. + + + + + Gets fee amount for the position. + + + + + Gets PnL swaps + + + + + The market price obtainable from your broker. + + + + + Closes position if quantity is not specified else - uses partial closing operation. + + + + + + + Gets StopLoss order which belongs to the position + + + + + Gets TakeProfit order which belongs to the position + + + + + Returns ticks amount between open and current price. + + + + + Represents a report grid which can hold system reports based on s and s content. + + + + + Columns collection + + + + + Rows collection + + + + + Initializes report grid + + + + + Adds coloring column to the report + + + + + Adds default(non-colored) column to the report + + + + + Exposes report cell decoration + + + + + Cell label + + + + + Cell value + + + + + Exposes report column decoration + + + + + Header of the column + + + + + Column value type + + + + + Exposes report row decoration + + + + + A collection of cells + + + + + Initializes cells in the row + + + + + Adds cells to the row by label and value + + + + + Adds cells to the row by value only + + + + + Defines report request parameters from which can be used in + + + + + Gets report Id + + + + + Gets report Name + + + + + report settings + + + + + Represents a permisions checking tool which use next priority order , , and + + + + + Represent access to symbol information and properties. + + + + + Gets symbol Id + + + + + Gets symbol name + + + + + Gets symbol description + + + + + Gets symbol type + + + + + Gets symbol base Asset + + + + + Gets symbol counter Asset + + + + + Gets Exchange of current symbol + + + + + Gets Exchange id of current symbol + + + + + Returns delay with which quote come in platform. + + + + + Gets symbol additional info + + + + + The highest trade allowed + + + + + The lowest trade allowed + + + + + Gets symbol NettingType + + + + + Gets SymbolGroup + + + + + Gets current SymbolQuotingType + + + + + Amount of base asset for one lot. + + + + + Stores list of symbol ticksizes + + + + + Cached value of TickSize (if 1 level) + + + + + Step of the lot changes + + + + + Step of the notional value changes + + + + + Gets derivative expiration date + + + + + Gets derivative last trading date + + + + + Gets derivative maturity date + + + + + Gets derivative strike price + + + + + Gets derivative option style + + + + + Gets derivative option type + + + + + Gets derivative option serie + + + + + Gets derivative underlier name + + + + + Gets derivative underlier symbol + + + + + Gets derivative underlier symbol id + + + + + Gets Ask price + + + + + Gets Ask size + + + + + Gets Bid price + + + + + Gets Bid size + + + + + Gets quote time + + + + + Gets last price + + + + + Gets last size + + + + + Gets last time + + + + + Gets mark price + + + + + Gets mark size + + + + + Gets open price + + + + + Gets previous close price + + + + + Gets high price + + + + + Gets low price + + + + + Gets volume value + + + + + Gets quote asset volume value + + + + + Gets PrevSettlement value + + + + + Gets ticks amount + + + + + Gets trades amount + + + + + Gets Level2 data + + + + + Gets spread value between Bid and Ask + + + + + Gets percentage value + + + + + Gets change value between Bid/Last and Close price + + + + + Gets percentage value + + + + + Default history type + + + + + List of all available history types + + + + + Gets SymbolVolumeType + + + + + List of all available history types + + + + + Will be triggered when new Level1 quote is comming + + + + + Will be triggered when new Level2 quote is comming + + + + + Will be triggered when new trade quote is comming + + + + + Will be triggered when new correctional quote is comming from the vendor. + + + + + Will be triggered when symbol updated. + + + + + Gets real time calculation setting + + + + + Gets real time calculation setting + + + + + Gets real time calculation setting + + + + + Gets real time calculation setting + + + + + Gets historical data according to period and other parameters + + + + + + + + + Gets historical data according to period and other parameters + + + + + + + + + + Gets historical data according to aggregation and other parameters + + + + + + + + + + Gets historical data according to given history request + + + + + + + Gets historical ticks data according to given parameters + + + + + + + + + Returns rounded to price + + + + + Calculates new price which equal to given price shifted by a number of given ticks + + + + + + + + Calculates ticks between two prices + + + + + + + + Gets cached symbol tick size or retrives it from the list + + + + + + + Gets symbol tick cost retrived from the list by price + + + + + + + Returns VariableTick if it can be retrived from list by price or null + + + + + + + Gets cached tick size if it available, else tries to obtain with Last, Bid, Ask, first element of list otherwise - + + + + + Formats price value to the appropriative string with a counting on tick precision. + + + + + + + Formats price value to the appropriative string with a counting on max tick precision. + + + + + + + Returns string with formatted ticks value + + + + + + + + Comparing by value + + + + + Gets symbol orders types list which are allowed. + + + + + + + Process order placing with given request parameters + + + + + + + An symbol has possibility to obtain the default account + + + + + Represents information about trade. + + + + + Gets the unique identifier of the order initiating the trade. + + + + + Gets a unique identifier of the position, which is related to this trade. + + + + + Get the price where trade was executed + + + + + Get the trade quantity + + + + + Get the date and time when trade was executed + + + + + Get the trade Gross P&L + + + + + Get the trade Net P&L + + + + + Get the fee value that was charged for this trade + + + + + Get the trade order type + + + + + Will be triggered on trade updating + + + + + The symbols list manager. + + + + + Gets the count. + + + + + Gets or Sets the settings. + + + + + Represent access to level2 data. + + + + + Gets Level2 Asks list + + + + + Gets Level2 Bids list + + + + + Gets current Level2 data + + Parameters of DepthOfMarket + + + + + Gets current Level2 data + + Parameters of request for Leve2Item collection + + + + + Leve2 data. Contains Bids and Ask collections + + + + + Represent parameters of DepthOfMarket + + + + + Represent parameters of request for Leve2Item collection + + + + + Aggregation method + + + + + Required amount of level2 + + + + + Use custom tick size + + + + + Calculate cumulative size + + + + + Aggregation method + + + + + Represent access to level2 item. + + + + + Cumulative size + + + + + Imbalance Percent + + + + + Price + + + + + Size + + + + + Time + + + + + MMID + + + + + проверка на вхождение + + + + + Represents information about connection and provides an access to the current trading information(Symbols, Orders, Position, Accounts etc.). + + + + + Provides access to all business objects which are belong to this connection + + + + + Gets connection Id + + + + + Gets connection Name + + + + + Gets connection's vendor name + + + + + Contains list of connection settings. Will be reused on each population time. + + + + + Gets connection's state (Connected/Connecting/Fail etc.) + + + + + Defines connection type + + + + + Will be triggered when changed. + + + + + Will be triggered when changed. + + + + + Represents connection ping time + + + + + Messages count that one is waited to process + + + + + Gets a matched available metadata info with the vendor's side + + + + + Establishes a connection to a specified vendor + + + + + + Closes a connection. + + + + + Только для использования в ConnectionCache + + + + + alexb: "Умный" поиск - разрешаем указывать слова в любом порядке + + + + + Generates a report with given requested parameters + + specifies Report's type + + + + + Gets connection's s list + + + + + Gets connection's symbol types list which are allowed in this connection + + + + + Gets connection's s list + + + + + Gets connection's s list + + + + + Gets all Exchanges which are allowed in this connection + + + + + Gets all Orders which are belong to this connection + + + + + Gets all Order Types which are available on this connection + + + + + Gets all Positions which are belong to this connection + + + + + Gets all Closed Positions which are belong to this connection + + + + + Gets all Corporate actions which are belong to this connection + + + + + Gets all Report Types which are available on this connection + + + + + Gets all Account operations which are available on this connection + + + + + Gets all Tradins signals which are available on this connection + + + + + Server connection status + + + + + Specifies connection using type. + + + + + Specifies how connection was created: by default or by user + + + + + Represents all needed parameters for the connection constructing process. + + + + + Gets a user friendly name of the connection + + + + + Gets connection group + + + + + Gets vendor's name + + + + + Gets connection Id + + + + + Favorites one will be displayed in Control center toolbar + + + + + Gets ConnectionState + + + + + Gets vendor's settings + + + + + Specifies how connection was created: by default or by user + + + + + realization + + + + + Creates connection info instance + + + + + + Creates connection info instance. + + + + + + + + + дефолтовый период пигования + + + + + Таймер, по которому проходит пинг + + + + + Запускаем таймер + + + + + Останавливаем таймер + + + + + Коннекты, которые нужно подключить + + + + + Сколько ждем до следующей попытки + + + + + The main entry point in the API. Core keeps access to all business logic entities and their properties: + connections, accounts, symbols, positions, orders, etc. Some of them can be reached through using managers or directly via specified collections. + You can always access the Core object via static Core.Instance property. + + + + + Gets a singleton instance of . API entry point + + + + + Gets an access to all created connections and manages them + + + + + Gets an access to the system logging mechanism + + + + + Obtains licence rules for the current user + + + + + Gets an access to all available trading data vendors and creates them + + + + + Gets an access to the all available aggregation types + + + + + Access to Volume Analysis calculations + + + + + Gets an access to the all available indicators and creates them + + + + + Gets an access to the all available trading strategies and manages them + + + + + Gets a permissions checking mechanism + + + + + Gets a time based conversion and synchronization mechanism + + + + + Gets SMTP mail service for sending emails + + + + + Represents current trading status + + + + + Will be triggered when changed + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections + + + + + Gets all available s from open connections. Otherwise returns empty list + > + + + + Gets all available s from open connections. Otherwise returns empty list + > + + + + Starts a initialization process which initializes given Managers, and Utils. And provides a subscribing on events. + + + + + Disposes all previously initialized modules and unsubscribe from events + + + + + Will be triggered when new added to the core + + + + + Gets an instance of exist Account or creates a new one with given info parameter + + + + + + + Will be triggered when new added to the core + + + + + Returns all s from open connections which satisfy given request parameters, otherwise returns empty list + + + + + + + Retrieves any by given request parameters. Otherwise returns null + + + Must be specified if open connections total is more than one. Will search only in Synthetic symbols list if id is equal to + + + + + + Gets an instance of exist symbol or creates a new one with given info parameter + + + + + + + Will be triggered when new placed + + + + + Will be triggered when canceled + + + + + Gets instance by given Id string. Otherwise returns null + + + Must be specified if open connections total is more than one + + + + + Gets instance by given Id string. Otherwise returns null + + + Must be specified if open connections total is more than one + + + + + Will be triggered when new opened + + + + + Will be triggered when closed + + + + + Gets instance by given Id string. Otherwise returns null + + + Must be specified if open connections total is more than one + + + + + Gets Profit'n'Loss with given request parameters from open connection. Otherwise returns null + + + + + + + Will be triggered when new added + + + + + Will be triggered when removed + + + + + Will be triggered when new occured + + + + + Gets collection of by given parameters + + + + + + + + Gets collection of by given parameters and callback + + + + + + + + + Will be triggered when new occured + + + + + Will be triggered when new added + + + + + Gets collection of by given parameters + + + + + + + + Places with given request parameters + + + + + + + Places multiple s with given request parameters + + + + + + + + Modifies by given request parameters + + + + + + + Closes with given request parameters + + + + + + + Cancels with given request parameters + + + + + + + Returns with given request parameters from open connection + + + + + + + Will be triggered when new received + + + + + Sends custom request if connection with given Id is open + + + + + + + Subscribe on custom messages + + custom message handler + custom messages Id + + + + Unsubscribe from custom messages + + custom message handler + custom messages Id + + + + Gets all previously configured s + + + + + Gets an access to s and manages them + + + + + Adds if given parameters are valid + + + + + + + Replaces exist list by new list in if given parameters are valid + + + + + + + Removes with a given name + + + + + + Replaces list name by new given name in if given parameters are valid + + + + + + + Represent all available Historical Symbols + + + + + Will be triggered when custom symbol added + + + + + Will be triggered when custom symbol removed + + + + + Will be triggered when custom symbol updated + + + + + Represent all available Synthetic items + + + + + Adds given Synthetic to the list if it does not contain + + + + + + Removes given Synthetic from the list if it exists + + + + + + Will be triggered when created/chenged/removed + + + + + Override to change string representation of aggregation + + + + + Represent access to historical data information and indicators control. + + + + + Gets HistoricalData symbol + + + + + Gets HistoricalData aggregation + + + + + Gets HistoricalData left time boundary + + + + + Gets HistoricalData right time boundary + + + + + Gets HistoricalData items amount + + + + + Retrieves HistoricalData item by indexing offset and direction to find. + + + + + + + + Will be triggered when new historical item created + + + + + Will be triggered when current historical item changed or updated + + + + + Gets access to built-in indicators + + + + + Reloads entire HistoricalData + + + + + Gets index by time with counting on search direction + + + + + Gets array of attached indicators + + + + + Creates indicator by it's name and if it successfully created adds it to the HistoricalData + + + + + + + + Adds indicator to the HistoricalData + + + + + + Removes indicator from the HistoricalData + + + + + + Will be triggered when volume analysis of current historical item changed or updated + + + + + Period that can be used as a basis for history aggregations + + + + + Represents historical data bar item + + + + + Gets bar's right time border + + + + + Defines bar's ticks count + + + + + Defines Open price + + + + + Defines High price + + + + + Defines Low price + + + + + Defines Close price + + + + + Gets Median (High+Low)/2 price + + + + + Gets Typical (High+Low+Close)/3 price + + + + + Gets Weighted (High+Low+Close+Close)/4 price + + + + + Defines ticks amount + + + + + Defines volume value + + + + + Gets price by indexing + + + + + + + Creates HistoryItemBar instance with default OHLC price = + + + + + Comparing by , OHLC, + + + + + + + Creates HistoryItemLast instance + + + + + Represents historical data trade item + + + + + Defines price value + + + + + Defines volume value + + + + + Defines trade operation side as aggressor flag + + + + + Gets price by indexing + + + + + + + Creates HistoryItemLast instance + + + + + Comparing by , OHLC, + + + + + + + Creates HistoryItemLast instance + + + + + Gets price by indexing + + + + + + + Creates HistoryItemLast instance + + + + + Represents historical data tick item + + + + + Defines Bid price + + + + + Defines Bid size + + + + + Defines Ask price + + + + + Defines Ask size + + + + + Gets price by indexing + + + + + + + Creates HistoryItemBar instance with default Ask/AskSize/Bid/BidSize = + + + + + Comparing by , OHLC, , , , + + + + + + + Resolves a history request parameters per symbol + + + + + Represents mechanism for supporting predefined and custom periods + + + + + Gets period multiplier + + + + + Gets base period type + + + + + Gets ticks value as an result of base period multiplicated by + + + + + Creates Period instance with greater than 0 + + + + + + + True if their base paeriods and are respectively equal. + + + + + + + + True if any of their base paeriods and are respectively not equal. + + + + + + + + True if their ticks values are satisfying initial condition + + + + + + + + True if their ticks values are satisfying initial condition + + + + + + + + True if their ticks values are satisfying initial condition + + + + + + + + True if their ticks values are satisfying initial condition + + + + + + + + True if their base paeriods and are respectively equal. + + + + + + + Formats given value to a specific user friendly string + + + + + + + Returns value in ticks according to base period type + + + + + + + Returns shorted string according to base period type + + + + + + + Converts time gap into dates range + + + + + + + Compares by + + + + + + + Serialize into object + + + + + + Deserialize from + + + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Predefined period + + + + + Represent access to DayBar quote, which contains summary information about instrument prices. + + + + + High price + + + + + Open price + + + + + Low price + + + + + Previous Close price + + + + + Ticks value + + + + + Volume value + + + + + Volume value + + + + + Previous settlement price + + + + + Bid price + + + + + Bid size + + + + + Ask price + + + + + Ask size + + + + + Last price + + + + + Last size + + + + + Trades value + + + + + Change value + + + + + Change value in percentage + + + + + + + + + + Represent access to DOM2 quote, which contains Bids and Asks. + + + + + Collection of Asks quotes + + + + + Collection of Bids quotes + + + + + Represent access to trade information. + + + + + Price at which trade occured + + + + + Size of the trade + + + + + Information about operation side of the trade + + + + + Shows the direction of price movement, comparing to previous value. + + + + + Represent access to Level2 quote. + + + + + Price type of Level2 quote: Bid or Ask + + + + + Price of Level2 quote + + + + + Size of Level2 quote + + + + + Unique ID of Level2 quote + + + + + Shows, whether Level2 quote is using only for removing from depth + + + + + Broker identifier that send level2 quote + + + + + specifies the implied quantity associated with the price for the quote. Subtracting this amount from the Size yields the outright quantity for the price level. A value of zero indicates that the implied size is not available/defined or that it is actually zero. + + + + + Time of the quote + + + + + Represent access to quote information. + + + + + Bid price + + + + + Bid size + + + + + Ask price + + + + + Ask size + + + + + Shows the direction of bid price movement, comparing to previous value. + + + + + Shows the direction of ask price movement, comparing to previous value. + + + + + Constants for licence keys + + + + + User licences info store + + + + + Current state of connection to licence server + + + + + Current connected user info + + + + + All active licences that user have + + + + + Check that user have licence + + + + + The loopback http listener without kestrel. + + + + + Initializes a new instance of the class. + + The port. + + + + Dispose + + + + + Wait for callback asynchronously. + + The token. + ]]> + + + + Specifies the reason of price data updating. + + + + + Indicates the processing of new historical bar. + + + + + Indicates the coming of new real-time tick. + + + + + Indicates a start of new real-time bar. + + + + + Specifies the type of events when indicator should recalculate itself. + + + + + Specifies the style of indicator line. + + + + + Moving average mode + + + + + Simple Moving Average + + + + + Exponential Moving Average + + + + + Smoothed Moving Average + + + + + Linearly Weighted Moving Average + + + + + + + + + + Displays HV schedule and percentile value. + + + + + Displays the percentile schedule only. + + + + + Returns an instance of the Exponential Moving Average (EMA) indicator. + EMA provides a weighted price calculation for the last N periods. + + Period of Exponential Moving Average + Sources prices for MA + Calculation type + + + + Gets the Linearly Weighted Moving Average + Linear Weighted Moving Average makes the most recent bar more important unlike SMA. + + Moving average period + Type of the price + + + + + Gets the SMA(Simple Moving Average) indicator. + The 'SMA' indicator provides an average price for the last N periods. + + Period of simple moving average. + Sources prices for MA. + + + + + Returns an instance of the Smoothed Moving Average (SMMA) indicator. + SMMA indicator provides a smoothed average price for the last N periods. + + Moving average period + Type of the price + Calculation type + + + + + Gets the PPMA(Pivot Point Moving Average) indicator. + The 'PPMA' indicator uses the pivot point calculation as the input a simple moving average. + + Period of PPMA indicator + + + + + + Gets the MAS3 (3MASignal) indicator. + The 'MAS3' indicator offers buy and sell signals according to intersections of three moving averages. + + Short moving average period. + Middle moving average period. + Long moving average period. + The count of bars. The trend will be determined on this interval. + + + + + + Gets the specific MA indicator, according to selected 'MaMode'. + + Period of moving average. + Type of price. + MA mode. + Calculation type + + + + + + Gets the BB(Bollinger Bands) indicator. + The 'BB' indicator provides a relative definition of high and low based on standard deviation and a simple moving average. + + Period of MA for envelopes. + Value of confidence interval. + Sources prices for MA. + Type of moving average. + Calculation type + + + + + Gets the Commodity Channel Index. + Measures the position of price in relation to its moving average. + + Period for CCI MA + Sources prices for CCI + MA mode for CCI + Calculation type + + + + + Returns an instance of the Modified Moving Average (MMA) indicator. + MMA comprises a sloping factor to help it overtake with the growing or declining value of the trading price of the currency. + + Period of Modified Moving Average + Sources prices for MA + + + + + Gets the Regression indicator + The Linear Regression Indicator plots the ending value of a Linear Regression Line for a specified number of bars; showing, statistically, where the price is expected to be. + + Moving average period + Type of the price + + + + + Gets the Channel (Price Channel) indicator. + The 'Channel' indicator is based on measurement of min and max prices for the definite number of periods. + + Period of price channel + + + + + + Gets the AFIRMA indicator + Autoregressive finite impulse response moving average. A digital filter accurately shows the price movement as powered with least square method to minimise time lag + + Moving average period + Type of the price + Afirma mode + with least squares method overlapping if true + + + + + Gets the RSI indicator. + Relative Strength Index (RSI) is a momentum oscillator that measures the speed and change of price movements. + + RSI Period + Price Type + RSI Mode (Simple or Exponential) + MA Mode for smooth data + MA period for smooth data + Calculation type + + + + + Returns an instance of the McGinley Dynamic indicator. + McGinley Dynamic avoids of most whipsaws and it rapidly moves up or down according to a quickly changing market. It needs no adjusting because it is dynamic and it adjusts itself. + + Period of exponential moving average + Dynamic tracking factor + Source price type + Calculation type + + + + + Gets the MAE (Moving Average Envelope) indicator. + The 'MAE' indicator demonstrates a range of the prices discrepancy from a Moving Average. + + Period of MA for envelopes. + Sources prices for MA. + Type of moving average. + Upband deviation in %. + Downband deviation in %. + Calculation type + + + + + Returns an instance of the Parabolic Time/Price System (SAR) indicator. + SAR indicator helps to define the direction of the prevailing trend and the moment to close positions opened during the reversal. + + Step of parabolic SAR system + Maximum value for the acceleration factor + + + + + + Gets the AO (Awesome Oscillator) indicator. + The 'AO' indicator determines market momentum. + + + + + + Gets the CMO (Chande Momentum Oscillator) indicator. + The CMO calculates the dividing of difference between the sum of all recent gains and the sum of all recent losses by the sum of all price movement over the period. + + Period of MA for envelopes. + Sources prices for MA. + + + + + + Returns an instance of the ZigZag indicator. + ZigZag is a trend following indicator that is used to predict when a given symbol's momentum is reversing. + + Percent Deviation + + + + + + Gets the Aroon indicator. + Reveals the beginning of a new trend and determines how strong it is + + Aroons period + + + + + + Returns an instance of the Standart Deviation (SD) indicator. + The SD shows the difference of the volatility value from the average one. + + Period of indicator + Sources prices for MA + Type of Moving Average + Calculation type + + + + + Gets the MACD (Moving Average Convergence/Divergence) indicator. + The MACD is a trend-following momentum indicator that shows the relationship between two moving averages of prices. + + Period of fast EMA. + Period of slow EMA. + Period of signal EMA. + Calculation type + + + + + Returns an instance of the Kairi Relative Index (KRI) indicator. + KRI calculates deviation of the current price from its simple moving average as a percent of the moving average. + + + + + + + + Returns an instance of the Bollinger Bands Flat (BBF) indicator. + The BBF provides the same data as BB, but drawn in separate field and easier to recognize whether price is in or out of the band. + + Period + Deviation + Sources prices for MA + Type of Moving Average + Calculation type + + + + + Gets the ROC (Rate of Change) indicator. + The ROC shows the speed at which price is changing. + + Period of momentum. + + + + + + Gets the Momentum indicator. + Momentum compares where the current price is in relation to where the price was in the past. + + Period for Momentum + Sources prices for Momentum + + + + + + Gets the %R Larry Williams. + Uses Stochastic to determine overbought and oversold levels. + + Period for Momentum + + + + + + Returns an instance of the Price Oscillator (PO) indicator. + PO calculates the variation between price moving averages. + + Period of MA1 + Period of MA2 + Sources prices for MA + Type of Moving Average + Calculation type + + + + + Gets the OsMA (Moving Average of Oscillator) indicator. + The OsMA reflects the difference between an oscillator (MACD) and its moving average (signal line). + + Period of fast EMA. + Period of slow EMA. + Period of signal EMA. + Calculation type + + + + + Gets On Balance Volume. + On Balance Volume (OBV) measures buying and selling pressure as a cumulative indicator that adds volume on up days and subtracts volume on down days. + + Sources prices for OBV + + + + + + Returns an instance of the Positive Volume Index (PVI) indicator. + The PVI value changes on the periods in which value of volume has increased in comparison with the previous period. + + + + + + + + Returns an instance of the Volume indicator. + Volume allows to confirm the strength of a trend or to suggest about it's weakness. + + + + + + + Gets the MFI(Money Flow Index) indicator. + The MFI(Money Flow Index) is an oscillator that uses both price and volume to measure buying and selling pressure. + + Period of MFI. + + + + + + Returns an instance of the Acceleration/Deceleration Oscillator (AC). + AC measures the acceleration and deceleration of the current momentum. + + + + + + + Gets the Average True Range (ATR) indicator. + The ATR measures of market volatility. + + + Period of Moving Average. + Type of Moving Average + Calculation type + + + + + Returns an instance of the Kaufman Adaptive Moving Average (KAMA) indicator. + KAMA is an exponential style average with a smoothing that varies according to recent data. + + Period + Fast factor + Slow factor + Sources prices for MA + + + + + + Returns an instance of the Qstick indicator. + The Qstick is a moving average that shows the difference between the prices at which an issue opens and closes. + + + + Calculation type + + + + + Get the Swing Index (SI) indicator. + The SI is used to confirm trend line breakouts on price charts. + + The divider. + + + + + + Get the True Strength Index (TSI) indicator. + + The TSI is a variation of the Relative Strength Indicator which uses a doubly-smoothed + EMA of price momentum to eliminate choppy price changes and spot trend changes. + + + First MA period. + Second MA period. + Calculation type + + + + + Returns an instance of the Average Directional Index (ADX) indicator. + The ADX determines the strength of a prevailing trend. + + Period + Type of Moving Average + Calculation type + + + + + Returns an instance of the Keltner Channel indicator. + Keltner Channels are volatility-based envelopes set above and below an exponential moving average. + + Period of MA for Keltner's Channel + Coefficient of channel's width + Sources prices for MA + Type of Moving Average + Calculation type + + + + + Returns an instance of the Percentage Price Oscillator (PPO). + Percentage Price Oscillator is a momentum indicator. Signal line is EMA of PPO. Formula: (FastEMA-SlowEMA)/SlowEMA. + + Fast EMA Period + Slow EMA Period + Signal EMA Period + Calculation type + + + + + Gets the Stochastic Slow. + Shows the location of the current close relative to the high/low range over a set number of periods (Slow). + + Period + Smoothing + Double smoothing + Moving type + Calculation type + + + + + Gets the Stochastic x Relative Strength Index. + StochRSI is an oscillator that measures the level of RSI relative to its range. + + Period + Smoothing + Double smoothing + + + + + + Gets the Ichimoku. + Enables to quickly discern and filter 'at a glance' the low-probability trading setups from those of higher probability. + + Tenkan Period + Kijun Period + Senkou Span B + + + + + + Gets the Directional Movement Index(DMI) indicator. + The DMI іdentifies whether there is a definable trend in the market. + + Period of Moving Average. + Type of Moving Average. + Calculation type + + + + + Gets the Alligator. + Three moving averages with different colors, periods and calculation methods. + + Type of Jaw Moving Average. + SourcePrice of Jaw Moving Average. + Period of Jaw Moving Average. + Shift of Jaw Moving Average. + Period of Moving Average. + Type of Moving Average. + Period of Moving Average. + Type of Moving Average. + Period of Moving Average. + Type of Moving Average. + Period of Moving Average. + Type of Moving Average. + + + + + + Base class for all indicators. + + + + + Short name of indicator + + + + + Access to current Symbol of indicator + + + + + Amount of items in internal buffers + + + + + Represent access to current used historical data. + + + + + Represent access indicator series + + + + + + + + + + Specified, whether indicator should use main or additional window on the chart + + + + + Specified, whether indicator should draw on chart background by default. + + + + + Specified, whether indicator should participate into price auto scale system. + + + + + Precision amount for formatting price (the count of digits after decimal point); By default = -1, which means to use precision from indicator's symbol + + + + + Indicator's settings + + + + + Represent access to the chart, that created indicator + + + + + Recalculate indicator + + + + + + + + + + + Sets the value of indicator into internal buffer + + Value + Index of indicator line + Offset value + + + + Gets the value of indicator from internal buffer + + Offset value + Index of indicator line + Offset start point + + + + + Set line break point. + + Offset value + Index of indicator line + Offset start point + + + + Remove line break point. + + Offset value + Index of indicator line + Offset start point + + + + Check if the point is a break point. + + Offset value + Index of indicator line + Offset start point + + + + Gets the price from historical data + + + + + + + + Get Bid price + + Offset value + + + + + Get Ask price + + Offset value + + + + + Get Last price + + Offset value + + + + + Get Open price + + Offset value + + + + + Get High price + + Offset value + + + + + Get Low price + + Offset value + + + + + Get Close price + + Offset value + + + + + Get Median price + + Offset value + + + + + Get Typical price + + Offset value + + + + + Get Weighted price + + Offset value + + + + + Get Volume + + Offset value + + + + + Get Volume in quoting asset + + Offset value + + + + + Get Ticks + + Offset value + + + + + Get Open interest + + Offset value + + + + + Get Funding rate + + Offset value + + + + + Get Time + + Offset value + + + + + Formatting price, using precision from assigned symbol or Digits value if specified + + Price value + + + + + Marks cloud begin between two line series with specific color + + First line series index + Second line series index + Cloud color + Offset + + + + Marks cloud end between two line series with specific color + + First line series index + Second line series index + Cloud color + Offset + + + + Using IndicatorLineMarker class you can mark by color or icon any point of your indicator's line. + + + + + Color of the marker + + + + + Icon that will be drawn above the indicator line + + + + + Icon that will be drawn beyond the indicator line + + + + + Constructor for IndicatorLineMarker + + + + + + + + Constructor for IndicatorLineMarker + + + + + Different type of icons for Indicator marker + + + + + Sets the bar number (from the data beginning) from which the drawing of the given indicator line must start; + + + + + Time shift of indicator's line + + + + + Redraws parts of indicator's line within the interval set by offset + + + + + Removes redrawn parts of indicator's line within the interval set by offset + + + + + + + + + + Fully clears markers from line + + + + + Use this attribute to mark input parameters of your script. You will see them in the settings screen on adding + + + + + Displayed name of input parameter + + + + + Sort index for input paramter + + + + + Minimal value for numeric input parameters + + + + + Maximal value for numeric input parameters + + + + + Increment value for numeric input parameters + + + + + Decimal palces for numeric input parameters + + + + + List of predefined values + + + + + Event occurred when write a new log + + + + + Get logs from the strategy for specified date range + + + + + + + + Write log message + + + + + + + The base class for strategies + + + + + Unique ID of the strategy + + + + + The current state of the strategy + + + + + Event occured when strategy write a new log + + + + + Event occured if any of strategy settings was changed + + + + + Run strategy + + + + + Stop strategy + + + + + Remove the strategy + + + + + Get current metrics from the strategy + + + + + + Get logs from the strategy for specified date range + + + + + + + + Write log message + + + + + + + Specifies constants that define which mouse button was pressed. + + + + + No mouse button was pressed. + + + + + The left mouse button was pressed. + + + + + The right mouse button was pressed. + + + + + The middle mouse button was pressed. + + + + + The first XButton was pressed. + + + + + The second XButton was pressed. + + + + + Gets or sets a value indicating whether the event was handled. + true to bypass the control's default handling; otherwise, false to also pass the event along to the default control handler. + + + + + Defines quote parameters for subscribtion + + + + + Symbol Id + + + + + Quote type + + + + + SubscribeQuotesParameters constructor + + + + + Id of the order + + + + + Gets or Sets the groups. + + + + + SettingItem cast type + + + + + SettingItem state + + + + + Specifies the DatePicker time format. + + + + + Date only + + + + + Date and time + + + + + Time only + + + + + Time with seconds + + + + + + + + + + Typecasts setting as TabControl item + + + + + Typecasts setting as GroupBox item + + + + + Typecasts setting as AccountLookup item + + + + + Typecasts setting as Button item + + + + + Typecasts setting as CheckBox item + + + + + Typecasts setting as CheckBox item + + + + + Typecasts setting as Color item + + + + + Typecasts setting as DateTimePicker item + + + + + Typecasts setting as NumericUpDown item + + + + + Typecasts setting as NumericUpDown item + + + + + Typecasts setting as AccountLookup item + + + + + Typecasts setting as Password item + + + + + Typecasts setting as Period item + + + + + Typecasts setting as ComboBox item + + + + + Typecasts setting as TextBox item + + + + + Typecasts setting as SymbolLookup item + + + + + Typecasts setting as TextBox item + + + + + Cancel all pending s + + the name of the initiator of the call (optional) + + + + Cancel all pending s by + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by and + + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by and + + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by , and + + + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by and + + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by , and + + + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by and + + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by , and + + + + + the name of the initiator of the call (optional) + + + + Cancel all pending s by + + + the name of the initiator of the call (optional) + + + + Close all s + + the name of the initiator of the call (optional) + List of + + + + Close all s by + + + the name of the initiator of the call (optional) + List of + + + + Close all s by + + + the name of the initiator of the call (optional) + List of + + + + Close all s by and + + + + the name of the initiator of the call (optional) + List of + + + + Close all s by + + + the name of the initiator of the call (optional) + List of + + + + Close all s by and + + + + the name of the initiator of the call (optional) + List of + + + + Close all positive s + + the name of the initiator of the call (optional) + List of + + + + Close all positive s by + + + the name of the initiator of the call (optional) + List of + + + + Close all negative s + + the name of the initiator of the call (optional) + List of + + + + Close all negative s by + + + the name of the initiator of the call (optional) + List of + + + + Reverse all s + + the name of the initiator of the call (optional) + List of + + + + Reverse all s by + + + the name of the initiator of the call (optional) + List of + + + + Reverse all s by + + + the name of the initiator of the call (optional) + List of + + + + Reverse by and + + + + the name of the initiator of the call (optional) + + + + + Cancel all s and close all s + + the name of the initiator of the call (optional) + + + + Cancel all s and close all s by + + + the name of the initiator of the call (optional) + + + + Cancel all s and close all s by and + + + + the name of the initiator of the call (optional) + + + + Modify SL to breakeven price + + + the name of the initiator of the call (optional) + + + + Modify SL to breakeven price with certain additional offset + + + offset in ticks + the name of the initiator of the call (optional) + + + + Modify SL to breakeven price + + + list of s that will be used for order placing + the name of the initiator of the call (optional) + + + + Adjust SL/TP for given + + + + the name of the initiator of the call (optional) + + + + Adjust stop loss for given + + + list of s + initial stop loss parameters (optional) + + the name of the initiator of the call (optional) + + + + + Adjust take profit for given + + + list of s + initial take profit parameters (optional) + + the name of the initiator of the call (optional) + + + + + The allowed result. + + + + + Gets the status. + + + + + Gets the reason. + + + + + Get the allowed result. + + An AllowedResult. + + + + Gets the not allowed result. + + The reason. + An AllowedResult. + + + + The action wrapper. + + + + + Gets or Sets the items. + + + + + Initializes a new instance of the class. + + + + + Froms the X element. + + The element. + The deserialization info. + + + + Tos the X element. + + A XElement. + + + + The aggressor flag calculator. + + + + + Initializes a new instance of the class. + + + + + Collect bid ask. + + The symbol. + The time ticks. + The bid. + The ask. + + + + Calculate aggressor flag. + + The symbol. + The time ticks. + The last. + An AggressorFlag. + + + + + + + + + Calculate aggressor flag. + + The previous bid. + The previous ask. + The last. + An AggressorFlag. + + + + The alert. + + + + + Gets or Sets the text. + + + + + Gets or Sets the symbol name. + + + + + Gets or Sets the connection name. + + + + + Gets or Sets the action on confirm. + + + + + Gets or Sets the name. + + + + + The alert data. + + + + + The name. + + + + + Gets or Sets the columns. + + + + + Gets or Sets the groups. + + + + + Gets or Sets the actions. + + + + + Gets or Sets a value indicating whether enabled. + + + + + Gets or Sets the table dictionary. + + + + + Gets or Sets a value indicating whether coloring is alert. + + + + + Gets or Sets a value indicating whether filter alert. + + + + + Gets or Sets a value indicating whether search alert. + + + + + Gets or Sets a value indicating whether to remove. + + + + + Gets or Sets the ID. + + + + + Gets or Sets the command sender. + + + + + Gets or Sets a value indicating whether need confirm trading. + + + + + Initializes a new instance of the class. + + + + + To the XML element. + + A XElement. + + + + From the XML element. + + The element. + The deserialization info. + + + + The arbitrage symbol. + + + + + Gets or Sets the index. + + + + + Gets the unique ID. + + + + + Gets the symbol id. + + + + + Gets or Sets a value indicating whether is selected. + + + + + Gets or Sets the symbol. + + + + + Gets or Sets the account. + + + + + Gets or Sets the commission. + + + + + Initializes a new instance of the class. + + + + + Compare to. + + The other. + An int. + + + + Froms the X element. + + The element. + The deserialization info. + + + + Tos the X element. + + A XElement. + + + + The action buffered processor. + + + + + The action buffered processor with priority. + + + + + Initializes a new instance of the class. + + The threads count. + + + + The buffered processor. + + + + + + Gets the state. + + + + + Gets the queue depth. + + + + + + + + + + + + + + + + + The subject. + + + + Wait all messages process. + + The external token. + + + + The buffered processor state. + + + + + + + + + + Агрегація, що буде використовуватись як дефолтна для "TimeFrameScreen". + Повинна відповідати агрегації чарта. + + + + + https://stackoverflow.com/questions/3060381/datetime-addmonths-adding-only-month-not-days + + Проблема: + (29 Feb).AddMonth(1) = 29 March + + + + + Defines advanced trading operation respond bundle + + + + + Respond operation state + + + + + Respond message + + + + + Respond value (optional) + + + + + Details of sub-operations + + + + + Get all available custom resources + + + + + Get custom resource stream by name + + + + + Check whether specified items was hidden by branding specification + + + + + + The assembly loader. + + + + + Load types. + + The folder name. + The target type. + The assembly name filter. + The search option. + If true, load in memory. + If true, allow loading references. + ]]> + + + + Quote type for subscribtion dictum + + + + + Level 1 quote + + + + + Level 2 quote + + + + + Last + + + + + Mark price + + + + + Trading operation state + + + + + Complex trading operation state + + + + + The aggressor flag calculator extensions. + + + + + Collect bid ask. + + The calculator. + The quote. + + + + Calculate aggressor flag. + + The calculator. + The last. + An AggressorFlag. + + + + Gets the price from historical data + + + + + + + + Get Bid price + + Offset value + + + + + Get Ask price + + Offset value + + + + + Get Last price + + Offset value + + + + + Get Open price + + Offset value + + + + + Get High price + + Offset value + + + + + Get Low price + + Offset value + + + + + Get Close price + + Offset value + + + + + Get Median price + + Offset value + + + + + Get Typical price + + Offset value + + + + + Get Weighted price + + Offset value + + + + + Get Volume + + Offset value + + + + + Get Volume in quoting asset + + Offset value + + + + + Get Ticks + + Offset value + + + + + Get Open interest + + Offset value + + + + + Get Funding rate + + Offset value + + + + + Get Time + + Offset value + + + + + Combines intersecting intervals + + + + + Get all settings or particular settings by provided hierarchy path + + + + + Write info log message + + + + + Write trading log message + + + + + Write error log message + + + + + Variable lifetime + + + + + The variable will be removed on exit + + + + + The variable will be written to file + + + + + Global variable entry + + + + + Variable name + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + List<GlobalVariable> global_List=new List<GlobalVariable>(); + + public override void Init() + { + if(GlobalVariablesManager.Count()>0) + { + global_List=GlobalVariablesManager.GetGlobalVariablesList(); + foreach (var el in global_List) + { + Print(el.Name); + Print(el.Value); + } + } + } + } + } + + + + + + + Variable value + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + List<GlobalVariable> global_List=new List<GlobalVariable>(); + public override void Init() + { + if(GlobalVariablesManager.Count()>0) + { + global_List=GlobalVariablesManager.GetGlobalVariablesList(); + foreach (var el in global_List) + { + Print(el.Name); + Print(el.Value); + } + } + } + } + } + + + + + + + Initializes new variable + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + List<GlobalVariable> global_List=new List<GlobalVariable>(); + public override void Init() + { + if(GlobalVariablesManager.Count()>0) + { + global_List=GlobalVariablesManager.GetGlobalVariablesList(); + foreach (var el in global_List) + { + //Simplified way to retrieve global variable value + el.GlobalVariable("new_global_variable_period", period) + + //However, to obtain certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. + } + } + } + } + } + + + + Variable name + Variable value + + + + Sets variable value to a global storage + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + GlobalVariablesManager(){ + base.ProjectName = "GlobalVariablesManager"; + base.Password=GetHashedPassword(ProjectName); + } + + [InputParameter("Period", 0, 1, 9999)] + public int period = 5; + + public override void OnQuote() + { + //Simplified way to store a global variable + + GlobalVariablesManager.SetValue("global_variable_period", period, VariableLifetime.SaveSession); + + //However, to indicate any variable belongs to certain indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. + + GlobalVariablesManager.SetValue("global_variable_period" +Symbols.Current.Name+period+Password, period, VariableLifetime.SaveSession); + } + } + } + + + + Variable name + New value + Variable lifetime + + + + Removes specified variable from global storage + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + public override void Init() + { + //Simplified way to remove a global variable + + GlobalVariablesManager.Remove("global_variable_period"); + + //However, to remove certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. + } + } + } + + + + Variable name + + + + Returns variables' count in global storage + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + public override void Init() + { + if(GlobalVariablesManager.Count()>0) + { + Print("Your session obtains "+GlobalVariablesManager.Count()+" global variables"); + } + } + } + } + + + + + + + + Removes all variables from global storage + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + public override void Init() + { + GlobalVariablesManager.RemoveAll(); + + if(GlobalVariablesManager.Count()==0) + { + Print("Your session does not have any global variables"); + } + } + } + } + + + + + + + Check if variable with specified name exists in global storage + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + public override void Init() + { + //Simplified way to check an existance of a global variable + + if(GlobalVariablesManager.Exists("global_variable_period")) + Print("Your session has this global variable"); + else + GlobalVariablesManager.SetValue("global_variable_period"); + + //However, to obtain certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. + } + } + } + + + + Variable name + + + + + Returns variable value by name + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + public override void Init() + { + //Simplified way to retrieve global variable value + + if(GlobalVariablesManager.Exists("global_variable_period")) + //Always perform a type casting before assigning any variable from global storage + period = (int)GlobalVariablesManager.GetValue("global_variable_period"); + + //However, to obtain certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. + } + } + } + + + + Variable name + Variable value + + + + Performs a variable assigning from a global storage if such name exists in a scope + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + public override void Init() + { + int new_period; + + //Simplified way to retrieve global variable value + + if(GlobalVariablesManager.TryGetValue("global_variable_period")) + Print("New variable is assigned from globals: " + new_period); + if(new_period==period) + Print("Matching, no need to re-assign globals: "); + else + GlobalVariablesManager.SetValue("global_variable_period", period, VariableLifetime.SaveSession); + + //However, to obtain certain variable, which belongs to indicator/strategy and to avoid unexpected erasing of data the best practice is to provide to a key holder multiple details such as name, params, hashed password etc. Follow SetValue() example. + } + } + } + + + + Variable name + Variable value + True if variable exists + + + + Saves all serializable variables to disk + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + Connection myConnection = Connection.CurrentConnection; + public override void OnQuote() + { + if(myConnection.Status==Disconnected) + GlobalVariablesManager.Flush(); + } + } + } + + + + + + + Returns all global variables as list + + + + + using System; + using System.Text; + using PTLRuntime.NETScript; + + namespace GlobalVariablesManager + { + public class GlobalVariablesManager : NETIndicator + { + List <GlobalVariable>global_List=new List<GlobalVariable>(); + public override void Init() + { + if(GlobalVariablesManager.Count()>0) + global_List=GlobalVariablesManager.GetGlobalVariablesList(); + + foreach (var el in global_List) + { + Print(el.Name); + } + } + } + } + + + + Collection of global variables + + + + Object is non-serializable + + + + + Object too big + + + + + The application logger event. + + + + + Current logging level + + + + + Sets logs with custom messag, logging level, connection name + + + + + Sets logs with exception and custom message (optional), logging level, connection name + + + + + Sets logs with objects inherited from ILoggable interface,logging level, connection name + + + + + Sets a log level in case of deep debug + + + + + Remove a log level if one has not needed already + + + + + Все параметры, которые могут использоваться при отправке email + + + + + Отправит сообщение по E-mail + + + + + + + + + + Чисто, як маркер для парсера xgettext.exe + + + + + + + Check, whether current translation equal to hidden + + + + + + + + + + + + + + + + + + + + + + + Allows to save Symbols into named lists + + + + + from: + http://www.codeguru.com/csharp/csharp/cs_date_time/timeroutines/article.php/c4207/C-SNTP-Client.htm + +++ добавлен диспозе, подправлен ToString(), убран лишний метод. + +++ ReceiveTimeoutбSendTimeout + + NTPClient is a C# class designed to connect to time servers on the Internet. + The implementation of the protocol is based on the RFC 2030. + + Public class members: + + LeapIndicator - Warns of an impending leap second to be inserted/deleted in the last + minute of the current day. (See the _LeapIndicator enum) + + VersionNumber - Version number of the protocol (3 or 4). + + Mode - Returns mode. (See the _Mode enum) + + Stratum - Stratum of the clock. (See the _Stratum enum) + + PollInterval - Maximum interval between successive messages. + + Precision - Precision of the clock. + + RootDelay - Round trip time to the primary reference source. + + RootDispersion - Nominal error relative to the primary reference source. + + ReferenceTimestamp - The time at which the clock was last set or corrected. + + OriginateTimestamp - The time at which the request departed the client for the server. + + ReceiveTimestamp - The time at which the request arrived at the server. + + Transmit Timestamp - The time at which the reply departed the server for client. + + RoundTripDelay - The time between the departure of request and arrival of reply. + + LocalClockOffset - The offset of the local clock relative to the primary reference + source. + + Initialize - Sets up data structure and prepares for connection. + + Connect - Connects to the time server and populates the data structure. + + IsResponseValid - Returns true if received data is valid and if comes from + a NTP-compliant time server. + + ToString - Returns a string representation of the object. + + ----------------------------------------------------------------------------- + Structure of the standard NTP header (as described in RFC 2030) + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |LI | VN |Mode | Stratum | Poll | Precision | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Root Delay | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Root Dispersion | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reference Identifier | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | Reference Timestamp (64) | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | Originate Timestamp (64) | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | Receive Timestamp (64) | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | Transmit Timestamp (64) | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Key Identifier (optional) (32) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | | + | Message Digest (optional) (128) | + | | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + ----------------------------------------------------------------------------- + + NTP Timestamp Format (as described in RFC 2030) + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seconds | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seconds Fraction (0-padded) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + + + + Connect to the time server + + + + + Defines a trading operation respond bundle + + + + + Respond operation state + + + + + Respond message + + + + + Respond order id + + + + + Provides VA calculation parameters + + + + + Provides VA calculation request per + + + + + Summary calculated Volume info + + + + + Volume info for each price + + + + + Fire in case of price level was added or existing was updated + + + + + Represent item with Volume Analysis calculation results + + + + + Volume Analysis calculations + + + + + Calculate volume profile for requested time range + + + + + Calculate volume profile for each bar in History Data + + + + + Calculate volume profile for requested time range + + + + + Calculate volume profile for each bar in History Data + + + + + + + + + + + + + + diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b6ac99d7..a36cee3c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,10 +1,10 @@ -{ - "recommendations": [ - "ms-dotnettools.csdevkit", - "ms-dotnettools.csharp", - "ms-dotnettools.dotnet-interactive-vscode", - "bierner.markdown-mermaid", - "visualstudioexptteam.vscodeintellicode", - "SonarSource.sonarlint-vscode" - ] +{ + "recommendations": [ + "ms-dotnettools.csdevkit", + "ms-dotnettools.csharp", + "ms-dotnettools.dotnet-interactive-vscode", + "bierner.markdown-mermaid", + "visualstudioexptteam.vscodeintellicode", + "SonarSource.sonarlint-vscode" + ] } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index dcc54733..52076cb7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,33 +1,33 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug Tests", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "dotnet", - "args": [ - "test", - "${workspaceFolder}/QuanTAlib.sln", - "--no-build" - ], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole", - "logging": { - "moduleLoad": false - } - }, - { - "name": "Debug Library", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}", - "justMyCode": true, - "logging": { - "moduleLoad": false - } - } - ] -} +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Tests", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "dotnet", + "args": [ + "test", + "${workspaceFolder}/QuanTAlib.sln", + "--no-build" + ], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole", + "logging": { + "moduleLoad": false + } + }, + { + "name": "Debug Library", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}", + "justMyCode": true, + "logging": { + "moduleLoad": false + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 895dc982..9722f421 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,203 +1,203 @@ -{ - // ??????????????????????????????????????????????????????????????????? - // GitHub Copilot Settings for QuanTAlib Workspace - // Optimized for high-performance financial library development - // ??????????????????????????????????????????????????????????????????? - - // ????????????????????????????????????????????????????????????????? - // Copilot Core Settings - // ????????????????????????????????????????????????????????????????? - - // Enable Copilot completions (suggestions appear automatically) - "github.copilot.editor.enableAutoCompletions": true, - - // Enable Copilot for all file types - "github.copilot.enable": { - "*": true, - "plaintext": false, - "markdown": true, - "scminput": false - }, - - // Show inline suggestions - "editor.inlineSuggest.enabled": true, - - // Always show the inline suggestion toolbar - "editor.inlineSuggest.showToolbar": "always", - - // ????????????????????????????????????????????????????????????????? - // Copilot Chat Settings (Manual Review Required) - // ????????????????????????????????????????????????????????????????? - - // DO NOT auto-apply chat edits - require manual review for quality control - "chat.editing.autoApply": "off", - - // Confirm before removing edit requests - "chat.editing.confirmEditRequestRemoval": true, - - // Show chat panel on the side - "chat.editor.wordWrap": "on", - - // ????????????????????????????????????????????????????????????????? - // Editor Settings for Productivity - // ????????????????????????????????????????????????????????????????? - - // Enable quick suggestions in all contexts - "editor.quickSuggestions": { - "other": true, - "comments": true, - "strings": true - }, - - // Show suggestions on trigger characters - "editor.suggestOnTriggerCharacters": true, - - // Accept suggestion on commit character (like dot, parenthesis) - "editor.acceptSuggestionOnCommitCharacter": true, - - // Faster suggestion appearance - "editor.quickSuggestionsDelay": 0, - - // Show snippet suggestions with other suggestions - "editor.snippetSuggestions": "inline", - - // Tab key behavior - "editor.tabCompletion": "on", - - // ????????????????????????????????????????????????????????????????? - // C# Specific Settings - // ????????????????????????????????????????????????????????????????? - - "[csharp]": { - "editor.formatOnSave": true, - "editor.formatOnPaste": true, - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - }, - "editor.quickSuggestions": { - "other": true, - "comments": true, - "strings": true - } - }, - - // ????????????????????????????????????????????????????????????????? - // Performance & Quality Control - // ????????????????????????????????????????????????????????????????? - - // Save automatically (helps with Copilot context) - "files.autoSave": "afterDelay", - "files.autoSaveDelay": 1000, - - // Show whitespace (important for performance-critical code) - "editor.renderWhitespace": "boundary", - - // Show inline parameter hints - "editor.inlayHints.enabled": "on", - - // Highlight matching brackets - "editor.bracketPairColorization.enabled": true, - "editor.guides.bracketPairs": true, - - // ????????????????????????????????????????????????????????????????? - // Git Integration - // ????????????????????????????????????????????????????????????????? - - // Auto-fetch git changes - "git.autofetch": true, - - // Confirm before synchronizing - "git.confirmSync": false, - - // Show inline blame - "git.decorations.enabled": true, - - // ????????????????????????????????????????????????????????????????? - // Terminal Settings - WSL Debian - // ????????????????????????????????????????????????????????????????? - - "terminal.integrated.defaultProfile.windows": "Debian", - "terminal.integrated.profiles.windows": { - "Debian": { - "path": "C:\\Windows\\System32\\wsl.exe", - "args": ["-d", "Debian", "--", "bash", "-l"], - "icon": "terminal-linux" - }, - "PowerShell": { - "source": "PowerShell", - "icon": "terminal-powershell" - } - }, - "terminal.integrated.shellIntegration.enabled": true, - "terminal.integrated.suggest.enabled": true, - - // ????????????????????????????????????????????????????????????????? - // Cline Settings - WSL Debian Bash - // ????????????????????????????????????????????????????????????????? - - "cline.terminalShell.windows": "C:\\Windows\\System32\\wsl.exe", - "cline.terminalShellArgs.windows": ["-d", "Debian", "--", "bash", "-l"], - - // ????????????????????????????????????????????????????????????????? - // File Exclusions (Reduce Noise) - // ????????????????????????????????????????????????????????????????? - - "files.exclude": { - "**/bin": true, - "**/obj": true, - "**/.vs": true, - "**/node_modules": true, - "**/.git": false - }, - - "search.exclude": { - "**/bin": true, - "**/obj": true, - "**/node_modules": true, - "**/.vs": true, - "**/coverage": true - }, - - // ????????????????????????????????????????????????????????????????? - // .NET Specific Settings - // ????????????????????????????????????????????????????????????????? - - "omnisharp.enableEditorConfigSupport": true, - "omnisharp.enableRoslynAnalyzers": true, - "dotnet.backgroundAnalysis.enabled": true, - - // ????????????????????????????????????????????????????????????????? - // Testing Integration - // ????????????????????????????????????????????????????????????????? - - "dotnet.defaultSolution": "QuanTAlib.sln", - "dotnet.testController.enabled": true, - "dotnet.unitTests.runSettingsPath": "coverage.runsettings", - "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, - "dotnet.server.useOmnisharp": false, - - "sonarlint.connectedMode.project": { - "connectionId": "mihakralj-quantalib", - "projectKey": "mihakralj_QuanTAlib" - }, - "coderabbit.agentType": "Cline", - "qodana.projectId": "KbxmN" - // Note: Native mode is configured in qodana.yaml with withinDocker: false - - // ??????????????????????????????????????????????????????????????????? - // Keyboard Shortcuts Reference - // ??????????????????????????????????????????????????????????????????? - // Tab - Accept inline suggestion - // Ctrl+? - Accept next word - // Ctrl+Enter - Accept line - // Esc - Dismiss suggestion - // Alt+] - Next suggestion - // Alt+[ - Previous suggestion - // Ctrl+I - Open Copilot Chat - // - // Quality Control Reminders: - // ? Review all Copilot suggestions for optimization patterns - // ? Run tests after accepting: dotnet test - // ? Check performance impact with benchmarks - // ? Validate against reference implementations -} +{ + // ??????????????????????????????????????????????????????????????????? + // GitHub Copilot Settings for QuanTAlib Workspace + // Optimized for high-performance financial library development + // ??????????????????????????????????????????????????????????????????? + + // ????????????????????????????????????????????????????????????????? + // Copilot Core Settings + // ????????????????????????????????????????????????????????????????? + + // Enable Copilot completions (suggestions appear automatically) + "github.copilot.editor.enableAutoCompletions": true, + + // Enable Copilot for all file types + "github.copilot.enable": { + "*": true, + "plaintext": false, + "markdown": true, + "scminput": false + }, + + // Show inline suggestions + "editor.inlineSuggest.enabled": true, + + // Always show the inline suggestion toolbar + "editor.inlineSuggest.showToolbar": "always", + + // ????????????????????????????????????????????????????????????????? + // Copilot Chat Settings (Manual Review Required) + // ????????????????????????????????????????????????????????????????? + + // DO NOT auto-apply chat edits - require manual review for quality control + "chat.editing.autoApply": "off", + + // Confirm before removing edit requests + "chat.editing.confirmEditRequestRemoval": true, + + // Show chat panel on the side + "chat.editor.wordWrap": "on", + + // ????????????????????????????????????????????????????????????????? + // Editor Settings for Productivity + // ????????????????????????????????????????????????????????????????? + + // Enable quick suggestions in all contexts + "editor.quickSuggestions": { + "other": true, + "comments": true, + "strings": true + }, + + // Show suggestions on trigger characters + "editor.suggestOnTriggerCharacters": true, + + // Accept suggestion on commit character (like dot, parenthesis) + "editor.acceptSuggestionOnCommitCharacter": true, + + // Faster suggestion appearance + "editor.quickSuggestionsDelay": 0, + + // Show snippet suggestions with other suggestions + "editor.snippetSuggestions": "inline", + + // Tab key behavior + "editor.tabCompletion": "on", + + // ????????????????????????????????????????????????????????????????? + // C# Specific Settings + // ????????????????????????????????????????????????????????????????? + + "[csharp]": { + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "editor.quickSuggestions": { + "other": true, + "comments": true, + "strings": true + } + }, + + // ????????????????????????????????????????????????????????????????? + // Performance & Quality Control + // ????????????????????????????????????????????????????????????????? + + // Save automatically (helps with Copilot context) + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 1000, + + // Show whitespace (important for performance-critical code) + "editor.renderWhitespace": "boundary", + + // Show inline parameter hints + "editor.inlayHints.enabled": "on", + + // Highlight matching brackets + "editor.bracketPairColorization.enabled": true, + "editor.guides.bracketPairs": true, + + // ????????????????????????????????????????????????????????????????? + // Git Integration + // ????????????????????????????????????????????????????????????????? + + // Auto-fetch git changes + "git.autofetch": true, + + // Confirm before synchronizing + "git.confirmSync": false, + + // Show inline blame + "git.decorations.enabled": true, + + // ????????????????????????????????????????????????????????????????? + // Terminal Settings - WSL Debian + // ????????????????????????????????????????????????????????????????? + + "terminal.integrated.defaultProfile.windows": "Debian", + "terminal.integrated.profiles.windows": { + "Debian": { + "path": "C:\\Windows\\System32\\wsl.exe", + "args": ["-d", "Debian", "--", "bash", "-l"], + "icon": "terminal-linux" + }, + "PowerShell": { + "source": "PowerShell", + "icon": "terminal-powershell" + } + }, + "terminal.integrated.shellIntegration.enabled": true, + "terminal.integrated.suggest.enabled": true, + + // ????????????????????????????????????????????????????????????????? + // Cline Settings - WSL Debian Bash + // ????????????????????????????????????????????????????????????????? + + "cline.terminalShell.windows": "C:\\Windows\\System32\\wsl.exe", + "cline.terminalShellArgs.windows": ["-d", "Debian", "--", "bash", "-l"], + + // ????????????????????????????????????????????????????????????????? + // File Exclusions (Reduce Noise) + // ????????????????????????????????????????????????????????????????? + + "files.exclude": { + "**/bin": true, + "**/obj": true, + "**/.vs": true, + "**/node_modules": true, + "**/.git": false + }, + + "search.exclude": { + "**/bin": true, + "**/obj": true, + "**/node_modules": true, + "**/.vs": true, + "**/coverage": true + }, + + // ????????????????????????????????????????????????????????????????? + // .NET Specific Settings + // ????????????????????????????????????????????????????????????????? + + "omnisharp.enableEditorConfigSupport": true, + "omnisharp.enableRoslynAnalyzers": true, + "dotnet.backgroundAnalysis.enabled": true, + + // ????????????????????????????????????????????????????????????????? + // Testing Integration + // ????????????????????????????????????????????????????????????????? + + "dotnet.defaultSolution": "QuanTAlib.sln", + "dotnet.testController.enabled": true, + "dotnet.unitTests.runSettingsPath": "coverage.runsettings", + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, + "dotnet.server.useOmnisharp": false, + + "sonarlint.connectedMode.project": { + "connectionId": "mihakralj-quantalib", + "projectKey": "mihakralj_QuanTAlib" + }, + "coderabbit.agentType": "Cline", + "qodana.projectId": "KbxmN" + // Note: Native mode is configured in qodana.yaml with withinDocker: false + + // ??????????????????????????????????????????????????????????????????? + // Keyboard Shortcuts Reference + // ??????????????????????????????????????????????????????????????????? + // Tab - Accept inline suggestion + // Ctrl+? - Accept next word + // Ctrl+Enter - Accept line + // Esc - Dismiss suggestion + // Alt+] - Next suggestion + // Alt+[ - Previous suggestion + // Ctrl+I - Open Copilot Chat + // + // Quality Control Reminders: + // ? Review all Copilot suggestions for optimization patterns + // ? Run tests after accepting: dotnet test + // ? Check performance impact with benchmarks + // ? Validate against reference implementations +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5a73d2d8..fe25bb98 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,54 +1,54 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "test-net10", - "command": "dotnet", - "type": "process", - "args": [ - "test", - "${workspaceFolder}/lib/QuanTAlib.Tests.csproj", - "--framework", - "net10.0" - ], - "problemMatcher": "$msCompile", - "group": { - "kind": "test", - "isDefault": true - }, - "presentation": { - "reveal": "always", - "panel": "new" - } - }, - { - "label": "test-all-frameworks", - "command": "dotnet", - "type": "process", - "args": [ - "test", - "${workspaceFolder}/lib/QuanTAlib.Tests.csproj" - ], - "problemMatcher": "$msCompile", - "group": "test", - "presentation": { - "reveal": "always", - "panel": "new" - } - }, - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/QuanTAlib.sln" - ], - "problemMatcher": "$msCompile", - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} +{ + "version": "2.0.0", + "tasks": [ + { + "label": "test-net10", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "${workspaceFolder}/lib/QuanTAlib.Tests.csproj", + "--framework", + "net10.0" + ], + "problemMatcher": "$msCompile", + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "new" + } + }, + { + "label": "test-all-frameworks", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "${workspaceFolder}/lib/QuanTAlib.Tests.csproj" + ], + "problemMatcher": "$msCompile", + "group": "test", + "presentation": { + "reveal": "always", + "panel": "new" + } + }, + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/QuanTAlib.sln" + ], + "problemMatcher": "$msCompile", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/Directory.Build.props b/Directory.Build.props index a0040729..a5ea7a6c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,85 +1,85 @@ - - - preview - $(NoWarn);NU1903;NU5104;NETSDK1057 - enable - enable - true - en-US - false - true - AnyCPU - True - bin\$(Configuration)\ - obj\$(MSBuildProjectName)\ - False - full - true - true - true - snupkg - AnyCPU - true - - - - true - true - true - $(MSBuildProjectDirectory)/roslyn.sarif - SARIF2.1 - - - - true - link - true - true - true - portable - true - true - true - true - true - false - false - false - false - true - false - true - - - - - $(NoWarn);S1144;S1944;S2053;S2245;S2259;S2583;S2589;S3329;S3655;S3776;S3949;S3966;S4158;S4347;S5773;S6781;MA0048;MA0051;RCS1159 - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - - - - - Z:\Quantower - $([System.IO.Directory]::GetDirectories("$(QuantowerRoot)\TradingPlatform", "v1*")[0]) - - - + + + preview + $(NoWarn);NU1903;NU5104;NETSDK1057 + enable + enable + true + en-US + false + true + AnyCPU + True + bin\$(Configuration)\ + obj\$(MSBuildProjectName)\ + False + full + true + true + true + snupkg + AnyCPU + true + + + + true + true + true + $(MSBuildProjectDirectory)/roslyn.sarif + SARIF2.1 + + + + true + link + true + true + true + portable + true + true + true + true + true + false + false + false + false + true + false + true + + + + + $(NoWarn);S1144;S1944;S2053;S2245;S2259;S2583;S2589;S3329;S3655;S3776;S3949;S3966;S4158;S4347;S5773;S6781;MA0048;MA0051;RCS1159 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + + Z:\Quantower + $([System.IO.Directory]::GetDirectories("$(QuantowerRoot)\TradingPlatform", "v1*")[0]) + + + diff --git a/docs/api.md b/docs/api.md index e430a41f..90652f76 100644 --- a/docs/api.md +++ b/docs/api.md @@ -33,7 +33,7 @@ Every indicator exposes the following core properties and methods: ## 2. Mode A: Batch (Stateless) -**Purpose:** Backtesting, Data Analysis, Optimization +**Purpose:** Backtesting, Data Analysis, Optimization **Method:** `static Batch` Batch mode provides stateless, SIMD-accelerated processing of historical arrays. It is optimized for maximum throughput and zero heap allocation. @@ -67,7 +67,7 @@ TSeries sma = Sma.Batch(history, 14); ## 3. Mode B: Streaming (Stateful) -**Purpose:** Live Trading, Event Processing +**Purpose:** Live Trading, Event Processing **Method:** `Update` Streaming mode handles real-time data ingestion using O(1) complexity per update. It maintains internal state (circular buffers, running sums) to process ticks with minimal latency. @@ -109,7 +109,7 @@ var sma = new Sma(source, 14); var ema = new Ema(sma, 5); // Updates flow automatically -source.Add(new TValue(time, price)); +source.Add(new TValue(time, price)); // sma updates, then ema updates automatically ``` @@ -117,7 +117,7 @@ source.Add(new TValue(time, price)); ## 4. Mode C: Priming (The Bridge) -**Purpose:** Switching from Batch to Streaming +**Purpose:** Switching from Batch to Streaming **Method:** `Prime` Priming mode hydrates a streaming instance using the minimal required tail of historical data. It calculates the intersection of *History Available* and *State Required*, allowing an indicator to become "Hot" without processing the entire history. @@ -132,7 +132,7 @@ double[] history = ...; // e.g., 100,000 bars // Efficiently processes only the last 'period' bars needed to fill the buffer // O(Warmup) initialization instead of O(History) -indicator.Prime(history); +indicator.Prime(history); // Indicator is now "Hot" and ready for the next live tick Console.WriteLine(indicator.IsHot); // true @@ -193,7 +193,7 @@ The initial portion of the output contains "cold" values. ```mermaid graph LR - H[Historical Data] + H[Historical Data] L[Live Data] subgraph "Mode A: Batch" diff --git a/docs/integration.md b/docs/integration.md index 8d6139dd..5f05839d 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -36,7 +36,7 @@ public class MySmaIndicator : Indicator { // Get price from Quantower double price = ClosePrice; - + // Update QuanTAlib // Note: Quantower handles bar updates, so a check is performed to determine whether this is a new bar or an update bool isNew = args.Reason == UpdateReason.NewBar; @@ -73,10 +73,10 @@ protected override void OnBarUpdate() { // NinjaTrader calls OnBarUpdate for every tick (if Calculate = OnEachTick) // or once per bar (if Calculate = OnBarClose) - + bool isNew = IsFirstTickOfBar; // Logic depends on Calculate mode var result = _sma.Update(new TValue(Time[0], Close[0]), isNew); - + Value[0] = result.Value; } ``` @@ -104,7 +104,7 @@ public class MyAlgorithm : QCAlgorithm { var bar = data.Bars["SPY"]; var result = _mySma.Update(new TValue(bar.EndTime, (double)bar.Close)); - + if (_mySma.IsHot) { Plot("Indicators", "SMA", result.Value); diff --git a/lib/Directory.Build.props b/lib/Directory.Build.props index 5af5edd4..75143476 100644 --- a/lib/Directory.Build.props +++ b/lib/Directory.Build.props @@ -1,13 +1,13 @@ - - - - - true - - - - obj\tests\ - bin\tests\ - bin\tests\$(Configuration)\ - - + + + + + true + + + + obj\tests\ + bin\tests\ + bin\tests\$(Configuration)\ + + diff --git a/lib/QuanTAlib.Tests.csproj b/lib/QuanTAlib.Tests.csproj index 80c4f35b..e8a4aad6 100644 --- a/lib/QuanTAlib.Tests.csproj +++ b/lib/QuanTAlib.Tests.csproj @@ -1,49 +1,49 @@ - - - - net10.0;net8.0 - net10.0 - enable - enable - false - true - $(NoWarn);CS8892 - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - daily_IBM.csv - - - - + + + + net10.0;net8.0 + net10.0 + enable + enable + false + true + $(NoWarn);CS8892 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + daily_IBM.csv + + + + diff --git a/lib/core/simd/SimdExtensions.Tests.cs b/lib/core/simd/SimdExtensions.Tests.cs index c4b6a987..aa17fe5b 100644 --- a/lib/core/simd/SimdExtensions.Tests.cs +++ b/lib/core/simd/SimdExtensions.Tests.cs @@ -113,7 +113,7 @@ public class SimdExtensionsTests double[] data = new double[1000]; for (int i = 0; i < data.Length; i++) data[i] = i + 1.0; - + var span = new ReadOnlySpan(data); double expected = 1000.0 * 1001.0 / 2.0; Assert.Equal(expected, span.SumSIMD(), precision: 8); @@ -324,7 +324,7 @@ public class SimdExtensionsTests { double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]; var span = new ReadOnlySpan(data); - + double variance = span.VarianceSIMD(); Assert.True(Math.Abs(variance - 4.571428) < 0.0001); } @@ -334,10 +334,10 @@ public class SimdExtensionsTests { double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]; var span = new ReadOnlySpan(data); - + double mean = 5.0; double variance = span.VarianceSIMD(mean); - + Assert.True(variance > 0); } @@ -379,7 +379,7 @@ public class SimdExtensionsTests { double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]; var span = new ReadOnlySpan(data); - + double stdDev = span.StdDevSIMD(); Assert.True(Math.Abs(stdDev - 2.138) < 0.01); } @@ -389,7 +389,7 @@ public class SimdExtensionsTests { double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]; var span = new ReadOnlySpan(data); - + double stdDev = span.StdDevSIMD(5.0); Assert.True(stdDev > 0); } @@ -556,14 +556,14 @@ public class SimdExtensionsTests public void SIMD_WorksWithTSeriesValues() { var series = new TSeries(100); - + for (int i = 0; i < 100; i++) { series.Add(DateTime.UtcNow.Ticks + i, i + 1.0); } var values = series.Values; - + double sum = values.SumSIMD(); double avg = values.AverageSIMD(); double min = values.MinSIMD(); @@ -587,7 +587,7 @@ public class SimdExtensionsTests var bars = gbm.Fetch(1000, startTime, interval); var closeValues = bars.Close.Values; - + double sum = closeValues.SumSIMD(); double avg = closeValues.AverageSIMD(); double min = closeValues.MinSIMD(); @@ -611,7 +611,7 @@ public class SimdExtensionsTests _ = closeValues.SumSIMD(); var sw = System.Diagnostics.Stopwatch.StartNew(); - + double sum = closeValues.SumSIMD(); double avg = closeValues.AverageSIMD(); double min = closeValues.MinSIMD(); @@ -619,7 +619,7 @@ public class SimdExtensionsTests var (minAlt, maxAlt) = closeValues.MinMaxSIMD(); double variance = closeValues.VarianceSIMD(); double stdDev = closeValues.StdDevSIMD(); - + sw.Stop(); Assert.True(sum > 0); @@ -630,8 +630,8 @@ public class SimdExtensionsTests Assert.Equal(max, maxAlt); Assert.True(variance > 0); Assert.True(stdDev > 0); - - Assert.True(sw.ElapsedMilliseconds < 50, + + Assert.True(sw.ElapsedMilliseconds < 50, $"SIMD operations took {sw.ElapsedMilliseconds}ms, expected < 50ms"); } @@ -640,12 +640,12 @@ public class SimdExtensionsTests { double[] data = [1.0, 2.0, 3.0]; var span = new ReadOnlySpan(data); - + Assert.Equal(6.0, span.SumSIMD()); Assert.Equal(1.0, span.MinSIMD()); Assert.Equal(3.0, span.MaxSIMD()); Assert.Equal(2.0, span.AverageSIMD()); - + var (min, max) = span.MinMaxSIMD(); Assert.Equal(1.0, min); Assert.Equal(3.0, max); diff --git a/lib/core/simd/SimdExtensions.md b/lib/core/simd/SimdExtensions.md index 9732f6ea..472f3fbf 100644 --- a/lib/core/simd/SimdExtensions.md +++ b/lib/core/simd/SimdExtensions.md @@ -1,52 +1,52 @@ -# 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 | -|--------|-------------| -| `ContainsNonFinite()` | Checks if span contains any non-finite values (NaN or Infinity). | -| `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. | -| `DotProduct()` | Calculates the dot product of two spans. | - -## 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(); - -// Check for valid data -bool hasInvalid = span.ContainsNonFinite(); - -// Calculate dot product -double dot = span.DotProduct(otherSpan); -``` +# 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 | +|--------|-------------| +| `ContainsNonFinite()` | Checks if span contains any non-finite values (NaN or Infinity). | +| `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. | +| `DotProduct()` | Calculates the dot product of two spans. | + +## 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(); + +// Check for valid data +bool hasInvalid = span.ContainsNonFinite(); + +// Calculate dot product +double dot = span.DotProduct(otherSpan); +``` diff --git a/lib/core/tbar/TBar.Tests.cs b/lib/core/tbar/TBar.Tests.cs index c010a193..5843e433 100644 --- a/lib/core/tbar/TBar.Tests.cs +++ b/lib/core/tbar/TBar.Tests.cs @@ -168,9 +168,9 @@ public class TBarTests { long time = DateTime.UtcNow.Ticks; var bar = new TBar(time, 100, 110, 90, 105, 1000); - + TValue tv = bar; - + Assert.Equal(time, tv.Time); Assert.Equal(105.0, tv.Value); } diff --git a/lib/core/tbar/TBar.md b/lib/core/tbar/TBar.md index e1e21c42..02a7daa9 100644 --- a/lib/core/tbar/TBar.md +++ b/lib/core/tbar/TBar.md @@ -1,112 +1,112 @@ -# TBar: OHLCV Bar Struct - -## What It Does - -`TBar` is a lightweight, immutable struct representing a single OHLCV (Open, High, Low, Close, Volume) bar. It serves as the fundamental unit for price data in QuanTAlib, designed to hold market data with minimal memory overhead while providing convenient accessors for common price derivations. - -## Design Philosophy - -Financial data processing often involves millions of bars. Storing these as classes would create massive GC pressure and memory fragmentation. `TBar` is designed as a **pure data struct** to ensure: - -* **Compactness**: Occupies exactly 48 bytes (1 `long` + 5 `double`s), fitting efficiently in memory. -* **Immutability**: Thread-safe by default; values cannot change once created. -* **Zero-Cost Abstractions**: Computed properties (like `HL2`) are calculated on-demand, requiring no extra storage. - -## How It Works - -`TBar` is a `readonly record struct` that stores: - -* **Time**: Timestamp in ticks. -* **Open, High, Low, Close**: Price components. -* **Volume**: Traded volume. - -It includes implicit conversions to `double` (defaulting to Close price) and `TValue` (Time + Close), allowing it to be used interchangeably with simpler types in many contexts. - -## Structure - -### Definition - -```csharp -public readonly record struct TBar(long Time, double Open, double High, double Low, double Close, double Volume); -``` - -### Core Properties - -| Property | Type | Description | -|----------|------|-------------| -| `Time` | `long` | Timestamp in ticks (UTC). | -| `Open` | `double` | Opening price. | -| `High` | `double` | Highest price. | -| `Low` | `double` | Lowest price. | -| `Close` | `double` | Closing price. | -| `Volume` | `double` | Traded volume. | - -### Computed Properties (Zero-Storage) - -| Property | Formula | Description | -|----------|---------|-------------| -| `HL2` | `(H + L) / 2` | Median Price. | -| `OC2` | `(O + C) / 2` | Midpoint Price. | -| `OHL3` | `(O + H + L) / 3` | Typical Price (Variant). | -| `HLC3` | `(H + L + C) / 3` | Typical Price. | -| `OHLC4` | `(O + H + L + C) / 4` | Weighted Close. | -| `HLCC4` | `(H + L + 2C) / 4` | Weighted Close (Variant). | - -### TValue Accessors - -Efficiently extracts components as `TValue` pairs: - -* `O`, `H`, `L`, `C`, `V` - -## Usage - -### Creating a Bar - -```csharp -var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000); -``` - -### Implicit Conversions - -```csharp -TBar bar = ...; - -// Treat as double (uses Close price) -double price = bar; - -// Treat as TValue (Time + Close) -TValue tv = bar; - -// Treat as DateTime -DateTime dt = bar; -``` - -### Using Computed Properties - -```csharp -// Calculate Typical Price on the fly -double typical = bar.HLC3; -``` - -## Performance Profile - -* **Memory**: 48 bytes per instance. -* **Allocation**: 0 bytes (Stack allocated). -* **Access**: Direct field access (no property overhead). - -## Integration - -`TBar` is the primary input for: - -* **TBarSeries**: A collection of bars. -* **Indicators**: Some indicators (like ATR) require full `TBar` input rather than just a single value. - -## Architecture Notes - -* **SkipLocalsInit**: Marked with `[SkipLocalsInit]` for performance in tight loops. -* **AggressiveInlining**: All computed properties are inlined to ensure they are as fast as writing the formula manually. - -## References - -* [OHLC Chart](https://en.wikipedia.org/wiki/Open-high-low-close_chart) -* [C# Record Structs](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record) +# TBar: OHLCV Bar Struct + +## What It Does + +`TBar` is a lightweight, immutable struct representing a single OHLCV (Open, High, Low, Close, Volume) bar. It serves as the fundamental unit for price data in QuanTAlib, designed to hold market data with minimal memory overhead while providing convenient accessors for common price derivations. + +## Design Philosophy + +Financial data processing often involves millions of bars. Storing these as classes would create massive GC pressure and memory fragmentation. `TBar` is designed as a **pure data struct** to ensure: + +* **Compactness**: Occupies exactly 48 bytes (1 `long` + 5 `double`s), fitting efficiently in memory. +* **Immutability**: Thread-safe by default; values cannot change once created. +* **Zero-Cost Abstractions**: Computed properties (like `HL2`) are calculated on-demand, requiring no extra storage. + +## How It Works + +`TBar` is a `readonly record struct` that stores: + +* **Time**: Timestamp in ticks. +* **Open, High, Low, Close**: Price components. +* **Volume**: Traded volume. + +It includes implicit conversions to `double` (defaulting to Close price) and `TValue` (Time + Close), allowing it to be used interchangeably with simpler types in many contexts. + +## Structure + +### Definition + +```csharp +public readonly record struct TBar(long Time, double Open, double High, double Low, double Close, double Volume); +``` + +### Core Properties + +| Property | Type | Description | +|----------|------|-------------| +| `Time` | `long` | Timestamp in ticks (UTC). | +| `Open` | `double` | Opening price. | +| `High` | `double` | Highest price. | +| `Low` | `double` | Lowest price. | +| `Close` | `double` | Closing price. | +| `Volume` | `double` | Traded volume. | + +### Computed Properties (Zero-Storage) + +| Property | Formula | Description | +|----------|---------|-------------| +| `HL2` | `(H + L) / 2` | Median Price. | +| `OC2` | `(O + C) / 2` | Midpoint Price. | +| `OHL3` | `(O + H + L) / 3` | Typical Price (Variant). | +| `HLC3` | `(H + L + C) / 3` | Typical Price. | +| `OHLC4` | `(O + H + L + C) / 4` | Weighted Close. | +| `HLCC4` | `(H + L + 2C) / 4` | Weighted Close (Variant). | + +### TValue Accessors + +Efficiently extracts components as `TValue` pairs: + +* `O`, `H`, `L`, `C`, `V` + +## Usage + +### Creating a Bar + +```csharp +var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000); +``` + +### Implicit Conversions + +```csharp +TBar bar = ...; + +// Treat as double (uses Close price) +double price = bar; + +// Treat as TValue (Time + Close) +TValue tv = bar; + +// Treat as DateTime +DateTime dt = bar; +``` + +### Using Computed Properties + +```csharp +// Calculate Typical Price on the fly +double typical = bar.HLC3; +``` + +## Performance Profile + +* **Memory**: 48 bytes per instance. +* **Allocation**: 0 bytes (Stack allocated). +* **Access**: Direct field access (no property overhead). + +## Integration + +`TBar` is the primary input for: + +* **TBarSeries**: A collection of bars. +* **Indicators**: Some indicators (like ATR) require full `TBar` input rather than just a single value. + +## Architecture Notes + +* **SkipLocalsInit**: Marked with `[SkipLocalsInit]` for performance in tight loops. +* **AggressiveInlining**: All computed properties are inlined to ensure they are as fast as writing the formula manually. + +## References + +* [OHLC Chart](https://en.wikipedia.org/wiki/Open-high-low-close_chart) +* [C# Record Structs](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record) diff --git a/lib/core/tbarseries/TBarSeries.Tests.cs b/lib/core/tbarseries/TBarSeries.Tests.cs index 07cbfdbe..ec104998 100644 --- a/lib/core/tbarseries/TBarSeries.Tests.cs +++ b/lib/core/tbarseries/TBarSeries.Tests.cs @@ -40,9 +40,9 @@ public class TBarSeriesTests { var series = new TBarSeries(); var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000); - + series.Add(bar, isNew: true); - + Assert.Single(series); Assert.Equal(105.0, series.Last.Close); } @@ -54,10 +54,10 @@ public class TBarSeriesTests long time = DateTime.UtcNow.Ticks; var bar1 = new TBar(time, 100, 110, 90, 105, 1000); var bar2 = new TBar(time, 100, 112, 90, 108, 1200); - + series.Add(bar1, isNew: true); series.Add(bar2, isNew: false); - + Assert.Single(series); Assert.Equal(108.0, series.Last.Close); Assert.Equal(112.0, series.Last.High); @@ -68,9 +68,9 @@ public class TBarSeriesTests { var series = new TBarSeries(); var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000); - + series.Add(bar, isNew: false); - + Assert.Single(series); } @@ -79,9 +79,9 @@ public class TBarSeriesTests { var series = new TBarSeries(); long time = DateTime.UtcNow.Ticks; - + series.Add(time, 100, 110, 90, 105, 1000, isNew: true); - + Assert.Single(series); Assert.Equal(time, series.Last.Time); } @@ -91,9 +91,9 @@ public class TBarSeriesTests { var series = new TBarSeries(); var dt = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc); - + series.Add(dt, 100, 110, 90, 105, 1000, isNew: true); - + Assert.Single(series); Assert.Equal(dt.Ticks, series.Last.Time); } @@ -108,9 +108,9 @@ public class TBarSeriesTests var lows = new double[] { 5, 15, 25 }; var closes = new double[] { 12, 22, 32 }; var volumes = new double[] { 100, 200, 300 }; - + series.Add(times, opens, highs, lows, closes, volumes); - + Assert.Equal(3, series.Count); Assert.Equal(10, series[0].Open); Assert.Equal(32, series[2].Close); @@ -121,15 +121,15 @@ public class TBarSeriesTests { var series = new TBarSeries(); var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000); - + series.Add(bar, isNew: true); - + Assert.Single(series.Open); Assert.Single(series.High); Assert.Single(series.Low); Assert.Single(series.Close); Assert.Single(series.Volume); - + Assert.Equal(100.0, series.Open.Last.Value); Assert.Equal(110.0, series.High.Last.Value); Assert.Equal(90.0, series.Low.Last.Value); @@ -143,7 +143,7 @@ public class TBarSeriesTests var series = new TBarSeries(); var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000); series.Add(bar, isNew: true); - + Assert.Same(series.Open, series.O); Assert.Same(series.High, series.H); Assert.Same(series.Low, series.L); @@ -155,7 +155,7 @@ public class TBarSeriesTests public void SubSeries_HaveCorrectNames() { var series = new TBarSeries(); - + Assert.Equal("Open", series.Open.Name); Assert.Equal("High", series.High.Name); Assert.Equal("Low", series.Low.Name); @@ -167,9 +167,9 @@ public class TBarSeriesTests public void Last_EmptySeries_ReturnsDefault() { var series = new TBarSeries(); - + var last = series.Last; - + Assert.Equal(0, last.Time); Assert.Equal(0.0, last.Open); Assert.Equal(0.0, last.Close); @@ -181,9 +181,9 @@ public class TBarSeriesTests var series = new TBarSeries(); series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); - + var last = series.Last; - + Assert.Equal(200, last.Time); Assert.Equal(22.0, last.Close); } @@ -201,7 +201,7 @@ public class TBarSeriesTests var series = new TBarSeries(); series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); - + Assert.Equal(200, series.LastTime); } @@ -218,7 +218,7 @@ public class TBarSeriesTests var series = new TBarSeries(); series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); - + Assert.Equal(20.0, series.LastOpen); } @@ -235,7 +235,7 @@ public class TBarSeriesTests var series = new TBarSeries(); series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); - + Assert.Equal(25.0, series.LastHigh); } @@ -252,7 +252,7 @@ public class TBarSeriesTests var series = new TBarSeries(); series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); - + Assert.Equal(15.0, series.LastLow); } @@ -269,7 +269,7 @@ public class TBarSeriesTests var series = new TBarSeries(); series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); - + Assert.Equal(22.0, series.LastClose); } @@ -286,7 +286,7 @@ public class TBarSeriesTests var series = new TBarSeries(); series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); - + Assert.Equal(200.0, series.LastVolume); } @@ -297,7 +297,7 @@ public class TBarSeriesTests series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); series.Add(300, 30, 35, 25, 32, 300); - + Assert.Equal(100, series[0].Time); Assert.Equal(10.0, series[0].Open); Assert.Equal(200, series[1].Time); @@ -310,12 +310,12 @@ public class TBarSeriesTests public void Count_ReturnsCorrectValue() { var series = new TBarSeries(); - + Assert.Empty(series); - + series.Add(100, 10, 15, 5, 12, 100); Assert.Single(series); - + series.Add(200, 20, 25, 15, 22, 200); Assert.Equal(2, series.Count); } @@ -327,9 +327,9 @@ public class TBarSeriesTests series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); series.Add(300, 30, 35, 25, 32, 300); - + var list = series.ToList(); - + Assert.Equal(3, list.Count); Assert.Equal(10.0, list[0].Open); Assert.Equal(22.0, list[1].Close); @@ -349,7 +349,7 @@ public class TBarSeriesTests { list.Add(item); } - + Assert.Equal(2, list.Count); } @@ -362,7 +362,7 @@ public class TBarSeriesTests var barToAdd = new TBar(100, 10, 15, 5, 12, 100); series.Add(barToAdd, isNew: true); - + Assert.NotNull(received); Assert.Equal(100, received.Value.Time); Assert.Equal(12.0, received.Value.Close); @@ -377,7 +377,7 @@ public class TBarSeriesTests series.Pub += (object? sender, in TBarEventArgs args) => received = args.Value; series.Add(100, 10, 18, 5, 15, 150, isNew: false); - + Assert.NotNull(received); Assert.Equal(15.0, received.Value.Close); Assert.Equal(18.0, received.Value.High); @@ -389,7 +389,7 @@ public class TBarSeriesTests var series = new TBarSeries(); series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); - + Assert.Equal(series.Open.Times[0], series.Close.Times[0]); Assert.Equal(series.High.Times[1], series.Volume.Times[1]); } @@ -398,11 +398,11 @@ public class TBarSeriesTests public void Add_MultipleBars_MaintainsOrder() { var series = new TBarSeries(); - + series.Add(100, 10, 15, 5, 12, 100); series.Add(200, 20, 25, 15, 22, 200); series.Add(300, 30, 35, 25, 32, 300); - + Assert.Equal(3, series.Count); Assert.Equal(100, series[0].Time); Assert.Equal(200, series[1].Time); diff --git a/lib/core/tbarseries/TBarSeries.md b/lib/core/tbarseries/TBarSeries.md index 3a66d197..471b7f22 100644 --- a/lib/core/tbarseries/TBarSeries.md +++ b/lib/core/tbarseries/TBarSeries.md @@ -1,121 +1,121 @@ -# TBarSeries: OHLCV Data Container - -## What It Does - -`TBarSeries` is a high-performance collection of OHLCV bars. It is the primary data structure for managing historical and real-time market data in QuanTAlib. It uses a **Structure of Arrays (SoA)** layout to optimize memory access and enable efficient SIMD operations across individual price components. - -## Design Philosophy - -A naive implementation of a bar series would be a `List`. However, this is inefficient for technical analysis. Most indicators only need one component at a time (e.g., SMA uses Close prices). Iterating over a `List` to get Close prices loads unnecessary Open, High, Low, and Volume data into the CPU cache, wasting bandwidth. - -`TBarSeries` solves this by storing each component in its own contiguous array. This allows: - -* **Component Views**: You can access `Close` prices as a `TSeries` without copying data. -* **Cache Efficiency**: Iterating over `Close` prices loads *only* Close prices. -* **Unified Time**: All component series share a single Time array, ensuring synchronization. - -## How It Works - -Internally, `TBarSeries` maintains six parallel lists: - -1. `_t` (Time) -2. `_o` (Open) -3. `_h` (High) -4. `_l` (Low) -5. `_c` (Close) -6. `_v` (Volume) - -It exposes these internal lists as `TSeries` properties (`Open`, `High`, `Low`, `Close`, `Volume`), which act as read-only views into the master data. - -## Structure - -### Definition - -```csharp -public class TBarSeries : IReadOnlyList -{ - // Component Views (TSeries) - 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)` | Adds a bar or updates the last one. | -| `Add(DateTime time, double o, double h, double l, double c, double v)` | 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 -bars.Add(new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000)); - -// Add raw values -bars.Add(DateTime.UtcNow, 100, 105, 95, 102, 1000); -``` - -### Accessing Data - -```csharp -// Get the last full bar -TBar lastBar = bars.Last; - -// Get the Close series (Zero-Copy) -TSeries closes = bars.Close; - -// Calculate SMA on Close prices -var sma = new Sma(14); -var result = sma.Calculate(bars.Close); -``` - -### Streaming Updates - -```csharp -// New minute starts -bars.Add(newBar, isNew: true); - -// Price updates within the same minute -bars.Add(updatedBar, isNew: false); // Updates the last bar in place -``` - -## Performance Profile - -* **Memory Layout**: SoA (Structure of Arrays). -* **Component Access**: Zero-copy `TSeries` views. -* **Iteration**: Cache-friendly for single-component analysis. - -## Integration - -`TBarSeries` is the standard input for multi-input indicators (like ATR, ADX) and the primary data source for trading strategies. - -* **Indicators**: Can be passed to indicators that require full bar data. -* **Strategies**: Provides the historical context needed for signal generation. - -## Architecture Notes - -* **Shared Storage**: The `TSeries` views (`Open`, `Close`, etc.) do not own their data; they point to the internal lists of the `TBarSeries`. This means modifying the `TBarSeries` automatically updates all views. -* **Synchronization**: Because all views share the same `_t` (Time) list, they are guaranteed to be perfectly synchronized. - -## References - -* [Structure of Arrays (SoA)](https://en.wikipedia.org/wiki/AOS_and_SOA) -* [Data Locality](https://gameprogrammingpatterns.com/data-locality.html) +# TBarSeries: OHLCV Data Container + +## What It Does + +`TBarSeries` is a high-performance collection of OHLCV bars. It is the primary data structure for managing historical and real-time market data in QuanTAlib. It uses a **Structure of Arrays (SoA)** layout to optimize memory access and enable efficient SIMD operations across individual price components. + +## Design Philosophy + +A naive implementation of a bar series would be a `List`. However, this is inefficient for technical analysis. Most indicators only need one component at a time (e.g., SMA uses Close prices). Iterating over a `List` to get Close prices loads unnecessary Open, High, Low, and Volume data into the CPU cache, wasting bandwidth. + +`TBarSeries` solves this by storing each component in its own contiguous array. This allows: + +* **Component Views**: You can access `Close` prices as a `TSeries` without copying data. +* **Cache Efficiency**: Iterating over `Close` prices loads *only* Close prices. +* **Unified Time**: All component series share a single Time array, ensuring synchronization. + +## How It Works + +Internally, `TBarSeries` maintains six parallel lists: + +1. `_t` (Time) +2. `_o` (Open) +3. `_h` (High) +4. `_l` (Low) +5. `_c` (Close) +6. `_v` (Volume) + +It exposes these internal lists as `TSeries` properties (`Open`, `High`, `Low`, `Close`, `Volume`), which act as read-only views into the master data. + +## Structure + +### Definition + +```csharp +public class TBarSeries : IReadOnlyList +{ + // Component Views (TSeries) + 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)` | Adds a bar or updates the last one. | +| `Add(DateTime time, double o, double h, double l, double c, double v)` | 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 +bars.Add(new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000)); + +// Add raw values +bars.Add(DateTime.UtcNow, 100, 105, 95, 102, 1000); +``` + +### Accessing Data + +```csharp +// Get the last full bar +TBar lastBar = bars.Last; + +// Get the Close series (Zero-Copy) +TSeries closes = bars.Close; + +// Calculate SMA on Close prices +var sma = new Sma(14); +var result = sma.Calculate(bars.Close); +``` + +### Streaming Updates + +```csharp +// New minute starts +bars.Add(newBar, isNew: true); + +// Price updates within the same minute +bars.Add(updatedBar, isNew: false); // Updates the last bar in place +``` + +## Performance Profile + +* **Memory Layout**: SoA (Structure of Arrays). +* **Component Access**: Zero-copy `TSeries` views. +* **Iteration**: Cache-friendly for single-component analysis. + +## Integration + +`TBarSeries` is the standard input for multi-input indicators (like ATR, ADX) and the primary data source for trading strategies. + +* **Indicators**: Can be passed to indicators that require full bar data. +* **Strategies**: Provides the historical context needed for signal generation. + +## Architecture Notes + +* **Shared Storage**: The `TSeries` views (`Open`, `Close`, etc.) do not own their data; they point to the internal lists of the `TBarSeries`. This means modifying the `TBarSeries` automatically updates all views. +* **Synchronization**: Because all views share the same `_t` (Time) list, they are guaranteed to be perfectly synchronized. + +## References + +* [Structure of Arrays (SoA)](https://en.wikipedia.org/wiki/AOS_and_SOA) +* [Data Locality](https://gameprogrammingpatterns.com/data-locality.html) diff --git a/lib/core/tseries/ITValuePublisher.cs b/lib/core/tseries/ITValuePublisher.cs index d44cb154..e1a3d795 100644 --- a/lib/core/tseries/ITValuePublisher.cs +++ b/lib/core/tseries/ITValuePublisher.cs @@ -16,9 +16,9 @@ public readonly struct TValueEventArgs // Performance-focused event args struct; not derived from EventArgs by design. // We intentionally deviate from the standard EventArgs pattern here for perf. +// MA0046 suppressed: struct-based args avoid heap allocations in high-frequency events. #pragma warning disable MA0046 // The second parameter must be of type 'System.EventArgs' or a derived type public delegate void TValuePublishedHandler(object? sender, in TValueEventArgs args); -#pragma warning restore MA0046 /// /// Interface for objects that publish TValue updates. @@ -30,3 +30,4 @@ public interface ITValuePublisher /// event TValuePublishedHandler? Pub; } +#pragma warning restore MA0046 diff --git a/lib/core/tseries/TSeries.Tests.cs b/lib/core/tseries/TSeries.Tests.cs index bd68482d..5a1c065c 100644 --- a/lib/core/tseries/TSeries.Tests.cs +++ b/lib/core/tseries/TSeries.Tests.cs @@ -298,7 +298,7 @@ public class TSeriesTests { var series = new TSeries(); TValue? received = null; - series.Pub += (object? sender, TValueEventArgs args) => received = args.Value; + series.Pub += (object? sender, in TValueEventArgs args) => received = args.Value; series.Add(100, 42.0); @@ -313,7 +313,7 @@ public class TSeriesTests var series = new TSeries(); TValue? received = null; series.Add(100, 42.0); - series.Pub += (object? sender, TValueEventArgs args) => received = args.Value; + series.Pub += (object? sender, in TValueEventArgs args) => received = args.Value; series.Add(100, 43.0, isNew: false); diff --git a/lib/core/tseries/TSeries.md b/lib/core/tseries/TSeries.md index 76dc232d..04c7d965 100644 --- a/lib/core/tseries/TSeries.md +++ b/lib/core/tseries/TSeries.md @@ -1,114 +1,114 @@ -# TSeries: Time Series Data Container - -## What It Does - -`TSeries` is a high-performance, memory-efficient container for time-series data. Unlike standard collections (like `List`), it uses a **Structure of Arrays (SoA)** layout internally. This means it stores timestamps and values in separate contiguous arrays, optimizing memory access patterns for numerical processing and SIMD vectorization. - -## Design Philosophy - -Standard object-oriented collections (Array of Structures - AoS) are cache-inefficient for numerical algorithms. When calculating a moving average, the CPU only needs the values, but an AoS layout forces it to load interleaved timestamps into the cache, wasting bandwidth. - -`TSeries` solves this by decoupling time and value storage: - -* **Cache Locality**: Iterating over values loads only values. -* **SIMD Readiness**: The internal value array can be exposed directly as a `Span` for AVX/SSE processing. -* **Zero-Copy Views**: Data is accessed without defensive copying, ensuring maximum throughput. - -## How It Works - -`TSeries` maintains two parallel internal lists: - -1. `List _t`: Stores timestamps. -2. `List _v`: Stores values. - -It implements `IReadOnlyList`, allowing it to be treated as a standard collection of `TValue` structs when needed, but its true power lies in its column-oriented properties (`Values`, `Times`). - -## Structure - -### Definition - -```csharp -public class TSeries : IReadOnlyList, ITValuePublisher -``` - -### Core Properties - -| Property | Type | Description | -|----------|------|-------------| -| `Values` | `ReadOnlySpan` | Direct access to the value array (SIMD-ready). | -| `Times` | `ReadOnlySpan` | Direct access to the timestamp array. | -| `Last` | `TValue` | The most recent time-value pair. | -| `Count` | `int` | Number of elements in the series. | -| `Name` | `string` | Optional identifier for the series. | - -### Events - -| Event | Type | Description | -|-------|------|-------------| -| `Pub` | `Action` | Fired whenever a new value is added or updated. | - -## Usage - -### Creating and Populating - -```csharp -var series = new TSeries(); - -// Add a new bar (isNew = true by default) -series.Add(DateTime.UtcNow, 100.0); - -// Add multiple values -series.Add(new List { 1.0, 2.0, 3.0 }); -``` - -### Streaming Updates (Real-time) - -`TSeries` supports "bar updates" where the last value changes until the bar closes. - -```csharp -// New minute starts -series.Add(time, 100.0, isNew: true); - -// Price updates within the same minute -series.Add(time, 101.0, isNew: false); // Overwrites last value -series.Add(time, 102.0, isNew: false); // Overwrites last value -``` - -### SIMD Processing - -```csharp -// Calculate average using SIMD (via Span) -double sum = 0; -foreach (var v in series.Values) { sum += v; } // Compiler vectorizes this -``` - -### Reactive Subscription - -```csharp -series.Pub += (item) => Console.WriteLine($"New value: {item}"); -``` - -## Performance Profile - -* **Memory Layout**: SoA (Structure of Arrays). -* **Access Speed**: O(1) for random access. -* **Iteration**: Cache-friendly linear scan. -* **SIMD**: Fully supported via `Values` span. - -## Integration - -`TSeries` is the standard output format for all indicators in QuanTAlib. - -* **Input**: Can be fed into indicators via `Update(TSeries)`. -* **Output**: Indicators return `TSeries` from their `Calculate` methods. -* **Visualization**: Easily mappable to charting libraries due to separate Time/Value arrays. - -## Architecture Notes - -* **CollectionsMarshal**: Uses `CollectionsMarshal.AsSpan` to expose internal list storage as spans without copying. This is unsafe if the list is modified during span access, but provides maximum performance for single-threaded algorithms. -* **Virtual Methods**: `Add` is virtual to allow derived classes (like `TBarSeries` components) to intercept updates if necessary. - -## References - -* [Data-Oriented Design](https://en.wikipedia.org/wiki/Data-oriented_design) -* [SIMD in .NET](https://learn.microsoft.com/en-us/dotnet/standard/simd) +# TSeries: Time Series Data Container + +## What It Does + +`TSeries` is a high-performance, memory-efficient container for time-series data. Unlike standard collections (like `List`), it uses a **Structure of Arrays (SoA)** layout internally. This means it stores timestamps and values in separate contiguous arrays, optimizing memory access patterns for numerical processing and SIMD vectorization. + +## Design Philosophy + +Standard object-oriented collections (Array of Structures - AoS) are cache-inefficient for numerical algorithms. When calculating a moving average, the CPU only needs the values, but an AoS layout forces it to load interleaved timestamps into the cache, wasting bandwidth. + +`TSeries` solves this by decoupling time and value storage: + +* **Cache Locality**: Iterating over values loads only values. +* **SIMD Readiness**: The internal value array can be exposed directly as a `Span` for AVX/SSE processing. +* **Zero-Copy Views**: Data is accessed without defensive copying, ensuring maximum throughput. + +## How It Works + +`TSeries` maintains two parallel internal lists: + +1. `List _t`: Stores timestamps. +2. `List _v`: Stores values. + +It implements `IReadOnlyList`, allowing it to be treated as a standard collection of `TValue` structs when needed, but its true power lies in its column-oriented properties (`Values`, `Times`). + +## Structure + +### Definition + +```csharp +public class TSeries : IReadOnlyList, ITValuePublisher +``` + +### Core Properties + +| Property | Type | Description | +|----------|------|-------------| +| `Values` | `ReadOnlySpan` | Direct access to the value array (SIMD-ready). | +| `Times` | `ReadOnlySpan` | Direct access to the timestamp array. | +| `Last` | `TValue` | The most recent time-value pair. | +| `Count` | `int` | Number of elements in the series. | +| `Name` | `string` | Optional identifier for the series. | + +### Events + +| Event | Type | Description | +|-------|------|-------------| +| `Pub` | `Action` | Fired whenever a new value is added or updated. | + +## Usage + +### Creating and Populating + +```csharp +var series = new TSeries(); + +// Add a new bar (isNew = true by default) +series.Add(DateTime.UtcNow, 100.0); + +// Add multiple values +series.Add(new List { 1.0, 2.0, 3.0 }); +``` + +### Streaming Updates (Real-time) + +`TSeries` supports "bar updates" where the last value changes until the bar closes. + +```csharp +// New minute starts +series.Add(time, 100.0, isNew: true); + +// Price updates within the same minute +series.Add(time, 101.0, isNew: false); // Overwrites last value +series.Add(time, 102.0, isNew: false); // Overwrites last value +``` + +### SIMD Processing + +```csharp +// Calculate average using SIMD (via Span) +double sum = 0; +foreach (var v in series.Values) { sum += v; } // Compiler vectorizes this +``` + +### Reactive Subscription + +```csharp +series.Pub += (item) => Console.WriteLine($"New value: {item}"); +``` + +## Performance Profile + +* **Memory Layout**: SoA (Structure of Arrays). +* **Access Speed**: O(1) for random access. +* **Iteration**: Cache-friendly linear scan. +* **SIMD**: Fully supported via `Values` span. + +## Integration + +`TSeries` is the standard output format for all indicators in QuanTAlib. + +* **Input**: Can be fed into indicators via `Update(TSeries)`. +* **Output**: Indicators return `TSeries` from their `Calculate` methods. +* **Visualization**: Easily mappable to charting libraries due to separate Time/Value arrays. + +## Architecture Notes + +* **CollectionsMarshal**: Uses `CollectionsMarshal.AsSpan` to expose internal list storage as spans without copying. This is unsafe if the list is modified during span access, but provides maximum performance for single-threaded algorithms. +* **Virtual Methods**: `Add` is virtual to allow derived classes (like `TBarSeries` components) to intercept updates if necessary. + +## References + +* [Data-Oriented Design](https://en.wikipedia.org/wiki/Data-oriented_design) +* [SIMD in .NET](https://learn.microsoft.com/en-us/dotnet/standard/simd) diff --git a/lib/core/tvalue/TValue.Tests.cs b/lib/core/tvalue/TValue.Tests.cs index df869b2d..cf38b587 100644 --- a/lib/core/tvalue/TValue.Tests.cs +++ b/lib/core/tvalue/TValue.Tests.cs @@ -54,9 +54,9 @@ namespace QuanTAlib.Tests public void ImplicitConversion_ToDouble_ReturnsValue() { var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0); - + double val = tValue; - + Assert.Equal(42.0, val); } diff --git a/lib/core/tvalue/TValue.md b/lib/core/tvalue/TValue.md index f34743a2..6bb2d419 100644 --- a/lib/core/tvalue/TValue.md +++ b/lib/core/tvalue/TValue.md @@ -1,99 +1,99 @@ -# TValue: Time-Value Pair - -## What It Does - -`TValue` is the fundamental atomic unit of data in QuanTAlib. It represents a single point in a time series, consisting of a timestamp and a double-precision floating-point value. It serves as the standard input and output format for all indicators and data streams. - -## Design Philosophy - -In high-frequency trading and quantitative analysis, memory allocation is a critical bottleneck. `TValue` is designed as a **lightweight, immutable struct** to ensure: - -* **Zero Heap Allocation**: Being a struct, it lives on the stack or embedded in arrays, avoiding Garbage Collector (GC) pressure. -* **Thread Safety**: Immutability guarantees safe concurrent access. -* **Minimal Footprint**: Occupies exactly 16 bytes (8 bytes for `long` Time + 8 bytes for `double` Value), fitting efficiently in CPU cache lines. - -## How It Works - -`TValue` is implemented as a `readonly record struct`. It encapsulates: - -* **Time**: A `long` representing ticks (UTC). -* **Value**: A `double` representing the data magnitude. - -It supports implicit conversions to `double` (extracting the value) and `DateTime` (extracting the time), making it syntactically fluid to use in calculations. - -## Structure - -### Definition - -```csharp -public readonly record struct TValue(long Time, double Value); -``` - -### Properties - -| Property | Type | Description | -|----------|------|-------------| -| `Time` | `long` | Timestamp in ticks (UTC). | -| `Value` | `double` | The data value. | -| `AsDateTime` | `DateTime` | Helper to view `Time` as a `DateTime` object. | - -### Constructors - -| Constructor | Description | -|-------------|-------------| -| `new TValue(long time, double value)` | Creates a TValue from raw ticks. | -| `new TValue(DateTime time, double value)` | Creates a TValue from a DateTime object. | - -## Usage - -### Creating TValues - -```csharp -// From DateTime -var t1 = new TValue(DateTime.UtcNow, 100.5); - -// From Ticks -var t2 = new TValue(DateTime.UtcNow.Ticks, 100.5); -``` - -### Implicit Conversions - -```csharp -TValue tv = new TValue(DateTime.UtcNow, 42.0); - -// Implicitly converts to double -double val = tv; // 42.0 - -// Implicitly converts to DateTime -DateTime dt = tv; // DateTime object -``` - -### String Representation - -```csharp -Console.WriteLine(tv); // Output: "[2024-01-01 12:00:00, 42.00]" -``` - -## Performance Profile - -* **Memory**: 16 bytes per instance. -* **Allocation**: 0 bytes (Stack allocated). -* **Copying**: Cheap (fits in two 64-bit registers). - -## Integration - -`TValue` is the primary currency of the library: - -* **Indicators**: `Update(TValue input)` accepts it. -* **Series**: `TSeries` stores collections of it. -* **Events**: `ITValuePublisher` broadcasts it. - -## Architecture Notes - -* **SkipLocalsInit**: The struct is marked with `[SkipLocalsInit]` to suppress zero-initialization of locals, squeezing out nanoseconds in tight loops. -* **AggressiveInlining**: All accessors and operators are inlined to ensure zero abstraction penalty. - -## References - -* [Structure of Arrays (SoA)](https://en.wikipedia.org/wiki/AOS_and_SOA) -* [C# Struct Performance](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct) +# TValue: Time-Value Pair + +## What It Does + +`TValue` is the fundamental atomic unit of data in QuanTAlib. It represents a single point in a time series, consisting of a timestamp and a double-precision floating-point value. It serves as the standard input and output format for all indicators and data streams. + +## Design Philosophy + +In high-frequency trading and quantitative analysis, memory allocation is a critical bottleneck. `TValue` is designed as a **lightweight, immutable struct** to ensure: + +* **Zero Heap Allocation**: Being a struct, it lives on the stack or embedded in arrays, avoiding Garbage Collector (GC) pressure. +* **Thread Safety**: Immutability guarantees safe concurrent access. +* **Minimal Footprint**: Occupies exactly 16 bytes (8 bytes for `long` Time + 8 bytes for `double` Value), fitting efficiently in CPU cache lines. + +## How It Works + +`TValue` is implemented as a `readonly record struct`. It encapsulates: + +* **Time**: A `long` representing ticks (UTC). +* **Value**: A `double` representing the data magnitude. + +It supports implicit conversions to `double` (extracting the value) and `DateTime` (extracting the time), making it syntactically fluid to use in calculations. + +## Structure + +### Definition + +```csharp +public readonly record struct TValue(long Time, double Value); +``` + +### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `Time` | `long` | Timestamp in ticks (UTC). | +| `Value` | `double` | The data value. | +| `AsDateTime` | `DateTime` | Helper to view `Time` as a `DateTime` object. | + +### Constructors + +| Constructor | Description | +|-------------|-------------| +| `new TValue(long time, double value)` | Creates a TValue from raw ticks. | +| `new TValue(DateTime time, double value)` | Creates a TValue from a DateTime object. | + +## Usage + +### Creating TValues + +```csharp +// From DateTime +var t1 = new TValue(DateTime.UtcNow, 100.5); + +// From Ticks +var t2 = new TValue(DateTime.UtcNow.Ticks, 100.5); +``` + +### Implicit Conversions + +```csharp +TValue tv = new TValue(DateTime.UtcNow, 42.0); + +// Implicitly converts to double +double val = tv; // 42.0 + +// Implicitly converts to DateTime +DateTime dt = tv; // DateTime object +``` + +### String Representation + +```csharp +Console.WriteLine(tv); // Output: "[2024-01-01 12:00:00, 42.00]" +``` + +## Performance Profile + +* **Memory**: 16 bytes per instance. +* **Allocation**: 0 bytes (Stack allocated). +* **Copying**: Cheap (fits in two 64-bit registers). + +## Integration + +`TValue` is the primary currency of the library: + +* **Indicators**: `Update(TValue input)` accepts it. +* **Series**: `TSeries` stores collections of it. +* **Events**: `ITValuePublisher` broadcasts it. + +## Architecture Notes + +* **SkipLocalsInit**: The struct is marked with `[SkipLocalsInit]` to suppress zero-initialization of locals, squeezing out nanoseconds in tight loops. +* **AggressiveInlining**: All accessors and operators are inlined to ensure zero abstraction penalty. + +## References + +* [Structure of Arrays (SoA)](https://en.wikipedia.org/wiki/AOS_and_SOA) +* [C# Struct Performance](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct) diff --git a/lib/core/tvalue/tvalue.cs b/lib/core/tvalue/tvalue.cs index 450ef84e..6f6a2d1b 100644 --- a/lib/core/tvalue/tvalue.cs +++ b/lib/core/tvalue/tvalue.cs @@ -14,7 +14,7 @@ public readonly record struct TValue(long Time, double Value) public DateTime AsDateTime => new(Time, DateTimeKind.Utc); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TValue(DateTime time, double value) + public TValue(DateTime time, double value) : this(time.Kind == DateTimeKind.Utc ? time.Ticks : time.ToUniversalTime().Ticks, value) { } diff --git a/lib/feeds/IFeed.cs b/lib/feeds/IFeed.cs index f40d0ff8..d3b7ba54 100644 --- a/lib/feeds/IFeed.cs +++ b/lib/feeds/IFeed.cs @@ -1,35 +1,35 @@ -namespace QuanTAlib; - -/// -/// Interface for data feeds that provide TBar (OHLCV) data. -/// Implementations include synthetic generators (GBM), API-based feeds (AlphaVantage), -/// file readers (CSV), and real-time streams (WebSocket). -/// -public interface IFeed -{ - /// - /// Gets the next bar from the feed with full bidirectional control. - /// - /// - /// Input: Request for new bar (true) or update current bar (false). - /// Output: Actual behavior - may differ if feed cannot honor request (e.g., end of data). - /// - /// The bar (new or updated) - TBar Next(ref bool isNew); - - /// - /// Gets the next bar from the feed with simple control. - /// - /// Request for new bar (true) or update current bar (false). Defaults to true. - /// The bar (new or updated) - TBar Next(bool isNew = true); - - /// - /// Gets multiple bars in batch with explicit time parameters. - /// - /// Number of bars to retrieve - /// Starting timestamp for first bar (in ticks) - /// Time interval between bars - /// Series containing the requested bars - TBarSeries Fetch(int count, long startTime, TimeSpan interval); -} +namespace QuanTAlib; + +/// +/// Interface for data feeds that provide TBar (OHLCV) data. +/// Implementations include synthetic generators (GBM), API-based feeds (AlphaVantage), +/// file readers (CSV), and real-time streams (WebSocket). +/// +public interface IFeed +{ + /// + /// Gets the next bar from the feed with full bidirectional control. + /// + /// + /// Input: Request for new bar (true) or update current bar (false). + /// Output: Actual behavior - may differ if feed cannot honor request (e.g., end of data). + /// + /// The bar (new or updated) + TBar Next(ref bool isNew); + + /// + /// Gets the next bar from the feed with simple control. + /// + /// Request for new bar (true) or update current bar (false). Defaults to true. + /// The bar (new or updated) + TBar Next(bool isNew = true); + + /// + /// Gets multiple bars in batch with explicit time parameters. + /// + /// Number of bars to retrieve + /// Starting timestamp for first bar (in ticks) + /// Time interval between bars + /// Series containing the requested bars + TBarSeries Fetch(int count, long startTime, TimeSpan interval); +} diff --git a/lib/feeds/IFeed.md b/lib/feeds/IFeed.md index 4b52fa5b..c00ec34e 100644 --- a/lib/feeds/IFeed.md +++ b/lib/feeds/IFeed.md @@ -1,45 +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). +# 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.Tests.cs b/lib/feeds/csv/CsvFeed.Tests.cs index 5aaa41c7..e8750dae 100644 --- a/lib/feeds/csv/CsvFeed.Tests.cs +++ b/lib/feeds/csv/CsvFeed.Tests.cs @@ -1,296 +1,296 @@ - -namespace QuanTAlib.Tests; - -public class CsvFeedTests -{ - private const string TestCsvPath = "daily_IBM.csv"; - - [Fact] - public void Constructor_ValidFile_LoadsData() - { - var feed = new CsvFeed(TestCsvPath); - Assert.NotNull(feed); - } - - [Fact] - public void Constructor_NonExistentFile_ThrowsFileNotFoundException() - { - Assert.Throws(() => new CsvFeed("nonexistent.csv")); - } - - [Fact] - public void Constructor_NullPath_ThrowsArgumentException() - { - Assert.Throws(() => new CsvFeed(null!)); - } - - [Fact] - public void Constructor_EmptyPath_ThrowsArgumentException() - { - Assert.Throws(() => new CsvFeed("")); - } - - [Fact] - public void Next_StreamsDataChronologically() - { - var feed = new CsvFeed(TestCsvPath); - - // Get first bar - var bar1 = feed.Next(isNew: true); - Assert.True(bar1.Time > 0); - - // Get second bar - should be later in time - var bar2 = feed.Next(isNew: true); - Assert.True(bar2.Time > bar1.Time); - - // Get third bar - var bar3 = feed.Next(isNew: true); - Assert.True(bar3.Time > bar2.Time); - } - - [Fact] - public void Next_WithRefParameter_StreamsCorrectly() - { - var feed = new CsvFeed(TestCsvPath); - - bool isNew = true; - var bar1 = feed.Next(ref isNew); - Assert.True(isNew); // Should still be true - Assert.True(bar1.Time > 0); - - isNew = true; - var bar2 = feed.Next(ref isNew); - Assert.True(isNew); - Assert.True(bar2.Time > bar1.Time); - } - - [Fact] - public void Next_UpdateCurrentBar_ReturnsSameBar() - { - var feed = new CsvFeed(TestCsvPath); - - // Get first bar - var bar1 = feed.Next(isNew: true); - - // Update current bar (should return same bar) - var bar2 = feed.Next(isNew: false); - Assert.Equal(bar1.Time, bar2.Time); - Assert.Equal(bar1.Close, bar2.Close); - - // Get next bar - var bar3 = feed.Next(isNew: true); - Assert.True(bar3.Time > bar1.Time); - } - - [Fact] - public void Next_EndOfData_SignalsNoMoreData() - { - var feed = new CsvFeed(TestCsvPath); - - // Stream through all data - TBar lastBar = default; - bool isNew = true; - int count = 0; - - while (isNew && count < 200) // Safety limit - { - lastBar = feed.Next(ref isNew); - count++; - } - - // Should have reached end and isNew should be false - Assert.False(isNew); - Assert.True(lastBar.Time > 0); - - // Calling again should return same bar with isNew=false - isNew = true; - var finalBar = feed.Next(ref isNew); - Assert.False(isNew); - Assert.Equal(lastBar.Time, finalBar.Time); - } - - [Fact] - public void Fetch_ReturnsCorrectNumberOfBars() - { - var feed = new CsvFeed(TestCsvPath); - - var startTime = new DateTime(2025, 7, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; - var interval = TimeSpan.FromDays(1); - - var series = feed.Fetch(10, startTime, interval); - - Assert.True(series.Count > 0); - Assert.True(series.Count <= 10); - } - - [Fact] - public void Fetch_InvalidCount_ThrowsArgumentException() - { - var feed = new CsvFeed(TestCsvPath); - - var startTime = DateTime.UtcNow.Ticks; - var interval = TimeSpan.FromDays(1); - - Assert.Throws(() => feed.Fetch(0, startTime, interval)); - Assert.Throws(() => feed.Fetch(-1, startTime, interval)); - } - - [Fact] - public void Fetch_ResetsStreamingPosition() - { - var feed = new CsvFeed(TestCsvPath); - - // Stream a few bars - feed.Next(isNew: true); - feed.Next(isNew: true); - feed.Next(isNew: true); - - // Fetch from start - var startTime = new DateTime(2025, 7, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; - feed.Fetch(5, startTime, TimeSpan.FromDays(1)); - - // Next should now stream from fetched position - var bar = feed.Next(isNew: true); - Assert.True(bar.Time >= startTime); - } - - [Fact] - public void LoadFromCsv_ParsesValuesCorrectly() - { - var feed = new CsvFeed(TestCsvPath); - - // Get first bar (oldest in chronological order) - var bar = feed.Next(isNew: true); - - // Verify it has valid OHLCV data - Assert.True(bar.Open > 0); - Assert.True(bar.High >= bar.Open); - Assert.True(bar.High >= bar.Close); - Assert.True(bar.Low <= bar.Open); - Assert.True(bar.Low <= bar.Close); - Assert.True(bar.Close > 0); - Assert.True(bar.Volume > 0); - } - - [Fact] - public void LoadFromCsv_DataInChronologicalOrder() - { - var feed = new CsvFeed(TestCsvPath); - - var bars = new List(); - bool isNew = true; - - // Collect first 10 bars - for (int i = 0; i < 10 && isNew; i++) - { - bars.Add(feed.Next(ref isNew)); - } - - // Verify chronological order (each bar later than previous) - for (int i = 1; i < bars.Count; i++) - { - Assert.True(bars[i].Time > bars[i - 1].Time, - $"Bar {i} time ({bars[i].AsDateTime}) should be after bar {i-1} time ({bars[i-1].AsDateTime})"); - } - } - - [Fact] - public void CsvFeed_WorksWithIFeedInterface() - { - IFeed feed = new CsvFeed(TestCsvPath); - - var bar1 = feed.Next(isNew: true); - Assert.True(bar1.Time > 0); - - var bar2 = feed.Next(isNew: true); - Assert.True(bar2.Time > bar1.Time); - } - - [Fact] - public void Next_MixedNewAndUpdate_WorksCorrectly() - { - var feed = new CsvFeed(TestCsvPath); - - var bar1 = feed.Next(isNew: true); - var bar1Update = feed.Next(isNew: false); - Assert.Equal(bar1.Time, bar1Update.Time); - - var bar2 = feed.Next(isNew: true); - Assert.True(bar2.Time > bar1.Time); - - var bar2Update = feed.Next(isNew: false); - Assert.Equal(bar2.Time, bar2Update.Time); - - var bar3 = feed.Next(isNew: true); - Assert.True(bar3.Time > bar2.Time); - } - - [Fact] - public void Fetch_WithEarlyStartTime_ReturnsData() - { - var feed = new CsvFeed(TestCsvPath); - - // Start from very early date (before any data) - var startTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; - var series = feed.Fetch(5, startTime, TimeSpan.FromDays(1)); - - // Should return data starting from first available bar - Assert.True(series.Count > 0); - } - - [Fact] - public void Fetch_WithFutureStartTime_ReturnsEmpty() - { - var feed = new CsvFeed(TestCsvPath); - - // Start from future date (after all data) - var startTime = new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; - var series = feed.Fetch(5, startTime, TimeSpan.FromDays(1)); - - // Should return empty or minimal data - Assert.True(series.Count == 0); - } - - [Fact] - public void Fetch_HandlesGapsCorrectly() - { - string tempCsv = Path.GetTempFileName() + ".csv"; - try - { - // Create CSV with gaps - // Date, Open, High, Low, Close, Volume - // 2023-01-01 (Sunday) - // 2023-01-02 (Monday) - // 2023-01-04 (Wednesday) - Gap of Tuesday - // 2023-01-05 (Thursday) - var lines = new[] - { - "Date,Open,High,Low,Close,Volume", - "2023-01-05,103,104,102,103,1000", - "2023-01-04,102,103,101,102,1000", - "2023-01-02,101,102,100,101,1000", - "2023-01-01,100,101,99,100,1000" - }; - File.WriteAllLines(tempCsv, lines); - - var feed = new CsvFeed(tempCsv); - var startTime = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; - var interval = TimeSpan.FromDays(1); - - // Fetch 5 bars. Should get 4 bars (Jan 1, 2, 4, 5). - var series = feed.Fetch(10, startTime, interval); - - Assert.Equal(4, series.Count); - Assert.Equal(startTime, series[0].Time); // Jan 1 - Assert.Equal(startTime + interval.Ticks, series[1].Time); // Jan 2 - // Gap here - Assert.Equal(startTime + 3 * interval.Ticks, series[2].Time); // Jan 4 - Assert.Equal(startTime + 4 * interval.Ticks, series[3].Time); // Jan 5 - } - finally - { - if (File.Exists(tempCsv)) - File.Delete(tempCsv); - } - } -} + +namespace QuanTAlib.Tests; + +public class CsvFeedTests +{ + private const string TestCsvPath = "daily_IBM.csv"; + + [Fact] + public void Constructor_ValidFile_LoadsData() + { + var feed = new CsvFeed(TestCsvPath); + Assert.NotNull(feed); + } + + [Fact] + public void Constructor_NonExistentFile_ThrowsFileNotFoundException() + { + Assert.Throws(() => new CsvFeed("nonexistent.csv")); + } + + [Fact] + public void Constructor_NullPath_ThrowsArgumentException() + { + Assert.Throws(() => new CsvFeed(null!)); + } + + [Fact] + public void Constructor_EmptyPath_ThrowsArgumentException() + { + Assert.Throws(() => new CsvFeed("")); + } + + [Fact] + public void Next_StreamsDataChronologically() + { + var feed = new CsvFeed(TestCsvPath); + + // Get first bar + var bar1 = feed.Next(isNew: true); + Assert.True(bar1.Time > 0); + + // Get second bar - should be later in time + var bar2 = feed.Next(isNew: true); + Assert.True(bar2.Time > bar1.Time); + + // Get third bar + var bar3 = feed.Next(isNew: true); + Assert.True(bar3.Time > bar2.Time); + } + + [Fact] + public void Next_WithRefParameter_StreamsCorrectly() + { + var feed = new CsvFeed(TestCsvPath); + + bool isNew = true; + var bar1 = feed.Next(ref isNew); + Assert.True(isNew); // Should still be true + Assert.True(bar1.Time > 0); + + isNew = true; + var bar2 = feed.Next(ref isNew); + Assert.True(isNew); + Assert.True(bar2.Time > bar1.Time); + } + + [Fact] + public void Next_UpdateCurrentBar_ReturnsSameBar() + { + var feed = new CsvFeed(TestCsvPath); + + // Get first bar + var bar1 = feed.Next(isNew: true); + + // Update current bar (should return same bar) + var bar2 = feed.Next(isNew: false); + Assert.Equal(bar1.Time, bar2.Time); + Assert.Equal(bar1.Close, bar2.Close); + + // Get next bar + var bar3 = feed.Next(isNew: true); + Assert.True(bar3.Time > bar1.Time); + } + + [Fact] + public void Next_EndOfData_SignalsNoMoreData() + { + var feed = new CsvFeed(TestCsvPath); + + // Stream through all data + TBar lastBar = default; + bool isNew = true; + int count = 0; + + while (isNew && count < 200) // Safety limit + { + lastBar = feed.Next(ref isNew); + count++; + } + + // Should have reached end and isNew should be false + Assert.False(isNew); + Assert.True(lastBar.Time > 0); + + // Calling again should return same bar with isNew=false + isNew = true; + var finalBar = feed.Next(ref isNew); + Assert.False(isNew); + Assert.Equal(lastBar.Time, finalBar.Time); + } + + [Fact] + public void Fetch_ReturnsCorrectNumberOfBars() + { + var feed = new CsvFeed(TestCsvPath); + + var startTime = new DateTime(2025, 7, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + var interval = TimeSpan.FromDays(1); + + var series = feed.Fetch(10, startTime, interval); + + Assert.True(series.Count > 0); + Assert.True(series.Count <= 10); + } + + [Fact] + public void Fetch_InvalidCount_ThrowsArgumentException() + { + var feed = new CsvFeed(TestCsvPath); + + var startTime = DateTime.UtcNow.Ticks; + var interval = TimeSpan.FromDays(1); + + Assert.Throws(() => feed.Fetch(0, startTime, interval)); + Assert.Throws(() => feed.Fetch(-1, startTime, interval)); + } + + [Fact] + public void Fetch_ResetsStreamingPosition() + { + var feed = new CsvFeed(TestCsvPath); + + // Stream a few bars + feed.Next(isNew: true); + feed.Next(isNew: true); + feed.Next(isNew: true); + + // Fetch from start + var startTime = new DateTime(2025, 7, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + feed.Fetch(5, startTime, TimeSpan.FromDays(1)); + + // Next should now stream from fetched position + var bar = feed.Next(isNew: true); + Assert.True(bar.Time >= startTime); + } + + [Fact] + public void LoadFromCsv_ParsesValuesCorrectly() + { + var feed = new CsvFeed(TestCsvPath); + + // Get first bar (oldest in chronological order) + var bar = feed.Next(isNew: true); + + // Verify it has valid OHLCV data + Assert.True(bar.Open > 0); + Assert.True(bar.High >= bar.Open); + Assert.True(bar.High >= bar.Close); + Assert.True(bar.Low <= bar.Open); + Assert.True(bar.Low <= bar.Close); + Assert.True(bar.Close > 0); + Assert.True(bar.Volume > 0); + } + + [Fact] + public void LoadFromCsv_DataInChronologicalOrder() + { + var feed = new CsvFeed(TestCsvPath); + + var bars = new List(); + bool isNew = true; + + // Collect first 10 bars + for (int i = 0; i < 10 && isNew; i++) + { + bars.Add(feed.Next(ref isNew)); + } + + // Verify chronological order (each bar later than previous) + for (int i = 1; i < bars.Count; i++) + { + Assert.True(bars[i].Time > bars[i - 1].Time, + $"Bar {i} time ({bars[i].AsDateTime}) should be after bar {i-1} time ({bars[i-1].AsDateTime})"); + } + } + + [Fact] + public void CsvFeed_WorksWithIFeedInterface() + { + IFeed feed = new CsvFeed(TestCsvPath); + + var bar1 = feed.Next(isNew: true); + Assert.True(bar1.Time > 0); + + var bar2 = feed.Next(isNew: true); + Assert.True(bar2.Time > bar1.Time); + } + + [Fact] + public void Next_MixedNewAndUpdate_WorksCorrectly() + { + var feed = new CsvFeed(TestCsvPath); + + var bar1 = feed.Next(isNew: true); + var bar1Update = feed.Next(isNew: false); + Assert.Equal(bar1.Time, bar1Update.Time); + + var bar2 = feed.Next(isNew: true); + Assert.True(bar2.Time > bar1.Time); + + var bar2Update = feed.Next(isNew: false); + Assert.Equal(bar2.Time, bar2Update.Time); + + var bar3 = feed.Next(isNew: true); + Assert.True(bar3.Time > bar2.Time); + } + + [Fact] + public void Fetch_WithEarlyStartTime_ReturnsData() + { + var feed = new CsvFeed(TestCsvPath); + + // Start from very early date (before any data) + var startTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + var series = feed.Fetch(5, startTime, TimeSpan.FromDays(1)); + + // Should return data starting from first available bar + Assert.True(series.Count > 0); + } + + [Fact] + public void Fetch_WithFutureStartTime_ReturnsEmpty() + { + var feed = new CsvFeed(TestCsvPath); + + // Start from future date (after all data) + var startTime = new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + var series = feed.Fetch(5, startTime, TimeSpan.FromDays(1)); + + // Should return empty or minimal data + Assert.True(series.Count == 0); + } + + [Fact] + public void Fetch_HandlesGapsCorrectly() + { + string tempCsv = Path.GetTempFileName() + ".csv"; + try + { + // Create CSV with gaps + // Date, Open, High, Low, Close, Volume + // 2023-01-01 (Sunday) + // 2023-01-02 (Monday) + // 2023-01-04 (Wednesday) - Gap of Tuesday + // 2023-01-05 (Thursday) + var lines = new[] + { + "Date,Open,High,Low,Close,Volume", + "2023-01-05,103,104,102,103,1000", + "2023-01-04,102,103,101,102,1000", + "2023-01-02,101,102,100,101,1000", + "2023-01-01,100,101,99,100,1000" + }; + File.WriteAllLines(tempCsv, lines); + + var feed = new CsvFeed(tempCsv); + var startTime = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + var interval = TimeSpan.FromDays(1); + + // Fetch 5 bars. Should get 4 bars (Jan 1, 2, 4, 5). + var series = feed.Fetch(10, startTime, interval); + + Assert.Equal(4, series.Count); + Assert.Equal(startTime, series[0].Time); // Jan 1 + Assert.Equal(startTime + interval.Ticks, series[1].Time); // Jan 2 + // Gap here + Assert.Equal(startTime + 3 * interval.Ticks, series[2].Time); // Jan 4 + Assert.Equal(startTime + 4 * interval.Ticks, series[3].Time); // Jan 5 + } + finally + { + if (File.Exists(tempCsv)) + File.Delete(tempCsv); + } + } +} diff --git a/lib/feeds/csv/CsvFeed.cs b/lib/feeds/csv/CsvFeed.cs index 56911e3a..41a6194a 100644 --- a/lib/feeds/csv/CsvFeed.cs +++ b/lib/feeds/csv/CsvFeed.cs @@ -1,207 +1,207 @@ -using System.Globalization; -using System.Runtime.CompilerServices; - -namespace QuanTAlib; - -/// -/// CSV file feed for loading historical OHLCV data. -/// Loads data in constructor and streams through it with Next() or returns batches with Fetch(). -/// CSV format: timestamp,open,high,low,close,volume (header required) -/// Timestamp format: YYYY-MM-DD (UTC midnight assumed) -/// -public class CsvFeed : IFeed -{ - private readonly TBarSeries _data; - - // Streaming state - private int _currentIndex; - private TBar _currentBar; - private bool _hasCurrentBar; - - /// - /// Loads CSV file and prepares data for streaming. - /// Data is reversed to chronological order (oldest first). - /// - /// Path to CSV file - public CsvFeed(string filePath) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException("File path cannot be null or empty", nameof(filePath)); - - if (!File.Exists(filePath)) - throw new FileNotFoundException($"CSV file not found: {filePath}", filePath); - - _data = LoadFromCsv(filePath); - _currentIndex = 0; - } - - /// - /// Parses CSV file into TBarSeries. - /// Expected format: timestamp,open,high,low,close,volume - /// Memory-efficient: reads lines into list, reverses in-place (no LINQ allocations). - /// - private static TBarSeries LoadFromCsv(string filePath) - { - var dataLines = new List(); - using (var reader = new StreamReader(filePath)) - { - var header = reader.ReadLine(); - if (header is null) - throw new InvalidDataException("CSV file is empty"); - - while (!reader.EndOfStream) - { - var line = reader.ReadLine(); - if (!string.IsNullOrWhiteSpace(line)) - dataLines.Add(line); - } - } - - if (dataLines.Count == 0) - throw new InvalidDataException("CSV file contains only header, no data"); - - // Reverse in-place to chronological order (oldest first) - dataLines.Reverse(); - - var series = new TBarSeries(dataLines.Count); - - for (int i = 0; i < dataLines.Count; i++) - { - var line = dataLines[i]; - - var parts = line.Split(','); - int originalLineNumber = dataLines.Count - i + 1; - if (parts.Length != 6) - throw new FormatException($"Invalid CSV format at line {originalLineNumber}. Expected 6 columns, found {parts.Length}"); - - // Parse timestamp (YYYY-MM-DD format, assume UTC midnight) - if (!DateTime.TryParseExact(parts[0].Trim(), "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var timestamp)) - { - throw new FormatException($"Failed to parse timestamp at line {originalLineNumber}: {line}"); - } - - // Parse OHLCV values - if (!double.TryParse(parts[1].Trim(), CultureInfo.InvariantCulture, out double open) || - !double.TryParse(parts[2].Trim(), CultureInfo.InvariantCulture, out double high) || - !double.TryParse(parts[3].Trim(), CultureInfo.InvariantCulture, out double low) || - !double.TryParse(parts[4].Trim(), CultureInfo.InvariantCulture, out double close) || - !double.TryParse(parts[5].Trim(), CultureInfo.InvariantCulture, out double volume)) - { - throw new FormatException($"Failed to parse CSV line {originalLineNumber}: {line}"); - } - - series.Add(timestamp, open, high, low, close, volume, isNew: true); - } - - return series; - } - - /// - /// Gets the next bar with full bidirectional control. - /// When end of data reached, returns last bar and sets isNew=false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TBar Next(ref bool isNew) - { - if (_data.Count == 0) - { - isNew = false; - return default; - } - - if (isNew || !_hasCurrentBar) - { - // Request for new bar - if (_currentIndex >= _data.Count) - { - // End of data - return last bar and signal no more data - isNew = false; - return _currentBar; - } - - _currentBar = _data[_currentIndex]; - _currentIndex++; - _hasCurrentBar = true; - } - else - { - // Update current bar - CSV has no intra-bar updates, return same bar - // No change to _currentBar or _currentIndex - } - - return _currentBar; - } - - /// - /// Gets the next bar with simple control. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TBar Next(bool isNew = true) - { - return Next(ref isNew); - } - /// - /// Returns a filtered subset of data matching the criteria. - /// Resets streaming position to start of returned data. - /// - public TBarSeries Fetch(int count, long startTime, TimeSpan interval) - { - if (count <= 0) - throw new ArgumentException("Count must be positive", nameof(count)); - - var result = new TBarSeries(count); - - // Find starting index - int startIndex = -1; - for (int i = 0; i < _data.Count; i++) - { - if (_data[i].Time >= startTime) - { - startIndex = i; - break; - } - } - - if (startIndex == -1) - return result; - - // Collect bars matching interval - long expectedTime = startTime; - int collected = 0; - - for (int i = startIndex; i < _data.Count && collected < count; i++) - { - var bar = _data[i]; - - // Check if bar time matches expected time (within tolerance) - long timeDiff = Math.Abs(bar.Time - expectedTime); - long tolerance = interval.Ticks / 2; // Allow 50% tolerance - - if (timeDiff <= tolerance) - { - result.Add(bar, isNew: true); - collected++; - expectedTime += interval.Ticks; - } - else if (bar.Time > expectedTime) - { - // Gap in data - skip forward - long gaps = (bar.Time - expectedTime) / interval.Ticks; - expectedTime += gaps * interval.Ticks; - - if (Math.Abs(bar.Time - expectedTime) <= tolerance) - { - result.Add(bar, isNew: true); - collected++; - expectedTime += interval.Ticks; - } - } - } - - // Reset streaming to start of returned data - _currentIndex = startIndex; - _hasCurrentBar = false; - - return result; - } -} +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace QuanTAlib; + +/// +/// CSV file feed for loading historical OHLCV data. +/// Loads data in constructor and streams through it with Next() or returns batches with Fetch(). +/// CSV format: timestamp,open,high,low,close,volume (header required) +/// Timestamp format: YYYY-MM-DD (UTC midnight assumed) +/// +public class CsvFeed : IFeed +{ + private readonly TBarSeries _data; + + // Streaming state + private int _currentIndex; + private TBar _currentBar; + private bool _hasCurrentBar; + + /// + /// Loads CSV file and prepares data for streaming. + /// Data is reversed to chronological order (oldest first). + /// + /// Path to CSV file + public CsvFeed(string filePath) + { + if (string.IsNullOrWhiteSpace(filePath)) + throw new ArgumentException("File path cannot be null or empty", nameof(filePath)); + + if (!File.Exists(filePath)) + throw new FileNotFoundException($"CSV file not found: {filePath}", filePath); + + _data = LoadFromCsv(filePath); + _currentIndex = 0; + } + + /// + /// Parses CSV file into TBarSeries. + /// Expected format: timestamp,open,high,low,close,volume + /// Memory-efficient: reads lines into list, reverses in-place (no LINQ allocations). + /// + private static TBarSeries LoadFromCsv(string filePath) + { + var dataLines = new List(); + using (var reader = new StreamReader(filePath)) + { + var header = reader.ReadLine(); + if (header is null) + throw new InvalidDataException("CSV file is empty"); + + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + if (!string.IsNullOrWhiteSpace(line)) + dataLines.Add(line); + } + } + + if (dataLines.Count == 0) + throw new InvalidDataException("CSV file contains only header, no data"); + + // Reverse in-place to chronological order (oldest first) + dataLines.Reverse(); + + var series = new TBarSeries(dataLines.Count); + + for (int i = 0; i < dataLines.Count; i++) + { + var line = dataLines[i]; + + var parts = line.Split(','); + int originalLineNumber = dataLines.Count - i + 1; + if (parts.Length != 6) + throw new FormatException($"Invalid CSV format at line {originalLineNumber}. Expected 6 columns, found {parts.Length}"); + + // Parse timestamp (YYYY-MM-DD format, assume UTC midnight) + if (!DateTime.TryParseExact(parts[0].Trim(), "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var timestamp)) + { + throw new FormatException($"Failed to parse timestamp at line {originalLineNumber}: {line}"); + } + + // Parse OHLCV values + if (!double.TryParse(parts[1].Trim(), CultureInfo.InvariantCulture, out double open) || + !double.TryParse(parts[2].Trim(), CultureInfo.InvariantCulture, out double high) || + !double.TryParse(parts[3].Trim(), CultureInfo.InvariantCulture, out double low) || + !double.TryParse(parts[4].Trim(), CultureInfo.InvariantCulture, out double close) || + !double.TryParse(parts[5].Trim(), CultureInfo.InvariantCulture, out double volume)) + { + throw new FormatException($"Failed to parse CSV line {originalLineNumber}: {line}"); + } + + series.Add(timestamp, open, high, low, close, volume, isNew: true); + } + + return series; + } + + /// + /// Gets the next bar with full bidirectional control. + /// When end of data reached, returns last bar and sets isNew=false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TBar Next(ref bool isNew) + { + if (_data.Count == 0) + { + isNew = false; + return default; + } + + if (isNew || !_hasCurrentBar) + { + // Request for new bar + if (_currentIndex >= _data.Count) + { + // End of data - return last bar and signal no more data + isNew = false; + return _currentBar; + } + + _currentBar = _data[_currentIndex]; + _currentIndex++; + _hasCurrentBar = true; + } + else + { + // Update current bar - CSV has no intra-bar updates, return same bar + // No change to _currentBar or _currentIndex + } + + return _currentBar; + } + + /// + /// Gets the next bar with simple control. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TBar Next(bool isNew = true) + { + return Next(ref isNew); + } + /// + /// Returns a filtered subset of data matching the criteria. + /// Resets streaming position to start of returned data. + /// + public TBarSeries Fetch(int count, long startTime, TimeSpan interval) + { + if (count <= 0) + throw new ArgumentException("Count must be positive", nameof(count)); + + var result = new TBarSeries(count); + + // Find starting index + int startIndex = -1; + for (int i = 0; i < _data.Count; i++) + { + if (_data[i].Time >= startTime) + { + startIndex = i; + break; + } + } + + if (startIndex == -1) + return result; + + // Collect bars matching interval + long expectedTime = startTime; + int collected = 0; + + for (int i = startIndex; i < _data.Count && collected < count; i++) + { + var bar = _data[i]; + + // Check if bar time matches expected time (within tolerance) + long timeDiff = Math.Abs(bar.Time - expectedTime); + long tolerance = interval.Ticks / 2; // Allow 50% tolerance + + if (timeDiff <= tolerance) + { + result.Add(bar, isNew: true); + collected++; + expectedTime += interval.Ticks; + } + else if (bar.Time > expectedTime) + { + // Gap in data - skip forward + long gaps = (bar.Time - expectedTime) / interval.Ticks; + expectedTime += gaps * interval.Ticks; + + if (Math.Abs(bar.Time - expectedTime) <= tolerance) + { + result.Add(bar, isNew: true); + collected++; + expectedTime += interval.Ticks; + } + } + } + + // Reset streaming to start of returned data + _currentIndex = startIndex; + _hasCurrentBar = false; + + return result; + } +} diff --git a/lib/feeds/csv/CsvFeed.md b/lib/feeds/csv/CsvFeed.md index dda86b7d..fda48c3a 100644 --- a/lib/feeds/csv/CsvFeed.md +++ b/lib/feeds/csv/CsvFeed.md @@ -1,72 +1,72 @@ -# 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)); +# 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.md b/lib/feeds/gbm/GBM.md index 6e5a3b6f..e78e3956 100644 --- a/lib/feeds/gbm/GBM.md +++ b/lib/feeds/gbm/GBM.md @@ -1,71 +1,71 @@ -# 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); +# 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/feeds/gbm/Gbm.Tests.cs b/lib/feeds/gbm/Gbm.Tests.cs index 7681a75e..31bc891e 100644 --- a/lib/feeds/gbm/Gbm.Tests.cs +++ b/lib/feeds/gbm/Gbm.Tests.cs @@ -1,281 +1,281 @@ - -namespace QuanTAlib.Tests; - -public class GBMTests -{ - [Fact] - public void Next_DefaultParameter_GeneratesNewBar() - { - var gbm = new GBM(startPrice: 100.0); - - var bar1 = gbm.Next(); - var bar2 = gbm.Next(); - - Assert.NotEqual(bar1.Time, bar2.Time); - Assert.True(bar2.Time > bar1.Time); - } - - [Fact] - public void Next_IsNewTrue_AdvancesToNewBar() - { - var gbm = new GBM(startPrice: 100.0); - - var bar1 = gbm.Next(isNew: true); - var bar2 = gbm.Next(isNew: true); - - Assert.NotEqual(bar1.Time, bar2.Time); - Assert.True(bar2.Time > bar1.Time); - } - - [Fact] - public void Next_IsNewFalse_UpdatesCurrentBar() - { - var gbm = new GBM(startPrice: 100.0); - - var bar1 = gbm.Next(isNew: true); - long initialTime = bar1.Time; - - var bar2 = gbm.Next(isNew: false); - - Assert.Equal(initialTime, bar2.Time); - // Price likely changed (GBM random walk) - Assert.NotEqual(bar1.Close, bar2.Close); - } - - [Fact] - public void Next_RefBool_HonorsRequest() - { - var gbm = new GBM(startPrice: 100.0); - - // GBM always honors isNew - parameter should remain unchanged - bool isNew1 = true; - var bar1 = gbm.Next(ref isNew1); - Assert.True(isNew1, "GBM should honor isNew=true request"); - - bool isNew2 = false; - long time1 = bar1.Time; - var bar2 = gbm.Next(ref isNew2); - Assert.False(isNew2, "GBM should honor isNew=false request"); - Assert.Equal(time1, bar2.Time); - - bool isNew3 = true; - var bar3 = gbm.Next(ref isNew3); - Assert.True(isNew3, "GBM should honor isNew=true request"); - Assert.NotEqual(time1, bar3.Time); - } - - [Fact] - public void Fetch_GeneratesCorrectCount() - { - var gbm = new GBM(startPrice: 100.0); - int count = 10; - long startTime = DateTime.UtcNow.Ticks; - var interval = TimeSpan.FromMinutes(1); - - var series = gbm.Fetch(count, startTime, interval); - - Assert.Equal(count, series.Count); - } - - [Fact] - public void Fetch_GeneratesSequentialBars() - { - var gbm = new GBM(startPrice: 100.0); - long startTime = DateTime.UtcNow.Ticks; - var interval = TimeSpan.FromMinutes(1); - - var series = gbm.Fetch(5, startTime, interval); - - // Verify time sequence - for (int i = 1; i < series.Count; i++) - { - Assert.True(series[i].Time > series[i - 1].Time); - } - } - - [Fact] - public void Fetch_RespectsInterval() - { - var gbm = new GBM(startPrice: 100.0); - var interval = TimeSpan.FromHours(1); - long startTime = DateTime.UtcNow.Ticks; - - var series = gbm.Fetch(5, startTime, interval); - - // Verify interval spacing - for (int i = 1; i < series.Count; i++) - { - long expectedDiff = interval.Ticks; - long actualDiff = series[i].Time - series[i - 1].Time; - Assert.Equal(expectedDiff, actualDiff); - } - } - - [Fact] - public void Fetch_StartsAtSpecifiedTime() - { - var gbm = new GBM(startPrice: 100.0); - var startTime = new DateTime(2024, 1, 1, 9, 30, 0, DateTimeKind.Utc).Ticks; - var interval = TimeSpan.FromMinutes(5); - - var series = gbm.Fetch(3, startTime, interval); - - Assert.Equal(startTime, series[0].Time); - Assert.Equal(startTime + interval.Ticks, series[1].Time); - Assert.Equal(startTime + 2 * interval.Ticks, series[2].Time); - } - - [Fact] - public void Fetch_WithDifferentIntervals_WorksCorrectly() - { - var gbm = new GBM(startPrice: 100.0); - long startTime = DateTime.UtcNow.Ticks; - - // Test different intervals - var intervals = new[] { - TimeSpan.FromMinutes(1), - TimeSpan.FromMinutes(5), - TimeSpan.FromHours(1) - }; - - foreach (var interval in intervals) - { - var series = gbm.Fetch(3, startTime, interval); - - // Verify spacing - for (int i = 1; i < series.Count; i++) - { - long expectedDiff = interval.Ticks; - long actualDiff = series[i].Time - series[i - 1].Time; - Assert.Equal(expectedDiff, actualDiff); - } - } - } - - [Fact] - public void GeneratesRealisticOHLCV() - { - var gbm = new GBM(startPrice: 100.0); - long startTime = DateTime.UtcNow.Ticks; - var interval = TimeSpan.FromMinutes(1); - var series = gbm.Fetch(10, startTime, interval); - - for (int i = 0; i < series.Count; i++) - { - var bar = series[i]; - - // High should be >= max(Open, Close) - Assert.True(bar.High >= Math.Max(bar.Open, bar.Close)); - - // Low should be <= min(Open, Close) - Assert.True(bar.Low <= Math.Min(bar.Open, bar.Close)); - - // Volume should be positive - Assert.True(bar.Volume > 0); - - // All prices should be positive - Assert.True(bar.Open > 0); - Assert.True(bar.High > 0); - Assert.True(bar.Low > 0); - Assert.True(bar.Close > 0); - } - } - - [Fact] - public void IntraBarUpdates_ModifyCurrentBar() - { - var gbm = new GBM(startPrice: 100.0); - - var bar1 = gbm.Next(isNew: true); - long initialTime = bar1.Time; - double initialClose = bar1.Close; - - // Loop until price changes (random walk might stay same but unlikely) - bool changed = false; - for (int i = 0; i < 10; i++) - { - var bar = gbm.Next(isNew: false); - Assert.Equal(initialTime, bar.Time); - if (Math.Abs(bar.Close - initialClose) > double.Epsilon) - { - changed = true; - break; - } - } - - Assert.True(changed, "Price should change during intra-bar updates"); - } - - [Fact] - public void MixedStreamingAndBatch_WorksCorrectly() - { - var gbm = new GBM(startPrice: 100.0); - - // Start with streaming - _ = gbm.Next(); - var bar2 = gbm.Next(); - - // Batch generation with explicit time - long startTime = bar2.Time + TimeSpan.FromMinutes(1).Ticks; - var interval = TimeSpan.FromMinutes(1); - var series = gbm.Fetch(3, startTime, interval); - - Assert.True(series[0].Time > bar2.Time); - Assert.Equal(3, series.Count); - - // Continue streaming after batch (uses internal state) - var bar3 = gbm.Next(); - Assert.True(bar3.Time > series[2].Time); - } - - [Fact] - public void DriftAndVolatility_AffectPriceMovement() - { - // High volatility should produce more price variation - var gbmLowVol = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.01); - var gbmHighVol = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.5); - - long startTime = DateTime.UtcNow.Ticks; - var interval = TimeSpan.FromMinutes(1); - var seriesLow = gbmLowVol.Fetch(100, startTime, interval); - var seriesHigh = gbmHighVol.Fetch(100, startTime, interval); - - // Calculate price ranges - double rangeLow = seriesLow[99].Close - seriesLow[0].Open; - double rangeHigh = seriesHigh[99].Close - seriesHigh[0].Open; - - // High volatility should generally produce larger absolute movements - Assert.True(Math.Abs(rangeHigh) > Math.Abs(rangeLow) * 0.5); - } - - [Fact] - public void ConsecutiveCalls_MaintainContinuity() - { - var gbm = new GBM(startPrice: 100.0); - - var previousBar = gbm.Next(); - var currentBar = gbm.Next(); - - // currentBar.Open should equal previousBar.Close (continuity) - Assert.Equal(previousBar.Close, currentBar.Open); - } - - [Fact] - public void Stateless_NoHistoryStorage() - { - var gbm = new GBM(startPrice: 100.0); - - // Generate multiple bars - for (int i = 0; i < 100; i++) - { - _ = gbm.Next(); - } - - // GBM should not expose any history storage - // Use typeof() instead of GetType() to satisfy trimming analyzer - var type = typeof(GBM); - var barsProperty = type.GetProperty("Bars"); - - Assert.Null(barsProperty); - } -} + +namespace QuanTAlib.Tests; + +public class GBMTests +{ + [Fact] + public void Next_DefaultParameter_GeneratesNewBar() + { + var gbm = new GBM(startPrice: 100.0); + + var bar1 = gbm.Next(); + var bar2 = gbm.Next(); + + Assert.NotEqual(bar1.Time, bar2.Time); + Assert.True(bar2.Time > bar1.Time); + } + + [Fact] + public void Next_IsNewTrue_AdvancesToNewBar() + { + var gbm = new GBM(startPrice: 100.0); + + var bar1 = gbm.Next(isNew: true); + var bar2 = gbm.Next(isNew: true); + + Assert.NotEqual(bar1.Time, bar2.Time); + Assert.True(bar2.Time > bar1.Time); + } + + [Fact] + public void Next_IsNewFalse_UpdatesCurrentBar() + { + var gbm = new GBM(startPrice: 100.0); + + var bar1 = gbm.Next(isNew: true); + long initialTime = bar1.Time; + + var bar2 = gbm.Next(isNew: false); + + Assert.Equal(initialTime, bar2.Time); + // Price likely changed (GBM random walk) + Assert.NotEqual(bar1.Close, bar2.Close); + } + + [Fact] + public void Next_RefBool_HonorsRequest() + { + var gbm = new GBM(startPrice: 100.0); + + // GBM always honors isNew - parameter should remain unchanged + bool isNew1 = true; + var bar1 = gbm.Next(ref isNew1); + Assert.True(isNew1, "GBM should honor isNew=true request"); + + bool isNew2 = false; + long time1 = bar1.Time; + var bar2 = gbm.Next(ref isNew2); + Assert.False(isNew2, "GBM should honor isNew=false request"); + Assert.Equal(time1, bar2.Time); + + bool isNew3 = true; + var bar3 = gbm.Next(ref isNew3); + Assert.True(isNew3, "GBM should honor isNew=true request"); + Assert.NotEqual(time1, bar3.Time); + } + + [Fact] + public void Fetch_GeneratesCorrectCount() + { + var gbm = new GBM(startPrice: 100.0); + int count = 10; + long startTime = DateTime.UtcNow.Ticks; + var interval = TimeSpan.FromMinutes(1); + + var series = gbm.Fetch(count, startTime, interval); + + Assert.Equal(count, series.Count); + } + + [Fact] + public void Fetch_GeneratesSequentialBars() + { + var gbm = new GBM(startPrice: 100.0); + long startTime = DateTime.UtcNow.Ticks; + var interval = TimeSpan.FromMinutes(1); + + var series = gbm.Fetch(5, startTime, interval); + + // Verify time sequence + for (int i = 1; i < series.Count; i++) + { + Assert.True(series[i].Time > series[i - 1].Time); + } + } + + [Fact] + public void Fetch_RespectsInterval() + { + var gbm = new GBM(startPrice: 100.0); + var interval = TimeSpan.FromHours(1); + long startTime = DateTime.UtcNow.Ticks; + + var series = gbm.Fetch(5, startTime, interval); + + // Verify interval spacing + for (int i = 1; i < series.Count; i++) + { + long expectedDiff = interval.Ticks; + long actualDiff = series[i].Time - series[i - 1].Time; + Assert.Equal(expectedDiff, actualDiff); + } + } + + [Fact] + public void Fetch_StartsAtSpecifiedTime() + { + var gbm = new GBM(startPrice: 100.0); + var startTime = new DateTime(2024, 1, 1, 9, 30, 0, DateTimeKind.Utc).Ticks; + var interval = TimeSpan.FromMinutes(5); + + var series = gbm.Fetch(3, startTime, interval); + + Assert.Equal(startTime, series[0].Time); + Assert.Equal(startTime + interval.Ticks, series[1].Time); + Assert.Equal(startTime + 2 * interval.Ticks, series[2].Time); + } + + [Fact] + public void Fetch_WithDifferentIntervals_WorksCorrectly() + { + var gbm = new GBM(startPrice: 100.0); + long startTime = DateTime.UtcNow.Ticks; + + // Test different intervals + var intervals = new[] { + TimeSpan.FromMinutes(1), + TimeSpan.FromMinutes(5), + TimeSpan.FromHours(1) + }; + + foreach (var interval in intervals) + { + var series = gbm.Fetch(3, startTime, interval); + + // Verify spacing + for (int i = 1; i < series.Count; i++) + { + long expectedDiff = interval.Ticks; + long actualDiff = series[i].Time - series[i - 1].Time; + Assert.Equal(expectedDiff, actualDiff); + } + } + } + + [Fact] + public void GeneratesRealisticOHLCV() + { + var gbm = new GBM(startPrice: 100.0); + long startTime = DateTime.UtcNow.Ticks; + var interval = TimeSpan.FromMinutes(1); + var series = gbm.Fetch(10, startTime, interval); + + for (int i = 0; i < series.Count; i++) + { + var bar = series[i]; + + // High should be >= max(Open, Close) + Assert.True(bar.High >= Math.Max(bar.Open, bar.Close)); + + // Low should be <= min(Open, Close) + Assert.True(bar.Low <= Math.Min(bar.Open, bar.Close)); + + // Volume should be positive + Assert.True(bar.Volume > 0); + + // All prices should be positive + Assert.True(bar.Open > 0); + Assert.True(bar.High > 0); + Assert.True(bar.Low > 0); + Assert.True(bar.Close > 0); + } + } + + [Fact] + public void IntraBarUpdates_ModifyCurrentBar() + { + var gbm = new GBM(startPrice: 100.0); + + var bar1 = gbm.Next(isNew: true); + long initialTime = bar1.Time; + double initialClose = bar1.Close; + + // Loop until price changes (random walk might stay same but unlikely) + bool changed = false; + for (int i = 0; i < 10; i++) + { + var bar = gbm.Next(isNew: false); + Assert.Equal(initialTime, bar.Time); + if (Math.Abs(bar.Close - initialClose) > double.Epsilon) + { + changed = true; + break; + } + } + + Assert.True(changed, "Price should change during intra-bar updates"); + } + + [Fact] + public void MixedStreamingAndBatch_WorksCorrectly() + { + var gbm = new GBM(startPrice: 100.0); + + // Start with streaming + _ = gbm.Next(); + var bar2 = gbm.Next(); + + // Batch generation with explicit time + long startTime = bar2.Time + TimeSpan.FromMinutes(1).Ticks; + var interval = TimeSpan.FromMinutes(1); + var series = gbm.Fetch(3, startTime, interval); + + Assert.True(series[0].Time > bar2.Time); + Assert.Equal(3, series.Count); + + // Continue streaming after batch (uses internal state) + var bar3 = gbm.Next(); + Assert.True(bar3.Time > series[2].Time); + } + + [Fact] + public void DriftAndVolatility_AffectPriceMovement() + { + // High volatility should produce more price variation + var gbmLowVol = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.01); + var gbmHighVol = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.5); + + long startTime = DateTime.UtcNow.Ticks; + var interval = TimeSpan.FromMinutes(1); + var seriesLow = gbmLowVol.Fetch(100, startTime, interval); + var seriesHigh = gbmHighVol.Fetch(100, startTime, interval); + + // Calculate price ranges + double rangeLow = seriesLow[99].Close - seriesLow[0].Open; + double rangeHigh = seriesHigh[99].Close - seriesHigh[0].Open; + + // High volatility should generally produce larger absolute movements + Assert.True(Math.Abs(rangeHigh) > Math.Abs(rangeLow) * 0.5); + } + + [Fact] + public void ConsecutiveCalls_MaintainContinuity() + { + var gbm = new GBM(startPrice: 100.0); + + var previousBar = gbm.Next(); + var currentBar = gbm.Next(); + + // currentBar.Open should equal previousBar.Close (continuity) + Assert.Equal(previousBar.Close, currentBar.Open); + } + + [Fact] + public void Stateless_NoHistoryStorage() + { + var gbm = new GBM(startPrice: 100.0); + + // Generate multiple bars + for (int i = 0; i < 100; i++) + { + _ = gbm.Next(); + } + + // GBM should not expose any history storage + // Use typeof() instead of GetType() to satisfy trimming analyzer + var type = typeof(GBM); + var barsProperty = type.GetProperty("Bars"); + + Assert.Null(barsProperty); + } +} diff --git a/lib/feeds/gbm/gbm.cs b/lib/feeds/gbm/gbm.cs index 876224b0..3abe1033 100644 --- a/lib/feeds/gbm/gbm.cs +++ b/lib/feeds/gbm/gbm.cs @@ -1,254 +1,254 @@ -using System.Runtime.CompilerServices; -using System.Security.Cryptography; - -namespace QuanTAlib; - -/// -/// Geometric Brownian Motion (GBM) generator for simulating OHLCV data. -/// Generates realistic price data for testing indicators and strategies. -/// Stateless design - only maintains minimal state needed for price continuity. -/// -[SkipLocalsInit] -#pragma warning disable S101 // Rename class 'GBM' to match pascal case naming rules -#pragma warning disable S2245 // Random is acceptable for simulation/testing purposes -public class GBM : IFeed -#pragma warning restore S101 -{ - private readonly Random? _rnd; - - private double _lastPrice; - private long _lastTime; - - private readonly double _mu; - private readonly double _sigma; - - // Precomputed GBM constants - private readonly double _drift; - private readonly double _vol; - private readonly long _defaultTimeStep; - - // State for streaming bar formation (only when isNew=false) - private TBar _currentBar; - private bool _hasCurrentBar; - - // Box-Muller optimization: cache second normal - private double _cachedZ; - private bool _hasCachedZ; - - /// - /// Creates a new GBM generator. - /// - /// Initial price (default: 100.0, must be positive) - /// Annual drift/return rate (default: 0.05 = 5%) - /// Annual volatility (default: 0.2 = 20%, must be non-negative) - /// Default timeframe for bars (default: 1 minute) - /// Optional random seed for reproducibility (default: null for non-deterministic) - public GBM( - double startPrice = 100.0, - double mu = 0.05, - double sigma = 0.2, - TimeSpan? defaultTimeframe = null, - int? seed = null) - { - ArgumentOutOfRangeException.ThrowIfNegativeOrZero(startPrice); - ArgumentOutOfRangeException.ThrowIfNegative(sigma); - - _rnd = seed.HasValue ? new Random(seed.Value) : null; - _lastPrice = startPrice; - _lastTime = DateTime.UtcNow.Ticks; - - _mu = mu; - _sigma = sigma; - - // Use provided timeframe or default to 1 minute - var timeframe = defaultTimeframe ?? TimeSpan.FromMinutes(1); - _defaultTimeStep = timeframe.Ticks; - - // Calculate dt based on timeframe (assuming 252 trading days/year, 6.5 hours/day) - double minutesPerYear = 252.0 * 6.5 * 60.0; - double dt = timeframe.TotalMinutes / minutesPerYear; - - _drift = (mu - 0.5 * sigma * sigma) * dt; - _vol = sigma * Math.Sqrt(dt); - } - - /// - /// Generates a random double in [0, 1) using either the seeded Random or RandomNumberGenerator. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private double NextDouble() - { - if (_rnd != null) - { - return _rnd.NextDouble(); - } - - Span buffer = stackalloc byte[8]; - RandomNumberGenerator.Fill(buffer); - ulong ul = BitConverter.ToUInt64(buffer); - return (ul >> 11) * (1.0 / (1ul << 53)); - } - - /// - /// Generates next standard normal using Box-Muller transform with caching. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private double NextNormal() - { - if (_hasCachedZ) - { - _hasCachedZ = false; - return _cachedZ; - } - - double u1 = 1.0 - NextDouble(); - double u2 = 1.0 - NextDouble(); - double mag = Math.Sqrt(-2.0 * Math.Log(u1)); - double angle = 2.0 * Math.PI * u2; - - _cachedZ = mag * Math.Sin(angle); - _hasCachedZ = true; - - return mag * Math.Cos(angle); - } - - /// - /// Gets the next bar with full bidirectional control. - /// GBM always honors the request - isNew parameter unchanged on return. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TBar Next(ref bool isNew) - { - // GBM always honors request - parameter unchanged - - if (isNew || !_hasCurrentBar) - { - // Generate new bar - long currentTime = _lastTime + _defaultTimeStep; - - double z = NextNormal(); - double price = _lastPrice * Math.Exp(_drift + _vol * z); - double volume = 1000 + NextDouble() * 1000; - - double open = _lastPrice; - double close = price; - double high = Math.Max(open, close) * (1.0 + Math.Abs(NextDouble()) * 0.01); - double low = Math.Min(open, close) * (1.0 - Math.Abs(NextDouble()) * 0.01); - - // Ensure valid OHLC - high = Math.Max(high, Math.Max(open, close)); - low = Math.Min(low, Math.Min(open, close)); - low = Math.Max(0.0, low); - - _currentBar = new TBar(currentTime, open, high, low, close, volume); - _hasCurrentBar = true; - - _lastPrice = close; - _lastTime = currentTime; - } - else - { - // Update current bar (intra-bar tick) - double z = NextNormal(); - double price = _lastPrice * Math.Exp(_drift + _vol * z); - double additionalVolume = 1000 + NextDouble() * 1000; - - var bar = _currentBar; - double newClose = price; - double newHigh = Math.Max(bar.High, newClose); - double newLow = Math.Min(bar.Low, newClose); - double newVolume = bar.Volume + additionalVolume; - - _currentBar = new TBar(bar.Time, bar.Open, newHigh, newLow, newClose, newVolume); - _lastPrice = newClose; - } - - return _currentBar; - } - - /// - /// Gets the next bar with simple control. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TBar Next(bool isNew = true) - { - // Delegate to ref version - return Next(ref isNew); - } - - /// - /// Generates a batch of bars using optimized batch processing with explicit time parameters. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TBarSeries Fetch(int count, long startTime, TimeSpan interval) - { - if (count <= 0) - throw new ArgumentException("Count must be positive", nameof(count)); - if (interval <= TimeSpan.Zero) - throw new ArgumentOutOfRangeException(nameof(interval), "Interval must be positive"); - - var series = new TBarSeries(count); - - // Pre-allocate arrays for SoA layout - long[] t = new long[count]; - double[] o = new double[count]; - double[] h = new double[count]; - double[] l = new double[count]; - double[] c = new double[count]; - double[] v = new double[count]; - - // Calculate dt for this specific interval - double minutesPerYear = 252.0 * 6.5 * 60.0; - double dt = interval.TotalMinutes / minutesPerYear; - double drift = (_mu - 0.5 * _sigma * _sigma) * dt; - double vol = _sigma * Math.Sqrt(dt); - - long timeStep = interval.Ticks; - double currentPrice = _lastPrice; - long currentTime = startTime; - - for (int i = 0; i < count; i++) - { - double z = NextNormal(); - double price = currentPrice * Math.Exp(drift + vol * z); - - double open = currentPrice; - double close = price; - - double rnd1 = NextDouble(); - double rnd2 = NextDouble(); - double rnd3 = NextDouble(); - - t[i] = currentTime; - o[i] = open; - c[i] = close; - - double high = Math.Max(open, close) * (1.0 + Math.Abs(rnd1) * 0.01); - double low = Math.Min(open, close) * (1.0 - Math.Abs(rnd2) * 0.01); - - // Ensure valid OHLC - high = Math.Max(high, Math.Max(open, close)); - low = Math.Min(low, Math.Min(open, close)); - low = Math.Max(0.0, low); - - h[i] = high; - l[i] = low; - v[i] = 1000 + rnd3 * 1000; - - currentPrice = price; - currentTime += timeStep; - } - - // Update internal state to continue from end of batch - _lastPrice = currentPrice; - _lastTime = currentTime - timeStep; // Last bar time, not next bar time - - // Bulk add to series - series.Add(t, o, h, l, c, v); - - // Reset streaming state after batch - _hasCurrentBar = false; - - return series; - } -} +using System.Runtime.CompilerServices; +using System.Security.Cryptography; + +namespace QuanTAlib; + +/// +/// Geometric Brownian Motion (GBM) generator for simulating OHLCV data. +/// Generates realistic price data for testing indicators and strategies. +/// Stateless design - only maintains minimal state needed for price continuity. +/// +[SkipLocalsInit] +#pragma warning disable S101 // Rename class 'GBM' to match pascal case naming rules +#pragma warning disable S2245 // Random is acceptable for simulation/testing purposes +public class GBM : IFeed +#pragma warning restore S101 +{ + private readonly Random? _rnd; + + private double _lastPrice; + private long _lastTime; + + private readonly double _mu; + private readonly double _sigma; + + // Precomputed GBM constants + private readonly double _drift; + private readonly double _vol; + private readonly long _defaultTimeStep; + + // State for streaming bar formation (only when isNew=false) + private TBar _currentBar; + private bool _hasCurrentBar; + + // Box-Muller optimization: cache second normal + private double _cachedZ; + private bool _hasCachedZ; + + /// + /// Creates a new GBM generator. + /// + /// Initial price (default: 100.0, must be positive) + /// Annual drift/return rate (default: 0.05 = 5%) + /// Annual volatility (default: 0.2 = 20%, must be non-negative) + /// Default timeframe for bars (default: 1 minute) + /// Optional random seed for reproducibility (default: null for non-deterministic) + public GBM( + double startPrice = 100.0, + double mu = 0.05, + double sigma = 0.2, + TimeSpan? defaultTimeframe = null, + int? seed = null) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(startPrice); + ArgumentOutOfRangeException.ThrowIfNegative(sigma); + + _rnd = seed.HasValue ? new Random(seed.Value) : null; + _lastPrice = startPrice; + _lastTime = DateTime.UtcNow.Ticks; + + _mu = mu; + _sigma = sigma; + + // Use provided timeframe or default to 1 minute + var timeframe = defaultTimeframe ?? TimeSpan.FromMinutes(1); + _defaultTimeStep = timeframe.Ticks; + + // Calculate dt based on timeframe (assuming 252 trading days/year, 6.5 hours/day) + double minutesPerYear = 252.0 * 6.5 * 60.0; + double dt = timeframe.TotalMinutes / minutesPerYear; + + _drift = (mu - 0.5 * sigma * sigma) * dt; + _vol = sigma * Math.Sqrt(dt); + } + + /// + /// Generates a random double in [0, 1) using either the seeded Random or RandomNumberGenerator. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private double NextDouble() + { + if (_rnd != null) + { + return _rnd.NextDouble(); + } + + Span buffer = stackalloc byte[8]; + RandomNumberGenerator.Fill(buffer); + ulong ul = BitConverter.ToUInt64(buffer); + return (ul >> 11) * (1.0 / (1ul << 53)); + } + + /// + /// Generates next standard normal using Box-Muller transform with caching. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private double NextNormal() + { + if (_hasCachedZ) + { + _hasCachedZ = false; + return _cachedZ; + } + + double u1 = 1.0 - NextDouble(); + double u2 = 1.0 - NextDouble(); + double mag = Math.Sqrt(-2.0 * Math.Log(u1)); + double angle = 2.0 * Math.PI * u2; + + _cachedZ = mag * Math.Sin(angle); + _hasCachedZ = true; + + return mag * Math.Cos(angle); + } + + /// + /// Gets the next bar with full bidirectional control. + /// GBM always honors the request - isNew parameter unchanged on return. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TBar Next(ref bool isNew) + { + // GBM always honors request - parameter unchanged + + if (isNew || !_hasCurrentBar) + { + // Generate new bar + long currentTime = _lastTime + _defaultTimeStep; + + double z = NextNormal(); + double price = _lastPrice * Math.Exp(_drift + _vol * z); + double volume = 1000 + NextDouble() * 1000; + + double open = _lastPrice; + double close = price; + double high = Math.Max(open, close) * (1.0 + Math.Abs(NextDouble()) * 0.01); + double low = Math.Min(open, close) * (1.0 - Math.Abs(NextDouble()) * 0.01); + + // Ensure valid OHLC + high = Math.Max(high, Math.Max(open, close)); + low = Math.Min(low, Math.Min(open, close)); + low = Math.Max(0.0, low); + + _currentBar = new TBar(currentTime, open, high, low, close, volume); + _hasCurrentBar = true; + + _lastPrice = close; + _lastTime = currentTime; + } + else + { + // Update current bar (intra-bar tick) + double z = NextNormal(); + double price = _lastPrice * Math.Exp(_drift + _vol * z); + double additionalVolume = 1000 + NextDouble() * 1000; + + var bar = _currentBar; + double newClose = price; + double newHigh = Math.Max(bar.High, newClose); + double newLow = Math.Min(bar.Low, newClose); + double newVolume = bar.Volume + additionalVolume; + + _currentBar = new TBar(bar.Time, bar.Open, newHigh, newLow, newClose, newVolume); + _lastPrice = newClose; + } + + return _currentBar; + } + + /// + /// Gets the next bar with simple control. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TBar Next(bool isNew = true) + { + // Delegate to ref version + return Next(ref isNew); + } + + /// + /// Generates a batch of bars using optimized batch processing with explicit time parameters. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TBarSeries Fetch(int count, long startTime, TimeSpan interval) + { + if (count <= 0) + throw new ArgumentException("Count must be positive", nameof(count)); + if (interval <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException(nameof(interval), "Interval must be positive"); + + var series = new TBarSeries(count); + + // Pre-allocate arrays for SoA layout + long[] t = new long[count]; + double[] o = new double[count]; + double[] h = new double[count]; + double[] l = new double[count]; + double[] c = new double[count]; + double[] v = new double[count]; + + // Calculate dt for this specific interval + double minutesPerYear = 252.0 * 6.5 * 60.0; + double dt = interval.TotalMinutes / minutesPerYear; + double drift = (_mu - 0.5 * _sigma * _sigma) * dt; + double vol = _sigma * Math.Sqrt(dt); + + long timeStep = interval.Ticks; + double currentPrice = _lastPrice; + long currentTime = startTime; + + for (int i = 0; i < count; i++) + { + double z = NextNormal(); + double price = currentPrice * Math.Exp(drift + vol * z); + + double open = currentPrice; + double close = price; + + double rnd1 = NextDouble(); + double rnd2 = NextDouble(); + double rnd3 = NextDouble(); + + t[i] = currentTime; + o[i] = open; + c[i] = close; + + double high = Math.Max(open, close) * (1.0 + Math.Abs(rnd1) * 0.01); + double low = Math.Min(open, close) * (1.0 - Math.Abs(rnd2) * 0.01); + + // Ensure valid OHLC + high = Math.Max(high, Math.Max(open, close)); + low = Math.Min(low, Math.Min(open, close)); + low = Math.Max(0.0, low); + + h[i] = high; + l[i] = low; + v[i] = 1000 + rnd3 * 1000; + + currentPrice = price; + currentTime += timeStep; + } + + // Update internal state to continue from end of batch + _lastPrice = currentPrice; + _lastTime = currentTime - timeStep; // Last bar time, not next bar time + + // Bulk add to series + series.Add(t, o, h, l, c, v); + + // Reset streaming state after batch + _hasCurrentBar = false; + + return series; + } +} diff --git a/lib/momentum/adx/Adx.Tests.cs b/lib/momentum/adx/Adx.Tests.cs index 0577778b..111c6878 100644 --- a/lib/momentum/adx/Adx.Tests.cs +++ b/lib/momentum/adx/Adx.Tests.cs @@ -69,13 +69,13 @@ public class AdxTests adx.Reset(); Assert.Equal(0, adx.Last.Value); Assert.False(adx.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { adx.Update(bars[i]); } - + Assert.True(double.IsFinite(adx.Last.Value)); } @@ -101,22 +101,22 @@ public class AdxTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void StaticCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var adx = new Adx(14); var streamingResults = new List(); for (int i = 0; i < bars.Count; i++) { streamingResults.Add(adx.Update(bars[i]).Value); } - + var staticResults = Adx.Batch(bars, 14); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < staticResults.Count; i++) { @@ -130,12 +130,12 @@ public class AdxTests var adx = new Adx(14); var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + // Test TBarSeries chain var result = adx.Update(bars); Assert.NotNull(result); Assert.IsType(result); - + // Test TBar chain (returns TValue) var result2 = adx.Update(bars[0]); Assert.IsType(result2); diff --git a/lib/momentum/adxr/Adxr.Tests.cs b/lib/momentum/adxr/Adxr.Tests.cs index 465ade12..d97abe9d 100644 --- a/lib/momentum/adxr/Adxr.Tests.cs +++ b/lib/momentum/adxr/Adxr.Tests.cs @@ -67,13 +67,13 @@ public class AdxrTests adxr.Reset(); Assert.Equal(0, adxr.Last.Value); Assert.False(adxr.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { adxr.Update(bars[i]); } - + Assert.True(double.IsFinite(adxr.Last.Value)); } @@ -99,22 +99,22 @@ public class AdxrTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void StaticCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var adxr = new Adxr(14); var streamingResults = new List(); for (int i = 0; i < bars.Count; i++) { streamingResults.Add(adxr.Update(bars[i]).Value); } - + var staticResults = Adxr.Batch(bars, 14); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < staticResults.Count; i++) { @@ -135,12 +135,12 @@ public class AdxrTests var adxr = new Adxr(14); var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + // Test TBarSeries chain var result = adxr.Update(bars); Assert.NotNull(result); Assert.IsType(result); - + // Test TBar chain (returns TValue) var result2 = adxr.Update(bars[0]); Assert.IsType(result2); diff --git a/lib/momentum/adxr/Adxr.cs b/lib/momentum/adxr/Adxr.cs index 87aa51ce..1cf913a5 100644 --- a/lib/momentum/adxr/Adxr.cs +++ b/lib/momentum/adxr/Adxr.cs @@ -64,11 +64,11 @@ public sealed class Adxr : ITValuePublisher // TA-Lib uses (Period-1) lag for ADXR. _adxHistory = new RingBuffer(period - 1); _p_adxHistory = new RingBuffer(period - 1); - + // ADXR needs valid ADX from 'period' bars ago. // ADX takes 2*period to warm up. // So ADXR takes 2*period + period - 1 to warm up. - WarmupPeriod = _adx.WarmupPeriod + period - 1; + WarmupPeriod = _adx.WarmupPeriod + period - 1; } /// diff --git a/lib/momentum/ao/Ao.Tests.cs b/lib/momentum/ao/Ao.Tests.cs index 351f0a13..960cf783 100644 --- a/lib/momentum/ao/Ao.Tests.cs +++ b/lib/momentum/ao/Ao.Tests.cs @@ -67,13 +67,13 @@ public class AoTests ao.Reset(); Assert.Equal(0, ao.Last.Value); Assert.False(ao.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { ao.Update(bars[i]); } - + Assert.True(double.IsFinite(ao.Last.Value)); } @@ -99,22 +99,22 @@ public class AoTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void StaticCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var ao = new Ao(5, 34); var streamingResults = new List(); for (int i = 0; i < bars.Count; i++) { streamingResults.Add(ao.Update(bars[i]).Value); } - + var staticResults = Ao.Batch(bars, 5, 34); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < staticResults.Count; i++) { @@ -128,12 +128,12 @@ public class AoTests var ao = new Ao(5, 34); var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + // Test TBarSeries chain var result = ao.Update(bars); Assert.NotNull(result); Assert.IsType(result); - + // Test TBar chain (returns TValue) var result2 = ao.Update(bars[0]); Assert.IsType(result2); diff --git a/lib/momentum/apo/Apo.Tests.cs b/lib/momentum/apo/Apo.Tests.cs index 8a8b309b..895265d0 100644 --- a/lib/momentum/apo/Apo.Tests.cs +++ b/lib/momentum/apo/Apo.Tests.cs @@ -67,13 +67,13 @@ public class ApoTests apo.Reset(); Assert.Equal(0, apo.Last.Value); Assert.False(apo.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { apo.Update(bars[i]); } - + Assert.True(double.IsFinite(apo.Last.Value)); } @@ -99,22 +99,22 @@ public class ApoTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void StaticCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var apo = new Apo(12, 26); var streamingResults = new List(); for (int i = 0; i < bars.Count; i++) { streamingResults.Add(apo.Update(bars[i]).Value); } - + var staticResults = Apo.Batch(bars.Close, 12, 26); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < staticResults.Count; i++) { @@ -128,12 +128,12 @@ public class ApoTests var apo = new Apo(12, 26); var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + // Test TBarSeries chain var result = apo.Update(bars.Close); Assert.NotNull(result); Assert.IsType(result); - + // Test TBar chain (returns TValue) var result2 = apo.Update(bars[0]); Assert.IsType(result2); diff --git a/lib/momentum/apo/Apo.cs b/lib/momentum/apo/Apo.cs index e1ffc1c5..676f1bcb 100644 --- a/lib/momentum/apo/Apo.cs +++ b/lib/momentum/apo/Apo.cs @@ -6,7 +6,7 @@ namespace QuanTAlib; /// APO: Absolute Price Oscillator /// /// -/// The Absolute Price Oscillator (APO) is a momentum indicator that shows the difference +/// The Absolute Price Oscillator (APO) is a momentum indicator that shows the difference /// between two Exponential Moving Averages (EMAs) of a security's price. /// /// Calculation: diff --git a/lib/momentum/aroon/Aroon.Tests.cs b/lib/momentum/aroon/Aroon.Tests.cs index d4ea4207..845ed846 100644 --- a/lib/momentum/aroon/Aroon.Tests.cs +++ b/lib/momentum/aroon/Aroon.Tests.cs @@ -71,13 +71,13 @@ public class AroonTests aroon.Reset(); Assert.Equal(0, aroon.Last.Value); Assert.False(aroon.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { aroon.Update(bars[i]); } - + Assert.True(double.IsFinite(aroon.Last.Value)); } @@ -103,22 +103,22 @@ public class AroonTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void StaticCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var aroon = new Aroon(14); var streamingResults = new List(); for (int i = 0; i < bars.Count; i++) { streamingResults.Add(aroon.Update(bars[i]).Value); } - + var staticResults = Aroon.Batch(bars, 14); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < staticResults.Count; i++) { @@ -140,20 +140,20 @@ public class AroonTests // Period = 2 // Highs: 10, 12, 11 // Lows: 8, 9, 7 - + // T=0: H=10, L=8. Not enough data. // T=1: H=12, L=9. Not enough data. - // T=2: H=11, L=7. + // T=2: H=11, L=7. // Window Highs: [10, 12, 11]. Max is 12 at index 1 (1 day ago). // Window Lows: [8, 9, 7]. Min is 7 at index 2 (0 days ago). - + // Up = ((2 - 1) / 2) * 100 = 50 // Down = ((2 - 0) / 2) * 100 = 100 // Osc = 50 - 100 = -50 var aroon = new Aroon(2); var time = DateTime.UtcNow; - + aroon.Update(new TBar(time, 10, 10, 8, 9, 100)); aroon.Update(new TBar(time.AddMinutes(1), 11, 12, 9, 10, 100)); var result = aroon.Update(new TBar(time.AddMinutes(2), 10, 11, 7, 8, 100)); diff --git a/lib/momentum/aroon/Aroon.Validation.Tests.cs b/lib/momentum/aroon/Aroon.Validation.Tests.cs index c25ec2df..bcdf5825 100644 --- a/lib/momentum/aroon/Aroon.Validation.Tests.cs +++ b/lib/momentum/aroon/Aroon.Validation.Tests.cs @@ -43,10 +43,10 @@ public sealed class AroonValidationTests : IDisposable // Verify Oscillator ValidationHelper.VerifyData(results, skenderResults, x => x.Oscillator); - + // Verify Up ValidationHelper.VerifyData(upResults, skenderResults, x => x.AroonUp); - + // Verify Down ValidationHelper.VerifyData(downResults, skenderResults, x => x.AroonDown); } @@ -82,13 +82,13 @@ public sealed class AroonValidationTests : IDisposable Assert.Equal(Core.RetCode.Success, retCodeOsc); int lookback = TALib.Functions.AroonLookback(14); - + // Verify Up ValidationHelper.VerifyData(upResults, outAroonUp, outRange, lookback); - + // Verify Down ValidationHelper.VerifyData(downResults, outAroonDown, outRange, lookback); - + // Verify Oscillator ValidationHelper.VerifyData(results, outAroonOsc, outRangeOsc, lookback); } diff --git a/lib/momentum/aroonosc/AroonOsc.Tests.cs b/lib/momentum/aroonosc/AroonOsc.Tests.cs index 6affe7a2..2e3c6b31 100644 --- a/lib/momentum/aroonosc/AroonOsc.Tests.cs +++ b/lib/momentum/aroonosc/AroonOsc.Tests.cs @@ -67,13 +67,13 @@ public class AroonOscTests aroon.Reset(); Assert.Equal(0, aroon.Last.Value); Assert.False(aroon.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { aroon.Update(bars[i]); } - + Assert.True(double.IsFinite(aroon.Last.Value)); } @@ -99,22 +99,22 @@ public class AroonOscTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void StaticCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var aroon = new AroonOsc(14); var streamingResults = new List(); for (int i = 0; i < bars.Count; i++) { streamingResults.Add(aroon.Update(bars[i]).Value); } - + var staticResults = AroonOsc.Batch(bars, 14); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < staticResults.Count; i++) { @@ -136,20 +136,20 @@ public class AroonOscTests // Period = 2 // Highs: 10, 12, 11 // Lows: 8, 9, 7 - + // T=0: H=10, L=8. Not enough data. // T=1: H=12, L=9. Not enough data. - // T=2: H=11, L=7. + // T=2: H=11, L=7. // Window Highs: [10, 12, 11]. Max is 12 at index 1 (1 day ago). // Window Lows: [8, 9, 7]. Min is 7 at index 2 (0 days ago). - + // Up = ((2 - 1) / 2) * 100 = 50 // Down = ((2 - 0) / 2) * 100 = 100 // Osc = 50 - 100 = -50 var aroon = new AroonOsc(2); var time = DateTime.UtcNow; - + aroon.Update(new TBar(time, 10, 10, 8, 9, 100)); aroon.Update(new TBar(time.AddMinutes(1), 11, 12, 9, 10, 100)); var result = aroon.Update(new TBar(time.AddMinutes(2), 10, 11, 7, 8, 100)); diff --git a/lib/momentum/aroonosc/AroonOsc.Validation.Tests.cs b/lib/momentum/aroonosc/AroonOsc.Validation.Tests.cs index 066a9ef1..9ffe343d 100644 --- a/lib/momentum/aroonosc/AroonOsc.Validation.Tests.cs +++ b/lib/momentum/aroonosc/AroonOsc.Validation.Tests.cs @@ -65,7 +65,7 @@ public sealed class AroonOscValidationTests : IDisposable Assert.Equal(Core.RetCode.Success, retCodeOsc); int lookback = TALib.Functions.AroonLookback(14); - + // Verify Oscillator ValidationHelper.VerifyData(results, outAroonOsc, outRangeOsc, lookback); } diff --git a/lib/momentum/bop/Bop.Tests.cs b/lib/momentum/bop/Bop.Tests.cs index 482ce1ed..82b9d243 100644 --- a/lib/momentum/bop/Bop.Tests.cs +++ b/lib/momentum/bop/Bop.Tests.cs @@ -68,7 +68,7 @@ public class BopTests bars.Add(new TBar(DateTime.UtcNow.AddMinutes(1), 15, 25, 10, 20, 100)); var batchResult = Bop.Update(bars); - + bop.Reset(); var streamResult1 = bop.Update(bars[0]); var streamResult2 = bop.Update(bars[1]); @@ -76,19 +76,19 @@ public class BopTests Assert.Equal(batchResult[0].Value, streamResult1.Value); Assert.Equal(batchResult[1].Value, streamResult2.Value); } - + [Fact] public void SpanMatchesBatch() { var bars = new TBarSeries(); bars.Add(new TBar(DateTime.UtcNow, 10, 20, 5, 15, 100)); bars.Add(new TBar(DateTime.UtcNow.AddMinutes(1), 15, 25, 10, 20, 100)); - + var batchResult = Bop.Batch(bars); - + var output = new double[bars.Count]; Bop.Calculate(bars.Open.Values, bars.High.Values, bars.Low.Values, bars.Close.Values, output); - + Assert.Equal(batchResult[0].Value, output[0]); Assert.Equal(batchResult[1].Value, output[1]); } diff --git a/lib/momentum/bop/Bop.Validation.Tests.cs b/lib/momentum/bop/Bop.Validation.Tests.cs index 28f4e66f..76addc83 100644 --- a/lib/momentum/bop/Bop.Validation.Tests.cs +++ b/lib/momentum/bop/Bop.Validation.Tests.cs @@ -42,10 +42,10 @@ public sealed class BopValidationTests : IDisposable var high = _data.Bars.High.Values.ToArray(); var low = _data.Bars.Low.Values.ToArray(); var close = _data.Bars.Close.Values.ToArray(); - + var talibResult = new double[_data.Bars.Count]; var retCode = TALib.Functions.Bop(open, high, low, close, 0..^0, talibResult, out var outRange); - + Assert.Equal(Core.RetCode.Success, retCode); var quanTAlibResult = Bop.Batch(_data.Bars); @@ -60,7 +60,7 @@ public sealed class BopValidationTests : IDisposable var high = _data.Bars.High.Values.ToArray(); var low = _data.Bars.Low.Values.ToArray(); var close = _data.Bars.Close.Values.ToArray(); - + double[][] inputs = { open, high, low, close }; double[] options = Array.Empty(); // No options for BOP @@ -89,7 +89,7 @@ public sealed class BopValidationTests : IDisposable var stockData = new StockData(ooplesData); var ooplesResult = stockData.CalculateBalanceOfPower().OutputValues["Bop"].ToArray(); - + var quanTAlibResult = Bop.Batch(_data.Bars); ValidationHelper.VerifyData(quanTAlibResult, ooplesResult, lookback: 0, skip: 0, tolerance: ValidationHelper.OoplesTolerance); diff --git a/lib/momentum/bop/Bop.cs b/lib/momentum/bop/Bop.cs index 05153b1e..84768315 100644 --- a/lib/momentum/bop/Bop.cs +++ b/lib/momentum/bop/Bop.cs @@ -165,7 +165,7 @@ public sealed class Bop : ITValuePublisher if (source.Count == 0) return new TSeries([], []); var len = source.Count; - + var t = new List(len); var v = new List(len); CollectionsMarshal.SetCount(t, len); diff --git a/lib/momentum/cfb/Cfb.Tests.cs b/lib/momentum/cfb/Cfb.Tests.cs index b63ea013..ce1bffea 100644 --- a/lib/momentum/cfb/Cfb.Tests.cs +++ b/lib/momentum/cfb/Cfb.Tests.cs @@ -64,7 +64,7 @@ public class CfbTests // 100, 101, 100, 101... // NetMove(4) = Abs(100 - 100) = 0. Ratio = 0. // NetMove(8) = 0. Ratio = 0. - + for (int i = 0; i < 100; i++) { double price = 100 + (i % 2); diff --git a/lib/momentum/cfb/Cfb.Validation.Tests.cs b/lib/momentum/cfb/Cfb.Validation.Tests.cs index 4cb866cd..4842ebc8 100644 --- a/lib/momentum/cfb/Cfb.Validation.Tests.cs +++ b/lib/momentum/cfb/Cfb.Validation.Tests.cs @@ -42,7 +42,7 @@ public class CfbValidationTests { // Verify that Batch(TSeries) and Batch(Span) produce identical results var batchResult = Cfb.Batch(_testData.Data); - + var spanInput = _testData.Data.Values.ToArray().AsSpan(); var spanOutput = new double[spanInput.Length]; Cfb.Batch(spanInput, spanOutput); diff --git a/lib/momentum/cfb/Cfb.cs b/lib/momentum/cfb/Cfb.cs index 25efc61c..d54bf175 100644 --- a/lib/momentum/cfb/Cfb.cs +++ b/lib/momentum/cfb/Cfb.cs @@ -235,9 +235,9 @@ public sealed class Cfb : ITValuePublisher Batch(source.Values, vSpan, _lengths); source.Times.CopyTo(tSpan); - // Restore state logic would go here if needed for continuity, + // Restore state logic would go here if needed for continuity, // but for batch processing we usually just return the result. - // To properly support "Update(TValue)" after "Update(TSeries)", we would need to + // To properly support "Update(TValue)" after "Update(TSeries)", we would need to // replay the last MaxLen bars to populate the buffers. // Replay last MaxLen bars to restore state diff --git a/lib/momentum/dmx/Dmx.Tests.cs b/lib/momentum/dmx/Dmx.Tests.cs index 1c5447f6..ec5b357c 100644 --- a/lib/momentum/dmx/Dmx.Tests.cs +++ b/lib/momentum/dmx/Dmx.Tests.cs @@ -66,13 +66,13 @@ public class DmxTests dmx.Reset(); Assert.Equal(0, dmx.Last.Value); - + // Feed again for (int i = 0; i < bars.Count; i++) { dmx.Update(bars[i]); } - + Assert.True(double.IsFinite(dmx.Last.Value)); } @@ -98,16 +98,16 @@ public class DmxTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void FirstBar_Handling() { var dmx = new Dmx(14); var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000); - + // First bar should produce 0 DMX because DM+ and DM- are 0 var result = dmx.Update(bar); - + Assert.Equal(0, result.Value); } diff --git a/lib/momentum/dmx/Dmx.Validation.Tests.cs b/lib/momentum/dmx/Dmx.Validation.Tests.cs index d2d6b706..afccfd95 100644 --- a/lib/momentum/dmx/Dmx.Validation.Tests.cs +++ b/lib/momentum/dmx/Dmx.Validation.Tests.cs @@ -18,7 +18,7 @@ public class DmxValidationTests { var gbm = new GBM(); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var dmx = new Dmx(14); var streamResult = new TSeries(); for (int i = 0; i < bars.Count; i++) @@ -42,7 +42,7 @@ public class DmxValidationTests { var gbm = new GBM(); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var dmx = new Dmx(14); for (int i = 0; i < bars.Count; i++) { @@ -94,7 +94,7 @@ public class DmxValidationTests { Assert.True(result[i].Value < 0, $"DMX should be negative in downtrend at index {i}, got {result[i].Value}"); } - + _output.WriteLine("DMX trend direction validated successfully"); } } diff --git a/lib/momentum/macd/Macd.Quantower.Tests.cs b/lib/momentum/macd/Macd.Quantower.Tests.cs index 309bb04f..559701be 100644 --- a/lib/momentum/macd/Macd.Quantower.Tests.cs +++ b/lib/momentum/macd/Macd.Quantower.Tests.cs @@ -85,7 +85,7 @@ public class MacdIndicatorTests // Process updates var args = new UpdateArgs(UpdateReason.HistoricalBar); - + for(int i=0; i<10; i++) { indicator.ProcessUpdate(args); @@ -95,7 +95,7 @@ public class MacdIndicatorTests double macd = indicator.LinesSeries[0].GetValue(0); double signal = indicator.LinesSeries[1].GetValue(0); double hist = indicator.LinesSeries[2].GetValue(0); - + // Just check they are valid numbers Assert.False(double.IsNaN(macd)); Assert.False(double.IsNaN(signal)); diff --git a/lib/momentum/macd/Macd.Tests.cs b/lib/momentum/macd/Macd.Tests.cs index 6dbfddb1..e08dbe1c 100644 --- a/lib/momentum/macd/Macd.Tests.cs +++ b/lib/momentum/macd/Macd.Tests.cs @@ -24,7 +24,7 @@ public class MacdTests } var batchResult = macd.Update(series); - + macd.Reset(); var streamResults = new System.Collections.Generic.List(); foreach (var item in series) @@ -38,7 +38,7 @@ public class MacdTests Assert.Equal(batchResult[i].Value, streamResults[i], 8); } } - + [Fact] public void SpanMatchesBatch() { @@ -49,12 +49,12 @@ public class MacdTests { series.Add(new TValue(DateTime.UtcNow.AddMinutes(i), 100 + Math.Sin(i * 0.1) * 10)); } - + var batchResult = macd.Update(series); - + var output = new double[series.Count]; Macd.Calculate(series.Values, output, 12, 26); - + for (int i = 0; i < series.Count; i++) { Assert.Equal(batchResult[i].Value, output[i], 8); diff --git a/lib/momentum/macd/Macd.Validation.Tests.cs b/lib/momentum/macd/Macd.Validation.Tests.cs index b947f77a..c3a86c93 100644 --- a/lib/momentum/macd/Macd.Validation.Tests.cs +++ b/lib/momentum/macd/Macd.Validation.Tests.cs @@ -62,9 +62,9 @@ public sealed class MacdValidationTests : IDisposable // Compare last 100 records // MACD Line ValidationHelper.VerifyData(qResult, sResult, (s) => s.Macd); - + // Signal Line - // We need to extract Signal line from QuanTAlib result. + // We need to extract Signal line from QuanTAlib result. // Since Update returns TSeries of MACD line, we need to access Signal property from the indicator instance // But for batch update, we need to re-run or capture signal. // The Macd.Update(TSeries) returns the MACD line series. @@ -180,7 +180,7 @@ public sealed class MacdValidationTests : IDisposable // Calculate Ooples MACD var stockData = new StockData(ooplesData); var oResult = stockData.CalculateMovingAverageConvergenceDivergence(fastLength: fastPeriod, slowLength: slowPeriod, signalLength: signalPeriod); - + var oMacd = oResult.OutputValues["Macd"]; var oSignal = oResult.OutputValues["Signal"]; var oHist = oResult.OutputValues["Histogram"]; @@ -223,10 +223,10 @@ public sealed class MacdValidationTests : IDisposable var macdIndicator = Tulip.Indicators.macd; double[][] inputs = { tData }; double[] options = { fastPeriod, slowPeriod, signalPeriod }; - + // Tulip MACD lookback int lookback = macdIndicator.Start(options); - double[][] outputs = { + double[][] outputs = { new double[tData.Length - lookback], // MACD new double[tData.Length - lookback], // Signal new double[tData.Length - lookback] // Histogram diff --git a/lib/momentum/macd/Macd.cs b/lib/momentum/macd/Macd.cs index b10c1c0e..11b218a5 100644 --- a/lib/momentum/macd/Macd.cs +++ b/lib/momentum/macd/Macd.cs @@ -41,12 +41,12 @@ public sealed class Macd : ITValuePublisher _slowEma = new Ema(slowPeriod); _signalEma = new Ema(signalPeriod); _handler = Handle; - + Name = $"Macd({fastPeriod},{slowPeriod},{signalPeriod})"; WarmupPeriod = Math.Max(fastPeriod, slowPeriod) + signalPeriod; } - public Macd(ITValuePublisher source, int fastPeriod = 12, int slowPeriod = 26, int signalPeriod = 9) + public Macd(ITValuePublisher source, int fastPeriod = 12, int slowPeriod = 26, int signalPeriod = 9) : this(fastPeriod, slowPeriod, signalPeriod) { source.Pub += _handler; @@ -99,7 +99,7 @@ public sealed class Macd : ITValuePublisher t.Add(source[i].Time); v.Add(Last.Value); } - + return new TSeries(t, v); } @@ -107,7 +107,7 @@ public sealed class Macd : ITValuePublisher { Update(args.Value, args.IsNew); } - + /// /// Calculates the MACD Line (Fast EMA - Slow EMA). /// Does not calculate Signal or Histogram. @@ -116,19 +116,19 @@ public sealed class Macd : ITValuePublisher { if (source.Length != destination.Length) throw new ArgumentException("Source and destination must be same length", nameof(destination)); - + int len = source.Length; double[] fastBuffer = ArrayPool.Shared.Rent(len); double[] slowBuffer = ArrayPool.Shared.Rent(len); - - try + + try { Span fastSpan = fastBuffer.AsSpan(0, len); Span slowSpan = slowBuffer.AsSpan(0, len); - + Ema.Batch(source, fastSpan, fastPeriod); Ema.Batch(source, slowSpan, slowPeriod); - + SimdExtensions.Subtract(fastSpan, slowSpan, destination); } finally diff --git a/lib/momentum/rsi/Rsi.Quantower.Tests.cs b/lib/momentum/rsi/Rsi.Quantower.Tests.cs index 3372c6ec..d55d7847 100644 --- a/lib/momentum/rsi/Rsi.Quantower.Tests.cs +++ b/lib/momentum/rsi/Rsi.Quantower.Tests.cs @@ -80,18 +80,18 @@ public class RsiIndicatorTests // Process updates var args = new UpdateArgs(UpdateReason.HistoricalBar); - + // We need to process updates sequentially to build state // But the mock might not support full stateful replay easily without calling ProcessUpdate multiple times // Let's just verify it runs without error and produces a value - + indicator.ProcessUpdate(args); // Bar 0 indicator.ProcessUpdate(args); // Bar 1 indicator.ProcessUpdate(args); // Bar 2 // Line series should have a value double rsi = indicator.LinesSeries[0].GetValue(0); - + // We just check it's a valid number (0-100) Assert.True(rsi >= 0 && rsi <= 100); } diff --git a/lib/momentum/rsi/Rsi.Tests.cs b/lib/momentum/rsi/Rsi.Tests.cs index a811bc6d..7edcfab5 100644 --- a/lib/momentum/rsi/Rsi.Tests.cs +++ b/lib/momentum/rsi/Rsi.Tests.cs @@ -270,7 +270,7 @@ public class RsiTests } var result = rsi.Update(series); - + // Flat line: no gains or losses, RSI = 50 Assert.Equal(50, result.Last.Value); } @@ -362,12 +362,12 @@ public class RsiTests var source = new double[20]; var gbm = new GBM(); var series = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + for (int i = 0; i < 20; i++) { source[i] = series.Close[i].Value; } - + source[10] = double.NaN; source[15] = double.NaN; diff --git a/lib/momentum/rsi/Rsi.Validation.Tests.cs b/lib/momentum/rsi/Rsi.Validation.Tests.cs index da78f795..389931bc 100644 --- a/lib/momentum/rsi/Rsi.Validation.Tests.cs +++ b/lib/momentum/rsi/Rsi.Validation.Tests.cs @@ -186,7 +186,7 @@ public sealed class RsiValidationTests : IDisposable var rsiIndicator = Tulip.Indicators.rsi; double[][] inputs = { tData }; double[] options = { period }; - + // Tulip RSI lookback int lookback = rsiIndicator.Start(options); double[][] outputs = { new double[tData.Length - lookback] }; @@ -222,7 +222,7 @@ public sealed class RsiValidationTests : IDisposable var rsiIndicator = Tulip.Indicators.rsi; double[][] inputs = { tData }; double[] options = { period }; - + // Tulip RSI lookback int lookback = rsiIndicator.Start(options); double[][] outputs = { new double[tData.Length - lookback] }; diff --git a/lib/momentum/rsi/Rsi.cs b/lib/momentum/rsi/Rsi.cs index c3cc218a..b8abefbb 100644 --- a/lib/momentum/rsi/Rsi.cs +++ b/lib/momentum/rsi/Rsi.cs @@ -191,7 +191,7 @@ public sealed class Rsi : AbstractBase { int vectorSize = Vector.Count; var vZero = Vector.Zero; - + // Start from 1, but align to vector size if possible or just process chunks // Since we need i-1, we can load vectors at i and i-1 for (; i <= len - vectorSize; i += vectorSize) @@ -249,15 +249,15 @@ public sealed class Rsi : AbstractBase // Handle edge cases where loss is zero var vLossIsZero = Vector.LessThan(vLoss, vEpsilon); var vGainIsZero = Vector.LessThan(vGain, vEpsilon); - + // If loss is zero: // If gain is also zero -> 50 // Else -> 100 var vFlat = Vector.BitwiseAnd(vLossIsZero, vGainIsZero); - + // First set to 100 if loss is zero var vResult = Vector.ConditionalSelect(vLossIsZero, v100, vRsi); - + // Then set to 50 if both are zero vResult = Vector.ConditionalSelect(vFlat, v50, vResult); diff --git a/lib/momentum/rsx/Rsx.Repro.Tests.cs b/lib/momentum/rsx/Rsx.Repro.Tests.cs index 1d503835..40d58a14 100644 --- a/lib/momentum/rsx/Rsx.Repro.Tests.cs +++ b/lib/momentum/rsx/Rsx.Repro.Tests.cs @@ -11,25 +11,25 @@ public class RsxReproTests { // Arrange var rsx = new Rsx(2); - + // Warmup to ensure initialization rsx.Update(new TValue(DateTime.UtcNow, 100), true); rsx.Update(new TValue(DateTime.UtcNow, 100), true); rsx.Update(new TValue(DateTime.UtcNow, 100), true); - + // Update: Valid value, isNew=false (Transient update) // This should NOT persist 200 as LastValidValue for the next bar. rsx.Update(new TValue(DateTime.UtcNow, 200), false); - + // Update: NaN value, isNew=true - // Should use LastValidValue. + // Should use LastValidValue. // If bug exists: uses 200. Momentum = 200 - 100 = 100. // If fixed: uses 100. Momentum = 100 - 100 = 0. var res = rsx.Update(new TValue(DateTime.UtcNow, double.NaN), true); - + // If momentum was 0, RSX should be 50. // If momentum was 100, RSX should be > 50. - + Assert.Equal(50.0, res.Value, 1e-6); } } diff --git a/lib/momentum/rsx/Rsx.Tests.cs b/lib/momentum/rsx/Rsx.Tests.cs index 2dc35f11..f76f5f45 100644 --- a/lib/momentum/rsx/Rsx.Tests.cs +++ b/lib/momentum/rsx/Rsx.Tests.cs @@ -33,7 +33,7 @@ public class RsxTests var rsx = new Rsx(14); rsx.Update(new TValue(DateTime.UtcNow, 100)); var result = rsx.Update(new TValue(DateTime.UtcNow, double.NaN)); - + // Should not be NaN Assert.False(double.IsNaN(result.Value)); Assert.InRange(result.Value, 0, 100); @@ -44,13 +44,13 @@ public class RsxTests { var rsx = new Rsx(14); var time = DateTime.UtcNow; - + // Update with isNew=true var val1 = rsx.Update(new TValue(time, 100), true); - + // Update with isNew=false (same time, different value) rsx.Update(new TValue(time, 105), false); - + // Update with isNew=false (same time, original value) - should match val1 if state rollback works var val3 = rsx.Update(new TValue(time, 100), false); @@ -65,15 +65,15 @@ public class RsxTests var bars = _gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; var rsx = new Rsx(period); - + var streamingResults = new List(); for (int i = 0; i < count; i++) { streamingResults.Add(rsx.Update(new TValue(series.Times[i], series.Values[i])).Value); } - + var staticResults = Rsx.Batch(series, period); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < count; i++) { @@ -89,17 +89,17 @@ public class RsxTests var bars = _gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; var rsx = new Rsx(period); - + var streamingResults = new List(); for (int i = 0; i < count; i++) { streamingResults.Add(rsx.Update(new TValue(series.Times[i], series.Values[i])).Value); } - + var spanInput = series.Values.ToArray(); var spanOutput = new double[count]; Rsx.Batch(spanInput, spanOutput, period); - + for (int i = 0; i < count; i++) { Assert.Equal(streamingResults[i], spanOutput[i], 1e-9); @@ -112,18 +112,18 @@ public class RsxTests var rsx = new Rsx(14); rsx.Update(new TValue(DateTime.UtcNow, 100)); rsx.Reset(); - + // After reset, it should behave like a new instance var val1 = rsx.Update(new TValue(DateTime.UtcNow, 100)); Assert.Equal(50.0, val1.Value); // Neutral start } - + [Fact] public void Chainability_Works() { var rsx = new Rsx(14); var rsx2 = new Rsx(rsx, 14); - + var result = rsx2.Update(new TValue(DateTime.UtcNow, 100)); Assert.False(double.IsNaN(result.Value)); } diff --git a/lib/momentum/rsx/Rsx.Validation.Tests.cs b/lib/momentum/rsx/Rsx.Validation.Tests.cs index cee1271d..8a26d43a 100644 --- a/lib/momentum/rsx/Rsx.Validation.Tests.cs +++ b/lib/momentum/rsx/Rsx.Validation.Tests.cs @@ -53,7 +53,7 @@ public class RsxValidationTests // Internal state variables for filter registers: private double f28, f30, f38, f40, f48, f50; private double f58, f60, f68, f70, f78, f80; - + // Added state for f10 logic private double lastF8; private bool initialized; @@ -76,14 +76,14 @@ public class RsxValidationTests { // Core RSX calculations (assuming price input as closing price): double f8 = 100 * price; - - + + if (!initialized) { lastF8 = f8; initialized = true; } - + double v8 = f8 - lastF8; lastF8 = f8; diff --git a/lib/momentum/vel/Vel.Tests.cs b/lib/momentum/vel/Vel.Tests.cs index 75de110c..98dd5a7a 100644 --- a/lib/momentum/vel/Vel.Tests.cs +++ b/lib/momentum/vel/Vel.Tests.cs @@ -62,7 +62,7 @@ public class VelTests vel.Update(new TValue(DateTime.UtcNow, 50)); // First value is 0 because PWMA(50) = 50 and WMA(50) = 50 Assert.Equal(0, vel.Last.Value); - + vel.Update(new TValue(DateTime.UtcNow, 60)); Assert.NotEqual(0, vel.Last.Value); Assert.NotEqual(valueBefore, vel.Last.Value); @@ -97,7 +97,7 @@ public class VelTests // PWMA(3) of 10,20,30 = 360/14 = 25.7142857... // WMA(3) of 10,20,30 = 140/6 = 23.3333333... // VEL = PWMA - WMA = 2.38095238... - + double expectedPwma = 360.0 / 14.0; double expectedWma = 140.0 / 6.0; double expectedVel = expectedPwma - expectedWma; @@ -116,7 +116,7 @@ public class VelTests var results = Vel.Batch(series, 3); Assert.Equal(3, results.Count); - + double expectedPwma = 360.0 / 14.0; double expectedWma = 140.0 / 6.0; double expectedVel = expectedPwma - expectedWma; @@ -157,7 +157,7 @@ public class VelTests { var vel = new Vel(10); var vel2 = new Vel(vel, 10); - + vel.Update(new TValue(DateTime.UtcNow, 100)); Assert.False(double.IsNaN(vel2.Last.Value)); } diff --git a/lib/momentum/vel/Vel.Validation.Tests.cs b/lib/momentum/vel/Vel.Validation.Tests.cs index cac3c934..4aed390b 100644 --- a/lib/momentum/vel/Vel.Validation.Tests.cs +++ b/lib/momentum/vel/Vel.Validation.Tests.cs @@ -17,12 +17,12 @@ public class VelValidationTests var wma = new Wma(period); var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 42); - + for (int i = 0; i < 100; i++) { var bar = gbm.Next(isNew: true); var input = new TValue(bar.Time, bar.Close); - + var v = vel.Update(input); var p = pwma.Update(input); var w = wma.Update(input); diff --git a/lib/quantalib.csproj b/lib/quantalib.csproj index 8990c293..c2e49236 100644 --- a/lib/quantalib.csproj +++ b/lib/quantalib.csproj @@ -1,51 +1,51 @@ - - - net10.0;net8.0 - net10.0 - QuanTAlib - Library of TA Calculations, Charts and Strategies for Quantower - Quantitative Technical Analysis Library in C# for Quantower - git - https://github.com/mihakralj/QuanTAlib - Miha Kralj - Miha Kralj - QuanTAlib - QuanTAlib - true - true - QuanTAlib2.png - Apache-2.0 - true - - Indicators;Stock;Market;Technical;Analysis;Algorithmic;Trading;Trade;Trend;Momentum;Finance;Algorithm;Algo; - AlgoTrading;Financial;Strategy;Chart;Charting;Oscillator;Overlay;Equity;Bitcoin;Crypto;Cryptocurrency;Forex; - Quantitative;Historical;Quotes; - - https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png - True - false - $(GitVersion_MajorMinorPatch) - $(GitVersion_MajorMinorPatch) - $(GitVersion_AssemblySemVer) - $(GitVersion_AssemblySemFileVer) - $(GitVersion_InformationalVersion) - latest - 6afc11a7-4355-4f5e-9fdf-22431e5b03cb - - - - - - - - - - - - - - - - - - + + + net10.0;net8.0 + net10.0 + QuanTAlib + Library of TA Calculations, Charts and Strategies for Quantower + Quantitative Technical Analysis Library in C# for Quantower + git + https://github.com/mihakralj/QuanTAlib + Miha Kralj + Miha Kralj + QuanTAlib + QuanTAlib + true + true + QuanTAlib2.png + Apache-2.0 + true + + Indicators;Stock;Market;Technical;Analysis;Algorithmic;Trading;Trade;Trend;Momentum;Finance;Algorithm;Algo; + AlgoTrading;Financial;Strategy;Chart;Charting;Oscillator;Overlay;Equity;Bitcoin;Crypto;Cryptocurrency;Forex; + Quantitative;Historical;Quotes; + + https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png + True + false + $(GitVersion_MajorMinorPatch) + $(GitVersion_MajorMinorPatch) + $(GitVersion_AssemblySemVer) + $(GitVersion_AssemblySemFileVer) + $(GitVersion_InformationalVersion) + latest + 6afc11a7-4355-4f5e-9fdf-22431e5b03cb + + + + + + + + + + + + + + + + + + diff --git a/lib/statistics/beta/Beta.Tests.cs b/lib/statistics/beta/Beta.Tests.cs index 6cbc6fcb..034c1315 100644 --- a/lib/statistics/beta/Beta.Tests.cs +++ b/lib/statistics/beta/Beta.Tests.cs @@ -23,7 +23,7 @@ public class BetaTests { int period = 5; var beta = new Beta(period); - + // We need period returns. // 1st update: initializes prev prices. No return. // 2nd update: 1st return. @@ -45,7 +45,7 @@ public class BetaTests { // Scenario: Asset returns are exactly 2x Market returns. // We need variable market returns to have non-zero variance. - + int period = 10; var beta = new Beta(period); @@ -66,9 +66,9 @@ public class BetaTests marketPrice *= (1 + marketReturn); assetPrice *= (1 + assetReturn); - + TValue result = beta.Update(assetPrice, marketPrice); - + if (beta.IsHot) { Assert.Equal(2.0, result.Value, precision: 6); @@ -88,7 +88,7 @@ public class BetaTests beta.Reset(); Assert.False(beta.IsHot); - + // Re-initialize beta.Update(100, 100); Assert.False(beta.IsHot); diff --git a/lib/statistics/beta/Beta.Validation.Tests.cs b/lib/statistics/beta/Beta.Validation.Tests.cs index a8b1a210..8baa4766 100644 --- a/lib/statistics/beta/Beta.Validation.Tests.cs +++ b/lib/statistics/beta/Beta.Validation.Tests.cs @@ -25,13 +25,13 @@ public sealed class BetaValidationTests : IDisposable { // Generate Market Data (use existing Data) var marketQuotes = _data.Data; - + // Generate Asset Data correlated to Market // Asset Returns = 1.5 * Market Returns + Noise var assetQuotes = new List(); double assetPrice = 100; double targetBeta = 1.5; - + // Use GBM for noise generation (sigma=0.2 gives ~0.0006 per step noise which matches original random noise level) var noiseGbm = new GBM(startPrice: 100, mu: 0, sigma: 0.2, seed: 777); @@ -40,13 +40,13 @@ public sealed class BetaValidationTests : IDisposable for (int i = 1; i < marketQuotes.Count; i++) { double marketReturn = (marketQuotes[i].Value - marketQuotes[i-1].Value) / marketQuotes[i-1].Value; - + // Get noise from GBM return var noiseBar = noiseGbm.Next(); double noise = (noiseBar.Close - noiseBar.Open) / noiseBar.Open; - + double assetReturn = targetBeta * marketReturn + noise; - + assetPrice *= (1 + assetReturn); assetQuotes.Add(new TBar(marketQuotes[i].Time, assetPrice, assetPrice, assetPrice, assetPrice, 1000)); } @@ -73,7 +73,7 @@ public sealed class BetaValidationTests : IDisposable // Skip warmup period. Skender Beta needs period returns, so period+1 prices? // Skender results align with input quotes. // First valid value should be at index 'period'. - + // We verify the last 100 values int count = qlBeta.Count; int skip = period + 5; // Safety margin @@ -82,7 +82,7 @@ public sealed class BetaValidationTests : IDisposable { double sk = (skenderBeta[i].Beta ?? 0); double ql = qlBeta[i]; - + // Skender might return null/0 for warmup. if (Math.Abs(sk) > 1e-10) { diff --git a/lib/statistics/beta/Beta.cs b/lib/statistics/beta/Beta.cs index 9e252128..9f41d956 100644 --- a/lib/statistics/beta/Beta.cs +++ b/lib/statistics/beta/Beta.cs @@ -9,14 +9,14 @@ namespace QuanTAlib; /// /// Beta is calculated as the covariance of the asset's returns and the market's returns, /// divided by the variance of the market's returns. -/// +/// /// Formula: /// Beta = Cov(Ra, Rm) / Var(Rm) -/// +/// /// Where: /// Ra = Return of Asset /// Rm = Return of Market -/// +/// /// This implementation uses the O(1) slope formula for linear regression of Ra vs Rm: /// Beta = (N * Sum(Ra*Rm) - Sum(Ra) * Sum(Rm)) / (N * Sum(Rm^2) - Sum(Rm)^2) /// @@ -25,7 +25,7 @@ public sealed class Beta : AbstractBase { private readonly RingBuffer _returnsAsset; private readonly RingBuffer _returnsMarket; - + private double _prevAsset; private double _prevMarket; private double _p_prevAsset; diff --git a/lib/statistics/covariance/Covariance.Quantower.cs b/lib/statistics/covariance/Covariance.Quantower.cs index 31664c5c..dcec77c6 100644 --- a/lib/statistics/covariance/Covariance.Quantower.cs +++ b/lib/statistics/covariance/Covariance.Quantower.cs @@ -63,7 +63,7 @@ public sealed class CovarianceIndicator : Indicator, IWatchlistIndicator var input1 = new TValue(time, val1); var input2 = new TValue(time, val2); - + TValue result = _cov!.Update(input1, input2, args.IsNewBar()); _series!.SetValue(result.Value, _cov.IsHot, ShowColdValues); diff --git a/lib/statistics/covariance/Covariance.Simd.Tests.cs b/lib/statistics/covariance/Covariance.Simd.Tests.cs index 64682afc..7dd67e62 100644 --- a/lib/statistics/covariance/Covariance.Simd.Tests.cs +++ b/lib/statistics/covariance/Covariance.Simd.Tests.cs @@ -31,7 +31,7 @@ public class CovarianceSimdTests // This will use SIMD if available and length >= 256 var simdResult = Covariance.Calculate(sourceX, sourceY, period); - // Calculate expected using scalar loop (simulating by using small chunks or manual calc, + // Calculate expected using scalar loop (simulating by using small chunks or manual calc, // but easier to just use the streaming update which is scalar) var scalarCov = new Covariance(period); var expectedValues = new double[count]; @@ -56,7 +56,7 @@ public class CovarianceSimdTests int period = 50; var dataX = Enumerable.Range(0, count).Select(x => (double)x).ToArray(); var dataY = Enumerable.Range(0, count).Select(x => (double)x * 2).ToArray(); - + // Inject NaN dataX[300] = double.NaN; dataY[350] = double.NaN; @@ -74,9 +74,9 @@ public class CovarianceSimdTests // Assert // Verify around the NaN values - // Index 300 has NaN in X. Covariance should handle it (likely treat as 0 or propagate last valid if logic dictates, + // Index 300 has NaN in X. Covariance should handle it (likely treat as 0 or propagate last valid if logic dictates, // but current implementation replaces non-finite with 0 in scalar core). - + // Let's verify against streaming which we know uses scalar logic // BUT: Batch implementation replaces NaN with 0, while Streaming propagates NaN. // To compare, we must feed 0 instead of NaN to streaming. @@ -92,7 +92,7 @@ public class CovarianceSimdTests Assert.Equal(res.Value, result.Values[i], precision: 9); } } - + [Fact] public void Covariance_Simd_Resync_Check() { @@ -102,13 +102,13 @@ public class CovarianceSimdTests // The SIMD loop starts at 'period' and goes up to 'simdEnd'. // So we need length > period + 1000. int period = 10; - int count = 2000; - + int count = 2000; + // Use simple linear data to make verification easy // y = 2x var dataX = Enumerable.Range(0, count).Select(x => (double)x).ToArray(); var dataY = Enumerable.Range(0, count).Select(x => (double)x * 2).ToArray(); - + var sourceX = new TSeries(); sourceX.Add(dataX); var sourceY = new TSeries(); @@ -123,7 +123,7 @@ public class CovarianceSimdTests // For period 10: 0..9. Variance is constant. // Var(0..9) = 9.16666... (Population) or 10.185... (Sample)? // Let's just compare with scalar truth. - + var scalarCov = new Covariance(period); for (int i = 0; i < count; i++) { diff --git a/lib/statistics/covariance/Covariance.Tests.cs b/lib/statistics/covariance/Covariance.Tests.cs index 1ae422ed..0035e688 100644 --- a/lib/statistics/covariance/Covariance.Tests.cs +++ b/lib/statistics/covariance/Covariance.Tests.cs @@ -10,7 +10,7 @@ public class CovarianceTests { // Arrange var cov = new Covariance(3, isPopulation: false); - + // Act & Assert // 1. Add (1, 2) // MeanX = 1, MeanY = 2 @@ -50,11 +50,11 @@ public class CovarianceTests { // Arrange var cov = new Covariance(3, isPopulation: true); - + // Act & Assert cov.Update(1, 2); cov.Update(2, 4); - + // 3. Add (3, 6) // X: {1, 2, 3}, Y: {2, 4, 6} // MeanX = 2, MeanY = 4 @@ -69,12 +69,12 @@ public class CovarianceTests { // Arrange var cov = new Covariance(3); - + // Act cov.Update(1, 1); cov.Update(2, 1); var res = cov.Update(3, 1); // Y is constant, variance Y is 0, covariance is 0 - + // Assert Assert.Equal(0, res.Value); } @@ -84,18 +84,18 @@ public class CovarianceTests { // Arrange var cov = new Covariance(3); - + // Act cov.Update(1, 3); cov.Update(2, 2); var res = cov.Update(3, 1); - + // X: {1, 2, 3}, MeanX = 2 // Y: {3, 2, 1}, MeanY = 2 // Cov = ((1-2)(3-2) + (2-2)(2-2) + (3-2)(1-2)) / 2 // = ((-1)(1) + 0 + (1)(-1)) / 2 // = (-1 - 1) / 2 = -1 - + // Assert Assert.Equal(-1, res.Value); } @@ -105,16 +105,16 @@ public class CovarianceTests { // Arrange var cov = new Covariance(3); - + // Act // Force many updates to trigger resync (ResyncInterval = 1000) - // We can't easily force 1000 updates in a simple test without loop, + // We can't easily force 1000 updates in a simple test without loop, // but we can verify the logic holds for a sequence. for (int i = 0; i < 1100; i++) { cov.Update(i, i * 2); } - + // Last 3: {1097, 1098, 1099}, {2194, 2196, 2198} // This is a perfect linear relationship y = 2x // Cov(X, 2X) = 2 * Var(X) @@ -123,7 +123,7 @@ public class CovarianceTests // SumSqDiff = (-1)^2 + 0 + 1^2 = 2 // Var = 2 / 2 = 1 // Cov = 2 * 1 = 2 - + // Assert Assert.Equal(2, cov.Last.Value, precision: 10); } @@ -133,29 +133,29 @@ public class CovarianceTests { // Arrange var cov = new Covariance(3); - + // Act cov.Update(1, 2); cov.Update(2, 4); cov.Update(3, 6); // Cov = 2 - + // Update last bar with new values // Change (3, 6) to (4, 8) // X: {1, 2, 4}, MeanX = 7/3 = 2.333... // Y: {2, 4, 8}, MeanY = 14/3 = 4.666... // This is harder to calc manually, let's use the property that it should match adding (4, 8) directly - + var res = cov.Update(4, 8, isNew: false); - + var cov2 = new Covariance(3); cov2.Update(1, 2); cov2.Update(2, 4); var expected = cov2.Update(4, 8); - + // Assert Assert.Equal(expected.Value, res.Value, precision: 10); } - + [Fact] public void Covariance_Throws_On_Single_Input() { diff --git a/lib/statistics/covariance/Covariance.Validation.Tests.cs b/lib/statistics/covariance/Covariance.Validation.Tests.cs index 53f2108c..fbbf9e43 100644 --- a/lib/statistics/covariance/Covariance.Validation.Tests.cs +++ b/lib/statistics/covariance/Covariance.Validation.Tests.cs @@ -13,7 +13,7 @@ public class CovarianceValidationTests var cov = new Covariance(period, isPopulation: false); var gbmX = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var gbmY = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 456); - + double[] x = new double[100]; double[] y = new double[100]; for (int i = 0; i < 100; i++) @@ -21,7 +21,7 @@ public class CovarianceValidationTests x[i] = gbmX.Next().Close; y[i] = gbmY.Next().Close; cov.Update(x[i], y[i]); - + if (i >= period - 1) { // Manual calculation for last 'period' items @@ -34,13 +34,13 @@ public class CovarianceValidationTests } double meanX = sumX / period; double meanY = sumY / period; - + double sumProd = 0; for (int j = 0; j < period; j++) { sumProd += (x[i - j] - meanX) * (y[i - j] - meanY); } - + double expected = sumProd / (period - 1); Assert.Equal(expected, cov.Last.Value, precision: 8); } @@ -55,7 +55,7 @@ public class CovarianceValidationTests var cov = new Covariance(period, isPopulation: true); var gbmX = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 456); var gbmY = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 789); - + double[] x = new double[100]; double[] y = new double[100]; for (int i = 0; i < 100; i++) @@ -63,7 +63,7 @@ public class CovarianceValidationTests x[i] = gbmX.Next().Close; y[i] = gbmY.Next().Close; cov.Update(x[i], y[i]); - + if (i >= period - 1) { // Manual calculation for last 'period' items @@ -76,13 +76,13 @@ public class CovarianceValidationTests } double meanX = sumX / period; double meanY = sumY / period; - + double sumProd = 0; for (int j = 0; j < period; j++) { sumProd += (x[i - j] - meanX) * (y[i - j] - meanY); } - + double expected = sumProd / period; Assert.Equal(expected, cov.Last.Value, precision: 8); } diff --git a/lib/statistics/linreg/LinReg.Validation.Tests.cs b/lib/statistics/linreg/LinReg.Validation.Tests.cs index 8665a35d..149d9ed0 100644 --- a/lib/statistics/linreg/LinReg.Validation.Tests.cs +++ b/lib/statistics/linreg/LinReg.Validation.Tests.cs @@ -29,7 +29,7 @@ public sealed class LinRegValidationTests : IDisposable { var period = 14; var skender = _data.SkenderQuotes.GetSlope(period).ToList(); - + var linreg = new LinReg(period); var slopeSeries = new TSeries(); foreach (var item in _data.Data) @@ -47,7 +47,7 @@ public sealed class LinRegValidationTests : IDisposable { var period = 14; var skender = _data.SkenderQuotes.GetSlope(period).ToList(); - + var linreg = new LinReg(period); var r2Series = new TSeries(); foreach (var item in _data.Data) diff --git a/lib/statistics/linreg/LinReg.cs b/lib/statistics/linreg/LinReg.cs index fb368d3c..3eea6900 100644 --- a/lib/statistics/linreg/LinReg.cs +++ b/lib/statistics/linreg/LinReg.cs @@ -166,7 +166,7 @@ public sealed class LinReg : AbstractBase _state.SumY = _buffer.Sum; _state.SumXY = 0; var span = _buffer.GetSpan(); - + // Vectorized SumY2 _state.SumY2 = span.DotProduct(span); @@ -248,7 +248,7 @@ public sealed class LinReg : AbstractBase // R2 = (n * sum_xy - sum_x * sum_y)^2 / ( (n * sum_x2 - sum_x^2) * (n * sum_y2 - sum_y^2) ) double numerator = Math.FusedMultiplyAdd(n, _state.SumXY, -sx * _state.SumY); double term2 = Math.FusedMultiplyAdd(n, _state.SumY2, -_state.SumY * _state.SumY); - + RSquared = Math.Abs(term2) < MinDenominator ? 1.0 // All y are same : numerator * numerator / (denom * term2); diff --git a/lib/statistics/median/Median.Tests.cs b/lib/statistics/median/Median.Tests.cs index 7f0dbd28..fae1e8cd 100644 --- a/lib/statistics/median/Median.Tests.cs +++ b/lib/statistics/median/Median.Tests.cs @@ -9,7 +9,7 @@ public class MedianTests { // Arrange var median = new Median(3); - + // Act median.Update(new TValue(DateTime.MinValue, 10)); median.Update(new TValue(DateTime.MinValue, 30)); @@ -25,7 +25,7 @@ public class MedianTests { // Arrange var median = new Median(4); - + // Act median.Update(new TValue(DateTime.MinValue, 10)); median.Update(new TValue(DateTime.MinValue, 40)); @@ -42,13 +42,13 @@ public class MedianTests { // Arrange var median = new Median(3); - + // Act median.Update(new TValue(DateTime.MinValue, 10)); median.Update(new TValue(DateTime.MinValue, 20)); - + // Update with 30 (isNew=true) - var r1 = median.Update(new TValue(DateTime.MinValue, 30)); + var r1 = median.Update(new TValue(DateTime.MinValue, 30)); // Window: [10, 20, 30] -> Median 20 Assert.Equal(20, r1.Value); @@ -79,7 +79,7 @@ public class MedianTests var medianBatch = Median.Batch(source, period); var medianStream = new Median(period); var streamResults = new List(); - + foreach (var val in source) { streamResults.Add(medianStream.Update(val).Value); @@ -91,7 +91,7 @@ public class MedianTests Assert.Equal(medianBatch.Values[i], streamResults[i], 1e-9); } } - + [Fact] public void Median_StaticBatch_Matches_ClassBatch() { @@ -99,11 +99,11 @@ public class MedianTests int period = 5; double[] data = new double[20]; for(int i=0; i(); @@ -139,7 +139,7 @@ public class SkewTests // StdDev (Pop) = sqrt(2/3) // M3 (Pop) = ((1-2)^3 + (2-2)^3 + (3-2)^3) / 3 = 0 // Skew (Pop) = 0 - + var skew = new Skew(3, isPopulation: true); skew.Update(new TValue(DateTime.UtcNow, 1)); skew.Update(new TValue(DateTime.UtcNow, 2)); @@ -166,7 +166,7 @@ public class SkewTests skew.Update(new TValue(DateTime.UtcNow, 1)); skew.Update(new TValue(DateTime.UtcNow, 2)); skew.Update(new TValue(DateTime.UtcNow, double.NaN)); // Should be treated as 0 or handled gracefully - + var result = skew.Last.Value; Assert.True(double.IsNaN(result) || result == 0); } @@ -177,12 +177,12 @@ public class SkewTests // Run for > 1000 updates to trigger Resync var skew = new Skew(10); var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); - + for (int i = 0; i < 1100; i++) { skew.Update(new TValue(DateTime.UtcNow, gbm.Next().Close)); } - + Assert.True(double.IsFinite(skew.Last.Value)); } @@ -195,10 +195,10 @@ public class SkewTests for (int i = 0; i < count; i++) data[i] = (double)i; var series = new TSeries(new System.Collections.Generic.List(new long[count]), new System.Collections.Generic.List(data)); - + // Batch calculation var batchResult = Skew.Calculate(series, 10); - + // Verify last value against streaming var skew = new Skew(10); double lastStreaming = 0; diff --git a/lib/statistics/skew/Skew.Validation.Tests.cs b/lib/statistics/skew/Skew.Validation.Tests.cs index 6520a463..42d7db05 100644 --- a/lib/statistics/skew/Skew.Validation.Tests.cs +++ b/lib/statistics/skew/Skew.Validation.Tests.cs @@ -22,7 +22,7 @@ public sealed class SkewValidationTests : IDisposable int period = 20; var skew = new Skew(period, isPopulation: false); var popSkew = new Skew(period, isPopulation: true); - + var quotes = _data.SkenderQuotes.ToList(); double[] input = quotes.Select(q => (double)q.Close).ToArray(); @@ -37,7 +37,7 @@ public sealed class SkewValidationTests : IDisposable var window = input[(i - period + 1)..(i + 1)]; double expected = Statistics.Skewness(window); double expectedPop = Statistics.PopulationSkewness(window); - + Assert.Equal(expected, val.Value, 1e-6); Assert.Equal(expectedPop, popVal.Value, 1e-6); } diff --git a/lib/statistics/skew/Skew.cs b/lib/statistics/skew/Skew.cs index bb61cd96..84f2165d 100644 --- a/lib/statistics/skew/Skew.cs +++ b/lib/statistics/skew/Skew.cs @@ -80,7 +80,7 @@ public sealed class Skew : AbstractBase { double oldNewest = _buffer.Newest; _buffer.UpdateNewest(input.Value); - + double val = input.Value; _sum = _sum - oldNewest + val; _sumSq = _sumSq - (oldNewest * oldNewest) + (val * val); @@ -92,7 +92,7 @@ public sealed class Skew : AbstractBase { double n = _buffer.Count; double mean = _sum / n; - + // Calculate 2nd moment (Variance) // m2 = Sum((x-mean)^2) / n = (SumSq - Sum^2/n) / n double m2Numerator = _sumSq - (_sum * _sum) / n; @@ -106,7 +106,7 @@ public sealed class Skew : AbstractBase // = SumCu - 3*mean*SumSq + 3*mean^2*Sum - n*mean^3 // Since Sum = n*mean: // = SumCu - 3*mean*SumSq + 2*n*mean^3 - + double m3Numerator = _sumCu - 3 * mean * _sumSq + 2 * n * mean * mean * mean; double m3 = m3Numerator / n; @@ -232,7 +232,7 @@ public sealed class Skew : AbstractBase double sum = 0; double sumSq = 0; double sumCu = 0; - + int i = 0; // Warmup phase @@ -259,11 +259,11 @@ public sealed class Skew : AbstractBase double oldVal = source[i - period]; if (!double.IsFinite(oldVal)) oldVal = 0; - + sum = sum - oldVal + val; sumSq = sumSq - (oldVal * oldVal) + (val * val); sumCu = sumCu - (oldVal * oldVal * oldVal) + (val * val * val); - + output[i] = CalculateSkewFromSums(sum, sumSq, sumCu, period, isPopulation); tickCount++; @@ -293,7 +293,7 @@ public sealed class Skew : AbstractBase private static double CalculateSkewFromSums(double sum, double sumSq, double sumCu, double n, bool isPopulation) { double mean = sum / n; - + double m2Numerator = sumSq - (sum * sum) / n; if (m2Numerator < Epsilon) return 0; double m2 = m2Numerator / n; @@ -326,7 +326,7 @@ public sealed class Skew : AbstractBase sum += val; sumSq += val * val; sumCu += val * val * val; - + double n = i + 1; Unsafe.Add(ref outRef, i) = (n >= 3) ? CalculateSkewFromSums(sum, sumSq, sumCu, n, isPopulation) : 0; } @@ -383,34 +383,34 @@ public sealed class Skew : AbstractBase var vShift1 = Avx2.Permute4x64(vDelta.AsUInt64(), 0b_10_01_00_00).AsDouble(); // skipcq: CS-R1131 vShift1 = Avx.Blend(vZero, vShift1, 0b_1110); var vP1 = Avx.Add(vDelta, vShift1); - + // Shift 2: [0, 0, d0, d0+d1] var vShift2 = Avx2.Permute4x64(vP1.AsUInt64(), 0b_01_00_00_00).AsDouble(); // skipcq: CS-R1131 vShift2 = Avx.Blend(vZero, vShift2, 0b_1100); var vP2 = Avx.Add(vP1, vShift2); - + var vSums = Avx.Add(Vector256.Create(sum), vP2); // Prefix sum for SumSq var vShiftSq1 = Avx2.Permute4x64(vDeltaSq.AsUInt64(), 0b_10_01_00_00).AsDouble(); // skipcq: CS-R1131 vShiftSq1 = Avx.Blend(vZero, vShiftSq1, 0b_1110); var vP1Sq = Avx.Add(vDeltaSq, vShiftSq1); - + var vShiftSq2 = Avx2.Permute4x64(vP1Sq.AsUInt64(), 0b_01_00_00_00).AsDouble(); // skipcq: CS-R1131 vShiftSq2 = Avx.Blend(vZero, vShiftSq2, 0b_1100); var vP2Sq = Avx.Add(vP1Sq, vShiftSq2); - + var vSumSqs = Avx.Add(Vector256.Create(sumSq), vP2Sq); // Prefix sum for SumCu var vShiftCu1 = Avx2.Permute4x64(vDeltaCu.AsUInt64(), 0b_10_01_00_00).AsDouble(); // skipcq: CS-R1131 vShiftCu1 = Avx.Blend(vZero, vShiftCu1, 0b_1110); var vP1Cu = Avx.Add(vDeltaCu, vShiftCu1); - + var vShiftCu2 = Avx2.Permute4x64(vP1Cu.AsUInt64(), 0b_01_00_00_00).AsDouble(); // skipcq: CS-R1131 vShiftCu2 = Avx.Blend(vZero, vShiftCu2, 0b_1100); var vP2Cu = Avx.Add(vP1Cu, vShiftCu2); - + var vSumCus = Avx.Add(Vector256.Create(sumCu), vP2Cu); // Calculate Skewness @@ -440,13 +440,13 @@ public sealed class Skew : AbstractBase // g1 = m3 / (m2 * sqrt(m2)) var vM2Sqrt = Avx.Sqrt(vM2); var vDenom = Avx.Multiply(vM2, vM2Sqrt); - + // Check for small m2 var vMask = Avx.Compare(vM2, vEpsilon, FloatComparisonMode.OrderedGreaterThanNonSignaling); - + var vG1 = Avx.Divide(vM3, vDenom); var vSkew = Avx.Multiply(vG1, vCorrection); - + // Apply mask vSkew = Avx.BlendVariable(vZero, vSkew, vMask); @@ -481,11 +481,11 @@ public sealed class Skew : AbstractBase { double val = Unsafe.Add(ref srcRef, i); double oldVal = Unsafe.Add(ref srcRef, i - period); - + sum = sum - oldVal + val; sumSq = sumSq - (oldVal * oldVal) + (val * val); sumCu = sumCu - (oldVal * oldVal * oldVal) + (val * val * val); - + Unsafe.Add(ref outRef, i) = CalculateSkewFromSums(sum, sumSq, sumCu, n, isPopulation); } } diff --git a/lib/statistics/stddev/StdDev.Tests.cs b/lib/statistics/stddev/StdDev.Tests.cs index eb6326e8..f406649e 100644 --- a/lib/statistics/stddev/StdDev.Tests.cs +++ b/lib/statistics/stddev/StdDev.Tests.cs @@ -25,7 +25,7 @@ public class StdDevTests // Sample StdDev: Sqrt(4.571428...) = 2.1380899... var data = new double[] { 2, 4, 4, 4, 5, 5, 7, 9 }; - + // Test Population StdDev var popStd = new StdDev(8, isPopulation: true); foreach (var val in data) @@ -48,7 +48,7 @@ public class StdDevTests { int period = 5; var stdDev = new StdDev(period); - + for (int i = 0; i < period; i++) { Assert.False(stdDev.IsHot); @@ -79,7 +79,7 @@ public class StdDevTests int count = 1000; var data = new double[count]; var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); - + for (int i = 0; i < count; i++) { data[i] = gbm.Next().Close; @@ -104,7 +104,7 @@ public class StdDevTests Assert.Equal(iterativeResults[i], batchResults[i], precision: 6); } } - + [Fact] public void Update_TSeries_Matches_Iterative() { @@ -112,7 +112,7 @@ public class StdDevTests int count = 1000; var data = new TSeries(); var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); - + for (int i = 0; i < count; i++) { var bar = gbm.Next(); diff --git a/lib/statistics/stddev/StdDev.Validation.Tests.cs b/lib/statistics/stddev/StdDev.Validation.Tests.cs index 35b0f909..48aae573 100644 --- a/lib/statistics/stddev/StdDev.Validation.Tests.cs +++ b/lib/statistics/stddev/StdDev.Validation.Tests.cs @@ -27,7 +27,7 @@ public class StdDevValidationTests var skenderList = skenderStdDev.ToList(); var quotes = _data.SkenderQuotes.ToList(); - + for (int i = 0; i < quotes.Count; i++) { var tValue = stdDev.Update(new TValue(quotes[i].Date, (double)quotes[i].Close)); @@ -46,7 +46,7 @@ public class StdDevValidationTests // TA-Lib STDDEV uses Population Standard Deviation (N) int period = 20; var stdDev = new StdDev(period, isPopulation: true); - + var quotes = _data.SkenderQuotes.ToList(); double[] input = quotes.Select(q => (double)q.Close).ToArray(); double[] output = new double[input.Length]; @@ -74,18 +74,18 @@ public class StdDevValidationTests // Tulip STDDEV uses Population Standard Deviation (N) int period = 20; var stdDev = new StdDev(period, isPopulation: true); - + var quotes = _data.SkenderQuotes.ToList(); double[] input = quotes.Select(q => (double)q.Close).ToArray(); - + // Tulip calculation var stdDevInd = Tulip.Indicators.stddev; double[][] inputs = { input }; double[] options = { period }; double[][] outputs = { new double[input.Length - stdDevInd.Start(options)] }; - + stdDevInd.Run(inputs, options, outputs); - + double[] output = outputs[0]; int lookback = stdDevInd.Start(options); @@ -107,7 +107,7 @@ public class StdDevValidationTests int period = 20; var stdDev = new StdDev(period, isPopulation: false); var popStdDev = new StdDev(period, isPopulation: true); - + var quotes = _data.SkenderQuotes.ToList(); double[] input = quotes.Select(q => (double)q.Close).ToArray(); @@ -121,7 +121,7 @@ public class StdDevValidationTests var window = input[(i - period + 1)..(i + 1)]; double expected = Statistics.StandardDeviation(window); double expectedPop = Statistics.PopulationStandardDeviation(window); - + Assert.Equal(expected, val.Value, ValidationHelper.DefaultTolerance); Assert.Equal(expectedPop, popVal.Value, ValidationHelper.DefaultTolerance); } diff --git a/lib/statistics/stddev/StdDev.cs b/lib/statistics/stddev/StdDev.cs index 83070d9f..bde81e82 100644 --- a/lib/statistics/stddev/StdDev.cs +++ b/lib/statistics/stddev/StdDev.cs @@ -14,10 +14,10 @@ namespace QuanTAlib; /// /// /// Standard Deviation is the square root of Variance. -/// +/// /// Formula: /// StdDev = Sqrt(Variance) -/// +/// /// This implementation wraps the optimized Variance indicator and applies a square root. /// [SkipLocalsInit] @@ -47,7 +47,7 @@ public sealed class StdDev : AbstractBase public override TValue Update(TValue input, bool isNew = true) { TValue varResult = _variance.Update(input, isNew); - + // Sqrt(Variance) // Handle potential negative zero or extremely small negative noise from Variance double val = varResult.Value; @@ -73,12 +73,12 @@ public sealed class StdDev : AbstractBase // 1. Calculate Variance Variance.Batch(source.Values, vSpan, _period, _isPopulation); - + // 2. Calculate Sqrt in-place SqrtSpan(vSpan); source.Times.CopyTo(tSpan); - + // Prime the state // We need to feed the last 'period' values into the _variance instance // so that subsequent streaming updates work correctly. @@ -122,7 +122,7 @@ public sealed class StdDev : AbstractBase { // 1. Calculate Variance Variance.Batch(source, output, period, isPopulation); - + // 2. Sqrt SqrtSpan(output); } @@ -139,7 +139,7 @@ public sealed class StdDev : AbstractBase const int VectorWidth = 8; int simdEnd = len - (len % VectorWidth); ref double dataRef = ref MemoryMarshal.GetReference(data); - + for (; i < simdEnd; i += VectorWidth) { var v = Vector512.LoadUnsafe(ref Unsafe.Add(ref dataRef, i)); @@ -153,7 +153,7 @@ public sealed class StdDev : AbstractBase const int VectorWidth = 4; int simdEnd = len - (len % VectorWidth); ref double dataRef = ref MemoryMarshal.GetReference(data); - + for (; i < simdEnd; i += VectorWidth) { var v = Vector256.LoadUnsafe(ref Unsafe.Add(ref dataRef, i)); @@ -167,7 +167,7 @@ public sealed class StdDev : AbstractBase const int VectorWidth = 2; int simdEnd = len - (len % VectorWidth); ref double dataRef = ref MemoryMarshal.GetReference(data); - + for (; i < simdEnd; i += VectorWidth) { var v = Vector128.LoadUnsafe(ref Unsafe.Add(ref dataRef, i)); diff --git a/lib/statistics/variance/Variance.Tests.cs b/lib/statistics/variance/Variance.Tests.cs index a1d5f655..4b8fc45c 100644 --- a/lib/statistics/variance/Variance.Tests.cs +++ b/lib/statistics/variance/Variance.Tests.cs @@ -23,7 +23,7 @@ public class VarianceTests // Sample Variance (N-1=7): 32 / 7 = 4.571428... var data = new double[] { 2, 4, 4, 4, 5, 5, 7, 9 }; - + // Test Population Variance var popVar = new Variance(8, isPopulation: true); foreach (var val in data) @@ -46,7 +46,7 @@ public class VarianceTests { int period = 5; var variance = new Variance(period); - + for (int i = 0; i < period; i++) { Assert.False(variance.IsHot); @@ -75,18 +75,18 @@ public class VarianceTests { // Test differential update var variance = new Variance(3, isPopulation: true); - + // Add 1, 2, 3. Mean=2. Var = ((1-2)^2 + (2-2)^2 + (3-2)^2)/3 = (1+0+1)/3 = 2/3 = 0.666... variance.Update(new TValue(DateTime.UtcNow, 1)); variance.Update(new TValue(DateTime.UtcNow, 2)); variance.Update(new TValue(DateTime.UtcNow, 3)); - + Assert.Equal(2.0/3.0, variance.Last.Value, precision: 6); // Update last value from 3 to 6. // Data: 1, 2, 6. Mean=3. Var = ((1-3)^2 + (2-3)^2 + (6-3)^2)/3 = (4+1+9)/3 = 14/3 = 4.666... variance.Update(new TValue(DateTime.UtcNow, 6), isNew: false); - + Assert.Equal(14.0/3.0, variance.Last.Value, precision: 6); } @@ -97,7 +97,7 @@ public class VarianceTests int count = 1000; var data = new double[count]; var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); - + for (int i = 0; i < count; i++) { data[i] = gbm.Next().Close; @@ -144,7 +144,7 @@ public class VarianceTests variance.Update(new TValue(DateTime.UtcNow, 1)); variance.Update(new TValue(DateTime.UtcNow, 2)); variance.Update(new TValue(DateTime.UtcNow, double.NaN)); - + var result = variance.Last.Value; Assert.True(double.IsNaN(result)); } @@ -155,12 +155,12 @@ public class VarianceTests // Run for > 1000 updates to trigger Resync var variance = new Variance(10); var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); - + for (int i = 0; i < 1100; i++) { variance.Update(new TValue(DateTime.UtcNow, gbm.Next().Close)); } - + Assert.True(double.IsFinite(variance.Last.Value)); Assert.True(variance.Last.Value >= 0); } @@ -174,10 +174,10 @@ public class VarianceTests for (int i = 0; i < count; i++) data[i] = (double)i; var series = new TSeries(new System.Collections.Generic.List(new long[count]), new System.Collections.Generic.List(data)); - + // Batch calculation var batchResult = Variance.Calculate(series, 10); - + // Verify last value against streaming var variance = new Variance(10); double lastStreaming = 0; diff --git a/lib/statistics/variance/Variance.Validation.Tests.cs b/lib/statistics/variance/Variance.Validation.Tests.cs index 79ef1b95..b001db89 100644 --- a/lib/statistics/variance/Variance.Validation.Tests.cs +++ b/lib/statistics/variance/Variance.Validation.Tests.cs @@ -23,14 +23,14 @@ public class VarianceValidationTests // Skender StdDev uses Population Standard Deviation (N) for calculation, // despite documentation often implying Sample (N-1). // Variance(isPopulation: true) should match StdDev^2. - + int period = 20; var variance = new Variance(period, isPopulation: true); var skenderStdDev = _data.SkenderQuotes.GetStdDev(period); var skenderList = skenderStdDev.ToList(); var quotes = _data.SkenderQuotes.ToList(); - + for (int i = 0; i < quotes.Count; i++) { var tValue = variance.Update(new TValue(quotes[i].Date, (double)quotes[i].Close)); @@ -50,7 +50,7 @@ public class VarianceValidationTests // TA-Lib VAR uses Population Variance (N) int period = 20; var variance = new Variance(period, isPopulation: true); - + var quotes = _data.SkenderQuotes.ToList(); double[] input = quotes.Select(q => (double)q.Close).ToArray(); double[] output = new double[input.Length]; @@ -78,18 +78,18 @@ public class VarianceValidationTests // Tulip VAR uses Population Variance (N) int period = 20; var variance = new Variance(period, isPopulation: true); - + var quotes = _data.SkenderQuotes.ToList(); double[] input = quotes.Select(q => (double)q.Close).ToArray(); - + // Tulip calculation var varInd = Tulip.Indicators.var; double[][] inputs = { input }; double[] options = { period }; double[][] outputs = { new double[input.Length - varInd.Start(options)] }; - + varInd.Run(inputs, options, outputs); - + double[] output = outputs[0]; int lookback = varInd.Start(options); @@ -111,7 +111,7 @@ public class VarianceValidationTests int period = 20; var variance = new Variance(period, isPopulation: false); var popVariance = new Variance(period, isPopulation: true); - + var quotes = _data.SkenderQuotes.ToList(); double[] input = quotes.Select(q => (double)q.Close).ToArray(); @@ -125,7 +125,7 @@ public class VarianceValidationTests var window = input[(i - period + 1)..(i + 1)]; double expected = Statistics.Variance(window); double expectedPop = Statistics.PopulationVariance(window); - + Assert.Equal(expected, val.Value, ValidationHelper.DefaultTolerance); Assert.Equal(expectedPop, popVal.Value, ValidationHelper.DefaultTolerance); } diff --git a/lib/statistics/variance/Variance.cs b/lib/statistics/variance/Variance.cs index 12fa7318..5faee25e 100644 --- a/lib/statistics/variance/Variance.cs +++ b/lib/statistics/variance/Variance.cs @@ -13,11 +13,11 @@ namespace QuanTAlib; /// /// /// Variance is calculated as the average of the squared differences from the Mean. -/// +/// /// Formula: /// Population Variance = Sum((x - Mean)^2) / N /// Sample Variance = Sum((x - Mean)^2) / (N - 1) -/// +/// /// This implementation uses the O(1) running sum of squares formula: /// Variance = (SumSq - (Sum * Sum) / N) / (N - 1) (for Sample) /// @@ -76,7 +76,7 @@ public sealed class Variance : AbstractBase // Differential update double oldNewest = _buffer.Newest; _buffer.UpdateNewest(input.Value); - + // Reconstruct SumSq from previous state is safer/cleaner than differential on current // But we updated buffer already. // _sumSq currently includes oldNewest^2. @@ -93,12 +93,12 @@ public sealed class Variance : AbstractBase // Var = (SumSq - 2*Mean*(N*Mean) + N*Mean^2) / ... // Var = (SumSq - 2*N*Mean^2 + N*Mean^2) / ... // Var = (SumSq - N*Mean^2) / ... - + // Using Sum: // Var = (SumSq - (Sum*Sum)/N) / ... - + double numerator = _sumSq - (_buffer.Sum * _buffer.Sum) / n; - + // Handle floating point noise if (numerator < 0) numerator = 0; @@ -221,14 +221,14 @@ public sealed class Variance : AbstractBase int len = source.Length; double sum = 0; double sumSq = 0; - + // We need a buffer to handle the sliding window removal // For scalar path, we can use a simple array or stackalloc const int StackAllocThreshold = 256; Span buffer = period <= StackAllocThreshold ? stackalloc double[period] : new double[period]; - + int bufferIndex = 0; int i = 0; @@ -265,11 +265,11 @@ public sealed class Variance : AbstractBase if (!double.IsFinite(val)) val = 0; // Fallback double oldVal = buffer[bufferIndex]; - + sum = sum - oldVal + val; sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, sumSq); sumSq = Math.FusedMultiplyAdd(val, val, sumSq); - + buffer[bufferIndex] = val; bufferIndex++; if (bufferIndex >= period) bufferIndex = 0; @@ -300,7 +300,7 @@ public sealed class Variance : AbstractBase double val = Unsafe.Add(ref srcRef, i); sum += val; sumSq = Math.FusedMultiplyAdd(val, val, sumSq); - + double n = i + 1; if (n > 1) { @@ -382,9 +382,9 @@ public sealed class Variance : AbstractBase var vSumSquared = Avx512F.Multiply(vSums, vSums); var vMeanTerm = Avx512F.Multiply(vSumSquared, vInvN); var vNumerator = Avx512F.Subtract(vSumSqs, vMeanTerm); - + vNumerator = Avx512F.Max(vZero, vNumerator); - + var vResult = Avx512F.Multiply(vNumerator, vInvDenom); Vector512.StoreUnsafe(vResult, ref Unsafe.Add(ref outRef, i)); @@ -414,11 +414,11 @@ public sealed class Variance : AbstractBase { double val = Unsafe.Add(ref srcRef, i); double oldVal = Unsafe.Add(ref srcRef, i - period); - + sum = sum - oldVal + val; sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, sumSq); sumSq = Math.FusedMultiplyAdd(val, val, sumSq); - + double numerator = sumSq - (sum * sum) * invN; if (numerator < 0) numerator = 0; Unsafe.Add(ref outRef, i) = numerator * invDenom; @@ -479,9 +479,9 @@ public sealed class Variance : AbstractBase var vSumSquared = AdvSimd.Arm64.Multiply(vSums, vSums); var vMeanTerm = AdvSimd.Arm64.Multiply(vSumSquared, vInvN); var vNumerator = AdvSimd.Arm64.Subtract(vSumSqs, vMeanTerm); - + vNumerator = AdvSimd.Arm64.Max(vZero, vNumerator); - + var vResult = AdvSimd.Arm64.Multiply(vNumerator, vInvDenom); Vector128.StoreUnsafe(vResult, ref Unsafe.Add(ref outRef, i)); @@ -511,11 +511,11 @@ public sealed class Variance : AbstractBase { double val = Unsafe.Add(ref srcRef, i); double oldVal = Unsafe.Add(ref srcRef, i - period); - + sum = sum - oldVal + val; sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, sumSq); sumSq = Math.FusedMultiplyAdd(val, val, sumSq); - + double numerator = sumSq - (sum * sum) * invN; if (numerator < 0) numerator = 0; Unsafe.Add(ref outRef, i) = numerator * invDenom; @@ -593,10 +593,10 @@ public sealed class Variance : AbstractBase var vSumSquared = Avx.Multiply(vSums, vSums); var vMeanTerm = Avx.Multiply(vSumSquared, vInvN); var vNumerator = Avx.Subtract(vSumSqs, vMeanTerm); - + // Max(0, numerator) to handle floating point noise vNumerator = Avx.Max(vZero, vNumerator); - + var vResult = Avx.Multiply(vNumerator, vInvDenom); Vector256.StoreUnsafe(vResult, ref Unsafe.Add(ref outRef, i)); @@ -628,11 +628,11 @@ public sealed class Variance : AbstractBase { double val = Unsafe.Add(ref srcRef, i); double oldVal = Unsafe.Add(ref srcRef, i - period); - + sum = sum - oldVal + val; sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, sumSq); sumSq = Math.FusedMultiplyAdd(val, val, sumSq); - + double numerator = sumSq - (sum * sum) * invN; if (numerator < 0) numerator = 0; Unsafe.Add(ref outRef, i) = numerator * invDenom; diff --git a/lib/trends/alma/Alma.Quantower.cs b/lib/trends/alma/Alma.Quantower.cs index ccf3c76c..811de56a 100644 --- a/lib/trends/alma/Alma.Quantower.cs +++ b/lib/trends/alma/Alma.Quantower.cs @@ -56,7 +56,7 @@ public class AlmaIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); Series!.SetValue(result.Value, ma.IsHot, ShowColdValues); diff --git a/lib/trends/alma/Alma.Tests.cs b/lib/trends/alma/Alma.Tests.cs index 04b7cb8e..9cfad28b 100644 --- a/lib/trends/alma/Alma.Tests.cs +++ b/lib/trends/alma/Alma.Tests.cs @@ -255,10 +255,10 @@ public class AlmaTests { var alma = new Alma(10); alma.Update(new TValue(DateTime.UtcNow, 100)); - + var r1 = alma.Update(new TValue(DateTime.UtcNow, double.NaN)); var r2 = alma.Update(new TValue(DateTime.UtcNow, double.NaN)); - + Assert.True(double.IsFinite(r1.Value)); Assert.True(double.IsFinite(r2.Value)); } @@ -271,7 +271,7 @@ public class AlmaTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Alma.Batch(series, period); double expected = batchSeries.Last.Value; diff --git a/lib/trends/alma/Alma.cs b/lib/trends/alma/Alma.cs index 5a8b78d4..e9811f1e 100644 --- a/lib/trends/alma/Alma.cs +++ b/lib/trends/alma/Alma.cs @@ -247,8 +247,8 @@ public sealed class Alma : AbstractBase, IDisposable // Precompute weights // Use stackalloc for small periods to avoid heap allocation, ArrayPool for large double[]? weightsArray = period > 256 ? ArrayPool.Shared.Rent(period) : null; - Span weights = period <= 256 - ? stackalloc double[period] + Span weights = period <= 256 + ? stackalloc double[period] : weightsArray!.AsSpan(0, period); double m = offset * (period - 1); @@ -266,8 +266,8 @@ public sealed class Alma : AbstractBase, IDisposable // Buffer for sliding window double[]? bufferArray = period > 256 ? ArrayPool.Shared.Rent(period) : null; - Span buffer = period <= 256 - ? stackalloc double[period] + Span buffer = period <= 256 + ? stackalloc double[period] : bufferArray!.AsSpan(0, period); int bufferIdx = 0; @@ -288,7 +288,7 @@ public sealed class Alma : AbstractBase, IDisposable // Add to circular buffer buffer[bufferIdx] = val; bufferIdx = (bufferIdx + 1) % period; - + if (count < period) { count++; diff --git a/lib/trends/bessel/Bessel.Quantower.cs b/lib/trends/bessel/Bessel.Quantower.cs index b09bcd75..c0fcd4d0 100644 --- a/lib/trends/bessel/Bessel.Quantower.cs +++ b/lib/trends/bessel/Bessel.Quantower.cs @@ -49,7 +49,7 @@ public class BesselIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = _filter!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); Series!.SetValue(result.Value, _filter.IsHot, ShowColdValues); diff --git a/lib/trends/bessel/Bessel.Tests.cs b/lib/trends/bessel/Bessel.Tests.cs index 8fe4eedb..8eb36321 100644 --- a/lib/trends/bessel/Bessel.Tests.cs +++ b/lib/trends/bessel/Bessel.Tests.cs @@ -12,7 +12,7 @@ public class BesselTests var bessel = new Bessel(2); Assert.NotNull(bessel); - + var bessel14 = new Bessel(14); Assert.NotNull(bessel14); } diff --git a/lib/trends/bessel/Bessel.cs b/lib/trends/bessel/Bessel.cs index 936b3e59..6f013347 100644 --- a/lib/trends/bessel/Bessel.cs +++ b/lib/trends/bessel/Bessel.cs @@ -9,7 +9,7 @@ namespace QuanTAlib; /// /// Bessel filter is a 2nd-order IIR low-pass filter with maximally flat group delay, /// adapted from John Ehlers' work for financial time series. -/// +/// /// Coefficients for a given length L: /// a = exp(-PI / L) /// b = 2 * a * cos(1.738 * PI / L) diff --git a/lib/trends/bilateral/Bilateral.Quantower.Tests.cs b/lib/trends/bilateral/Bilateral.Quantower.Tests.cs index fe8b9030..19618bc7 100644 --- a/lib/trends/bilateral/Bilateral.Quantower.Tests.cs +++ b/lib/trends/bilateral/Bilateral.Quantower.Tests.cs @@ -166,7 +166,7 @@ public class BilateralIndicatorTests indicator.Period = 20; indicator.SigmaSRatio = 1.0; indicator.SigmaRMult = 2.0; - + Assert.Equal(20, indicator.Period); Assert.Equal(1.0, indicator.SigmaSRatio); Assert.Equal(2.0, indicator.SigmaRMult); diff --git a/lib/trends/bilateral/Bilateral.Quantower.cs b/lib/trends/bilateral/Bilateral.Quantower.cs index d2dafc04..4de03bd2 100644 --- a/lib/trends/bilateral/Bilateral.Quantower.cs +++ b/lib/trends/bilateral/Bilateral.Quantower.cs @@ -56,7 +56,7 @@ public class BilateralIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = _bilateral!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); Series!.SetValue(result.Value, _bilateral.IsHot, ShowColdValues); diff --git a/lib/trends/bilateral/Bilateral.Tests.cs b/lib/trends/bilateral/Bilateral.Tests.cs index 74440c24..5b005ee7 100644 --- a/lib/trends/bilateral/Bilateral.Tests.cs +++ b/lib/trends/bilateral/Bilateral.Tests.cs @@ -23,13 +23,13 @@ public class BilateralTests public void IsHot_BecomesTrueWhenBufferFull() { var indicator = new Bilateral(3); - + indicator.Update(new TValue(DateTime.UtcNow, 1)); Assert.False(indicator.IsHot); - + indicator.Update(new TValue(DateTime.UtcNow, 2)); Assert.False(indicator.IsHot); - + indicator.Update(new TValue(DateTime.UtcNow, 3)); Assert.True(indicator.IsHot); } @@ -42,13 +42,13 @@ public class BilateralTests // If sigma_r is high, range weights are ~1. // If sigma_s is high, spatial weights are ~1. // Then it becomes a simple average. - + var indicator = new Bilateral(3, sigmaSRatio: 100, sigmaRMult: 100); - + indicator.Update(new TValue(DateTime.UtcNow, 1)); indicator.Update(new TValue(DateTime.UtcNow, 2)); var result = indicator.Update(new TValue(DateTime.UtcNow, 3)); - + // Expected: (1+2+3)/3 = 2 Assert.Equal(2.0, result.Value, 1); } @@ -57,11 +57,11 @@ public class BilateralTests public void Update_HandlesNaN() { var indicator = new Bilateral(3); - + indicator.Update(new TValue(DateTime.UtcNow, 1)); indicator.Update(new TValue(DateTime.UtcNow, double.NaN)); // Should use 1 var result = indicator.Update(new TValue(DateTime.UtcNow, 3)); - + // Buffer: [1, 1, 3] // StDev of [1, 1, 3]: Mean=1.66, Var=((1-1.66)^2 + (1-1.66)^2 + (3-1.66)^2)/3 = (0.44 + 0.44 + 1.77)/3 = 0.88. StDev ~ 0.94 // Calculation will proceed with these values. @@ -73,23 +73,23 @@ public class BilateralTests public void Update_IsNew_False_UpdatesCorrectly() { var indicator = new Bilateral(3); - + indicator.Update(new TValue(DateTime.UtcNow, 1)); indicator.Update(new TValue(DateTime.UtcNow, 2)); - + // Update with 3, isNew=true indicator.Update(new TValue(DateTime.UtcNow, 3), isNew: true); - + // Update with 4, isNew=false (correction) var res2 = indicator.Update(new TValue(DateTime.UtcNow, 4), isNew: false); - + // Verify state was updated // If we had updated with 4 directly: [1, 2, 4] var indicator2 = new Bilateral(3); indicator2.Update(new TValue(DateTime.UtcNow, 1)); indicator2.Update(new TValue(DateTime.UtcNow, 2)); var resExpected = indicator2.Update(new TValue(DateTime.UtcNow, 4)); - + Assert.Equal(resExpected.Value, res2.Value); } @@ -100,9 +100,9 @@ public class BilateralTests indicator.Update(new TValue(DateTime.UtcNow, 1)); indicator.Update(new TValue(DateTime.UtcNow, 2)); indicator.Update(new TValue(DateTime.UtcNow, 3)); - + indicator.Reset(); - + Assert.False(indicator.IsHot); Assert.Equal(1, indicator.Update(new TValue(DateTime.UtcNow, 1)).Value); // Center val 1, weights 0? No, center val is returned if weights 0. } @@ -112,10 +112,10 @@ public class BilateralTests { // Test edge case: calling Update with isNew:false before any isNew:true var indicator = new Bilateral(3); - + // This should not crash - buffer is empty, so we treat it as first value var result = indicator.Update(new TValue(DateTime.UtcNow, 5.0), isNew: false); - + // Should have added the value to the buffer Assert.True(double.IsFinite(result.Value)); Assert.Equal(5.0, result.Value); // Single value, so result is that value @@ -126,18 +126,18 @@ public class BilateralTests { // Test edge case: calling Update with isNew:false after Reset var indicator = new Bilateral(3); - + indicator.Update(new TValue(DateTime.UtcNow, 1)); indicator.Update(new TValue(DateTime.UtcNow, 2)); indicator.Reset(); - + // Buffer is now empty, isNew:false should not crash var result = indicator.Update(new TValue(DateTime.UtcNow, 7.0), isNew: false); - + Assert.True(double.IsFinite(result.Value)); Assert.Equal(7.0, result.Value); } - + [Fact] public void AllModes_ProduceSameResult() { diff --git a/lib/trends/bilateral/Bilateral.Validation.Tests.cs b/lib/trends/bilateral/Bilateral.Validation.Tests.cs index 533e4549..9d2c2f22 100644 --- a/lib/trends/bilateral/Bilateral.Validation.Tests.cs +++ b/lib/trends/bilateral/Bilateral.Validation.Tests.cs @@ -115,12 +115,12 @@ public sealed class BilateralValidationTests : IDisposable { var reference = new BilateralReference(period, sigmaSRatio, sigmaRMult); var results = new List(); - + foreach (var item in _testData.Data) { results.Add(reference.Update(item.Value)); } - + return results; } @@ -149,7 +149,7 @@ public sealed class BilateralValidationTests : IDisposable if (_history.Count == 0) return double.NaN; double sigmaS = Math.Max(_length * _sigmaSRatio, 1e-10); - + // Calculate StDev of current window double stdev = CalculateStDev(_history); double sigmaR = Math.Max(stdev * _sigmaRMult, 1e-10); @@ -161,18 +161,18 @@ public sealed class BilateralValidationTests : IDisposable // Iterate through history // i=0 is newest (index Count-1) int loopLen = _history.Count; - + for (int i = 0; i < loopLen; i++) { double valI = _history[_history.Count - 1 - i]; double diffSpatial = i; double diffRange = centerVal - valI; - + double weightSpatial = Math.Exp(-(diffSpatial * diffSpatial) / (2.0 * sigmaS * sigmaS)); double weightRange = Math.Exp(-(diffRange * diffRange) / (2.0 * sigmaR * sigmaR)); - + double weight = weightSpatial * weightRange; - + sumWeights += weight; sumWeightedSrc += weight * valI; } @@ -183,7 +183,7 @@ public sealed class BilateralValidationTests : IDisposable private static double CalculateStDev(IReadOnlyList values) { if (values.Count < 2) return 0; - + double avg = values.Average(); double sumSqDiff = values.Sum(d => (d - avg) * (d - avg)); // Population StDev to match implementation diff --git a/lib/trends/bilateral/Bilateral.cs b/lib/trends/bilateral/Bilateral.cs index c6314179..e572c534 100644 --- a/lib/trends/bilateral/Bilateral.cs +++ b/lib/trends/bilateral/Bilateral.cs @@ -57,7 +57,7 @@ public sealed class Bilateral : AbstractBase PrecalculateSpatialWeights(); } - public Bilateral(ITValuePublisher source, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0) + public Bilateral(ITValuePublisher source, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0) : this(period, sigmaSRatio, sigmaRMult) { source.Pub += _handler; @@ -139,7 +139,7 @@ public sealed class Bilateral : AbstractBase Update(new TValue(source.Times[i], source.Values[i])); vSpan[i] = Last.Value; } - + return new TSeries(t, v); } @@ -149,10 +149,10 @@ public sealed class Bilateral : AbstractBase if (isNew) { _p_state = _state; - + double val = GetValidValue(input.Value); double removed = _buffer.Add(val); - + _state.SumSq += (val * val); if (_buffer.IsFull) { @@ -163,12 +163,12 @@ public sealed class Bilateral : AbstractBase { // Preserve SumSq as it tracks the buffer which is already at T double currentSumSq = _state.SumSq; - + _state = _p_state; _state.SumSq = currentSumSq; - + double val = GetValidValue(input.Value); - + // Defensive check: if buffer is empty, treat as first value if (_buffer.Count == 0) { @@ -179,7 +179,7 @@ public sealed class Bilateral : AbstractBase { double oldNewest = _buffer.Newest; // Get current newest before overwriting _buffer.UpdateNewest(val); - + _state.SumSq -= (oldNewest * oldNewest); _state.SumSq += (val * val); } @@ -210,7 +210,7 @@ public sealed class Bilateral : AbstractBase // Calculate StDev double count = _buffer.Count; double sum = _buffer.Sum; - + // Variance = (SumSq - (Sum*Sum)/N) / N // Use Math.Max(0, ...) to handle potential floating point negative zero double variance = Math.Max(0, (_state.SumSq - (sum * sum) / count) / count); @@ -226,12 +226,12 @@ public sealed class Bilateral : AbstractBase // Iterate from 0 to Count-1 // i=0 corresponds to Newest (src[0]) // i corresponds to buffer[Count - 1 - i] - + // Use InternalBuffer to avoid allocations from GetSpan() when wrapped ReadOnlySpan buffer = _buffer.InternalBuffer; int capacity = _buffer.Capacity; int startIndex = _buffer.StartIndex; - + // Newest element index int newestIndex = (startIndex + (int)count - 1) % capacity; @@ -241,16 +241,16 @@ public sealed class Bilateral : AbstractBase // (newestIndex - i) handling wrap-around int idx = newestIndex - i; if (idx < 0) idx += capacity; - + double val = buffer[idx]; double diffRange = centerVal - val; - + // weight_spatial = _spatialWeights[i] // weight_range = exp(-(diff^2) / (2 * sigma_r^2)) - + double weightRange = Math.Exp(-(diffRange * diffRange) / twoSigmaRSq); double weight = _spatialWeights[i] * weightRange; - + sumWeights += weight; sumWeightedSrc += weight * val; } @@ -284,7 +284,7 @@ public sealed class Bilateral : AbstractBase if (destination.Length < source.Length) throw new ArgumentException("Destination must have length >= source length", nameof(destination)); - + // Precalculate spatial weights double sigmaS = Math.Max(period * sigmaSRatio, 1e-10); double twoSigmaSSq = 2.0 * sigmaS * sigmaS; @@ -306,7 +306,7 @@ public sealed class Bilateral : AbstractBase break; } } - + // If all NaNs, fill with NaN if (double.IsNaN(lastValid)) { @@ -340,11 +340,11 @@ public sealed class Bilateral : AbstractBase sum -= removed; sumSq -= removed * removed; } - + window[windowIdx] = val; sum += val; sumSq += val * val; - + int currentNewestIdx = windowIdx; windowIdx = (windowIdx + 1) % period; if (count < period) count++; @@ -367,13 +367,13 @@ public sealed class Bilateral : AbstractBase // k=1 is previous... int idx = currentNewestIdx - k; if (idx < 0) idx += period; - + double wVal = window[idx]; double diffRange = centerVal - wVal; - + double weightRange = Math.Exp(-(diffRange * diffRange) / twoSigmaRSq); double weight = spatialWeights[k] * weightRange; - + sumWeights += weight; sumWeightedSrc += weight * wVal; } diff --git a/lib/trends/blma/Blma.Quantower.cs b/lib/trends/blma/Blma.Quantower.cs index a641e551..e4c3fdc9 100644 --- a/lib/trends/blma/Blma.Quantower.cs +++ b/lib/trends/blma/Blma.Quantower.cs @@ -50,7 +50,7 @@ public class BlmaIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); _series!.SetValue(result.Value, _ma.IsHot, ShowColdValues); diff --git a/lib/trends/blma/Blma.Tests.cs b/lib/trends/blma/Blma.Tests.cs index e95f2e59..9b8b03c7 100644 --- a/lib/trends/blma/Blma.Tests.cs +++ b/lib/trends/blma/Blma.Tests.cs @@ -33,15 +33,15 @@ public class BlmaTests { var blma = new Blma(3); var input = new[] { 10.0, 20.0, 30.0 }; - + // Bar 1: Count=1. Weights for n=1: [1]. Result = 10. var r1 = blma.Update(new TValue(DateTime.UtcNow, input[0])); Assert.Equal(10.0, r1.Value); - + // Bar 2: Count=2. Weights for n=2 sum to 0. Fallback to average: (10+20)/2 = 15. var r2 = blma.Update(new TValue(DateTime.UtcNow, input[1])); Assert.Equal(15.0, r2.Value); - + // Bar 3: Count=3. Weights [0, 1, 0]. Sum=1. Result=20. var r3 = blma.Update(new TValue(DateTime.UtcNow, input[2])); Assert.Equal(20.0, r3.Value, 1e-6); @@ -82,45 +82,45 @@ public class BlmaTests public void NaN_Handling() { var blma = new Blma(5); - + blma.Update(new TValue(DateTime.UtcNow, 10)); blma.Update(new TValue(DateTime.UtcNow, 20)); // For N=2, weights sum to 0. Fallback to average: (10+20)/2 = 15. - + var result = blma.Update(new TValue(DateTime.UtcNow, double.NaN)); - + Assert.Equal(15.0, result.Value); // Should return last valid value Assert.Equal(15.0, blma.Last.Value); // Should retain last valid value } - + [Fact] public void IsNew_Behavior() { var blma = new Blma(3); - + // Bar 1 blma.Update(new TValue(DateTime.UtcNow, 10), isNew: true); - + // Bar 2 blma.Update(new TValue(DateTime.UtcNow, 20), isNew: true); - + // Bar 3 (Update) blma.Update(new TValue(DateTime.UtcNow, 30), isNew: true); var val1 = blma.Last.Value; - + // Bar 3 (Correction) blma.Update(new TValue(DateTime.UtcNow, 40), isNew: false); var val2 = blma.Last.Value; - + // For Blackman window, the newest value (index N-1) has weight 0. // So changing the newest value does NOT change the current result. Assert.Equal(val1, val2); - + // However, the internal buffer MUST be updated. // Case A: Bar 3 = 40 (current state) blma.Update(new TValue(DateTime.UtcNow, 100), isNew: true); var valWith40 = blma.Last.Value; - + // Case B: Reconstruct scenario with Bar 3 = 30 var blma2 = new Blma(3); blma2.Update(new TValue(DateTime.UtcNow, 10), isNew: true); @@ -128,7 +128,7 @@ public class BlmaTests blma2.Update(new TValue(DateTime.UtcNow, 30), isNew: true); blma2.Update(new TValue(DateTime.UtcNow, 100), isNew: true); var valWith30 = blma2.Last.Value; - + Assert.NotEqual(valWith30, valWith40); } @@ -138,12 +138,12 @@ public class BlmaTests var blma = new Blma(5); var input = new double[] { 1, 2, 3, 4, 5 }; var timestamps = new List(); - - blma.Pub += (object? sender, TValueEventArgs args) => timestamps.Add(args.Value.AsDateTime); - + blma.Pub += (object? sender, in TValueEventArgs args) => timestamps.Add(args.Value.AsDateTime); + + blma.Prime(input); - + Assert.Equal(input.Length, timestamps.Count); // Verify timestamps are unique and increasing for (int i = 1; i < timestamps.Count; i++) @@ -157,19 +157,19 @@ public class BlmaTests { var blma = new Blma(5); var now = DateTime.UtcNow; - TValue[] input = - [ - new(now, 1), - new(now.AddMinutes(1), 2), - new(now.AddMinutes(2), 3) + TValue[] input = + [ + new(now, 1), + new(now.AddMinutes(1), 2), + new(now.AddMinutes(2), 3) ]; var timestamps = new List(); - - blma.Pub += (object? sender, TValueEventArgs args) => timestamps.Add(args.Value.AsDateTime); - + blma.Pub += (object? sender, in TValueEventArgs args) => timestamps.Add(args.Value.AsDateTime); + + blma.Prime(input); - + Assert.Equal(input.Length, timestamps.Count); Assert.Equal(input[0].AsDateTime, timestamps[0]); Assert.Equal(input[1].AsDateTime, timestamps[1]); diff --git a/lib/trends/blma/Blma.Validation.Tests.cs b/lib/trends/blma/Blma.Validation.Tests.cs index bcd90111..d53c1df3 100644 --- a/lib/trends/blma/Blma.Validation.Tests.cs +++ b/lib/trends/blma/Blma.Validation.Tests.cs @@ -50,15 +50,15 @@ public class BlmaValidationTests for (int i = 0; i < source.Count; i++) { buffer.Add(source[i].Value); - + // PineScript logic: // int p = math.min(bar_index + 1, period) int p = Math.Min(buffer.Count, period); - + // Calculate weights var weights = new double[p]; double totalWeight = 0; - + if (p == 1) { weights[0] = 1.0; @@ -88,15 +88,15 @@ public class BlmaValidationTests // float price = source[i] (where source[0] is newest) // float w = array.get(weights, i) // So weights[0] * newest, weights[1] * 2nd newest... - + // My C# buffer is chronological (0 is oldest). // So buffer[buffer.Count - 1] is newest. // buffer[buffer.Count - 1 - j] is j-th lag. - + // Wait, in Blma.cs I implemented: // sum += buffer[i] * weights[i] (where buffer[0] is oldest) // So weights[0] * oldest. - + // PineScript: weights[0] * newest. // Since Blackman window is symmetric, weights[0] == weights[p-1]. // So weights[0] * newest == weights[p-1] * newest (if symmetric). @@ -111,11 +111,11 @@ public class BlmaValidationTests // cos(4pi * (1-r)) = cos(4pi - 4pi*r) = cos(4pi*r). // So yes, w(j) == w(p-1-j). // So applying weights[0] to newest or oldest doesn't matter for the sum. - + // However, I should match my implementation in Blma.cs. // In Blma.cs: sum += buffer[i] * weights[i] (buffer[0] is oldest). // So weights[0] * oldest. - + // In this reference implementation, let's do the same. // Use the last p elements of buffer. int start = buffer.Count - p; diff --git a/lib/trends/blma/Blma.cs b/lib/trends/blma/Blma.cs index 840ca551..62a71587 100644 --- a/lib/trends/blma/Blma.cs +++ b/lib/trends/blma/Blma.cs @@ -29,7 +29,7 @@ public sealed class Blma : AbstractBase, IDisposable WarmupPeriod = period; _buffer = new RingBuffer(period); _weights = new double[period]; - + // Pre-calculate weights for the full period _weightSum = CalculateWeights(period, _weights); _handler = Handle; @@ -102,7 +102,7 @@ public sealed class Blma : AbstractBase, IDisposable { Span currentWeights = stackalloc double[count]; double currentWeightSum = CalculateWeights(count, currentWeights); - + // Fallback for cases where weights sum to zero (e.g. N=2) result = Math.Abs(currentWeightSum) < double.Epsilon ? _buffer.Average() @@ -130,7 +130,7 @@ public sealed class Blma : AbstractBase, IDisposable var result = new TSeries(); Span output = new double[source.Count]; Calculate(source.Values, output, _period); - + for (int i = 0; i < source.Count; i++) { result.Add(new TValue(source[i].Time, output[i])); @@ -182,7 +182,7 @@ public sealed class Blma : AbstractBase, IDisposable int start = buffer.StartIndex; int count = buffer.Count; int capacity = buffer.Capacity; - + if (start + count <= capacity) { return buffer.InternalBuffer.Slice(start, count).DotProduct(weights); @@ -219,7 +219,7 @@ public sealed class Blma : AbstractBase, IDisposable for (int i = 0; i < source.Length; i++) { int count = Math.Min(i + 1, period); - + if (count < period) { // Warmup: dynamic weights @@ -231,7 +231,7 @@ public sealed class Blma : AbstractBase, IDisposable { Span currentWeights = warmupWeightsBuffer.Slice(0, count); double currentWeightSum = CalculateWeights(count, currentWeights); - + if (Math.Abs(currentWeightSum) < double.Epsilon) { // Fallback for zero sum weights (e.g. N=2) diff --git a/lib/trends/butter/Butter.Quantower.cs b/lib/trends/butter/Butter.Quantower.cs index 95b869d6..49d2de56 100644 --- a/lib/trends/butter/Butter.Quantower.cs +++ b/lib/trends/butter/Butter.Quantower.cs @@ -50,7 +50,7 @@ public class ButterIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = _ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); _series!.SetValue(result.Value, _ma.IsHot, ShowColdValues); diff --git a/lib/trends/butter/Butter.Tests.cs b/lib/trends/butter/Butter.Tests.cs index 5e0719c7..f37dbe95 100644 --- a/lib/trends/butter/Butter.Tests.cs +++ b/lib/trends/butter/Butter.Tests.cs @@ -45,7 +45,7 @@ public class ButterTests butter.Update(new TValue(DateTime.UtcNow, 100)); butter.Update(new TValue(DateTime.UtcNow, 101)); Assert.True(butter.IsHot); - + butter.Reset(); Assert.False(butter.IsHot); } @@ -107,30 +107,30 @@ public class ButterTests Assert.Equal(expected, streamingResult, 1e-9); Assert.Equal(expected, eventingResult, 1e-9); } - + [Fact] public void IterativeCorrections_RestoreToOriginalState() { int period = 10; var butter = new Butter(period); - + // Feed 10 values for (int i = 0; i < 10; i++) { butter.Update(new TValue(DateTime.UtcNow, 100 + i)); } - + double expected = butter.Last.Value; - + // Feed 5 updates with isNew=false for (int i = 0; i < 5; i++) { butter.Update(new TValue(DateTime.UtcNow, 200 + i), isNew: false); } - + // Feed original 10th value again with isNew=false var result = butter.Update(new TValue(DateTime.UtcNow, 109), isNew: false); - + Assert.Equal(expected, result.Value, 1e-9); } } diff --git a/lib/trends/butter/Butter.Validation.Tests.cs b/lib/trends/butter/Butter.Validation.Tests.cs index 452e985d..c411dfe9 100644 --- a/lib/trends/butter/Butter.Validation.Tests.cs +++ b/lib/trends/butter/Butter.Validation.Tests.cs @@ -78,7 +78,7 @@ public class ButterValidationTests // Compare Assert.Equal(quantalibResult.Count, ooplesValues.Count); - + // Check last 100 bars for (int i = quantalibResult.Count - 100; i < quantalibResult.Count; i++) { @@ -91,7 +91,7 @@ public class ButterValidationTests private static IReadOnlyList CalculateReference(TSeries source, int period) { var result = new List(); - + // PineScript logic: // float pi = math.pi // int safe_length = math.max(length, 2) @@ -105,7 +105,7 @@ public class ButterValidationTests // float b0 = (1.0 - cos_omega) / 2.0 // float b1 = 1.0 - cos_omega // float b2 = (1.0 - cos_omega) / 2.0 - + int safe_length = Math.Max(period, 2); double omega = 2.0 * Math.PI / safe_length; double sin_omega = Math.Sin(omega); @@ -117,22 +117,22 @@ public class ButterValidationTests double b0 = (1.0 - cos_omega) / 2.0; double b1 = 1.0 - cos_omega; double b2 = (1.0 - cos_omega) / 2.0; - + double filt = 0; double filt1 = 0; double filt2 = 0; - + // Need to track history for src[1], src[2] // In PineScript, src[1] is previous bar's src. // We iterate through source. - + double src1 = 0; double src2 = 0; for (int i = 0; i < source.Count; i++) { double src = source[i].Value; - + // if bar_index < 2 // filt := nz(src, 0.0) if (i < 2) @@ -142,38 +142,38 @@ public class ButterValidationTests // In PineScript, src[1] at index 0 is NaN (nz -> 0.0 or something?) // Actually, nz(src, 0.0) means if src is NaN, use 0.0. // But here src is valid. - + // At i=0: src[1] is NaN, src[2] is NaN. // At i=1: src[1] is src[i-1], src[2] is NaN. - + // But the PineScript code says: // if bar_index < 2: filt := nz(src, 0.0) // else: ... formula ... - + // So for i=0 and i=1, filt = src. } else { // float ssrc = nz(src, src[1]) -> if src is NaN use src[1]. Assuming src is valid. double ssrc = src; - + // float src1 = nz(src[1], ssrc) -> previous src. // float src2 = nz(src[2], src1) -> 2nd previous src. - + // float filt1 = nz(filt[1], ssrc) -> previous filt. // float filt2 = nz(filt[2], filt1) -> 2nd previous filt. - + // filt := (b0 * ssrc + b1 * src1 + b2 * src2 - a1 * filt1 - a2 * filt2) / a0 - + filt = (b0 * ssrc + b1 * src1 + b2 * src2 - a1 * filt1 - a2 * filt2) / a0; } - + result.Add(filt); - + // Update history src2 = src1; src1 = src; - + filt2 = filt1; filt1 = filt; } diff --git a/lib/trends/butter/Butter.cs b/lib/trends/butter/Butter.cs index 59781bf0..d3bb8f2c 100644 --- a/lib/trends/butter/Butter.cs +++ b/lib/trends/butter/Butter.cs @@ -58,7 +58,7 @@ public sealed class Butter : AbstractBase double a0 = 1.0 + alpha; a1 = -2.0 * cosOmega; a2 = 1.0 - alpha; - + b0 = (1.0 - cosOmega) / 2.0; b1 = 1.0 - cosOmega; b2 = (1.0 - cosOmega) / 2.0; @@ -123,7 +123,7 @@ public sealed class Butter : AbstractBase _state.X1 = x; _state.Y2 = _state.Y1; _state.Y1 = y; - + if (_state.Count < 2) { _state.Count++; @@ -140,7 +140,7 @@ public sealed class Butter : AbstractBase var result = new TSeries(); Span output = new double[source.Count]; Calculate(source.Values, output, _period, double.NaN); - + for (int i = 0; i < source.Count; i++) { result.Add(new TValue(source[i].Time, output[i])); @@ -148,11 +148,11 @@ public sealed class Butter : AbstractBase // Restore state Reset(); - + // Replay a reasonable amount (e.g. 4*period) for convergence of IIR state. int replayCount = Math.Min(source.Count, 4 * _period); int start = source.Count - replayCount; - + for (int i = start; i < source.Count; i++) { Update(source[i]); diff --git a/lib/trends/conv/Conv.Quantower.cs b/lib/trends/conv/Conv.Quantower.cs index 6dcac8e4..17de986e 100644 --- a/lib/trends/conv/Conv.Quantower.cs +++ b/lib/trends/conv/Conv.Quantower.cs @@ -66,7 +66,7 @@ public class ConvIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = _conv!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); Series!.SetValue(result.Value, _conv.IsHot, ShowColdValues); diff --git a/lib/trends/conv/Conv.Tests.cs b/lib/trends/conv/Conv.Tests.cs index 3be1312b..e68a76a3 100644 --- a/lib/trends/conv/Conv.Tests.cs +++ b/lib/trends/conv/Conv.Tests.cs @@ -171,7 +171,7 @@ public class ConvTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Conv.Batch(series, kernel); double expected = batchSeries.Last.Value; diff --git a/lib/trends/dema/Dema.Quantower.cs b/lib/trends/dema/Dema.Quantower.cs index 0ec540f9..803f3506 100644 --- a/lib/trends/dema/Dema.Quantower.cs +++ b/lib/trends/dema/Dema.Quantower.cs @@ -50,7 +50,7 @@ public class DemaIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); Series!.SetValue(result.Value, ma.IsHot, ShowColdValues); diff --git a/lib/trends/dema/Dema.Tests.cs b/lib/trends/dema/Dema.Tests.cs index 811796f6..fa6d4af6 100644 --- a/lib/trends/dema/Dema.Tests.cs +++ b/lib/trends/dema/Dema.Tests.cs @@ -22,7 +22,7 @@ public class DemaTests var tVal = new TValue(bar.Time, bar.Close); var dVal = dema.Update(tVal); - + var e1Val = ema1.Update(tVal); var e2Val = ema2.Update(e1Val); double expected = 2 * e1Val.Value - e2Val.Value; @@ -48,7 +48,7 @@ public class DemaTests // Act var demaSeries = Dema.Calculate(source, period); var demaObj = new Dema(period); - + // Assert for (int i = 0; i < source.Count; i++) { @@ -134,7 +134,7 @@ public class DemaTests // Act var demaSeries = Dema.Calculate(source, alpha); var demaObj = new Dema(alpha); - + // Assert for (int i = 0; i < source.Count; i++) { @@ -193,9 +193,9 @@ public class DemaTests var dema = new Dema(10); dema.Update(new TValue(DateTime.UtcNow, 100)); dema.Update(new TValue(DateTime.UtcNow, 110)); - + dema.Reset(); - + Assert.Equal(0, dema.Last.Value); Assert.False(dema.IsHot); } @@ -278,7 +278,7 @@ public class DemaTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Dema.Calculate(series, period); double expected = batchSeries.Last.Value; @@ -321,11 +321,11 @@ public class DemaTests Dema.Calculate(source, output, 3); - // We expect the first two outputs to be NaN because the input was NaN + // We expect the first two outputs to be NaN because the input was NaN Assert.True(double.IsNaN(output[0]), $"Output[0] should be NaN, but was {output[0]}"); Assert.True(double.IsNaN(output[1]), $"Output[1] should be NaN, but was {output[1]}"); - // The first valid value is 10.0. + // The first valid value is 10.0. Assert.Equal(10.0, output[2], 1e-9); } } diff --git a/lib/trends/dema/Dema.Validation.Tests.cs b/lib/trends/dema/Dema.Validation.Tests.cs index 23be6d91..b3afe792 100644 --- a/lib/trends/dema/Dema.Validation.Tests.cs +++ b/lib/trends/dema/Dema.Validation.Tests.cs @@ -106,13 +106,13 @@ public sealed class DemaValidationTests : IDisposable var demaIndicator = Tulip.Indicators.dema; double[][] inputs = { tData }; double[] options = { period }; - + // Tulip DEMA lookback is usually period-1 for EMA, but DEMA is 2*EMA - EMA(EMA) // Let's rely on the output length to align. // Tulip DEMA lookback is same as EMA lookback? No, it involves double smoothing. // Actually, Tulip's DEMA implementation might have a specific lookback. // We'll calculate it based on output length. - + // Tulip.Indicators.dema.Run expects outputs to be sized correctly. // We'll use a large buffer and resize if needed, or just calculate lookback. // For DEMA(n), lookback is roughly n-1 (same as EMA). @@ -120,7 +120,7 @@ public sealed class DemaValidationTests : IDisposable // Let's try with n-1 first, if it fails we adjust. // Actually, TA-Lib DEMA lookback is 2*(period-1). // Let's assume Tulip is similar. - int lookback = 2 * (period - 1); + int lookback = 2 * (period - 1); double[][] outputs = { new double[tData.Length - lookback] }; demaIndicator.Run(inputs, options, outputs); @@ -177,7 +177,7 @@ public sealed class DemaValidationTests : IDisposable for (int i = 0; i < _testData.Data.Count; i++) { var item = _testData.Data[i]; - + // QuanTAlib DEMA var qVal = dema.Update(item); diff --git a/lib/trends/dwma/Dwma.Quantower.cs b/lib/trends/dwma/Dwma.Quantower.cs index 92b7dae4..7fabb169 100644 --- a/lib/trends/dwma/Dwma.Quantower.cs +++ b/lib/trends/dwma/Dwma.Quantower.cs @@ -50,7 +50,7 @@ public class DwmaIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); Series!.SetValue(result.Value, ma.IsHot, ShowColdValues); diff --git a/lib/trends/dwma/Dwma.Tests.cs b/lib/trends/dwma/Dwma.Tests.cs index 44a96569..e18780a4 100644 --- a/lib/trends/dwma/Dwma.Tests.cs +++ b/lib/trends/dwma/Dwma.Tests.cs @@ -22,20 +22,20 @@ public class DwmaTests // 3: (1*1 + 2*2 + 3*3) / 6 = 14/6 = 2.333... // 4: (1*2 + 2*3 + 3*4) / 6 = 20/6 = 3.333... // 5: (1*3 + 2*4 + 3*5) / 6 = 26/6 = 4.333... - + // WMA(3) results: [1, 1.666, 2.333, 3.333, 4.333] - + // DWMA(3) = WMA(3) of [1, 1.666, 2.333, 3.333, 4.333] // 1: 1 // 2: (1*1 + 2*1.666) / 3 = 4.333/3 = 1.444... // 3: (1*1 + 2*1.666 + 3*2.333) / 6 = (1 + 3.333 + 7) / 6 = 11.333/6 = 1.888... - + var dwma = new Dwma(3); - + var v1 = dwma.Update(new TValue(DateTime.UtcNow, 1)).Value; var v2 = dwma.Update(new TValue(DateTime.UtcNow, 2)).Value; var v3 = dwma.Update(new TValue(DateTime.UtcNow, 3)).Value; - + Assert.Equal(1.0, v1, 6); Assert.Equal(1.444444, v2, 5); Assert.Equal(1.888888, v3, 5); @@ -45,23 +45,23 @@ public class DwmaTests public void Update_IsNewFalse_CorrectsValue() { var dwma = new Dwma(3); - + dwma.Update(new TValue(DateTime.UtcNow, 1)); dwma.Update(new TValue(DateTime.UtcNow, 2)); - + // Update with 3, then correct to 4 var v3 = dwma.Update(new TValue(DateTime.UtcNow, 3), isNew: true).Value; var v3_corrected = dwma.Update(new TValue(DateTime.UtcNow, 4), isNew: false).Value; - + // Manual calc for sequence [1, 2, 4] // WMA(3): // 1: 1 // 2: 1.666 // 4: (1*1 + 2*2 + 3*4) / 6 = 17/6 = 2.8333 - + // DWMA(3) of [1, 1.666, 2.8333] // 3: (1*1 + 2*1.666 + 3*2.8333) / 6 = (1 + 3.333 + 8.5) / 6 = 12.833/6 = 2.1388 - + Assert.Equal(1.888888, v3, 5); // From previous test Assert.Equal(2.138888, v3_corrected, 5); } @@ -72,9 +72,9 @@ public class DwmaTests var dwma = new Dwma(3); dwma.Update(new TValue(DateTime.UtcNow, 1)); dwma.Update(new TValue(DateTime.UtcNow, 2)); - + dwma.Reset(); - + Assert.False(dwma.IsHot); var v1 = dwma.Update(new TValue(DateTime.UtcNow, 1)).Value; Assert.Equal(1.0, v1); @@ -87,15 +87,15 @@ public class DwmaTests int count = 100; var source = new TSeries(); var dwma = new Dwma(period); - + for (int i = 0; i < count; i++) { source.Add(new TValue(DateTime.UtcNow.AddMinutes(i), i)); dwma.Update(source.Last); } - + var staticResult = Dwma.Batch(source, period); - + Assert.Equal(source.Count, staticResult.Count); Assert.Equal(dwma.Last.Value, staticResult.Last.Value, 8); } @@ -178,7 +178,7 @@ public class DwmaTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Dwma.Batch(series, period); double expected = batchSeries.Last.Value; diff --git a/lib/trends/dwma/Dwma.Validation.Tests.cs b/lib/trends/dwma/Dwma.Validation.Tests.cs index 7dcdef2e..69b8543e 100644 --- a/lib/trends/dwma/Dwma.Validation.Tests.cs +++ b/lib/trends/dwma/Dwma.Validation.Tests.cs @@ -46,24 +46,24 @@ public sealed class DwmaValidationTests : IDisposable public void Validate_Against_DoubleWma() { // DWMA should be exactly WMA(WMA(source, period), period) - + int period = 10; - + var dwma = new Dwma(period); var wma1 = new Wma(period); var wma2 = new Wma(period); - + for (int i = 0; i < _testData.Data.Count; i++) { var val = _testData.Data[i]; - + // Calculate DWMA var dwmaVal = dwma.Update(val); - + // Calculate WMA(WMA) manually var wma1Val = wma1.Update(val); var wma2Val = wma2.Update(wma1Val); - + Assert.Equal(wma2Val.Value, dwmaVal.Value, ValidationHelper.DefaultTolerance); } } @@ -73,24 +73,24 @@ public sealed class DwmaValidationTests : IDisposable { // Ooples Finance does not have a specific DWMA indicator, but it can be calculated // by chaining two Weighted Moving Averages - + int period = 14; - + var dwma = new Dwma(period); var wma1 = new Wma(period); // Simulates first CalculateWeightedMovingAverage var wma2 = new Wma(period); // Simulates second CalculateWeightedMovingAverage - + for (int i = 0; i < _testData.Data.Count; i++) { var val = _testData.Data[i]; - + // QuanTAlib DWMA var qVal = dwma.Update(val); - + // Ooples Logic (Chained WMA) var w1 = wma1.Update(val); var w2 = wma2.Update(w1); - + Assert.Equal(w2.Value, qVal.Value, ValidationHelper.DefaultTolerance); } } diff --git a/lib/trends/ema/Ema.Tests.cs b/lib/trends/ema/Ema.Tests.cs index 2bbbb53e..65eb5081 100644 --- a/lib/trends/ema/Ema.Tests.cs +++ b/lib/trends/ema/Ema.Tests.cs @@ -1,651 +1,651 @@ -namespace QuanTAlib.Tests; - -#pragma warning disable S2245 // Random is acceptable for simulation/testing purposes -public class EmaTests -{ - [Fact] - public void Ema_Constructor_Period_ValidatesInput() - { - Assert.Throws(() => new Ema(0)); - Assert.Throws(() => new Ema(-1)); - - var ema = new Ema(10); - Assert.NotNull(ema); - } - - [Fact] - public void Ema_Constructor_Alpha_ValidatesInput() - { - Assert.Throws(() => new Ema(0.0)); - Assert.Throws(() => new Ema(-0.1)); - Assert.Throws(() => new Ema(1.1)); - - var ema = new Ema(0.5); - Assert.NotNull(ema); - } - - [Fact] - public void Ema_Calc_ReturnsValue() - { - var ema = new Ema(10); - - Assert.Equal(0, ema.Last.Value); - - TValue result = ema.Update(new TValue(DateTime.UtcNow, 100)); - - Assert.True(result.Value > 0); - Assert.Equal(result.Value, ema.Last.Value); - } - - [Fact] - public void Ema_Calc_IsNew_AcceptsParameter() - { - var ema = new Ema(10); - - ema.Update(new TValue(DateTime.UtcNow, 100), isNew: true); - double value1 = ema.Last.Value; - - ema.Update(new TValue(DateTime.UtcNow, 105), isNew: true); - double value2 = ema.Last.Value; - - // Values should change with new bars - Assert.NotEqual(value1, value2); - } - - [Fact] - public void Ema_Calc_IsNew_False_UpdatesValue() - { - var ema = new Ema(10); - - ema.Update(new TValue(DateTime.UtcNow, 100)); - ema.Update(new TValue(DateTime.UtcNow, 110), isNew: true); - double beforeUpdate = ema.Last.Value; - - ema.Update(new TValue(DateTime.UtcNow, 120), isNew: false); - double afterUpdate = ema.Last.Value; - - // Update should change the value - Assert.NotEqual(beforeUpdate, afterUpdate); - } - - [Fact] - public void Ema_Reset_ClearsState() - { - var ema = new Ema(10); - - ema.Update(new TValue(DateTime.UtcNow, 100)); - ema.Update(new TValue(DateTime.UtcNow, 105)); - double valueBefore = ema.Last.Value; - - ema.Reset(); - - Assert.Equal(0, ema.Last.Value); - - // After reset, should accept new values - ema.Update(new TValue(DateTime.UtcNow, 50)); - Assert.NotEqual(0, ema.Last.Value); - Assert.NotEqual(valueBefore, ema.Last.Value); - } - - [Fact] - public void Ema_Properties_Accessible() - { - var ema = new Ema(10); - - Assert.Equal(0, ema.Last.Value); - Assert.False(ema.IsHot); - - ema.Update(new TValue(DateTime.UtcNow, 100)); - - Assert.NotEqual(0, ema.Last.Value); - } - - [Fact] - public void Ema_IsHot_BecomesTrueAt95PercentCoverage() - { - var ema = new Ema(10); - - // Initially IsHot should be false - Assert.False(ema.IsHot); - - // IsHot triggers at 95% coverage (E <= 0.05) - // E = (1 - alpha)^N where alpha = 2 / (period + 1) - // For period 10: alpha = 2/11 ≈ 0.1818, (1-alpha) ≈ 0.8182 - // N = ln(0.05) / ln(0.8182) ≈ 14.93, so ~15 bars - - int steps = 0; - while (!ema.IsHot && steps < 1000) - { - ema.Update(new TValue(DateTime.UtcNow, 100)); - steps++; - } - - Assert.True(ema.IsHot); - Assert.True(steps > 0); - // For period 10, should become hot around 15 bars - Assert.InRange(steps, 14, 16); - } - - [Fact] - public void Ema_IsHot_IsPeriodDependent() - { - // Test that different periods result in different warmup times - // Formula: N = ln(0.05) / ln((p-1)/(p+1)) - - int[] periods = [10, 20, 50, 100]; - int[] expectedSteps = new int[periods.Length]; - - for (int i = 0; i < periods.Length; i++) - { - int period = periods[i]; - var ema = new Ema(period); - - int steps = 0; - while (!ema.IsHot && steps < 500) - { - ema.Update(new TValue(DateTime.UtcNow, 100)); - steps++; - } - - expectedSteps[i] = steps; - } - - // Verify warmup times increase with period - // Period 10 → ~15 bars, Period 20 → ~30 bars, Period 50 → ~75 bars, Period 100 → ~150 bars - Assert.True(expectedSteps[0] < expectedSteps[1], $"Period 10 ({expectedSteps[0]}) should be less than Period 20 ({expectedSteps[1]})"); - Assert.True(expectedSteps[1] < expectedSteps[2], $"Period 20 ({expectedSteps[1]}) should be less than Period 50 ({expectedSteps[2]})"); - Assert.True(expectedSteps[2] < expectedSteps[3], $"Period 50 ({expectedSteps[2]}) should be less than Period 100 ({expectedSteps[3]})"); - - // Verify approximate expected values (N ≈ 1.5 * period for 95% coverage) - Assert.InRange(expectedSteps[0], 14, 17); // Period 10 → ~15 - Assert.InRange(expectedSteps[1], 28, 32); // Period 20 → ~30 - Assert.InRange(expectedSteps[2], 73, 78); // Period 50 → ~75 - Assert.InRange(expectedSteps[3], 147, 153); // Period 100 → ~150 - } - - [Fact] - public void Ema_PeriodEquivalence_BothConstructorsWork() - { - int period = 20; - double alpha = 2.0 / (period + 1); - - var emaPeriod = new Ema(period); - var emaAlpha = new Ema(alpha); - - // Both should accept Calc calls and produce same result - TValue result1 = emaPeriod.Update(new TValue(DateTime.UtcNow, 100)); - TValue result2 = emaAlpha.Update(new TValue(DateTime.UtcNow, 100)); - - Assert.Equal(result1.Value, result2.Value, 1e-10); - } - - [Fact] - public void Ema_IterativeCorrections_RestoreToOriginalState() - { - var ema = new Ema(10); - var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1); - - // Feed 10 new values - TValue tenthInput = default; - for (int i = 0; i < 10; i++) - { - var bar = gbm.Next(isNew: true); - tenthInput = new TValue(bar.Time, bar.Close); - ema.Update(tenthInput, isNew: true); - } - - // Remember EMA state after 10 values - double emaAfterTen = ema.Last.Value; - - // Generate 9 corrections with isNew=false (different values) - for (int i = 0; i < 9; i++) - { - var bar = gbm.Next(isNew: false); - ema.Update(new TValue(bar.Time, bar.Close), isNew: false); - } - - // Feed the remembered 10th input again with isNew=false - TValue finalEma = ema.Update(tenthInput, isNew: false); - - // EMA should match the original state after 10 values - Assert.Equal(emaAfterTen, finalEma.Value, 1e-10); - } - - [Fact] - public void Ema_BatchCalc_MatchesIterativeCalc() - { - var emaIterative = new Ema(10); - var emaBatch = new Ema(10); - var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1); - - // Generate data - var series = new TSeries(); - for (int i = 0; i < 100; i++) - { - var bar = gbm.Next(isNew: true); - series.Add(bar.Time, bar.Close); - } - - Assert.True(series.Count > 0); - - // Calculate iteratively - var iterativeResults = new TSeries(); - foreach (var item in series) - { - iterativeResults.Add(emaIterative.Update(item)); - } - - // Calculate batch - var batchResults = emaBatch.Update(series); - - // Compare - Assert.Equal(iterativeResults.Count, batchResults.Count); - for (int i = 0; i < iterativeResults.Count; i++) - { - Assert.Equal(iterativeResults[i].Value, batchResults[i].Value, 1e-10); - Assert.Equal(iterativeResults[i].Time, batchResults[i].Time); - } - } - - [Fact] - public void Ema_Result_ImplicitConversionToDouble() - { - var ema = new Ema(10); - ema.Update(new TValue(DateTime.UtcNow, 100)); - - // This should compile and work because TValue has implicit conversion to double - double result = ema.Last.Value; - - Assert.Equal(100.0, result, 1e-10); - } - - [Fact] - public void Ema_NaN_Input_UsesLastValidValue() - { - var ema = new Ema(10); - - // Feed some valid values - ema.Update(new TValue(DateTime.UtcNow, 100)); - ema.Update(new TValue(DateTime.UtcNow, 110)); - - // Feed NaN - should use last valid value (110) - var resultAfterNaN = ema.Update(new TValue(DateTime.UtcNow, double.NaN)); - - // Result should be finite (not NaN) - Assert.True(double.IsFinite(resultAfterNaN.Value)); - // EMA should continue to evolve (may differ slightly due to substitution) - Assert.NotEqual(0, resultAfterNaN.Value); - } - - [Fact] - public void Ema_Infinity_Input_UsesLastValidValue() - { - var ema = new Ema(10); - - // Feed some valid values - ema.Update(new TValue(DateTime.UtcNow, 100)); - ema.Update(new TValue(DateTime.UtcNow, 110)); - - // Feed positive infinity - should use last valid value - var resultAfterPosInf = ema.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity)); - Assert.True(double.IsFinite(resultAfterPosInf.Value)); - - // Feed negative infinity - should use last valid value - var resultAfterNegInf = ema.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity)); - Assert.True(double.IsFinite(resultAfterNegInf.Value)); - } - - [Fact] - public void Ema_MultipleNaN_ContinuesWithLastValid() - { - var ema = new Ema(10); - - // Feed valid values - ema.Update(new TValue(DateTime.UtcNow, 100)); - ema.Update(new TValue(DateTime.UtcNow, 110)); - ema.Update(new TValue(DateTime.UtcNow, 120)); - - // Feed multiple NaN values - var r1 = ema.Update(new TValue(DateTime.UtcNow, double.NaN)); - var r2 = ema.Update(new TValue(DateTime.UtcNow, double.NaN)); - var r3 = ema.Update(new TValue(DateTime.UtcNow, double.NaN)); - - // All results should be finite - Assert.True(double.IsFinite(r1.Value)); - Assert.True(double.IsFinite(r2.Value)); - Assert.True(double.IsFinite(r3.Value)); - - // EMA should converge toward last valid value (120) with repeated substitution - // Values should be getting closer to 120 - Assert.True(r3.Value > r1.Value || Math.Abs(r3.Value - 120) < Math.Abs(r1.Value - 120)); - } - - [Fact] - public void Ema_BatchCalc_HandlesNaN() - { - var ema = new Ema(10); - - // Create series with NaN values interspersed - var series = new TSeries(); - series.Add(DateTime.UtcNow.Ticks, 100); - series.Add(DateTime.UtcNow.Ticks + 1, 110); - series.Add(DateTime.UtcNow.Ticks + 2, double.NaN); - series.Add(DateTime.UtcNow.Ticks + 3, 120); - series.Add(DateTime.UtcNow.Ticks + 4, double.PositiveInfinity); - series.Add(DateTime.UtcNow.Ticks + 5, 130); - - var results = ema.Update(series); - - // All results should be finite - foreach (var result in results) - { - Assert.True(double.IsFinite(result.Value), $"Expected finite value but got {result.Value}"); - } - } - - [Fact] - public void Ema_Reset_ClearsLastValidValue() - { - var ema = new Ema(10); - - // Feed values including NaN - ema.Update(new TValue(DateTime.UtcNow, 100)); - ema.Update(new TValue(DateTime.UtcNow, double.NaN)); - - // Reset - ema.Reset(); - - // After reset, first valid value should establish new baseline - var result = ema.Update(new TValue(DateTime.UtcNow, 50)); - Assert.Equal(50.0, result.Value, 1e-10); - } - - // ============== Span API Tests ============== - - [Fact] - public void Ema_SpanBatch_Period_ValidatesInput() - { - double[] source = [1, 2, 3, 4, 5]; - double[] output = new double[5]; - double[] wrongSizeOutput = new double[3]; - - // Period must be > 0 - Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), 0)); - Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), -1)); - - // Output must be same length as source - Assert.Throws(() => Ema.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3)); - } - - [Fact] - public void Ema_SpanBatch_Alpha_ValidatesInput() - { - double[] source = [1, 2, 3, 4, 5]; - double[] output = new double[5]; - - // Alpha must be > 0 and <= 1 - Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), 0.0)); - Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), -0.1)); - Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), 1.1)); - } - - [Fact] - public void Ema_SpanBatch_MatchesTSeriesBatch() - { - var series = new TSeries(); - double[] source = new double[100]; - double[] output = new double[100]; - - var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 42); - for (int i = 0; i < 100; i++) - { - var bar = gbm.Next(isNew: true); - source[i] = bar.Close; - series.Add(bar.Time, bar.Close); - } - - // Calculate with TSeries API - var tseriesResult = Ema.Batch(series, 10); - - // Calculate with Span API - Ema.Batch(source.AsSpan(), output.AsSpan(), 10); - - // Compare results - allow small tolerance due to bias correction differences - for (int i = 0; i < 100; i++) - { - Assert.Equal(tseriesResult[i].Value, output[i], 1e-9); - } - } - - [Fact] - public void Ema_SpanBatch_PeriodAndAlphaEquivalent() - { - double[] source = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; - double[] outputPeriod = new double[10]; - double[] outputAlpha = new double[10]; - - int period = 5; - double alpha = 2.0 / (period + 1); - - Ema.Batch(source.AsSpan(), outputPeriod.AsSpan(), period); - Ema.Batch(source.AsSpan(), outputAlpha.AsSpan(), alpha); - - // Results should be identical - for (int i = 0; i < 10; i++) - { - Assert.Equal(outputPeriod[i], outputAlpha[i], 1e-10); - } - } - - [Fact] - public void Ema_SpanBatch_ZeroAllocation() - { - double[] source = new double[10000]; - double[] output = new double[10000]; - - var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42); - for (int i = 0; i < source.Length; i++) - source[i] = gbm.Next().Close; - - // Warm up - Ema.Batch(source.AsSpan(), output.AsSpan(), 100); - - // This test verifies the method runs without throwing - Assert.True(double.IsFinite(output[^1])); - } - - [Fact] - public void Ema_SpanBatch_HandlesNaN() - { - double[] source = [100, 110, double.NaN, 120, 130]; - double[] output = new double[5]; - - Ema.Batch(source.AsSpan(), output.AsSpan(), 3); - - // All outputs should be finite - foreach (var val in output) - { - Assert.True(double.IsFinite(val), $"Expected finite value but got {val}"); - } - } - - [Fact] - public void Ema_SpanBatch_BiasCorrection_Works() - { - double[] source = [100, 100, 100, 100, 100]; - double[] output = new double[5]; - - Ema.Batch(source.AsSpan(), output.AsSpan(), 3); - - // With bias correction, first value should equal input - Assert.Equal(100.0, output[0], 1e-10); - - // All values should converge to 100 since input is constant - foreach (var val in output) - { - Assert.Equal(100.0, val, 1e-9); - } - } - - [Fact] - public void Ema_SpanBatch_Alpha_DirectUsage() - { - double[] source = [10, 20, 30, 40, 50]; - double[] output = new double[5]; - - // Use alpha = 0.5 directly - Ema.Batch(source.AsSpan(), output.AsSpan(), 0.5); - - // Results should be finite and reasonable - Assert.True(double.IsFinite(output[^1])); - Assert.True(output[^1] > 10 && output[^1] <= 50); - } - - [Fact] - public void Chainability_Works() - { - var source = new TSeries(); - var ema = new Ema(source, 10); - - source.Add(new TValue(DateTime.UtcNow, 100)); - Assert.Equal(100, ema.Last.Value, 1e-10); - } - - [Fact] - public void Prime_SetsStateCorrectly() - { - var ema = new Ema(5); - double[] history = [10, 20, 30, 40, 50]; - - ema.Prime(history); - - // EMA(5) of 10,20,30,40,50 - // Alpha = 2/6 = 1/3 - // 10 -> 10 - // 20 -> 10 + 1/3(10) = 13.33... - // ... - // We can verify against a fresh EMA fed with same data - var verifyEma = new Ema(5); - foreach (var val in history) verifyEma.Update(new TValue(DateTime.UtcNow, val)); - - Assert.Equal(verifyEma.Last.Value, ema.Last.Value, 1e-10); - Assert.Equal(verifyEma.IsHot, ema.IsHot); - - // Verify it continues correctly - ema.Update(new TValue(DateTime.UtcNow, 60)); - verifyEma.Update(new TValue(DateTime.UtcNow, 60)); - Assert.Equal(verifyEma.Last.Value, ema.Last.Value, 1e-10); - } - - [Fact] - public void Prime_HandlesNaN_InHistory() - { - var ema = new Ema(5); - double[] history = [10, 20, double.NaN, 40, 50]; - - ema.Prime(history); - - var verifyEma = new Ema(5); - foreach (var val in history) verifyEma.Update(new TValue(DateTime.UtcNow, val)); - - Assert.Equal(verifyEma.Last.Value, ema.Last.Value, 1e-10); - } - - [Fact] - public void Prime_AllNaNs_ReturnsNaN() - { - var ema = new Ema(5); - double[] history = [double.NaN, double.NaN, double.NaN]; - - ema.Prime(history); - - Assert.True(double.IsNaN(ema.Last.Value)); - } - - [Fact] - public void Calculate_ReturnsCorrectResultsAndHotIndicator() - { - var series = new TSeries(); - for (int i = 1; i <= 20; i++) series.Add(DateTime.UtcNow, i * 10); - - // EMA(5) - var (results, indicator) = Ema.Calculate(series, 5); - - // Check results - Assert.Equal(20, results.Count); - - // Verify against standard calculation - var verifyEma = new Ema(5); - var verifyResults = verifyEma.Update(series); - - Assert.Equal(verifyResults.Last.Value, results.Last.Value, 1e-10); - Assert.Equal(verifyEma.Last.Value, indicator.Last.Value, 1e-10); - - // Check indicator state - Assert.True(indicator.IsHot); - - // Verify indicator continues correctly - indicator.Update(new TValue(DateTime.UtcNow, 210)); - verifyEma.Update(new TValue(DateTime.UtcNow, 210)); - Assert.Equal(verifyEma.Last.Value, indicator.Last.Value, 1e-10); - } - - [Fact] - public void Ema_Batch_AllNaNs_ReturnsNaN() - { - double[] source = [double.NaN, double.NaN, double.NaN]; - double[] output = new double[3]; - - Ema.Batch(source.AsSpan(), output.AsSpan(), 5); - - // Should be all NaNs, not 0s - foreach (var val in output) - { - Assert.True(double.IsNaN(val), $"Expected NaN but got {val}"); - } - } - - [Fact] - public void Ema_AllModes_ProduceSameResult() - { - // Arrange - int period = 10; - var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); - var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - var series = bars.Close; - - // 1. Batch Mode - var batchSeries = Ema.Batch(series, period); - double expected = batchSeries.Last.Value; - - // 2. Span Mode - var tValues = series.Values.ToArray(); // Need array for Span modification safety if any - var spanInput = new ReadOnlySpan(tValues); - var spanOutput = new double[tValues.Length]; - Ema.Batch(spanInput, spanOutput, period); - double spanResult = spanOutput[^1]; - - // 3. Streaming Mode - var streamingInd = new Ema(period); - for (int i = 0; i < series.Count; i++) - { - streamingInd.Update(series[i]); - } - double streamingResult = streamingInd.Last.Value; - - // 4. Eventing Mode - var pubSource = new TSeries(); - var eventingInd = new Ema(pubSource, period); - for (int i = 0; i < series.Count; i++) - { - pubSource.Add(series[i]); - } - double eventingResult = eventingInd.Last.Value; - - // Assert - // Precision 9 due to potential accumulation differences in loop vs batch optimizations - Assert.Equal(expected, spanResult, precision: 9); - Assert.Equal(expected, streamingResult, precision: 9); - Assert.Equal(expected, eventingResult, precision: 9); - } -} +namespace QuanTAlib.Tests; + +#pragma warning disable S2245 // Random is acceptable for simulation/testing purposes +public class EmaTests +{ + [Fact] + public void Ema_Constructor_Period_ValidatesInput() + { + Assert.Throws(() => new Ema(0)); + Assert.Throws(() => new Ema(-1)); + + var ema = new Ema(10); + Assert.NotNull(ema); + } + + [Fact] + public void Ema_Constructor_Alpha_ValidatesInput() + { + Assert.Throws(() => new Ema(0.0)); + Assert.Throws(() => new Ema(-0.1)); + Assert.Throws(() => new Ema(1.1)); + + var ema = new Ema(0.5); + Assert.NotNull(ema); + } + + [Fact] + public void Ema_Calc_ReturnsValue() + { + var ema = new Ema(10); + + Assert.Equal(0, ema.Last.Value); + + TValue result = ema.Update(new TValue(DateTime.UtcNow, 100)); + + Assert.True(result.Value > 0); + Assert.Equal(result.Value, ema.Last.Value); + } + + [Fact] + public void Ema_Calc_IsNew_AcceptsParameter() + { + var ema = new Ema(10); + + ema.Update(new TValue(DateTime.UtcNow, 100), isNew: true); + double value1 = ema.Last.Value; + + ema.Update(new TValue(DateTime.UtcNow, 105), isNew: true); + double value2 = ema.Last.Value; + + // Values should change with new bars + Assert.NotEqual(value1, value2); + } + + [Fact] + public void Ema_Calc_IsNew_False_UpdatesValue() + { + var ema = new Ema(10); + + ema.Update(new TValue(DateTime.UtcNow, 100)); + ema.Update(new TValue(DateTime.UtcNow, 110), isNew: true); + double beforeUpdate = ema.Last.Value; + + ema.Update(new TValue(DateTime.UtcNow, 120), isNew: false); + double afterUpdate = ema.Last.Value; + + // Update should change the value + Assert.NotEqual(beforeUpdate, afterUpdate); + } + + [Fact] + public void Ema_Reset_ClearsState() + { + var ema = new Ema(10); + + ema.Update(new TValue(DateTime.UtcNow, 100)); + ema.Update(new TValue(DateTime.UtcNow, 105)); + double valueBefore = ema.Last.Value; + + ema.Reset(); + + Assert.Equal(0, ema.Last.Value); + + // After reset, should accept new values + ema.Update(new TValue(DateTime.UtcNow, 50)); + Assert.NotEqual(0, ema.Last.Value); + Assert.NotEqual(valueBefore, ema.Last.Value); + } + + [Fact] + public void Ema_Properties_Accessible() + { + var ema = new Ema(10); + + Assert.Equal(0, ema.Last.Value); + Assert.False(ema.IsHot); + + ema.Update(new TValue(DateTime.UtcNow, 100)); + + Assert.NotEqual(0, ema.Last.Value); + } + + [Fact] + public void Ema_IsHot_BecomesTrueAt95PercentCoverage() + { + var ema = new Ema(10); + + // Initially IsHot should be false + Assert.False(ema.IsHot); + + // IsHot triggers at 95% coverage (E <= 0.05) + // E = (1 - alpha)^N where alpha = 2 / (period + 1) + // For period 10: alpha = 2/11 ≈ 0.1818, (1-alpha) ≈ 0.8182 + // N = ln(0.05) / ln(0.8182) ≈ 14.93, so ~15 bars + + int steps = 0; + while (!ema.IsHot && steps < 1000) + { + ema.Update(new TValue(DateTime.UtcNow, 100)); + steps++; + } + + Assert.True(ema.IsHot); + Assert.True(steps > 0); + // For period 10, should become hot around 15 bars + Assert.InRange(steps, 14, 16); + } + + [Fact] + public void Ema_IsHot_IsPeriodDependent() + { + // Test that different periods result in different warmup times + // Formula: N = ln(0.05) / ln((p-1)/(p+1)) + + int[] periods = [10, 20, 50, 100]; + int[] expectedSteps = new int[periods.Length]; + + for (int i = 0; i < periods.Length; i++) + { + int period = periods[i]; + var ema = new Ema(period); + + int steps = 0; + while (!ema.IsHot && steps < 500) + { + ema.Update(new TValue(DateTime.UtcNow, 100)); + steps++; + } + + expectedSteps[i] = steps; + } + + // Verify warmup times increase with period + // Period 10 → ~15 bars, Period 20 → ~30 bars, Period 50 → ~75 bars, Period 100 → ~150 bars + Assert.True(expectedSteps[0] < expectedSteps[1], $"Period 10 ({expectedSteps[0]}) should be less than Period 20 ({expectedSteps[1]})"); + Assert.True(expectedSteps[1] < expectedSteps[2], $"Period 20 ({expectedSteps[1]}) should be less than Period 50 ({expectedSteps[2]})"); + Assert.True(expectedSteps[2] < expectedSteps[3], $"Period 50 ({expectedSteps[2]}) should be less than Period 100 ({expectedSteps[3]})"); + + // Verify approximate expected values (N ≈ 1.5 * period for 95% coverage) + Assert.InRange(expectedSteps[0], 14, 17); // Period 10 → ~15 + Assert.InRange(expectedSteps[1], 28, 32); // Period 20 → ~30 + Assert.InRange(expectedSteps[2], 73, 78); // Period 50 → ~75 + Assert.InRange(expectedSteps[3], 147, 153); // Period 100 → ~150 + } + + [Fact] + public void Ema_PeriodEquivalence_BothConstructorsWork() + { + int period = 20; + double alpha = 2.0 / (period + 1); + + var emaPeriod = new Ema(period); + var emaAlpha = new Ema(alpha); + + // Both should accept Calc calls and produce same result + TValue result1 = emaPeriod.Update(new TValue(DateTime.UtcNow, 100)); + TValue result2 = emaAlpha.Update(new TValue(DateTime.UtcNow, 100)); + + Assert.Equal(result1.Value, result2.Value, 1e-10); + } + + [Fact] + public void Ema_IterativeCorrections_RestoreToOriginalState() + { + var ema = new Ema(10); + var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1); + + // Feed 10 new values + TValue tenthInput = default; + for (int i = 0; i < 10; i++) + { + var bar = gbm.Next(isNew: true); + tenthInput = new TValue(bar.Time, bar.Close); + ema.Update(tenthInput, isNew: true); + } + + // Remember EMA state after 10 values + double emaAfterTen = ema.Last.Value; + + // Generate 9 corrections with isNew=false (different values) + for (int i = 0; i < 9; i++) + { + var bar = gbm.Next(isNew: false); + ema.Update(new TValue(bar.Time, bar.Close), isNew: false); + } + + // Feed the remembered 10th input again with isNew=false + TValue finalEma = ema.Update(tenthInput, isNew: false); + + // EMA should match the original state after 10 values + Assert.Equal(emaAfterTen, finalEma.Value, 1e-10); + } + + [Fact] + public void Ema_BatchCalc_MatchesIterativeCalc() + { + var emaIterative = new Ema(10); + var emaBatch = new Ema(10); + var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1); + + // Generate data + var series = new TSeries(); + for (int i = 0; i < 100; i++) + { + var bar = gbm.Next(isNew: true); + series.Add(bar.Time, bar.Close); + } + + Assert.True(series.Count > 0); + + // Calculate iteratively + var iterativeResults = new TSeries(); + foreach (var item in series) + { + iterativeResults.Add(emaIterative.Update(item)); + } + + // Calculate batch + var batchResults = emaBatch.Update(series); + + // Compare + Assert.Equal(iterativeResults.Count, batchResults.Count); + for (int i = 0; i < iterativeResults.Count; i++) + { + Assert.Equal(iterativeResults[i].Value, batchResults[i].Value, 1e-10); + Assert.Equal(iterativeResults[i].Time, batchResults[i].Time); + } + } + + [Fact] + public void Ema_Result_ImplicitConversionToDouble() + { + var ema = new Ema(10); + ema.Update(new TValue(DateTime.UtcNow, 100)); + + // This should compile and work because TValue has implicit conversion to double + double result = ema.Last.Value; + + Assert.Equal(100.0, result, 1e-10); + } + + [Fact] + public void Ema_NaN_Input_UsesLastValidValue() + { + var ema = new Ema(10); + + // Feed some valid values + ema.Update(new TValue(DateTime.UtcNow, 100)); + ema.Update(new TValue(DateTime.UtcNow, 110)); + + // Feed NaN - should use last valid value (110) + var resultAfterNaN = ema.Update(new TValue(DateTime.UtcNow, double.NaN)); + + // Result should be finite (not NaN) + Assert.True(double.IsFinite(resultAfterNaN.Value)); + // EMA should continue to evolve (may differ slightly due to substitution) + Assert.NotEqual(0, resultAfterNaN.Value); + } + + [Fact] + public void Ema_Infinity_Input_UsesLastValidValue() + { + var ema = new Ema(10); + + // Feed some valid values + ema.Update(new TValue(DateTime.UtcNow, 100)); + ema.Update(new TValue(DateTime.UtcNow, 110)); + + // Feed positive infinity - should use last valid value + var resultAfterPosInf = ema.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity)); + Assert.True(double.IsFinite(resultAfterPosInf.Value)); + + // Feed negative infinity - should use last valid value + var resultAfterNegInf = ema.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity)); + Assert.True(double.IsFinite(resultAfterNegInf.Value)); + } + + [Fact] + public void Ema_MultipleNaN_ContinuesWithLastValid() + { + var ema = new Ema(10); + + // Feed valid values + ema.Update(new TValue(DateTime.UtcNow, 100)); + ema.Update(new TValue(DateTime.UtcNow, 110)); + ema.Update(new TValue(DateTime.UtcNow, 120)); + + // Feed multiple NaN values + var r1 = ema.Update(new TValue(DateTime.UtcNow, double.NaN)); + var r2 = ema.Update(new TValue(DateTime.UtcNow, double.NaN)); + var r3 = ema.Update(new TValue(DateTime.UtcNow, double.NaN)); + + // All results should be finite + Assert.True(double.IsFinite(r1.Value)); + Assert.True(double.IsFinite(r2.Value)); + Assert.True(double.IsFinite(r3.Value)); + + // EMA should converge toward last valid value (120) with repeated substitution + // Values should be getting closer to 120 + Assert.True(r3.Value > r1.Value || Math.Abs(r3.Value - 120) < Math.Abs(r1.Value - 120)); + } + + [Fact] + public void Ema_BatchCalc_HandlesNaN() + { + var ema = new Ema(10); + + // Create series with NaN values interspersed + var series = new TSeries(); + series.Add(DateTime.UtcNow.Ticks, 100); + series.Add(DateTime.UtcNow.Ticks + 1, 110); + series.Add(DateTime.UtcNow.Ticks + 2, double.NaN); + series.Add(DateTime.UtcNow.Ticks + 3, 120); + series.Add(DateTime.UtcNow.Ticks + 4, double.PositiveInfinity); + series.Add(DateTime.UtcNow.Ticks + 5, 130); + + var results = ema.Update(series); + + // All results should be finite + foreach (var result in results) + { + Assert.True(double.IsFinite(result.Value), $"Expected finite value but got {result.Value}"); + } + } + + [Fact] + public void Ema_Reset_ClearsLastValidValue() + { + var ema = new Ema(10); + + // Feed values including NaN + ema.Update(new TValue(DateTime.UtcNow, 100)); + ema.Update(new TValue(DateTime.UtcNow, double.NaN)); + + // Reset + ema.Reset(); + + // After reset, first valid value should establish new baseline + var result = ema.Update(new TValue(DateTime.UtcNow, 50)); + Assert.Equal(50.0, result.Value, 1e-10); + } + + // ============== Span API Tests ============== + + [Fact] + public void Ema_SpanBatch_Period_ValidatesInput() + { + double[] source = [1, 2, 3, 4, 5]; + double[] output = new double[5]; + double[] wrongSizeOutput = new double[3]; + + // Period must be > 0 + Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), 0)); + Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), -1)); + + // Output must be same length as source + Assert.Throws(() => Ema.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 3)); + } + + [Fact] + public void Ema_SpanBatch_Alpha_ValidatesInput() + { + double[] source = [1, 2, 3, 4, 5]; + double[] output = new double[5]; + + // Alpha must be > 0 and <= 1 + Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), 0.0)); + Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), -0.1)); + Assert.Throws(() => Ema.Batch(source.AsSpan(), output.AsSpan(), 1.1)); + } + + [Fact] + public void Ema_SpanBatch_MatchesTSeriesBatch() + { + var series = new TSeries(); + double[] source = new double[100]; + double[] output = new double[100]; + + var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: 42); + for (int i = 0; i < 100; i++) + { + var bar = gbm.Next(isNew: true); + source[i] = bar.Close; + series.Add(bar.Time, bar.Close); + } + + // Calculate with TSeries API + var tseriesResult = Ema.Batch(series, 10); + + // Calculate with Span API + Ema.Batch(source.AsSpan(), output.AsSpan(), 10); + + // Compare results - allow small tolerance due to bias correction differences + for (int i = 0; i < 100; i++) + { + Assert.Equal(tseriesResult[i].Value, output[i], 1e-9); + } + } + + [Fact] + public void Ema_SpanBatch_PeriodAndAlphaEquivalent() + { + double[] source = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; + double[] outputPeriod = new double[10]; + double[] outputAlpha = new double[10]; + + int period = 5; + double alpha = 2.0 / (period + 1); + + Ema.Batch(source.AsSpan(), outputPeriod.AsSpan(), period); + Ema.Batch(source.AsSpan(), outputAlpha.AsSpan(), alpha); + + // Results should be identical + for (int i = 0; i < 10; i++) + { + Assert.Equal(outputPeriod[i], outputAlpha[i], 1e-10); + } + } + + [Fact] + public void Ema_SpanBatch_ZeroAllocation() + { + double[] source = new double[10000]; + double[] output = new double[10000]; + + var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42); + for (int i = 0; i < source.Length; i++) + source[i] = gbm.Next().Close; + + // Warm up + Ema.Batch(source.AsSpan(), output.AsSpan(), 100); + + // This test verifies the method runs without throwing + Assert.True(double.IsFinite(output[^1])); + } + + [Fact] + public void Ema_SpanBatch_HandlesNaN() + { + double[] source = [100, 110, double.NaN, 120, 130]; + double[] output = new double[5]; + + Ema.Batch(source.AsSpan(), output.AsSpan(), 3); + + // All outputs should be finite + foreach (var val in output) + { + Assert.True(double.IsFinite(val), $"Expected finite value but got {val}"); + } + } + + [Fact] + public void Ema_SpanBatch_BiasCorrection_Works() + { + double[] source = [100, 100, 100, 100, 100]; + double[] output = new double[5]; + + Ema.Batch(source.AsSpan(), output.AsSpan(), 3); + + // With bias correction, first value should equal input + Assert.Equal(100.0, output[0], 1e-10); + + // All values should converge to 100 since input is constant + foreach (var val in output) + { + Assert.Equal(100.0, val, 1e-9); + } + } + + [Fact] + public void Ema_SpanBatch_Alpha_DirectUsage() + { + double[] source = [10, 20, 30, 40, 50]; + double[] output = new double[5]; + + // Use alpha = 0.5 directly + Ema.Batch(source.AsSpan(), output.AsSpan(), 0.5); + + // Results should be finite and reasonable + Assert.True(double.IsFinite(output[^1])); + Assert.True(output[^1] > 10 && output[^1] <= 50); + } + + [Fact] + public void Chainability_Works() + { + var source = new TSeries(); + var ema = new Ema(source, 10); + + source.Add(new TValue(DateTime.UtcNow, 100)); + Assert.Equal(100, ema.Last.Value, 1e-10); + } + + [Fact] + public void Prime_SetsStateCorrectly() + { + var ema = new Ema(5); + double[] history = [10, 20, 30, 40, 50]; + + ema.Prime(history); + + // EMA(5) of 10,20,30,40,50 + // Alpha = 2/6 = 1/3 + // 10 -> 10 + // 20 -> 10 + 1/3(10) = 13.33... + // ... + // We can verify against a fresh EMA fed with same data + var verifyEma = new Ema(5); + foreach (var val in history) verifyEma.Update(new TValue(DateTime.UtcNow, val)); + + Assert.Equal(verifyEma.Last.Value, ema.Last.Value, 1e-10); + Assert.Equal(verifyEma.IsHot, ema.IsHot); + + // Verify it continues correctly + ema.Update(new TValue(DateTime.UtcNow, 60)); + verifyEma.Update(new TValue(DateTime.UtcNow, 60)); + Assert.Equal(verifyEma.Last.Value, ema.Last.Value, 1e-10); + } + + [Fact] + public void Prime_HandlesNaN_InHistory() + { + var ema = new Ema(5); + double[] history = [10, 20, double.NaN, 40, 50]; + + ema.Prime(history); + + var verifyEma = new Ema(5); + foreach (var val in history) verifyEma.Update(new TValue(DateTime.UtcNow, val)); + + Assert.Equal(verifyEma.Last.Value, ema.Last.Value, 1e-10); + } + + [Fact] + public void Prime_AllNaNs_ReturnsNaN() + { + var ema = new Ema(5); + double[] history = [double.NaN, double.NaN, double.NaN]; + + ema.Prime(history); + + Assert.True(double.IsNaN(ema.Last.Value)); + } + + [Fact] + public void Calculate_ReturnsCorrectResultsAndHotIndicator() + { + var series = new TSeries(); + for (int i = 1; i <= 20; i++) series.Add(DateTime.UtcNow, i * 10); + + // EMA(5) + var (results, indicator) = Ema.Calculate(series, 5); + + // Check results + Assert.Equal(20, results.Count); + + // Verify against standard calculation + var verifyEma = new Ema(5); + var verifyResults = verifyEma.Update(series); + + Assert.Equal(verifyResults.Last.Value, results.Last.Value, 1e-10); + Assert.Equal(verifyEma.Last.Value, indicator.Last.Value, 1e-10); + + // Check indicator state + Assert.True(indicator.IsHot); + + // Verify indicator continues correctly + indicator.Update(new TValue(DateTime.UtcNow, 210)); + verifyEma.Update(new TValue(DateTime.UtcNow, 210)); + Assert.Equal(verifyEma.Last.Value, indicator.Last.Value, 1e-10); + } + + [Fact] + public void Ema_Batch_AllNaNs_ReturnsNaN() + { + double[] source = [double.NaN, double.NaN, double.NaN]; + double[] output = new double[3]; + + Ema.Batch(source.AsSpan(), output.AsSpan(), 5); + + // Should be all NaNs, not 0s + foreach (var val in output) + { + Assert.True(double.IsNaN(val), $"Expected NaN but got {val}"); + } + } + + [Fact] + public void Ema_AllModes_ProduceSameResult() + { + // Arrange + int period = 10; + var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); + var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + var series = bars.Close; + + // 1. Batch Mode + var batchSeries = Ema.Batch(series, period); + double expected = batchSeries.Last.Value; + + // 2. Span Mode + var tValues = series.Values.ToArray(); // Need array for Span modification safety if any + var spanInput = new ReadOnlySpan(tValues); + var spanOutput = new double[tValues.Length]; + Ema.Batch(spanInput, spanOutput, period); + double spanResult = spanOutput[^1]; + + // 3. Streaming Mode + var streamingInd = new Ema(period); + for (int i = 0; i < series.Count; i++) + { + streamingInd.Update(series[i]); + } + double streamingResult = streamingInd.Last.Value; + + // 4. Eventing Mode + var pubSource = new TSeries(); + var eventingInd = new Ema(pubSource, period); + for (int i = 0; i < series.Count; i++) + { + pubSource.Add(series[i]); + } + double eventingResult = eventingInd.Last.Value; + + // Assert + // Precision 9 due to potential accumulation differences in loop vs batch optimizations + Assert.Equal(expected, spanResult, precision: 9); + Assert.Equal(expected, streamingResult, precision: 9); + Assert.Equal(expected, eventingResult, precision: 9); + } +} diff --git a/lib/trends/ema/Ema.Validation.Tests.cs b/lib/trends/ema/Ema.Validation.Tests.cs index bd654789..9763644c 100644 --- a/lib/trends/ema/Ema.Validation.Tests.cs +++ b/lib/trends/ema/Ema.Validation.Tests.cs @@ -1,320 +1,320 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using OoplesFinance.StockIndicators; -using OoplesFinance.StockIndicators.Models; -using Skender.Stock.Indicators; -using TALib; -using Tulip; -using Xunit.Abstractions; - -namespace QuanTAlib.Tests; - -public sealed class EmaValidationTests : IDisposable -{ - private readonly ValidationTestData _testData; - private readonly ITestOutputHelper _output; - private bool _disposed; - - public EmaValidationTests(ITestOutputHelper output) - { - _output = output; - _testData = new ValidationTestData(); - } - - public void Dispose() - { - Dispose(true); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _disposed = true; - - if (disposing) - { - _testData?.Dispose(); - } - } - - [Fact] - public void Validate_Skender_Batch() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (batch TSeries) - var ema = new global::QuanTAlib.Ema(period); - var qResult = ema.Update(_testData.Data); - - // Calculate Skender EMA - var sResult = _testData.SkenderQuotes.GetEma(period).ToList(); - - // Compare last 100 records - ValidationHelper.VerifyData(qResult, sResult, (s) => s.Ema); - } - _output.WriteLine("EMA Batch(TSeries) validated successfully against Skender"); - } - - [Fact] - public void Validate_Skender_Streaming() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (streaming) - var ema = new global::QuanTAlib.Ema(period); - var qResults = new List(); - foreach (var item in _testData.Data) - { - qResults.Add(ema.Update(item).Value); - } - - // Calculate Skender EMA - var sResult = _testData.SkenderQuotes.GetEma(period).ToList(); - - // Compare last 100 records - ValidationHelper.VerifyData(qResults, sResult, (s) => s.Ema); - } - _output.WriteLine("EMA Streaming validated successfully against Skender"); - } - - [Fact] - public void Validate_Skender_Span() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - // Prepare data for Span API - double[] sourceData = _testData.RawData.ToArray(); - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (Span API) - double[] qOutput = new double[sourceData.Length]; - global::QuanTAlib.Ema.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period); - - // Calculate Skender EMA - var sResult = _testData.SkenderQuotes.GetEma(period).ToList(); - - // Compare last 100 records - ValidationHelper.VerifyData(qOutput, sResult, (s) => s.Ema); - } - _output.WriteLine("EMA Span validated successfully against Skender"); - } - - [Fact] - public void Validate_Talib_Batch() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - // Prepare data for TA-Lib (double[]) - double[] tData = _testData.RawData.ToArray(); - double[] output = new double[tData.Length]; - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (batch TSeries) - var ema = new global::QuanTAlib.Ema(period); - var qResult = ema.Update(_testData.Data); - - // Calculate TA-Lib EMA - var retCode = TALib.Functions.Ema(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); - - int lookback = TALib.Functions.EmaLookback(period); - - // Compare last 100 records - ValidationHelper.VerifyData(qResult, output, outRange, lookback); - } - _output.WriteLine("EMA Batch(TSeries) validated successfully against TA-Lib"); - } - - [Fact] - public void Validate_Talib_Streaming() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - // Prepare data for TA-Lib (double[]) - double[] tData = _testData.RawData.ToArray(); - double[] output = new double[tData.Length]; - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (streaming) - var ema = new global::QuanTAlib.Ema(period); - var qResults = new List(); - foreach (var item in _testData.Data) - { - qResults.Add(ema.Update(item).Value); - } - - // Calculate TA-Lib EMA - var retCode = TALib.Functions.Ema(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); - - int lookback = TALib.Functions.EmaLookback(period); - - // Compare last 100 records - ValidationHelper.VerifyData(qResults, output, outRange, lookback); - } - _output.WriteLine("EMA Streaming validated successfully against TA-Lib"); - } - - [Fact] - public void Validate_Talib_Span() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - // Prepare data - double[] sourceData = _testData.RawData.ToArray(); - double[] talibOutput = new double[sourceData.Length]; - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (Span API) - double[] qOutput = new double[sourceData.Length]; - global::QuanTAlib.Ema.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period); - - // Calculate TA-Lib EMA - var retCode = TALib.Functions.Ema(sourceData, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); - - int lookback = TALib.Functions.EmaLookback(period); - - // Compare last 100 records - ValidationHelper.VerifyData(qOutput, talibOutput, outRange, lookback); - } - _output.WriteLine("EMA Span validated successfully against TA-Lib"); - } - - [Fact] - public void Validate_Tulip_Batch() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - // Prepare data for Tulip (double[]) - double[] tData = _testData.RawData.ToArray(); - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (batch TSeries) - var ema = new global::QuanTAlib.Ema(period); - var qResult = ema.Update(_testData.Data); - - // Calculate Tulip EMA - var emaIndicator = Tulip.Indicators.ema; - double[][] inputs = { tData }; - double[] options = { period }; - double[][] outputs = { new double[tData.Length] }; - - emaIndicator.Run(inputs, options, outputs); - var tResult = outputs[0]; - - // Compare last 100 records - ValidationHelper.VerifyData(qResult, tResult, 0); - } - _output.WriteLine("EMA Batch(TSeries) validated successfully against Tulip"); - } - - [Fact] - public void Validate_Tulip_Streaming() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - // Prepare data for Tulip (double[]) - double[] tData = _testData.RawData.ToArray(); - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (streaming) - var ema = new global::QuanTAlib.Ema(period); - var qResults = new List(); - foreach (var item in _testData.Data) - { - qResults.Add(ema.Update(item).Value); - } - - // Calculate Tulip EMA - var emaIndicator = Tulip.Indicators.ema; - double[][] inputs = { tData }; - double[] options = { period }; - double[][] outputs = { new double[tData.Length] }; - - emaIndicator.Run(inputs, options, outputs); - var tResult = outputs[0]; - - // Compare last 100 records - ValidationHelper.VerifyData(qResults, tResult, 0); - } - _output.WriteLine("EMA Streaming validated successfully against Tulip"); - } - - [Fact] - public void Validate_Tulip_Span() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - // Prepare data - double[] sourceData = _testData.RawData.ToArray(); - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA (Span API) - double[] qOutput = new double[sourceData.Length]; - global::QuanTAlib.Ema.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period); - - // Calculate Tulip EMA - var emaIndicator = Tulip.Indicators.ema; - double[][] inputs = { sourceData }; - double[] options = { period }; - double[][] outputs = { new double[sourceData.Length] }; - - emaIndicator.Run(inputs, options, outputs); - var tResult = outputs[0]; - - // Compare last 100 records - ValidationHelper.VerifyData(qOutput, tResult, 0); - } - _output.WriteLine("EMA Span validated successfully against Tulip"); - } - - [Fact] - public void Validate_Against_Ooples() - { - int[] periods = { 5, 10, 20, 50, 100 }; - - // Prepare data for Ooples (List) - var ooplesData = _testData.SkenderQuotes.Select(q => new TickerData - { - Date = q.Date, - Close = (double)q.Close, - High = (double)q.High, - Low = (double)q.Low, - Open = (double)q.Open, - Volume = (double)q.Volume - }).ToList(); - - foreach (var period in periods) - { - // Calculate QuanTAlib EMA - var ema = new global::QuanTAlib.Ema(period); - var qResult = ema.Update(_testData.Data); - - // Calculate Ooples EMA - var stockData = new StockData(ooplesData); - var oResult = stockData.CalculateExponentialMovingAverage(period); - var oValues = oResult.OutputValues.Values.First(); - - // Compare - ValidationHelper.VerifyData(qResult, oValues, (s) => s, tolerance: ValidationHelper.OoplesTolerance); - } - _output.WriteLine("EMA validated successfully against Ooples"); - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using OoplesFinance.StockIndicators; +using OoplesFinance.StockIndicators.Models; +using Skender.Stock.Indicators; +using TALib; +using Tulip; +using Xunit.Abstractions; + +namespace QuanTAlib.Tests; + +public sealed class EmaValidationTests : IDisposable +{ + private readonly ValidationTestData _testData; + private readonly ITestOutputHelper _output; + private bool _disposed; + + public EmaValidationTests(ITestOutputHelper output) + { + _output = output; + _testData = new ValidationTestData(); + } + + public void Dispose() + { + Dispose(true); + } + + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + + if (disposing) + { + _testData?.Dispose(); + } + } + + [Fact] + public void Validate_Skender_Batch() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (batch TSeries) + var ema = new global::QuanTAlib.Ema(period); + var qResult = ema.Update(_testData.Data); + + // Calculate Skender EMA + var sResult = _testData.SkenderQuotes.GetEma(period).ToList(); + + // Compare last 100 records + ValidationHelper.VerifyData(qResult, sResult, (s) => s.Ema); + } + _output.WriteLine("EMA Batch(TSeries) validated successfully against Skender"); + } + + [Fact] + public void Validate_Skender_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (streaming) + var ema = new global::QuanTAlib.Ema(period); + var qResults = new List(); + foreach (var item in _testData.Data) + { + qResults.Add(ema.Update(item).Value); + } + + // Calculate Skender EMA + var sResult = _testData.SkenderQuotes.GetEma(period).ToList(); + + // Compare last 100 records + ValidationHelper.VerifyData(qResults, sResult, (s) => s.Ema); + } + _output.WriteLine("EMA Streaming validated successfully against Skender"); + } + + [Fact] + public void Validate_Skender_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for Span API + double[] sourceData = _testData.RawData.ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Ema.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate Skender EMA + var sResult = _testData.SkenderQuotes.GetEma(period).ToList(); + + // Compare last 100 records + ValidationHelper.VerifyData(qOutput, sResult, (s) => s.Ema); + } + _output.WriteLine("EMA Span validated successfully against Skender"); + } + + [Fact] + public void Validate_Talib_Batch() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for TA-Lib (double[]) + double[] tData = _testData.RawData.ToArray(); + double[] output = new double[tData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (batch TSeries) + var ema = new global::QuanTAlib.Ema(period); + var qResult = ema.Update(_testData.Data); + + // Calculate TA-Lib EMA + var retCode = TALib.Functions.Ema(tData, 0..^0, output, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.EmaLookback(period); + + // Compare last 100 records + ValidationHelper.VerifyData(qResult, output, outRange, lookback); + } + _output.WriteLine("EMA Batch(TSeries) validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Talib_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for TA-Lib (double[]) + double[] tData = _testData.RawData.ToArray(); + double[] output = new double[tData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (streaming) + var ema = new global::QuanTAlib.Ema(period); + var qResults = new List(); + foreach (var item in _testData.Data) + { + qResults.Add(ema.Update(item).Value); + } + + // Calculate TA-Lib EMA + var retCode = TALib.Functions.Ema(tData, 0..^0, output, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.EmaLookback(period); + + // Compare last 100 records + ValidationHelper.VerifyData(qResults, output, outRange, lookback); + } + _output.WriteLine("EMA Streaming validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Talib_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data + double[] sourceData = _testData.RawData.ToArray(); + double[] talibOutput = new double[sourceData.Length]; + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Ema.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate TA-Lib EMA + var retCode = TALib.Functions.Ema(sourceData, 0..^0, talibOutput, out var outRange, period); + Assert.Equal(Core.RetCode.Success, retCode); + + int lookback = TALib.Functions.EmaLookback(period); + + // Compare last 100 records + ValidationHelper.VerifyData(qOutput, talibOutput, outRange, lookback); + } + _output.WriteLine("EMA Span validated successfully against TA-Lib"); + } + + [Fact] + public void Validate_Tulip_Batch() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for Tulip (double[]) + double[] tData = _testData.RawData.ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (batch TSeries) + var ema = new global::QuanTAlib.Ema(period); + var qResult = ema.Update(_testData.Data); + + // Calculate Tulip EMA + var emaIndicator = Tulip.Indicators.ema; + double[][] inputs = { tData }; + double[] options = { period }; + double[][] outputs = { new double[tData.Length] }; + + emaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; + + // Compare last 100 records + ValidationHelper.VerifyData(qResult, tResult, 0); + } + _output.WriteLine("EMA Batch(TSeries) validated successfully against Tulip"); + } + + [Fact] + public void Validate_Tulip_Streaming() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for Tulip (double[]) + double[] tData = _testData.RawData.ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (streaming) + var ema = new global::QuanTAlib.Ema(period); + var qResults = new List(); + foreach (var item in _testData.Data) + { + qResults.Add(ema.Update(item).Value); + } + + // Calculate Tulip EMA + var emaIndicator = Tulip.Indicators.ema; + double[][] inputs = { tData }; + double[] options = { period }; + double[][] outputs = { new double[tData.Length] }; + + emaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; + + // Compare last 100 records + ValidationHelper.VerifyData(qResults, tResult, 0); + } + _output.WriteLine("EMA Streaming validated successfully against Tulip"); + } + + [Fact] + public void Validate_Tulip_Span() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data + double[] sourceData = _testData.RawData.ToArray(); + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA (Span API) + double[] qOutput = new double[sourceData.Length]; + global::QuanTAlib.Ema.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period); + + // Calculate Tulip EMA + var emaIndicator = Tulip.Indicators.ema; + double[][] inputs = { sourceData }; + double[] options = { period }; + double[][] outputs = { new double[sourceData.Length] }; + + emaIndicator.Run(inputs, options, outputs); + var tResult = outputs[0]; + + // Compare last 100 records + ValidationHelper.VerifyData(qOutput, tResult, 0); + } + _output.WriteLine("EMA Span validated successfully against Tulip"); + } + + [Fact] + public void Validate_Against_Ooples() + { + int[] periods = { 5, 10, 20, 50, 100 }; + + // Prepare data for Ooples (List) + var ooplesData = _testData.SkenderQuotes.Select(q => new TickerData + { + Date = q.Date, + Close = (double)q.Close, + High = (double)q.High, + Low = (double)q.Low, + Open = (double)q.Open, + Volume = (double)q.Volume + }).ToList(); + + foreach (var period in periods) + { + // Calculate QuanTAlib EMA + var ema = new global::QuanTAlib.Ema(period); + var qResult = ema.Update(_testData.Data); + + // Calculate Ooples EMA + var stockData = new StockData(ooplesData); + var oResult = stockData.CalculateExponentialMovingAverage(period); + var oValues = oResult.OutputValues.Values.First(); + + // Compare + ValidationHelper.VerifyData(qResult, oValues, (s) => s, tolerance: ValidationHelper.OoplesTolerance); + } + _output.WriteLine("EMA validated successfully against Ooples"); + } +} diff --git a/lib/trends/ema/Ema.cs b/lib/trends/ema/Ema.cs index 7e5deb2a..b26ac39c 100644 --- a/lib/trends/ema/Ema.cs +++ b/lib/trends/ema/Ema.cs @@ -22,7 +22,7 @@ namespace QuanTAlib; /// No buffer required, only previous EMA value and compensator state. /// /// IsHot: -/// Becomes true when n = ln(0.05) / ln(1 - alpha) +/// Becomes true when n = ln(0.05) / ln(1 - alpha) /// [SkipLocalsInit] public sealed class Ema : AbstractBase @@ -303,7 +303,7 @@ public sealed class Ema : AbstractBase else val = lastValidValue; - + state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * val); state.E *= decay; diff --git a/lib/trends/ema/Ema.md b/lib/trends/ema/Ema.md index 38fd9972..c8ce761c 100644 --- a/lib/trends/ema/Ema.md +++ b/lib/trends/ema/Ema.md @@ -1,67 +1,67 @@ -# EMA: Exponential Moving Average - -> "The AK-47 of technical indicators. It's been around forever, everyone uses it, and it gets the job done. It's not fancy, but it works." - -EMA (Exponential Moving Average) is the standard by which all other averages are judged. Unlike the SMA, which treats data from 10 days ago with the same reverence as data from 10 seconds ago, the EMA understands that in markets, recency is relevance. It applies an exponentially decaying weight to older prices, reacting faster to new information. - -## Historical Context - -The EMA was brought to the financial world to solve the "drop-off effect" of the SMA (where an old price dropping out of the window causes the average to jump). By using a recursive formula, the EMA includes *all* past data in its calculation, with weights diminishing to infinity. It is the infinite impulse response (IIR) filter of the trading world. - -## Architecture & Physics - -The EMA is defined by its smoothing factor, $\alpha$. - -- **High $\alpha$**: Fast decay, responsive, noisy. -- **Low $\alpha$**: Slow decay, smooth, laggy. - -The QuanTAlib implementation includes a **Compensator** for the warmup phase. A standard EMA starts at 0 (or the first price) and takes time to converge. This early-stage bias is corrected mathematically so the EMA is accurate from the very first few bars, rather than waiting for $3 \times N$ bars to stabilize. - -## Mathematical Foundation - -The standard recursive formula: - -$$ \alpha = \frac{2}{N + 1} $$ - -$$ \text{EMA}_t = \alpha \cdot P_t + (1 - \alpha) \cdot \text{EMA}_{t-1} $$ - -### The Compensator (Warmup Correction) - -To handle the initialization bias (where $\text{EMA}_0$ is unknown), the sum of weights is tracked: - -$$ E_t = (1 - \alpha)^t $$ - -$$ \text{Corrected EMA}_t = \frac{\text{Uncorrected EMA}_t}{1 - E_t} $$ - -This ensures the EMA is statistically valid even during the warmup period. - -## Performance Profile - -This is as fast as it gets. - -| Metric | Score | Notes | -| :--- | :--- | :--- | -| **Throughput** | ★★★★★ | Single multiplication and addition. | -| **Allocations** | ★★★★★ | 0 bytes; hot path is allocation-free. | -| **Complexity** | ★★★★★ | O(1) recursive calculation. | -| **Precision** | ★★★★★ | `double` precision. | - -### Zero-Allocation Design - -EMA is implemented using a simple scalar state variable. The calculation is purely algebraic and requires no heap allocations during the `Update` cycle. - -## Validation - -Validated against TA-Lib, Skender, Tulip, and Ooples. - -| Library | Status | Notes | -| :--- | :--- | :--- | -| **TA-Lib** | ✅ | Matches `TA_EMA`. | -| **Skender** | ✅ | Matches `GetEma`. | -| **Tulip** | ✅ | Matches `ema`. | -| **Ooples** | ✅ | Matches `CalculateExponentialMovingAverage`. | - -### Common Pitfalls - -1. **The "First Value" Problem**: Most libraries seed the EMA with the first price or an SMA of the first N prices. In QuanTAlib, a mathematical compensator is used. Results during the first N bars are *more accurate* than TA-Lib, which might look like a discrepancy. It is not; the QuanTAlib implementation is correct and TA-Lib is approximating. -2. **Alpha vs. Period**: Remember that $N$ is just a proxy for $\alpha$. You can construct an EMA directly with an $\alpha$ (e.g., 0.1) if you prefer signal processing terminology over trader terminology. +# EMA: Exponential Moving Average + +> "The AK-47 of technical indicators. It's been around forever, everyone uses it, and it gets the job done. It's not fancy, but it works." + +EMA (Exponential Moving Average) is the standard by which all other averages are judged. Unlike the SMA, which treats data from 10 days ago with the same reverence as data from 10 seconds ago, the EMA understands that in markets, recency is relevance. It applies an exponentially decaying weight to older prices, reacting faster to new information. + +## Historical Context + +The EMA was brought to the financial world to solve the "drop-off effect" of the SMA (where an old price dropping out of the window causes the average to jump). By using a recursive formula, the EMA includes *all* past data in its calculation, with weights diminishing to infinity. It is the infinite impulse response (IIR) filter of the trading world. + +## Architecture & Physics + +The EMA is defined by its smoothing factor, $\alpha$. + +- **High $\alpha$**: Fast decay, responsive, noisy. +- **Low $\alpha$**: Slow decay, smooth, laggy. + +The QuanTAlib implementation includes a **Compensator** for the warmup phase. A standard EMA starts at 0 (or the first price) and takes time to converge. This early-stage bias is corrected mathematically so the EMA is accurate from the very first few bars, rather than waiting for $3 \times N$ bars to stabilize. + +## Mathematical Foundation + +The standard recursive formula: + +$$ \alpha = \frac{2}{N + 1} $$ + +$$ \text{EMA}_t = \alpha \cdot P_t + (1 - \alpha) \cdot \text{EMA}_{t-1} $$ + +### The Compensator (Warmup Correction) + +To handle the initialization bias (where $\text{EMA}_0$ is unknown), the sum of weights is tracked: + +$$ E_t = (1 - \alpha)^t $$ + +$$ \text{Corrected EMA}_t = \frac{\text{Uncorrected EMA}_t}{1 - E_t} $$ + +This ensures the EMA is statistically valid even during the warmup period. + +## Performance Profile + +This is as fast as it gets. + +| Metric | Score | Notes | +| :--- | :--- | :--- | +| **Throughput** | ★★★★★ | Single multiplication and addition. | +| **Allocations** | ★★★★★ | 0 bytes; hot path is allocation-free. | +| **Complexity** | ★★★★★ | O(1) recursive calculation. | +| **Precision** | ★★★★★ | `double` precision. | + +### Zero-Allocation Design + +EMA is implemented using a simple scalar state variable. The calculation is purely algebraic and requires no heap allocations during the `Update` cycle. + +## Validation + +Validated against TA-Lib, Skender, Tulip, and Ooples. + +| Library | Status | Notes | +| :--- | :--- | :--- | +| **TA-Lib** | ✅ | Matches `TA_EMA`. | +| **Skender** | ✅ | Matches `GetEma`. | +| **Tulip** | ✅ | Matches `ema`. | +| **Ooples** | ✅ | Matches `CalculateExponentialMovingAverage`. | + +### Common Pitfalls + +1. **The "First Value" Problem**: Most libraries seed the EMA with the first price or an SMA of the first N prices. In QuanTAlib, a mathematical compensator is used. Results during the first N bars are *more accurate* than TA-Lib, which might look like a discrepancy. It is not; the QuanTAlib implementation is correct and TA-Lib is approximating. +2. **Alpha vs. Period**: Remember that $N$ is just a proxy for $\alpha$. You can construct an EMA directly with an $\alpha$ (e.g., 0.1) if you prefer signal processing terminology over trader terminology. diff --git a/lib/trends/hma/Hma.Quantower.cs b/lib/trends/hma/Hma.Quantower.cs index 34b7852b..c72c6fc5 100644 --- a/lib/trends/hma/Hma.Quantower.cs +++ b/lib/trends/hma/Hma.Quantower.cs @@ -50,7 +50,7 @@ public class HmaIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); Series!.SetValue(result.Value, ma.IsHot, ShowColdValues); diff --git a/lib/trends/hma/Hma.Tests.cs b/lib/trends/hma/Hma.Tests.cs index 678ed288..2a43571e 100644 --- a/lib/trends/hma/Hma.Tests.cs +++ b/lib/trends/hma/Hma.Tests.cs @@ -27,7 +27,7 @@ public class HmaTests // Full WMA needs 9 // Half WMA needs 4 // Sqrt WMA needs 3 - // Pipeline: + // Pipeline: // 1. Full/Half produce valid values immediately (but with warmup ramp) // 2. Sqrt consumes them. // IsHot is defined as Full.IsHot && Sqrt.IsHot. @@ -160,9 +160,9 @@ public class HmaTests var hma = new Hma(10); hma.Update(new TValue(DateTime.UtcNow, 100)); hma.Update(new TValue(DateTime.UtcNow, 110)); - + hma.Reset(); - + Assert.Equal(0, hma.Last.Value); Assert.False(hma.IsHot); } @@ -245,7 +245,7 @@ public class HmaTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Hma.Batch(series, period); double expected = batchSeries.Last.Value; diff --git a/lib/trends/htit/Htit.Tests.cs b/lib/trends/htit/Htit.Tests.cs index c55f8f66..d4609c6e 100644 --- a/lib/trends/htit/Htit.Tests.cs +++ b/lib/trends/htit/Htit.Tests.cs @@ -35,7 +35,7 @@ public class HtitTests var series = data; var resultSeries = htit.Update(series); - + // Reset and calculate streaming htit.Reset(); var streamingResults = new List(); @@ -58,10 +58,10 @@ public class HtitTests var series = data; var resultSeries = htit.Update(series); - + var spanInput = data.Values.ToArray(); var spanOutput = new double[spanInput.Length]; - + Htit.Calculate(spanInput, spanOutput); for (int i = 0; i < resultSeries.Count; i++) @@ -76,7 +76,7 @@ public class HtitTests var htit = new Htit(); htit.Update(new TValue(DateTime.UtcNow.Ticks, 100.0)); htit.Update(new TValue(DateTime.UtcNow.Ticks, double.NaN)); - + Assert.Equal(100.0, htit.Last.Value); } @@ -94,9 +94,9 @@ public class HtitTests var htit = new Htit(); htit.Update(new TValue(DateTime.UtcNow, 100)); htit.Update(new TValue(DateTime.UtcNow, 110)); - + htit.Reset(); - + Assert.True(double.IsNaN(htit.Last.Value)); Assert.False(htit.IsHot); } @@ -163,7 +163,7 @@ public class HtitTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Htit.Batch(series); double expected = batchSeries.Last.Value; diff --git a/lib/trends/htit/Htit.Validation.Tests.cs b/lib/trends/htit/Htit.Validation.Tests.cs index eeb475c9..c9adbdca 100644 --- a/lib/trends/htit/Htit.Validation.Tests.cs +++ b/lib/trends/htit/Htit.Validation.Tests.cs @@ -106,7 +106,7 @@ public sealed class HtitValidationTests : IDisposable // Calculate QuanTAlib HTIT Streaming var htit = new Htit(); var streamingResults = new List(); - + foreach (var item in _data.Data) { streamingResults.Add(htit.Update(item).Value); diff --git a/lib/trends/htit/Htit.cs b/lib/trends/htit/Htit.cs index b381b715..9e8a2374 100644 --- a/lib/trends/htit/Htit.cs +++ b/lib/trends/htit/Htit.cs @@ -310,7 +310,7 @@ public sealed class Htit : AbstractBase for (int i = 0; i < source.Length; i++) { double price = source[i]; - + // Handle non-finite input: skip processing if no valid price seen yet if (!double.IsFinite(price)) { diff --git a/lib/trends/jma/Jma.Quantower.cs b/lib/trends/jma/Jma.Quantower.cs index c7dc0531..812b60fd 100644 --- a/lib/trends/jma/Jma.Quantower.cs +++ b/lib/trends/jma/Jma.Quantower.cs @@ -61,7 +61,7 @@ public class JmaIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { var item = HistoricalData[Count - 1, SeekOriginHistory.Begin]; - + TValue result = ma!.Update(new TValue(item.TimeLeft.Ticks, _priceSelector!(item)), isNew: args.IsNewBar()); Series!.SetValue(result.Value, ma.IsHot, ShowColdValues); diff --git a/lib/trends/jma/Jma.Tests.cs b/lib/trends/jma/Jma.Tests.cs index f9faae85..e6eb0e2e 100644 --- a/lib/trends/jma/Jma.Tests.cs +++ b/lib/trends/jma/Jma.Tests.cs @@ -183,7 +183,7 @@ public class JmaTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Jma.Batch(series, period); double expected = batchSeries.Last.Value; diff --git a/lib/trends/jma/Jma.Validation.Tests.cs b/lib/trends/jma/Jma.Validation.Tests.cs index 31264b39..4b33f795 100644 --- a/lib/trends/jma/Jma.Validation.Tests.cs +++ b/lib/trends/jma/Jma.Validation.Tests.cs @@ -10,10 +10,10 @@ public class JmaValidationTests { // JMA should generally follow the price. // If price goes up, JMA should eventually go up. - + var jma = new Jma(10); double previousJma = 0; - + // Uptrend for (int i = 0; i < 100; i++) { @@ -32,15 +32,15 @@ public class JmaValidationTests // JMA should stay within the range of recent prices (roughly) // It's a moving average, so it shouldn't overshoot wildly unless phase is negative and high volatility? // With default phase 0, it should be well behaved. - + var jma = new Jma(10); var gbm = new GBM(startPrice: 100, mu: 0, sigma: 0.5); - + for (int i = 0; i < 1000; i++) { var bar = gbm.Next(isNew: true); var result = jma.Update(new TValue(bar.Time, bar.Close)); - + if (i > 20) { // Update bounds of recent price history (simplified) diff --git a/lib/trends/jma/Jma.ZeroDiv.Tests.cs b/lib/trends/jma/Jma.ZeroDiv.Tests.cs index bbd9f7ba..af12f7a1 100644 --- a/lib/trends/jma/Jma.ZeroDiv.Tests.cs +++ b/lib/trends/jma/Jma.ZeroDiv.Tests.cs @@ -31,11 +31,11 @@ public class JmaZeroDivTests // Since we can't easily access private fields, we'll rely on the calculation logic check // If the fix is applied, we shouldn't see -Infinity in internal calculations if we could see them. // But we can check if the output is exactly the input, which implies adapt=0 (if logic holds). - + var jma = new Jma(period: 1); var result = jma.Update(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, result.Value); - + result = jma.Update(new TValue(DateTime.UtcNow, 200)); // If adapt is 0 (due to -Infinity log), bands snap to price. // If JMA(1) is identity, result should be 200. diff --git a/lib/trends/kama/Kama.Tests.cs b/lib/trends/kama/Kama.Tests.cs index b627d4ac..7deefc5d 100644 --- a/lib/trends/kama/Kama.Tests.cs +++ b/lib/trends/kama/Kama.Tests.cs @@ -157,7 +157,7 @@ public class KamaTests Assert.Equal(0, kama.Last.Value); Assert.False(kama.IsHot); } - + [Fact] public void Kama_FlatLine_ReturnsSameValue() { @@ -166,7 +166,7 @@ public class KamaTests { kama.Update(new TValue(DateTime.UtcNow, 100)); } - + Assert.Equal(100, kama.Last.Value); } @@ -243,7 +243,7 @@ public class KamaTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Kama.Batch(series, period); double expected = batchSeries.Last.Value; diff --git a/lib/trends/kama/Kama.Validation.Tests.cs b/lib/trends/kama/Kama.Validation.Tests.cs index 88a0bb79..f72c3043 100644 --- a/lib/trends/kama/Kama.Validation.Tests.cs +++ b/lib/trends/kama/Kama.Validation.Tests.cs @@ -191,7 +191,7 @@ public sealed class KamaValidationTests : IDisposable var kamaIndicator = Tulip.Indicators.kama; double[][] inputs = { cData }; double[] options = { period }; - + // Tulip KAMA lookback int lookback = kamaIndicator.Start(options); double[][] outputs = { new double[cData.Length - lookback] }; @@ -227,7 +227,7 @@ public sealed class KamaValidationTests : IDisposable var kamaIndicator = Tulip.Indicators.kama; double[][] inputs = { cData }; double[] options = { period }; - + // Tulip KAMA lookback int lookback = kamaIndicator.Start(options); double[][] outputs = { new double[cData.Length - lookback] }; diff --git a/lib/trends/lsma/.github/instructions/codacy.instructions.md b/lib/trends/lsma/.github/instructions/codacy.instructions.md index eefc8773..387bdacc 100644 --- a/lib/trends/lsma/.github/instructions/codacy.instructions.md +++ b/lib/trends/lsma/.github/instructions/codacy.instructions.md @@ -28,7 +28,7 @@ Configuration for AI behavior when interacting with Codacy's MCP Server - Wait for the user to respond before proceeding with any other actions ## After every response -- If you made any file edits in this conversation, verify you ran `codacy_cli_analyze` tool from Codacy's MCP Server +- If you made any file edits in this conversation, verify you ran `codacy_cli_analyze` tool from Codacy's MCP Server ## When there are no Codacy MCP Server tools available, or the MCP Server is not reachable - Suggest the user the following troubleshooting steps: diff --git a/lib/trends/lsma/Lsma.Tests.cs b/lib/trends/lsma/Lsma.Tests.cs index a8baf061..a358bb92 100644 --- a/lib/trends/lsma/Lsma.Tests.cs +++ b/lib/trends/lsma/Lsma.Tests.cs @@ -36,7 +36,7 @@ public class LsmaTests // For a perfect linear trend y = x, LSMA should return x int period = 10; var lsma = new Lsma(period); - + for (int i = 0; i < period * 2; i++) { var result = lsma.Update(new TValue(DateTime.UtcNow, i)); @@ -53,7 +53,7 @@ public class LsmaTests int period = 10; var lsma = new Lsma(period); double value = 123.45; - + for (int i = 0; i < period * 2; i++) { var result = lsma.Update(new TValue(DateTime.UtcNow, value)); @@ -67,16 +67,16 @@ public class LsmaTests // y = 2x + 1 // At x=10, y=21. Slope=2, Intercept=1 // LSMA(offset=1) should project to x=11 -> y=23 - + int period = 5; int offset = 1; var lsma = new Lsma(period, offset); - + for (int i = 0; i < 20; i++) { double y = 2 * i + 1; var result = lsma.Update(new TValue(DateTime.UtcNow, y)); - + if (i >= period) { double expected = 2 * (i + offset) + 1; @@ -89,21 +89,21 @@ public class LsmaTests public void Update_BarCorrection_UpdatesCorrectly() { var lsma = new Lsma(5); - + // Fill buffer for (int i = 0; i < 5; i++) { lsma.Update(new TValue(DateTime.UtcNow, i)); } - + // New bar var result1 = lsma.Update(new TValue(DateTime.UtcNow, 10)); - + // Update same bar with different value var result2 = lsma.Update(new TValue(DateTime.UtcNow, 20), isNew: false); - + Assert.NotEqual(result1.Value, result2.Value); - + // Verify internal state by adding next bar // If state was corrupted, this would fail var result3 = lsma.Update(new TValue(DateTime.UtcNow, 30)); @@ -114,11 +114,11 @@ public class LsmaTests public void Update_NaN_HandlesGracefully() { var lsma = new Lsma(5); - + lsma.Update(new TValue(DateTime.UtcNow, 1)); lsma.Update(new TValue(DateTime.UtcNow, 2)); var result = lsma.Update(new TValue(DateTime.UtcNow, double.NaN)); - + // Input sequence becomes: 1, 2, 2 (NaN replaced by last valid 2) // Regression on (2,1), (1,2), (0,2) // Result should be 2.166666667 @@ -132,17 +132,17 @@ public class LsmaTests int count = 100; var source = new TSeries(); var gbm = new GBM(startPrice: 100, seed: 42); - + for (int i = 0; i < count; i++) { var bar = gbm.Next(); source.Add(bar.C); } - + var lsma = new Lsma(period); var series1 = lsma.Update(source); var series2 = Lsma.Batch(source, period); - + Assert.Equal(series1.Count, series2.Count); for (int i = 0; i < count; i++) { @@ -158,15 +158,15 @@ public class LsmaTests var values = new double[count]; var output = new double[count]; var gbm = new GBM(startPrice: 100, seed: 42); - + for (int i = 0; i < count; i++) { var bar = gbm.Next(); values[i] = bar.Close; } - + Lsma.Calculate(values, output, period); - + var lsma = new Lsma(period); for (int i = 0; i < count; i++) { @@ -183,14 +183,14 @@ public class LsmaTests { lsma.Update(new TValue(DateTime.UtcNow, i)); } - + Assert.True(lsma.IsHot); - + lsma.Reset(); - + Assert.False(lsma.IsHot); Assert.Equal(0, lsma.Last.Value); - + // Should behave like new instance var result = lsma.Update(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, result.Value); @@ -201,13 +201,13 @@ public class LsmaTests { int period = 5; var lsma = new Lsma(period); - + for (int i = 0; i < period; i++) { Assert.False(lsma.IsHot); lsma.Update(new TValue(DateTime.UtcNow, i)); } - + Assert.True(lsma.IsHot); } @@ -216,7 +216,7 @@ public class LsmaTests { var source = new TSeries(); var lsma = new Lsma(source, 10); - + source.Add(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, lsma.Last.Value); } @@ -226,14 +226,14 @@ public class LsmaTests { var source = new TSeries(); var lsma = new Lsma(source, 5); - + // Verify subscription works source.Add(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, lsma.Last.Value); - + // Dispose and verify unsubscription lsma.Dispose(); - + // Add more data - lsma should NOT update source.Add(new TValue(DateTime.UtcNow, 200)); Assert.Equal(100, lsma.Last.Value); // Should remain at previous value @@ -244,9 +244,9 @@ public class LsmaTests { var source = new TSeries(); var lsma = new Lsma(source, 5); - + source.Add(new TValue(DateTime.UtcNow, 100)); - + // Multiple Dispose calls should not throw // Suppressing S3966: Multiple Dispose calls are intentional to test idempotency #pragma warning disable S3966 @@ -254,7 +254,7 @@ public class LsmaTests lsma.Dispose(); lsma.Dispose(); #pragma warning restore S3966 - + // Verify still unsubscribed source.Add(new TValue(DateTime.UtcNow, 200)); Assert.Equal(100, lsma.Last.Value); @@ -265,18 +265,18 @@ public class LsmaTests { var source = new TSeries(); var lsma = new Lsma(source, 5); - + source.Add(new TValue(DateTime.UtcNow, 100)); - + // Dispose from multiple threads simultaneously var tasks = new System.Threading.Tasks.Task[10]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = System.Threading.Tasks.Task.Run(() => lsma.Dispose()); } - + await System.Threading.Tasks.Task.WhenAll(tasks); - + // Verify unsubscribed source.Add(new TValue(DateTime.UtcNow, 200)); Assert.Equal(100, lsma.Last.Value); @@ -287,14 +287,14 @@ public class LsmaTests { // Lsma created without source parameter var lsma = new Lsma(5); - + // Should not throw even though there's no source to unsubscribe from // Suppressing S3966: Multiple Dispose calls are intentional to test idempotency #pragma warning disable S3966 lsma.Dispose(); lsma.Dispose(); // Idempotent #pragma warning restore S3966 - + // Verify state remains valid Assert.False(lsma.IsHot); } diff --git a/lib/trends/mama/Mama.Tests.cs b/lib/trends/mama/Mama.Tests.cs index 252eb0b5..4fea12a8 100644 --- a/lib/trends/mama/Mama.Tests.cs +++ b/lib/trends/mama/Mama.Tests.cs @@ -41,21 +41,21 @@ public class MamaTests public void Update_InfinityInputs_DoesNotHang() { var mama = new Mama(); - + // Warmup with valid data to get past initialization phase for (int i = 0; i < 60; i++) { mama.Update(new TValue(DateTime.UtcNow, 100.0 + i)); } - + // Test positive infinity - should not hang var result1 = mama.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity)); Assert.True(double.IsFinite(result1.Value), "Positive infinity should produce finite result"); - + // Test negative infinity - should not hang var result2 = mama.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity)); Assert.True(double.IsFinite(result2.Value), "Negative infinity should produce finite result"); - + // Test NaN - should not hang var result3 = mama.Update(new TValue(DateTime.UtcNow, double.NaN)); Assert.True(double.IsFinite(result3.Value), "NaN should produce finite result"); @@ -66,25 +66,25 @@ public class MamaTests { var data = new double[100]; var gbm = new GBM(startPrice: 100, seed: 42); - + // Fill with mostly valid data for (int i = 0; i < 100; i++) { data[i] = gbm.Next().Close; } - + // Insert non-finite values at various points data[20] = double.NaN; data[40] = double.PositiveInfinity; data[60] = double.NegativeInfinity; data[80] = double.NaN; - + var output = new double[100]; var famaOutput = new double[100]; - + // This should complete without hanging Mama.Calculate(data, output, famaOutput: famaOutput); - + // Verify all outputs are finite (no NaN or Infinity propagation) for (int i = 0; i < 100; i++) { @@ -113,7 +113,7 @@ public class MamaTests // Manually chain for test bool eventFired = false; - mama.Pub += (object? sender, TValueEventArgs args) => eventFired = true; + mama.Pub += (object? sender, in TValueEventArgs args) => eventFired = true; mama.Update(new TValue(DateTime.UtcNow, 100.0)); @@ -160,14 +160,14 @@ public class MamaTests public void IsHot_BecomesTrueAfterWarmup() { var mama = new Mama(); - + // MAMA needs 50 bars to warmup (Index > 50) for (int i = 0; i < 50; i++) { mama.Update(new TValue(DateTime.UtcNow, 100)); Assert.False(mama.IsHot); } - + mama.Update(new TValue(DateTime.UtcNow, 100)); Assert.True(mama.IsHot); } @@ -181,9 +181,9 @@ public class MamaTests mama.Update(new TValue(DateTime.UtcNow, 100)); } Assert.True(mama.IsHot); - + mama.Reset(); - + Assert.False(mama.IsHot); Assert.True(double.IsNaN(mama.Last.Value)); } @@ -192,21 +192,21 @@ public class MamaTests public void Update_BarCorrection_UpdatesCorrectly() { var mama = new Mama(); - + // Warmup for (int i = 0; i < 10; i++) { mama.Update(new TValue(DateTime.UtcNow, 100)); } - + // New bar var result1 = mama.Update(new TValue(DateTime.UtcNow, 110)); - + // Update same bar with different value var result2 = mama.Update(new TValue(DateTime.UtcNow, 120), isNew: false); - + Assert.NotEqual(result1.Value, result2.Value); - + // Verify internal state by adding next bar var result3 = mama.Update(new TValue(DateTime.UtcNow, 130)); Assert.True(double.IsFinite(result3.Value)); @@ -217,17 +217,17 @@ public class MamaTests { var source = new TSeries(); var gbm = new GBM(startPrice: 100, seed: 42); - + for (int i = 0; i < 50; i++) { var bar = gbm.Next(); source.Add(bar.C); } - + var mama = new Mama(); var series1 = mama.Update(source); var series2 = Mama.Batch(source); - + Assert.Equal(series1.Count, series2.Count); for (int i = 0; i < source.Count; i++) { @@ -319,13 +319,13 @@ public class MamaTests var output1 = new double[count]; var output2 = new double[count]; - + // Call without famaOutput parameter (backwards compatibility) Mama.Calculate(data, output1); - + // Call with empty famaOutput span Mama.Calculate(data, output2, famaOutput: Span.Empty); - + // Both should produce identical MAMA results for (int i = 0; i < count; i++) { @@ -339,8 +339,8 @@ public class MamaTests var data = new double[10]; var mamaOutput = new double[10]; var famaOutput = new double[5]; - - var ex = Assert.Throws(() => + + var ex = Assert.Throws(() => Mama.Calculate(data, mamaOutput, famaOutput: famaOutput)); Assert.Equal("famaOutput", ex.ParamName); } diff --git a/lib/trends/mama/Mama.Validation.Tests.cs b/lib/trends/mama/Mama.Validation.Tests.cs index ee744c2a..e8bca453 100644 --- a/lib/trends/mama/Mama.Validation.Tests.cs +++ b/lib/trends/mama/Mama.Validation.Tests.cs @@ -49,7 +49,7 @@ public class MamaValidationTests // The optimized version handles quadrants correctly (-pi to pi) and wraps phase differences (-pi to pi), // while original (and Skender) uses Atan (-pi/2 to pi/2) and ignores phase wrapping, causing divergence. ValidationHelper.VerifyData(qResult, sResult, x => x.Mama, skip: 100, tolerance: 40.0); - + _output.WriteLine("MAMA Batch validated successfully against Skender"); } @@ -78,7 +78,7 @@ public class MamaValidationTests // 3. Verify MAMA // Tolerance increased to 40.0 due to optimized Phase calculation and Phase Wrapping correction. ValidationHelper.VerifyData(qMamaResults, sResult, x => x.Mama, skip: 100, tolerance: 40.0); - + // 4. Verify FAMA ValidationHelper.VerifyData(qFamaResults, sResult, x => x.Fama, skip: 100, tolerance: 40.0); @@ -117,14 +117,14 @@ public class MamaValidationTests // 2. Precision: Ooples uses 4-decimal constants, QuanTAlib uses exact fractions. // 3. Phase Wrapping: QuanTAlib correctly handles phase wrapping, Ooples does not. ValidationHelper.VerifyData(qResult, oMama, x => x, skip: 100, tolerance: 40.0); - + // 4. Verify FAMA // QuanTAlib stores Fama in a separate property, not in the main TSeries result // We need to extract Fama from the indicator instance or capture it during streaming // But Update(TSeries) returns only the main series (Mama). // To verify Fama batch, we might need to iterate or expose it. // For now, let's verify Mama. - + _output.WriteLine("MAMA Batch validated successfully against Ooples"); } } diff --git a/lib/trends/mama/Mama.md b/lib/trends/mama/Mama.md index c832ea82..7bd88c75 100644 --- a/lib/trends/mama/Mama.md +++ b/lib/trends/mama/Mama.md @@ -158,7 +158,7 @@ Ehlers used `atan(Q/I)` with manual zero-checks because TradeStation's `atan2` d ```csharp // Period calculation: atan2 handles all quadrants correctly double angle = Math.Atan2(_state.Im, _state.Re); -double period = Math.Abs(angle) > MinDeltaRadians +double period = Math.Abs(angle) > MinDeltaRadians ? TwoPi / Math.Abs(angle) : _p_state.Period; diff --git a/lib/trends/mgdi/Mgdi.Tests.cs b/lib/trends/mgdi/Mgdi.Tests.cs index f502d439..282734bb 100644 --- a/lib/trends/mgdi/Mgdi.Tests.cs +++ b/lib/trends/mgdi/Mgdi.Tests.cs @@ -10,10 +10,10 @@ public class MgdiTests public void NaN_FirstValue_DoesNotInitializeToZero() { var mgdi = new Mgdi(14, 0.6); - + // First value is NaN var result = mgdi.Update(new TValue(DateTime.UtcNow, double.NaN)); - + // Should be NaN, not 0.0 Assert.True(double.IsNaN(result.Value), $"Expected NaN but got {result.Value}"); } @@ -22,15 +22,15 @@ public class MgdiTests public void NaN_Sequence_InitializesOnFirstValid() { var mgdi = new Mgdi(14, 0.6); - + // Sequence of NaNs mgdi.Update(new TValue(DateTime.UtcNow, double.NaN)); mgdi.Update(new TValue(DateTime.UtcNow, double.NaN)); - + // First valid value double firstValid = 100.0; var result = mgdi.Update(new TValue(DateTime.UtcNow, firstValid)); - + Assert.Equal(firstValid, result.Value); } @@ -40,7 +40,7 @@ public class MgdiTests var mgdi = new Mgdi(14, 0.6); mgdi.Update(new TValue(DateTime.UtcNow, 100.0)); var result = mgdi.Update(new TValue(DateTime.UtcNow, 101.0)); - + Assert.True(result.Value > 100.0); Assert.True(result.Value < 101.0); } @@ -50,7 +50,7 @@ public class MgdiTests { var source = new double[10]; var output = new double[10]; - + Assert.Throws(() => Mgdi.Calculate(source, output, 14, double.NaN)); Assert.Throws(() => Mgdi.Calculate(source, output, 14, double.PositiveInfinity)); Assert.Throws(() => Mgdi.Calculate(source, output, 14, double.NegativeInfinity)); diff --git a/lib/trends/mgdi/Mgdi.Validation.Tests.cs b/lib/trends/mgdi/Mgdi.Validation.Tests.cs index 349324ec..9c2273f3 100644 --- a/lib/trends/mgdi/Mgdi.Validation.Tests.cs +++ b/lib/trends/mgdi/Mgdi.Validation.Tests.cs @@ -58,7 +58,7 @@ public sealed class MgdiValidationTests : IDisposable // Calculate QuanTAlib MGDI Streaming var mgdi = new Mgdi(14, 0.6); var streamingResults = new List(); - + foreach (var item in _data.Data) { streamingResults.Add(mgdi.Update(item).Value); diff --git a/lib/trends/pwma/Pwma.Tests.cs b/lib/trends/pwma/Pwma.Tests.cs index cdab964d..fa194460 100644 --- a/lib/trends/pwma/Pwma.Tests.cs +++ b/lib/trends/pwma/Pwma.Tests.cs @@ -361,7 +361,7 @@ public class PwmaTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Pwma.Batch(series, period); double expected = batchSeries.Last.Value; diff --git a/lib/trends/rma/Rma.Tests.cs b/lib/trends/rma/Rma.Tests.cs index a56a0de7..084472f3 100644 --- a/lib/trends/rma/Rma.Tests.cs +++ b/lib/trends/rma/Rma.Tests.cs @@ -208,7 +208,7 @@ public class RmaTests { var source = new TSeries(); var rma = new Rma(source, 10); - + source.Add(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, rma.Last.Value, 1e-9); } diff --git a/lib/trends/rma/Rma.Validation.Tests.cs b/lib/trends/rma/Rma.Validation.Tests.cs index 9711e771..797729db 100644 --- a/lib/trends/rma/Rma.Validation.Tests.cs +++ b/lib/trends/rma/Rma.Validation.Tests.cs @@ -43,7 +43,7 @@ public sealed class RmaValidationTests : IDisposable { // Arrange int period = 14; - + // QuanTAlib RMA var rma = new Rma(period); var quantalibResults = new TSeries(); @@ -94,7 +94,7 @@ public sealed class RmaValidationTests : IDisposable // Skip warmup period for comparison int skip = period * 30; int itemsToVerify = _testData.Data.Count - skip; - + ValidationHelper.VerifyData(qResult, oValues, (s) => s, skip: itemsToVerify, tolerance: ValidationHelper.OoplesTolerance); } } diff --git a/lib/trends/sma/Sma.Tests.cs b/lib/trends/sma/Sma.Tests.cs index fcabcae9..cea6c42d 100644 --- a/lib/trends/sma/Sma.Tests.cs +++ b/lib/trends/sma/Sma.Tests.cs @@ -472,7 +472,7 @@ public class SmaTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Sma.Batch(series, period); double expected = batchSeries.Last.Value; @@ -512,7 +512,7 @@ public class SmaTests { var source = new TSeries(); var sma = new Sma(source, 10); - + source.Add(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, sma.Last.Value); } @@ -529,12 +529,12 @@ public class SmaTests { var sma = new Sma(5); double[] history = [10, 20, 30, 40, 50]; // SMA(5) = 30 - + sma.Prime(history); Assert.True(sma.IsHot); Assert.Equal(30.0, sma.Last.Value, 1e-10); - + // Verify it continues correctly sma.Update(new TValue(DateTime.UtcNow, 60)); // 20,30,40,50,60 -> 40 Assert.Equal(40.0, sma.Last.Value, 1e-10); @@ -544,8 +544,8 @@ public class SmaTests public void Prime_WithInsufficientHistory_IsNotHot() { var sma = new Sma(10); - double[] history = [10, 20, 30, 40, 50]; - + double[] history = [10, 20, 30, 40, 50]; + sma.Prime(history); Assert.False(sma.IsHot); @@ -556,14 +556,14 @@ public class SmaTests public void Prime_HandlesNaN_InHistory() { var sma = new Sma(3); - double[] history = [10, 20, double.NaN, 40]; + double[] history = [10, 20, double.NaN, 40]; // 10 // 10, 20 // 10, 20, 20 (NaN replaced by 20) -> Avg(10,20,20) = 16.666... // 20, 20, 40 -> Avg(20,20,40) = 26.666... - + sma.Prime(history); - + Assert.True(sma.IsHot); Assert.Equal(80.0 / 3.0, sma.Last.Value, 1e-9); } @@ -574,7 +574,7 @@ public class SmaTests var series = new TSeries(); for (int i = 1; i <= 10; i++) series.Add(DateTime.UtcNow, i * 10); // 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 - + // SMA(5) var (results, indicator) = Sma.Calculate(series, 5); @@ -589,7 +589,7 @@ public class SmaTests Assert.Equal(5, indicator.WarmupPeriod); // Verify indicator continues correctly - indicator.Update(new TValue(DateTime.UtcNow, 110)); + indicator.Update(new TValue(DateTime.UtcNow, 110)); // Window was [60, 70, 80, 90, 100] -> Avg 80 // New Window [70, 80, 90, 100, 110] -> Avg 90 Assert.Equal(90.0, indicator.Last.Value); diff --git a/lib/trends/sma/Sma.Tolerance.Tests.cs b/lib/trends/sma/Sma.Tolerance.Tests.cs index ce978790..991c8a19 100644 --- a/lib/trends/sma/Sma.Tolerance.Tests.cs +++ b/lib/trends/sma/Sma.Tolerance.Tests.cs @@ -29,7 +29,7 @@ public sealed class SmaToleranceTests : IDisposable var sResult = _testData.SkenderQuotes.GetSma(period).ToList(); ValidationHelper.VerifyData(qResult, sResult, (s) => s.Sma); - + // Add explicit assertion to satisfy SonarQube Assert.True(qResult.Count > 0); } diff --git a/lib/trends/sma/Sma.ZeroDiv.Tests.cs b/lib/trends/sma/Sma.ZeroDiv.Tests.cs index 8c1e67be..b38080e5 100644 --- a/lib/trends/sma/Sma.ZeroDiv.Tests.cs +++ b/lib/trends/sma/Sma.ZeroDiv.Tests.cs @@ -10,13 +10,13 @@ public class SmaZeroDivTests public void Sma_Update_WithIsNewFalse_OnEmptyBuffer_DoesNotThrow() { var sma = new Sma(10); - + // Buffer is empty initially. // Calling Update with isNew=false should not cause division by zero. // It should return NaN or 0 or Last, but definitely not throw or return Infinity. - + var result = sma.Update(new TValue(DateTime.UtcNow, 100), isNew: false); - + // Since buffer count is 0, we expect NaN based on our fix. Assert.True(double.IsNaN(result.Value), $"Expected NaN but got {result.Value}"); } @@ -27,10 +27,10 @@ public class SmaZeroDivTests var sma = new Sma(10); sma.Update(new TValue(DateTime.UtcNow, 100)); sma.Reset(); - + // Buffer is empty after Reset. var result = sma.Update(new TValue(DateTime.UtcNow, 200), isNew: false); - + Assert.True(double.IsNaN(result.Value), $"Expected NaN but got {result.Value}"); } } diff --git a/lib/trends/sma/Sma.cs b/lib/trends/sma/Sma.cs index b4d1e934..f50c8eef 100644 --- a/lib/trends/sma/Sma.cs +++ b/lib/trends/sma/Sma.cs @@ -189,7 +189,7 @@ public sealed class Sma : AbstractBase { // Capture previous state BEFORE any mutation _p_state = _state; - + double val = GetValidValue(input.Value); UpdateState(val); _state.LastInput = val; @@ -199,11 +199,11 @@ public sealed class Sma : AbstractBase { // Restore scalar state to pre-mutation values _state = _p_state; - + double val = GetValidValue(input.Value); // Update sum: remove the value that was added during isNew=true, add the new correction value _state.Sum = _state.Sum - _currentBarValue + val; - + // Update the buffer's newest value and sync its internal sum with our state sum _buffer.UpdateNewest(val); _state.Sum = _buffer.RecalculateSum(); // Ensure sums stay in sync diff --git a/lib/trends/ssf/Ssf.Tests.cs b/lib/trends/ssf/Ssf.Tests.cs index 7fe40073..3c246935 100644 --- a/lib/trends/ssf/Ssf.Tests.cs +++ b/lib/trends/ssf/Ssf.Tests.cs @@ -249,7 +249,7 @@ public class SsfTests var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var bars = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // 1. Batch Mode var batchSeries = Ssf.Calculate(series, period).Results; double expected = batchSeries.Last.Value; diff --git a/lib/trends/ssf/Ssf.cs b/lib/trends/ssf/Ssf.cs index a2ca0661..7931c878 100644 --- a/lib/trends/ssf/Ssf.cs +++ b/lib/trends/ssf/Ssf.cs @@ -178,11 +178,11 @@ public sealed class Ssf : AbstractBase double ssf = (_state.Count < 4) ? val : (_c1 * (val + _state.PrevInput) * 0.5) + (_c2 * _state.Ssf1) + (_c3 * _state.Ssf2); - + _state.Ssf2 = _state.Ssf1; _state.Ssf1 = ssf; _state.PrevInput = val; - + if (isNew) _state.Count++; if (!_state.IsHot && _state.Count >= WarmupPeriod) _state.IsHot = true; @@ -216,7 +216,7 @@ public sealed class Ssf : AbstractBase sourceTimes.CopyTo(tSpan); _p_state = _state; - + Last = new TValue(tSpan[len - 1], vSpan[len - 1]); return new TSeries(t, v); @@ -239,12 +239,12 @@ public sealed class Ssf : AbstractBase state.Ssf1 = state.LastValidValue; state.Ssf2 = state.LastValidValue; state.PrevInput = state.LastValidValue; - output[i] = state.LastValidValue; + output[i] = state.LastValidValue; state.Count = 1; i++; break; } - output[i] = double.NaN; + output[i] = double.NaN; } // Handle all-NaN case: if no finite value was found, set remaining outputs to NaN and return diff --git a/lib/trends/super/Super.Quantower.cs b/lib/trends/super/Super.Quantower.cs index 82b4a837..94ececf8 100644 --- a/lib/trends/super/Super.Quantower.cs +++ b/lib/trends/super/Super.Quantower.cs @@ -54,7 +54,7 @@ public sealed class SuperIndicator : Indicator, IWatchlistIndicator bool isNew = args.IsNewBar(); var bar = this.GetInputBar(args); double value = _super!.Update(bar, isNew).Value; - + _series!.SetValue(value, _super.IsHot, ShowColdValues); _upperBand!.SetValue(_super.UpperBand.Value, _super.IsHot, ShowColdValues); _lowerBand!.SetValue(_super.LowerBand.Value, _super.IsHot, ShowColdValues); diff --git a/lib/trends/super/Super.Tests.cs b/lib/trends/super/Super.Tests.cs index c11cb70c..96ff12f1 100644 --- a/lib/trends/super/Super.Tests.cs +++ b/lib/trends/super/Super.Tests.cs @@ -70,13 +70,13 @@ public class SuperTests super.Reset(); Assert.Equal(0, super.Last.Value); Assert.False(super.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { super.Update(bars[i]); } - + Assert.True(double.IsFinite(super.Last.Value)); } @@ -110,14 +110,14 @@ public class SuperTests } } } - + [Fact] public void Warmup_Handling() { var super = new Super(10, 3.0); var gbm = new GBM(); var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + // First 10 bars should be NaN for (int i = 0; i < 10; i++) { @@ -125,7 +125,7 @@ public class SuperTests Assert.True(double.IsNaN(result.Value), $"Bar {i} should be NaN"); Assert.False(super.IsHot); } - + // 11th bar (index 10) should be valid var result11 = super.Update(bars[10]); Assert.True(double.IsFinite(result11.Value), "Bar 10 should be finite"); @@ -146,16 +146,16 @@ public class SuperTests { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + var super = new Super(10, 3.0); var streamingResults = new List(); for (int i = 0; i < bars.Count; i++) { streamingResults.Add(super.Update(bars[i]).Value); } - + var staticResults = Super.Batch(bars, 10, 3.0); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < staticResults.Count; i++) { @@ -176,12 +176,12 @@ public class SuperTests var super = new Super(10, 3.0); var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + // Test TBarSeries chain var result = super.Update(bars); Assert.NotNull(result); Assert.IsType(result); - + // Test TBar chain (returns TValue) var result2 = super.Update(bars[0]); Assert.IsType(result2); diff --git a/lib/trends/t3/T3.Tests.cs b/lib/trends/t3/T3.Tests.cs index ab67ee7b..03b9a912 100644 --- a/lib/trends/t3/T3.Tests.cs +++ b/lib/trends/t3/T3.Tests.cs @@ -66,13 +66,13 @@ public class T3Tests t3.Reset(); Assert.Equal(0, t3.Last.Value); Assert.False(t3.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { t3.Update(new TValue(bars[i].Time, bars[i].Close)); } - + Assert.True(double.IsFinite(t3.Last.Value)); } @@ -99,23 +99,23 @@ public class T3Tests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void BatchCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var t3 = new T3(5, 0.7); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(t3.Update(series[i]).Value); } - + var batchResults = T3.Batch(series, 5, 0.7); - + Assert.Equal(streamingResults.Count, batchResults.Count); for (int i = 0; i < batchResults.Count; i++) { @@ -129,17 +129,17 @@ public class T3Tests var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var t3 = new T3(5, 0.7); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(t3.Update(series[i]).Value); } - + var spanResults = new double[series.Count]; T3.Batch(series.Values, spanResults, 5, 0.7); - + for (int i = 0; i < spanResults.Length; i++) { Assert.Equal(streamingResults[i], spanResults[i], 1e-9); @@ -153,12 +153,12 @@ public class T3Tests var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // Test TSeries chain var result = t3.Update(series); Assert.NotNull(result); Assert.IsType(result); - + // Test TValue chain var result2 = t3.Update(series[0]); Assert.IsType(result2); @@ -255,13 +255,13 @@ public class T3Tests { var input = new double[10]; var output = new double[10]; - + var ex1 = Assert.Throws(() => T3.Batch(input, output, 5, 0.0)); Assert.Equal("vfactor", ex1.ParamName); - + var ex2 = Assert.Throws(() => T3.Batch(input, output, 5, -0.5)); Assert.Equal("vfactor", ex2.ParamName); - + var ex3 = Assert.Throws(() => T3.Batch(input, output, 5, 1.5)); Assert.Equal("vfactor", ex3.ParamName); } @@ -277,7 +277,7 @@ public class T3Tests { var source = new TestPublisher(); _ = new T3(source, 5); - + Assert.Equal(1, source.SubscriberCount); } @@ -286,11 +286,11 @@ public class T3Tests { var source = new TestPublisher(); var t3 = new T3(source, 5); - + Assert.Equal(1, source.SubscriberCount); - + t3.Dispose(); - + Assert.Equal(0, source.SubscriberCount); } @@ -299,12 +299,12 @@ public class T3Tests { var source = new TestPublisher(); var t3 = new T3(source, 5); - + t3.Dispose(); #pragma warning disable S3966 // Objects should not be disposed more than once t3.Dispose(); #pragma warning restore S3966 // Objects should not be disposed more than once - + Assert.Equal(0, source.SubscriberCount); } @@ -312,7 +312,7 @@ public class T3Tests public void Dispose_DoesNothing_WhenNoSource() { var t3 = new T3(5); - + var exception = Record.Exception(() => t3.Dispose()); Assert.Null(exception); } diff --git a/lib/trends/t3/T3.cs b/lib/trends/t3/T3.cs index 91c27650..e8b114e6 100644 --- a/lib/trends/t3/T3.cs +++ b/lib/trends/t3/T3.cs @@ -8,19 +8,19 @@ namespace QuanTAlib; /// T3: Tillson T3 Moving Average /// /// -/// T3 works by running price data through a series of six EMAs, then combining the outputs +/// T3 works by running price data through a series of six EMAs, then combining the outputs /// of these EMAs using carefully calculated weights. -/// +/// /// Formula: /// T3 = c1*e6 + c2*e5 + c3*e4 + c4*e3 -/// +/// /// Where: /// e1..e6 are cascaded EMAs /// c1 = -v^3 /// c2 = 3(v^2 + v^3) /// c3 = -3(2v^2 + v + v^3) /// c4 = 1 + 3v + 3v^2 + v^3 -/// +/// /// v is volume factor (default 0.7) /// alpha = 2 / (period + 1) /// @@ -154,7 +154,7 @@ public sealed class T3 : AbstractBase, IDisposable // Since Compute returns the result but also updates state, we can't easily get the last result without re-running or storing it. // However, Prime is usually followed by Update or we just need the state ready. // If we want Last to be correct, we should probably store the last result. - // But AbstractBase.Prime doesn't strictly require Last to be set to the very last value of source, + // But AbstractBase.Prime doesn't strictly require Last to be set to the very last value of source, // though it's good practice. // Let's re-run the last value computation to set Last correctly. if (len > 0) diff --git a/lib/trends/tema/Tema.Quantower.Tests.cs b/lib/trends/tema/Tema.Quantower.Tests.cs index 4544746c..c543d03b 100644 --- a/lib/trends/tema/Tema.Quantower.Tests.cs +++ b/lib/trends/tema/Tema.Quantower.Tests.cs @@ -68,7 +68,7 @@ public class TemaIndicatorTests for (int i = 0; i < 10; i++) { indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102); - + // Process update var args = new UpdateArgs(UpdateReason.HistoricalBar); indicator.ProcessUpdate(args); diff --git a/lib/trends/tema/Tema.Tests.cs b/lib/trends/tema/Tema.Tests.cs index c52bfdb3..381a4c18 100644 --- a/lib/trends/tema/Tema.Tests.cs +++ b/lib/trends/tema/Tema.Tests.cs @@ -66,13 +66,13 @@ public class TemaTests tema.Reset(); Assert.Equal(0, tema.Last.Value); Assert.False(tema.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { tema.Update(new TValue(bars[i].Time, bars[i].Close)); } - + Assert.True(double.IsFinite(tema.Last.Value)); } @@ -99,23 +99,23 @@ public class TemaTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void BatchCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var tema = new Tema(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(tema.Update(series[i]).Value); } - + var batchResults = Tema.Batch(series, 10); - + Assert.Equal(streamingResults.Count, batchResults.Count); for (int i = 0; i < batchResults.Count; i++) { @@ -129,17 +129,17 @@ public class TemaTests var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var tema = new Tema(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(tema.Update(series[i]).Value); } - + var spanResults = new double[series.Count]; Tema.Batch(series.Values, spanResults, 10); - + for (int i = 0; i < spanResults.Length; i++) { Assert.Equal(streamingResults[i], spanResults[i], 1e-9); @@ -153,12 +153,12 @@ public class TemaTests var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // Test TSeries chain var result = tema.Update(series); Assert.NotNull(result); Assert.IsType(result); - + // Test TValue chain var result2 = tema.Update(series[0]); Assert.IsType(result2); diff --git a/lib/trends/tema/Tema.Validation.Tests.cs b/lib/trends/tema/Tema.Validation.Tests.cs index b7a65fb5..60d1dd43 100644 --- a/lib/trends/tema/Tema.Validation.Tests.cs +++ b/lib/trends/tema/Tema.Validation.Tests.cs @@ -85,9 +85,9 @@ public class TemaValidationTests var temaIndicator = Tulip.Indicators.tema; double[][] inputs = { _testData.RawData.ToArray() }; double[] options = { period }; - + // Tulip TEMA lookback is 3*(period-1) - int lookback = 3 * (period - 1); + int lookback = 3 * (period - 1); double[][] outputs = { new double[_testData.RawData.Length - lookback] }; temaIndicator.Run(inputs, options, outputs); diff --git a/lib/trends/trima/Trima.Tests.cs b/lib/trends/trima/Trima.Tests.cs index bb6c34d8..e0a31e3b 100644 --- a/lib/trends/trima/Trima.Tests.cs +++ b/lib/trends/trima/Trima.Tests.cs @@ -66,13 +66,13 @@ public class TrimaTests trima.Reset(); Assert.Equal(0, trima.Last.Value); Assert.False(trima.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { trima.Update(new TValue(bars[i].Time, bars[i].Close)); } - + Assert.True(double.IsFinite(trima.Last.Value)); } @@ -99,23 +99,23 @@ public class TrimaTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void BatchCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var trima = new Trima(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(trima.Update(series[i]).Value); } - + var batchResults = Trima.Batch(series, 10); - + Assert.Equal(streamingResults.Count, batchResults.Count); for (int i = 0; i < batchResults.Count; i++) { @@ -129,17 +129,17 @@ public class TrimaTests var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var trima = new Trima(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(trima.Update(series[i]).Value); } - + var spanResults = new double[series.Count]; Trima.Batch(series.Values, spanResults, 10); - + for (int i = 0; i < spanResults.Length; i++) { Assert.Equal(streamingResults[i], spanResults[i], 1e-9); @@ -153,12 +153,12 @@ public class TrimaTests var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // Test TSeries chain var result = trima.Update(series); Assert.NotNull(result); Assert.IsType(result); - + // Test TValue chain var result2 = trima.Update(series[0]); Assert.IsType(result2); diff --git a/lib/trends/trima/Trima.Validation.Tests.cs b/lib/trends/trima/Trima.Validation.Tests.cs index 48f78a92..d0ab658c 100644 --- a/lib/trends/trima/Trima.Validation.Tests.cs +++ b/lib/trends/trima/Trima.Validation.Tests.cs @@ -36,14 +36,14 @@ public class TrimaValidationTests int p2 = (period + 1) / 2; var sma1Results = _testData.SkenderQuotes.GetSma(p1).ToList(); - + // Map SMA1 results to Quotes for the second pass // Note: We use 0 for null values during warmup, which might affect early values // but should stabilize for the verification window (last 100 records) - var quotes2 = sma1Results.Select(r => new Quote - { - Date = r.Date, - Close = (decimal)(r.Sma ?? 0) + var quotes2 = sma1Results.Select(r => new Quote + { + Date = r.Date, + Close = (decimal)(r.Sma ?? 0) }).ToList(); var sResult = quotes2.GetSma(p2).ToList(); @@ -99,10 +99,10 @@ public class TrimaValidationTests // Usually it's period-1 for simple averages, but TRIMA is double smoothed. // We'll rely on the output length to align. // Tulip.Indicators.trima.Run expects outputs to be sized correctly. - // We can try to run it with a large buffer and see what happens, + // We can try to run it with a large buffer and see what happens, // or calculate the expected lookback. // For TRIMA(n), lookback is roughly n-1. - int lookback = period - 1; + int lookback = period - 1; double[][] outputs = { new double[_testData.RawData.Length - lookback] }; trimaIndicator.Run(inputs, options, outputs); diff --git a/lib/trends/usf/Usf.Quantower.Tests.cs b/lib/trends/usf/Usf.Quantower.Tests.cs index 92d60189..a72e40f3 100644 --- a/lib/trends/usf/Usf.Quantower.Tests.cs +++ b/lib/trends/usf/Usf.Quantower.Tests.cs @@ -19,10 +19,10 @@ public class UsfIndicatorTests public void Indicator_ProcessesData() { var indicator = new UsfIndicator(); - + // Simulate Init indicator.GetType().GetMethod("OnInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.Invoke(indicator, null); - + Assert.NotNull(indicator); } } diff --git a/lib/trends/usf/Usf.Tests.cs b/lib/trends/usf/Usf.Tests.cs index 41b17af1..6f7f67d5 100644 --- a/lib/trends/usf/Usf.Tests.cs +++ b/lib/trends/usf/Usf.Tests.cs @@ -66,13 +66,13 @@ public class UsfTests usf.Reset(); Assert.Equal(0, usf.Last.Value); Assert.False(usf.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { usf.Update(new TValue(bars[i].Time, bars[i].Close)); } - + Assert.True(double.IsFinite(usf.Last.Value)); } @@ -99,23 +99,23 @@ public class UsfTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void BatchCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var usf = new Usf(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(usf.Update(series[i]).Value); } - + var batchResults = Usf.Calculate(series, 10).Results; - + Assert.Equal(streamingResults.Count, batchResults.Count); for (int i = 0; i < batchResults.Count; i++) { @@ -129,17 +129,17 @@ public class UsfTests var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var usf = new Usf(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(usf.Update(series[i]).Value); } - + var spanResults = new double[series.Count]; Usf.Calculate(series.Values, spanResults, 10); - + for (int i = 0; i < spanResults.Length; i++) { Assert.Equal(streamingResults[i], spanResults[i], 1e-9); @@ -153,12 +153,12 @@ public class UsfTests var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // Test TSeries chain var result = usf.Update(series); Assert.NotNull(result); Assert.IsType(result); - + // Test TValue chain var result2 = usf.Update(series[0]); Assert.IsType(result2); diff --git a/lib/trends/usf/Usf.cs b/lib/trends/usf/Usf.cs index b323411e..a15f1256 100644 --- a/lib/trends/usf/Usf.cs +++ b/lib/trends/usf/Usf.cs @@ -168,12 +168,12 @@ public sealed class Usf : AbstractBase double usf = (_state.Count < 4) ? val : (1.0 - _c1) * val + (2.0 * _c1 - _c2) * _state.PrevInput1 - (_c1 + _c3) * _state.PrevInput2 + _c2 * _state.Usf1 + _c3 * _state.Usf2; - + _state.Usf2 = _state.Usf1; _state.Usf1 = usf; _state.PrevInput2 = _state.PrevInput1; _state.PrevInput1 = val; - + if (isNew && !initialized) _state.Count++; if (!_state.IsHot && _state.Count >= WarmupPeriod) _state.IsHot = true; @@ -207,7 +207,7 @@ public sealed class Usf : AbstractBase sourceTimes.CopyTo(tSpan); _p_state = _state; - + Last = new TValue(tSpan[len - 1], vSpan[len - 1]); return new TSeries(t, v); @@ -231,12 +231,12 @@ public sealed class Usf : AbstractBase state.Usf2 = state.LastValidValue; state.PrevInput1 = state.LastValidValue; state.PrevInput2 = state.LastValidValue; - output[i] = state.LastValidValue; + output[i] = state.LastValidValue; state.Count = 1; i++; break; } - output[i] = double.NaN; + output[i] = double.NaN; } } diff --git a/lib/trends/vidya/Vidya.Quantower.Tests.cs b/lib/trends/vidya/Vidya.Quantower.Tests.cs index 28124175..71e0a235 100644 --- a/lib/trends/vidya/Vidya.Quantower.Tests.cs +++ b/lib/trends/vidya/Vidya.Quantower.Tests.cs @@ -59,7 +59,7 @@ public class VidyaIndicatorTests for (int i = 0; i < 10; i++) { indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102); - + // Process update var args = new UpdateArgs(UpdateReason.HistoricalBar); indicator.ProcessUpdate(args); diff --git a/lib/trends/vidya/Vidya.Tests.cs b/lib/trends/vidya/Vidya.Tests.cs index 4cc92f87..4013d0cc 100644 --- a/lib/trends/vidya/Vidya.Tests.cs +++ b/lib/trends/vidya/Vidya.Tests.cs @@ -65,13 +65,13 @@ public class VidyaTests vidya.Reset(); Assert.Equal(0, vidya.Last.Value); - + // Feed again for (int i = 0; i < bars.Count; i++) { vidya.Update(new TValue(bars[i].Time, bars[i].Close)); } - + Assert.True(double.IsFinite(vidya.Last.Value)); } @@ -98,23 +98,23 @@ public class VidyaTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void BatchCalculate_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var vidya = new Vidya(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(vidya.Update(series[i]).Value); } - + var batchResults = Vidya.Batch(series, 10); - + Assert.Equal(streamingResults.Count, batchResults.Count); for (int i = 0; i < batchResults.Count; i++) { @@ -128,17 +128,17 @@ public class VidyaTests var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var vidya = new Vidya(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(vidya.Update(series[i]).Value); } - + var spanResults = new double[series.Count]; Vidya.Batch(series.Values, spanResults, 10); - + for (int i = 0; i < spanResults.Length; i++) { Assert.Equal(streamingResults[i], spanResults[i], 1e-9); @@ -152,12 +152,12 @@ public class VidyaTests var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // Test TSeries chain var result = vidya.Update(series); Assert.NotNull(result); Assert.IsType(result); - + // Test TValue chain var result2 = vidya.Update(series[0]); Assert.IsType(result2); diff --git a/lib/trends/vidya/Vidya.cs b/lib/trends/vidya/Vidya.cs index 8de15f22..2b000c18 100644 --- a/lib/trends/vidya/Vidya.cs +++ b/lib/trends/vidya/Vidya.cs @@ -201,7 +201,7 @@ public sealed class Vidya : AbstractBase, IDisposable // Process all data to build up state // For recursive indicators like VIDYA, we generally need to process from the start // or at least a significant warmup period. - // Given we don't know the "correct" previous VIDYA without processing, + // Given we don't know the "correct" previous VIDYA without processing, // we process the whole provided history. double prevClose = source[0]; @@ -254,7 +254,7 @@ public sealed class Vidya : AbstractBase, IDisposable _state.LastVidya = lastVidya; // Set Last - // Note: Time is not available in Span, so we use MinValue. + // Note: Time is not available in Span, so we use MinValue. // It will be updated on next Update. Last = new TValue(DateTime.MinValue, _state.CurrentVidya); _p_state = _state; @@ -286,7 +286,7 @@ public sealed class Vidya : AbstractBase, IDisposable double alpha = 2.0 / (period + 1); - // Use arrays for buffers to avoid heap allocations if possible, + // Use arrays for buffers to avoid heap allocations if possible, // but period is dynamic. // We can use ArrayPool or just new double[period] if period is small. // For simplicity and safety with large periods, let's use ArrayPool. diff --git a/lib/trends/wma/Wma.Coverage.Tests.cs b/lib/trends/wma/Wma.Coverage.Tests.cs index a345e7b5..23a59558 100644 --- a/lib/trends/wma/Wma.Coverage.Tests.cs +++ b/lib/trends/wma/Wma.Coverage.Tests.cs @@ -17,7 +17,7 @@ public class WmaCoverageTests for (int i = 0; i < len; i++) source[i] = i; double[] output = new double[len]; - + // This should trigger CalculateScalarCore internally Wma.Batch(source.AsSpan(), output.AsSpan(), period); @@ -45,26 +45,26 @@ public class WmaCoverageTests // However, we can't easily invoke it directly. // But wait, I previously wrote a test that called a *copy* of the method. // Calling the *actual* private method with Spans via reflection is not possible in C# (TargetInvocationException). - - // Strategy change: + + // Strategy change: // Since we cannot invoke private methods with Span args via reflection, // and we cannot change the visibility of the methods (they should remain private), // we are limited in how we can "force" coverage of the private AVX2 method if AVX512 is present. - + // However, we CAN use the fact that Wma.Batch checks for Avx512F.IsSupported. // We cannot change that runtime flag. - + // Actually, we can't easily cover the AVX2 path on an AVX512 machine without code modification or a "TestAccessor" pattern. // But wait, the user asked "why is coverage only 46%". // If I can't run the code, I can't cover it. - + // BUT, I can verify the Scalar Core logic by using the small data test (done above). // For AVX2, if I can't invoke it, I can't cover it on this machine. - + // Let's double check if there's any way to invoke it. // Maybe I can use `MethodInfo.CreateDelegate`? // Delegates can take Spans if defined correctly. - + InvokePrivateStaticMethod_WithSpans("CalculateSimdCore", source, output, period); } catch (Exception ex) @@ -98,7 +98,7 @@ public class WmaCoverageTests // Create a delegate that matches the signature // Note: ReadOnlySpan and Span in delegate signature var del = methodInfo.CreateDelegate(); - + del(source.AsSpan(), output.AsSpan(), period); } } diff --git a/lib/trends/wma/Wma.Quantower.Tests.cs b/lib/trends/wma/Wma.Quantower.Tests.cs index 0ef1be2a..4e0b681a 100644 --- a/lib/trends/wma/Wma.Quantower.Tests.cs +++ b/lib/trends/wma/Wma.Quantower.Tests.cs @@ -68,7 +68,7 @@ public class WmaIndicatorTests for (int i = 0; i < 10; i++) { indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102); - + // Process update var args = new UpdateArgs(UpdateReason.HistoricalBar); indicator.ProcessUpdate(args); diff --git a/lib/trends/wma/Wma.Tests.cs b/lib/trends/wma/Wma.Tests.cs index 29742556..a0295e98 100644 --- a/lib/trends/wma/Wma.Tests.cs +++ b/lib/trends/wma/Wma.Tests.cs @@ -66,13 +66,13 @@ public class WmaTests wma.Reset(); Assert.Equal(0, wma.Last.Value); Assert.False(wma.IsHot); - + // Feed again for (int i = 0; i < bars.Count; i++) { wma.Update(new TValue(bars[i].Time, bars[i].Close)); } - + Assert.True(double.IsFinite(wma.Last.Value)); } @@ -99,23 +99,23 @@ public class WmaTests Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9); } } - + [Fact] public void StaticBatch_Matches_Streaming() { var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var wma = new Wma(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(wma.Update(series[i]).Value); } - + var staticResults = Wma.Batch(series, 10); - + Assert.Equal(streamingResults.Count, staticResults.Count); for (int i = 0; i < staticResults.Count; i++) { @@ -129,17 +129,17 @@ public class WmaTests var gbm = new GBM(); var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + var wma = new Wma(10); var streamingResults = new List(); for (int i = 0; i < series.Count; i++) { streamingResults.Add(wma.Update(series[i]).Value); } - + var spanResults = new double[series.Count]; Wma.Batch(series.Values, spanResults, 10); - + for (int i = 0; i < spanResults.Length; i++) { Assert.Equal(streamingResults[i], spanResults[i], 1e-9); @@ -153,12 +153,12 @@ public class WmaTests var gbm = new GBM(); var bars = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); var series = bars.Close; - + // Test TSeries chain var result = wma.Update(series); Assert.NotNull(result); Assert.IsType(result); - + // Test TValue chain var result2 = wma.Update(series[0]); Assert.IsType(result2); @@ -176,14 +176,14 @@ public class WmaTests { var source = new TSeries(); var wma = new Wma(source, 10); - + // Verify subscription works source.Add(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, wma.Last.Value); - + // Dispose wma.Dispose(); - + // Verify unsubscription source.Add(new TValue(DateTime.UtcNow, 200)); // Last value should remain unchanged if unsubscribed @@ -251,11 +251,11 @@ public class WmaTests public void Update_IsNewFalse_OnEmptyBuffer_ThrowsInvalidOperationException() { var wma = new Wma(10); - + // Calling Update with isNew=false on an empty buffer should throw var exception = Assert.Throws(() => wma.Update(new TValue(DateTime.UtcNow, 100.0), isNew: false)); - + Assert.Contains("isNew=false", exception.Message, StringComparison.Ordinal); Assert.Contains("buffer is empty", exception.Message, StringComparison.Ordinal); Assert.Contains("isNew=true", exception.Message, StringComparison.Ordinal); @@ -267,20 +267,20 @@ public class WmaTests var wma = new Wma(10); var gbm = new GBM(); var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + // Feed some data for (int i = 0; i < 10; i++) { wma.Update(new TValue(bars[i].Time, bars[i].Close)); } - + // Reset clears the buffer wma.Reset(); - + // Calling Update with isNew=false after reset should throw var exception = Assert.Throws(() => wma.Update(new TValue(bars[10].Time, bars[10].Close), isNew: false)); - + Assert.Contains("isNew=false", exception.Message, StringComparison.Ordinal); Assert.Contains("buffer is empty", exception.Message, StringComparison.Ordinal); } @@ -291,16 +291,16 @@ public class WmaTests var wma = new Wma(10); var gbm = new GBM(); var bars = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - + // Feed some data first for (int i = 0; i < 5; i++) { wma.Update(new TValue(bars[i].Time, bars[i].Close), isNew: true); } - + // Now isNew=false should work (buffer has data) var result = wma.Update(new TValue(bars[4].Time, bars[4].Close + 10), isNew: false); - + // Should not throw and should return a finite value Assert.True(double.IsFinite(result.Value)); } diff --git a/lib/volatility/atr/Atr.Validation.Tests.cs b/lib/volatility/atr/Atr.Validation.Tests.cs index e3ef9797..50d4b924 100644 --- a/lib/volatility/atr/Atr.Validation.Tests.cs +++ b/lib/volatility/atr/Atr.Validation.Tests.cs @@ -170,7 +170,7 @@ public sealed class AtrValidationTests : IDisposable var atrIndicator = Tulip.Indicators.atr; double[][] inputs = { hData, lData, cData }; double[] options = { period }; - + // Tulip ATR lookback int lookback = atrIndicator.Start(options); double[][] outputs = { new double[hData.Length - lookback] }; @@ -208,7 +208,7 @@ public sealed class AtrValidationTests : IDisposable var atrIndicator = Tulip.Indicators.atr; double[][] inputs = { hData, lData, cData }; double[] options = { period }; - + // Tulip ATR lookback int lookback = atrIndicator.Start(options); double[][] outputs = { new double[hData.Length - lookback] }; diff --git a/lib/volatility/atr/Atr.cs b/lib/volatility/atr/Atr.cs index 285d9658..3d5f6df2 100644 --- a/lib/volatility/atr/Atr.cs +++ b/lib/volatility/atr/Atr.cs @@ -59,8 +59,8 @@ public sealed class Atr : AbstractBase var tr = CalculateTrueRange(source); _rma.Prime(tr.Values); Last = _rma.Last; - // We can't automatically subscribe to TBarSeries updates via this constructor - // because AbstractBase doesn't enforce TBarSeries subscription structure, + // We can't automatically subscribe to TBarSeries updates via this constructor + // because AbstractBase doesn't enforce TBarSeries subscription structure, // but we can rely on manual updates or the user subscribing. } @@ -73,8 +73,8 @@ public sealed class Atr : AbstractBase /// /// Initializes the indicator state using the provided history. - /// Note: ATR needs OHLCV data to calculate TR properly. - /// This Prime method expects pre-calculated TR values or handles basic priming + /// Note: ATR needs OHLCV data to calculate TR properly. + /// This Prime method expects pre-calculated TR values or handles basic priming /// if the user erroneously passes non-TR data. Ideally, use Batched TBarSeries. /// public override void Prime(ReadOnlySpan source, TimeSpan? step = null) diff --git a/lib/volume/adl/Adl.Tests.cs b/lib/volume/adl/Adl.Tests.cs index c33d2e69..9df5c892 100644 --- a/lib/volume/adl/Adl.Tests.cs +++ b/lib/volume/adl/Adl.Tests.cs @@ -11,7 +11,7 @@ public class AdlTests // Arrange var adl = new Adl(); var time = DateTime.UtcNow; - + // Bar 1: Close=10, High=12, Low=8. Range=4. // MFM = ((10-8) - (12-10)) / 4 = (2 - 2) / 4 = 0. // Vol = 100. MFV = 0. ADL = 0. @@ -59,7 +59,7 @@ public class AdlTests var adl = new Adl(); var bar = new TBar(DateTime.UtcNow, 10, 12, 8, 12, 100); adl.Update(bar); - + Assert.True(adl.IsHot); Assert.NotEqual(0, adl.Last.Value); @@ -77,17 +77,17 @@ public class AdlTests var val = adl.Update(bar); Assert.Equal(0, val.Value); } - + [Fact] public void Adl_TValueUpdate_DoesNotChangeValue() { var adl = new Adl(); var bar = new TBar(DateTime.UtcNow, 10, 12, 8, 12, 100); adl.Update(bar); // ADL = 100 - + // Update with TValue (no volume info) adl.Update(new TValue(DateTime.UtcNow, 15)); - + // Should remain 100 Assert.Equal(100, adl.Last.Value); } @@ -103,7 +103,7 @@ public class AdlTests { var adl = new Adl(); bool eventFired = false; - adl.Pub += (object? sender, TValueEventArgs args) => eventFired = true; + adl.Pub += (object? sender, in TValueEventArgs args) => eventFired = true; adl.Update(new TBar(DateTime.UtcNow, 10, 12, 8, 10, 100)); Assert.True(eventFired); @@ -115,14 +115,14 @@ public class AdlTests var adl = new Adl(); var bars = new TBarSeries(); var time = DateTime.UtcNow; - + // Add same bars as in BasicCalculation bars.Add(new TBar(time, 10, 12, 8, 10, 100)); // ADL=0 bars.Add(new TBar(time.AddMinutes(1), 10, 12, 8, 12, 200)); // ADL=200 bars.Add(new TBar(time.AddMinutes(2), 12, 12, 8, 8, 100)); // ADL=100 var result = adl.Update(bars); - + Assert.Equal(3, result.Count); Assert.Equal(0, result[0].Value); Assert.Equal(200, result[1].Value); @@ -134,19 +134,19 @@ public class AdlTests { var bars = new TBarSeries(); var time = DateTime.UtcNow; - + bars.Add(new TBar(time, 10, 12, 8, 10, 100)); bars.Add(new TBar(time.AddMinutes(1), 10, 12, 8, 12, 200)); bars.Add(new TBar(time.AddMinutes(2), 12, 12, 8, 8, 100)); var result = Adl.Calculate(bars); - + Assert.Equal(3, result.Count); Assert.Equal(0, result[0].Value); Assert.Equal(200, result[1].Value); Assert.Equal(100, result[2].Value); } - + [Fact] public void Adl_CalculateSpan_ReturnsCorrectValues() { @@ -157,7 +157,7 @@ public class AdlTests double[] output = new double[3]; Adl.Calculate(high, low, close, volume, output); - + Assert.Equal(0, output[0]); Assert.Equal(200, output[1]); Assert.Equal(100, output[2]); @@ -172,7 +172,7 @@ public class AdlTests double[] volume = { 100 }; // Short double[] output = new double[2]; - Assert.Throws(() => + Assert.Throws(() => Adl.Calculate(high, low, close, volume, output)); } diff --git a/lib/volume/adl/Adl.Validation.Tests.cs b/lib/volume/adl/Adl.Validation.Tests.cs index b18f9b81..93977ba1 100644 --- a/lib/volume/adl/Adl.Validation.Tests.cs +++ b/lib/volume/adl/Adl.Validation.Tests.cs @@ -44,7 +44,7 @@ public class AdlValidationTests var close = _data.Bars.Close.Values.ToArray(); var volume = _data.Bars.Volume.Values.ToArray(); var talibValues = new double[high.Length]; - + var retCode = TALib.Functions.Ad(high, low, close, volume, 0..^0, talibValues, out var outRange); Assert.Equal(TALib.Core.RetCode.Success, retCode); @@ -67,7 +67,7 @@ public class AdlValidationTests var low = _data.Bars.Low.Values.ToArray(); var close = _data.Bars.Close.Values.ToArray(); var volume = _data.Bars.Volume.Values.ToArray(); - + var tulipIndicator = Tulip.Indicators.ad; double[][] inputs = { high, low, close, volume }; double[] options = Array.Empty(); diff --git a/lib/volume/adosc/Adosc.Tests.cs b/lib/volume/adosc/Adosc.Tests.cs index c05c61b8..2f7493ae 100644 --- a/lib/volume/adosc/Adosc.Tests.cs +++ b/lib/volume/adosc/Adosc.Tests.cs @@ -57,7 +57,7 @@ public class AdoscTests var modifiedBar = new TBar(_bars[0].Time, _bars[0].Open, _bars[0].High, _bars[0].Low, _bars[0].Close * 1.1, _bars[0].Volume); adosc.Update(modifiedBar, isNew: false); - + Assert.NotEqual(firstResult, adosc.Last.Value); } @@ -87,7 +87,7 @@ public class AdoscTests { var adosc = new Adosc(3, 10); var batchResult = Adosc.Batch(_bars, 3, 10); - + var streamResult = new List(); foreach (var bar in _bars) { diff --git a/omnisharp.json b/omnisharp.json index e907f421..6b7b6a33 100644 --- a/omnisharp.json +++ b/omnisharp.json @@ -1,19 +1,19 @@ -{ - "RoslynExtensionsOptions": { - "enableAnalyzersSupport": true, - "enableDecompilationSupport": true - }, - "FormattingOptions": { - "enableEditorConfigSupport": true - }, - "Telemetry": { - "enableTelemetry": false - }, - "fileOptions": { - "systemExcludeSearchPatterns": [ - "**/node_modules/**/*", - "**/bin/**/*", - "**/obj/**/*" - ] - } -} +{ + "RoslynExtensionsOptions": { + "enableAnalyzersSupport": true, + "enableDecompilationSupport": true + }, + "FormattingOptions": { + "enableEditorConfigSupport": true + }, + "Telemetry": { + "enableTelemetry": false + }, + "fileOptions": { + "systemExcludeSearchPatterns": [ + "**/node_modules/**/*", + "**/bin/**/*", + "**/obj/**/*" + ] + } +} diff --git a/perf/perf.csproj b/perf/perf.csproj index 43eb5bc1..44e08192 100644 --- a/perf/perf.csproj +++ b/perf/perf.csproj @@ -18,7 +18,7 @@ - + diff --git a/quantower/Directory.Build.props b/quantower/Directory.Build.props index 639b4fcc..c5930d51 100644 --- a/quantower/Directory.Build.props +++ b/quantower/Directory.Build.props @@ -9,7 +9,7 @@ obj\Tests\ - + true diff --git a/quantower/Trends.csproj b/quantower/Trends.csproj index 25f2087d..7dc18ebf 100644 --- a/quantower/Trends.csproj +++ b/quantower/Trends.csproj @@ -1,34 +1,34 @@ - - - - net10.0;net8.0 - net10.0 - Trends - Indicator - bin\$(Configuration)\ - false - false - true - - - - - - - - - - - - ..\.github\TradingPlatform.BusinessLayer.dll - - - TradingPlatform.BusinessLayer.xml - - - - - - - - + + + + net10.0;net8.0 + net10.0 + Trends + Indicator + bin\$(Configuration)\ + false + false + true + + + + + + + + + + + + ..\.github\TradingPlatform.BusinessLayer.dll + + + TradingPlatform.BusinessLayer.xml + + + + + + + +