From fbe4046b5df3a35b4e326388fe149a0f0d0fce5d Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Wed, 16 Oct 2024 18:28:06 -0700 Subject: [PATCH 1/5] Documentation --- Tests/test_iTBar.cs | 6 +- Tests/test_quantower.cs | 3 +- Tests/test_updates_volatility.cs | 15 - docs/Progress.csv | 134 ++ docs/Progress.md | 318 ++++- docs/_sidebar.md | 44 +- docs/index.html | 115 +- docs/styles.css | 1602 +++++++++++++++++++++++ lib/averages/Ema.cs | 63 +- lib/averages/Jma.cs | 146 ++- lib/volatility/Jvolty.cs | 108 +- lib/volatility/todo.md | 2 +- notebooks/jma.dib | 159 ++- quantower/Averages/JmaIndicator.cs | 2 +- quantower/Averages/MacdIndicator.cs | 95 ++ quantower/Volatility/JbandsIndicator.cs | 64 + quantower/Volatility/JvoltyIndicator.cs | 29 +- 17 files changed, 2619 insertions(+), 286 deletions(-) create mode 100644 docs/Progress.csv create mode 100644 docs/styles.css create mode 100644 quantower/Averages/MacdIndicator.cs create mode 100644 quantower/Volatility/JbandsIndicator.cs diff --git a/Tests/test_iTBar.cs b/Tests/test_iTBar.cs index 1cf5f787..59796179 100644 --- a/Tests/test_iTBar.cs +++ b/Tests/test_iTBar.cs @@ -25,7 +25,7 @@ public class BarIndicatorTests private static readonly ITValue[] indicators = new ITValue[] { new Atr(period: 14), - new Jvolty(period: 14) + // Add other TBar-based indicators here }; @@ -80,14 +80,14 @@ public class BarIndicatorTests if (methods.Count > 0) { // Prefer the method with TBar parameter - var method = methods.FirstOrDefault(m => + var method = methods.Find(m => { var parameters = m.GetParameters(); return parameters.Length == 1 && parameters[0].ParameterType == typeof(TBar); }); // If not found, return the first method - return method ?? methods.First(); + return method ?? methods[0]; } type = type.BaseType!; diff --git a/Tests/test_quantower.cs b/Tests/test_quantower.cs index 844f1733..23430830 100644 --- a/Tests/test_quantower.cs +++ b/Tests/test_quantower.cs @@ -13,7 +13,7 @@ namespace QuanTAlib { public class QuantowerTests { - private void TestIndicator(string fieldName = "ma") where T : Indicator, new() + private static void TestIndicator(string fieldName = "ma") where T : Indicator, new() { var indicator = new T(); try @@ -95,7 +95,6 @@ namespace QuanTAlib // Volatility Indicators [Fact] public void Atr() => TestIndicator("atr"); - [Fact] public void Jvolty() => TestIndicator("jvolty"); [Fact] public void Historical() => TestIndicator("historical"); [Fact] public void Realized() => TestIndicator("realized"); diff --git a/Tests/test_updates_volatility.cs b/Tests/test_updates_volatility.cs index 901006cc..ba7976ee 100644 --- a/Tests/test_updates_volatility.cs +++ b/Tests/test_updates_volatility.cs @@ -57,21 +57,6 @@ public class VolatilityUpdateTests Assert.Equal(initialValue, finalValue, precision); } - [Fact] - public void Jvolty_Update() - { - var indicator = new Jvolty(period: 14); - double initialValue = indicator.Calc(new TBar(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true)); - - for (int i = 0; i < RandomUpdates; i++) - { - indicator.Calc(GetRandomBar(false)); - } - double finalValue = indicator.Calc(new TBar(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: false)); - - Assert.Equal(initialValue, finalValue, precision); - } - [Fact] public void Realized_Update() { diff --git a/docs/Progress.csv b/docs/Progress.csv new file mode 100644 index 00000000..7ea5a50e --- /dev/null +++ b/docs/Progress.csv @@ -0,0 +1,134 @@ +AC,Acceleration Oscillator +AD,Chaikin A/D Line +ADOSC,Chaikin A/D Oscillator +ADL,Accumulation / Distribution Line +ADX,Average Directional Movement Index +ADXR,Average Directional Movement Index Rating +Alligator,Alligator Indicator +ALMA,Arnaud Legoux Moving Average +AO,Awesome Oscillator +APO,Absolute Price Oscillator +AROON,Aroon Indicator +AROONOSC,Aroon Oscillator +ATS,ATR Trailing Stop +ATR,Average True Range +AVGPRICE,Average Price +BB,Bollinger Bands +BBF,Bollinger Bands Flat +BBP,Bull and Bear Power +BC,Beta Coefficient +BETA,Beta +BOP,Balance of Power +CCI,Commodity Channel Index +CE,Chandelier Exit +Channel,Price Channel +CI,Choppiness Index +CMF,Chaikin Money Flow +CMO,Chande Momentum Oscillator +CORREL,Pearson's Correlation Coefficient +CRSI,ConnorsRSI +DC,Donchian Channels +DCP,Dominant Cycle Periods +DEMA,Double Exponential Moving Average +DMI,Directional Movement Index +DPO,Detrended Price Oscillator +DX,Directional Movement Index +EMA,Exponential Moving Average +EPMA,Endpoint Moving Average +ERI,Elder-ray Index +FCB,Fractal Chaos Bands +FI,Force Index +GO,Gator Oscillator +HE,Hurst Exponent +HMA,Hull Moving Average +HT_TRENDLINE,Hilbert Transform - Instantaneous Trendline +HT_TRENDMODE,Hilbert Transform - Trend vs Cycle Mode +HV,Historical Volatility +IC,Ichimoku Cloud +KAMA,Kaufman Adaptive Moving Average +KDJ,KDJ Index +Keltner,Keltner Channel +KVO,Klinger Volume Oscillator +LINEARREG,Linear Regression +LINEARREG_ANGLE,Linear Regression Angle +LINEARREG_INTERCEPT,Linear Regression Intercept +LINEARREG_SLOPE,Linear Regression Slope +LSMA,Least Squares Moving Average +LWMA,Linearly Weighted Moving Average +MACDEXT,MACD with controllable MA type +MACDFIX,Moving Average Convergence Divergence Fix 12/26 +MAD,Mean absolute deviation +MAE,Moving Average Envelope +MAMA,MESA Adaptive Moving Average +MAPE,Mean absolute percentage error +MAX,Highest value over a specified period +MAXINDEX,Index of highest value over a specified period +MD,McGinley Dynamic +MEDPRICE,Median Price +MFI,Money Flow Index +MIN,Lowest value over a specified period +MININDEX,Index of lowest value over a specified period +MINMAX,Lowest and highest values over a specified period +MINMAXINDEX,Indexes of lowest and highest values over a period +MMA,Modified Moving Average +MOM,Momentum +MSE,Mean square error +NATR,Normalized Average True Range +OBV,On Balance Volume +OsMA,Moving Average of Oscillator +PAZ,Price Action Zones +Pivots,Pivots +PMO,Price Momentum Oscillator +PP,Pivot Points +PPO,Percentage Price Oscillator +PPMA,Pivot Point Moving Average +PRS,Price Relative Strength +PVI,Positive Volume Index +PVO,Percentage Volume Oscillator +Qstick,Qstick Indicator +R2,R-Squared (Coefficient of Determination) +Regression,Regression Line Indicator +RLW,%R Larry Williams +RMA,Running Moving Average +ROC,Rate of Change +ROCB,ROC with Bands +RPP,Rolling Pivot Points +RRA,Rescaled Range Analysis +RSI,Relative Strength Index +SAR,Parabolic SAR +SAREXT,Parabolic SAR - Extended +SDC,Standard Deviation Channels +SI,Swing Index +SLR,Slope and Linear Regression +SMA,Simple Moving Average +SMI,Stochastic Momentum Index +SMMA,Smoothed Moving Average +ST,SuperTrend +STARC,STARC Bands +STC,Schaff Trend Cycle +STDDEV,Standard Deviation +STOCH,Stochastic +STOCHF,Stochastic Fast +STOCHRSI,Stochastic Relative Strength Index +SUM,Summation +T3,Triple Exponential Moving Average (T3) +TEMA,Triple Exponential Moving Average +TRANGE,True Range +TRIMA,Triangular Moving Average +TRIX,1-day Rate-Of-Change (ROC) of a Triple Smooth EMA +TSI,True Strength Index +TYPPRICE,Typical Price +UI,Ulcer Index +ULTOSC,Ultimate Oscillator +VAR,Variance +VI,Vortex Indicator +Volume,Volume Indicator +VS,Volatility Stop +VWAP,Volume Weighted Average Price +VWMA,Volume Weighted Moving Average +WA,Williams Alligator +WCLPRICE,Weighted Close Price +WF,Williams Fractal +WMA,Weighted Moving Average +ZS,Z-Score +ZZ,ZigZag Indicator diff --git a/docs/Progress.md b/docs/Progress.md index 793f4881..eaf705a9 100644 --- a/docs/Progress.md +++ b/docs/Progress.md @@ -1,5 +1,313 @@ -# Backlog and done - -|**QT**|**Chart**|Cmnt|Docs|isNew|Validation| -|--|:--:|:--:|:--:|:--:|:--:| -|AFIRMA|✔️||||| \ No newline at end of file +| AD | Chaikin A/D Line | +| AROON | Aroon Indicator | +| ADX | Average Directional Movement Index | +| ADXR | Average Directional Movement Index Rating | +| DX | Directional Movement Index | +| SAR | Parabolic SAR | +| SAREXT | Parabolic SAR - Extended | +| HT_TRENDLINE | Hilbert Transform - Instantaneous Trendline | +| HT_TRENDMODE | Hilbert Transform - Trend vs Cycle Mode | +| ZZ | ZigZag Indicator | +| DMI | Directional Movement Index | +| Alligator | Alligator Indicator | +| Regression | Regression Line Indicator | +| SI | Swing Index | +| ATS | ATR Trailing Stop | +| ERI | Elder-ray Index | +| GO | Gator Oscillator | +| HE | Hurst Exponent | +| IC | Ichimoku Cloud | +| ST | SuperTrend | +| VI | Vortex Indicator | +| WA | Williams Alligator | +| RSI | Relative Strength Index | +| CCI | Commodity Channel Index | +| MOM | Momentum | +| ROC | Rate of Change | +| PPO | Percentage Price Oscillator | +| AO | Awesome Oscillator | +| CMO | Chande Momentum Oscillator | +| TRIX | 1-day Rate-Of-Change (ROC) of a Triple Smooth EMA | +| ULTOSC | Ultimate Oscillator | +| AROONOSC | Aroon Oscillator | +| ADOSC | Chaikin A/D Oscillator | +| APO | Absolute Price Oscillator | +| STOCH | Stochastic | +| STOCHF | Stochastic Fast | +| STOCHRSI | Stochastic Relative Strength Index | +| Qstick | Qstick Indicator | +| RLW | %R Larry Williams | +| AC | Acceleration Oscillator | +| TSI | True Strength Index | +| CRSI | ConnorsRSI | +| DPO | Detrended Price Oscillator | +| KDJ | KDJ Index | +| STC | Schaff Trend Cycle | +| SMI | Stochastic Momentum Index | +| BB | Bollinger Bands | +| Keltner | Keltner Channel | +| BBF | Bollinger Bands Flat | +| Channel | Price Channel | +| MAE | Moving Average Envelope | +| PAZ | Price Action Zones | +| DC | Donchian Channels | +| FCB | Fractal Chaos Bands | +| PP | Pivot Points | +| RPP | Rolling Pivot Points | +| STARC | STARC Bands | +| SDC | Standard Deviation Channels | +| OBV | On Balance Volume | +| PVI | Positive Volume Index | +| Volume | Volume Indicator | +| MFI | Money Flow Index | +| ADL | Accumulation / Distribution Line | +| CMF | Chaikin Money Flow | +| FI | Force Index | +| KVO | Klinger Volume Oscillator | +| PVO | Percentage Volume Oscillator | +| ATS | ATR Trailing Stop | +| CE | Chandelier Exit | +| SAR | Parabolic SAR | +| ST | SuperTrend | +| VS | Volatility Stop | +| Pivots | Pivots | +| WF | Williams Fractal | +| EMA | Exponential Moving Average | +| SMA | Simple Moving Average | +| LWMA | Linearly Weighted Moving Average | +| SMMA | Smoothed Moving Average | +| MMA | Modified Moving Average | +| KAMA | Kaufman Adaptive Moving Average | +| DEMA | Double Exponential Moving Average | +| TEMA | Triple Exponential Moving Average | +| MAMA | MESA Adaptive Moving Average | +| TRIMA | Triangular Moving Average | +| T3 | Triple Exponential Moving Average (T3) | +| PPMA | Pivot Point Moving Average | +| WMA | Weighted Moving Average | +| ALMA | Arnaud Legoux Moving Average | +| EPMA | Endpoint Moving Average | +| HMA | Hull Moving Average | +| LSMA | Least Squares Moving Average | +| MD | McGinley Dynamic | +| RMA | Running Moving Average | +| VWAP | Volume Weighted Average Price | +| VWMA | Volume Weighted Moving Average | +| ATR | Average True Range | +| NATR | Normalized Average True Range | +| TRANGE | True Range | +| STDDEV | Standard Deviation | +| HV | Historical Volatility | +| BOP | Balance of Power | +| BBP | Bull and Bear Power | +| CI | Choppiness Index | +| DCP | Dominant Cycle Periods | +| PMO | Price Momentum Oscillator | +| PRS | Price Relative Strength | +| ROCB | ROC with Bands | +| RRA | Rescaled Range Analysis | +| UI | Ulcer Index | +| CORREL | Pearson's Correlation Coefficient | +| BETA | Beta | +| VAR | Variance | +| AVGPRICE | Average Price | +| MEDPRICE | Median Price | +| TYPPRICE | Typical Price | +| WCLPRICE | Weighted Close Price | +| SUM | Summation | +| MAX | Highest value over a specified period | +| MIN | Lowest value over a specified period | +| MAXINDEX | Index of highest value over a specified period | +| MININDEX | Index of lowest value over a specified period | +| MINMAX | Lowest and highest values over a specified period | +| MINMAXINDEX | Indexes of lowest and highest values over a period | +| BC | Beta Coefficient | +| MAD | Mean absolute deviation | +| MAPE | Mean absolute percentage error | +| MSE | Mean square error | +| R2 | R-Squared (Coefficient of Determination) | +| SLR | Slope and Linear Regression | +| ZS | Z-Score | +| AD | Chaikin A/D Line | +| AROON | Aroon Indicator | +| ADX | Average Directional Movement Index | +| ADXR | Average Directional Movement Index Rating | +| DX | Directional Movement Index | +| SAR | Parabolic SAR | +| SAREXT | Parabolic SAR - Extended | +| HT_TRENDLINE | Hilbert Transform - Instantaneous Trendline | +| HT_TRENDMODE | Hilbert Transform - Trend vs Cycle Mode | +| ZZ | ZigZag Indicator | +| DMI | Directional Movement Index | +| Alligator | Alligator Indicator | +| Regression | Regression Line Indicator | +| SI | Swing Index | +| ATS | ATR Trailing Stop | +| ERI | Elder-ray Index | +| GO | Gator Oscillator | +| HE | Hurst Exponent | +| IC | Ichimoku Cloud | +| ST | SuperTrend | +| VI | Vortex Indicator | +| WA | Williams Alligator | +| RSI | Relative Strength Index | +| CCI | Commodity Channel Index | +| MOM | Momentum | +| ROC | Rate of Change | +| PPO | Percentage Price Oscillator | +| AO | Awesome Oscillator | +| CMO | Chande Momentum Oscillator | +| TRIX | 1-day Rate-Of-Change (ROC) of a Triple Smooth EMA | +| ULTOSC | Ultimate Oscillator | +| AROONOSC | Aroon Oscillator | +| ADOSC | Chaikin A/D Oscillator | +| APO | Absolute Price Oscillator | +| STOCH | Stochastic | +| STOCHF | Stochastic Fast | +| STOCHRSI | Stochastic Relative Strength Index | +| Qstick | Qstick Indicator | +| RLW | %R Larry Williams | +| AC | Acceleration Oscillator | +| TSI | True Strength Index | +| CRSI | ConnorsRSI | +| DPO | Detrended Price Oscillator | +| KDJ | KDJ Index | +| STC | Schaff Trend Cycle | +| SMI | Stochastic Momentum Index | +| BB | Bollinger Bands | +| Keltner | Keltner Channel | +| BBF | Bollinger Bands Flat | +| Channel | Price Channel | +| MAE | Moving Average Envelope | +| PAZ | Price Action Zones | +| DC | Donchian Channels | +| FCB | Fractal Chaos Bands | +| PP | Pivot Points | +| RPP | Rolling Pivot Points | +| STARC | STARC Bands | +| SDC | Standard Deviation Channels | +| OBV | On Balance Volume | +| PVI | Positive Volume Index | +| Volume | Volume Indicator | +| MFI | Money Flow Index | +| ADL | Accumulation / Distribution Line | +| CMF | Chaikin Money Flow | +| FI | Force Index | +| KVO | Klinger Volume Oscillator | +| PVO | Percentage Volume Oscillator | +| ATS | ATR Trailing Stop | +| CE | Chandelier Exit | +| SAR | Parabolic SAR | +| ST | SuperTrend | +| VS | Volatility Stop | +| Pivots | Pivots | +| WF | Williams Fractal | +| ALMA | Arnaud Legoux Moving Average | +| EPMA | Endpoint Moving Average | +| HMA | Hull Moving Average | +| LSMA | Least Squares Moving Average | +| MD | McGinley Dynamic | +| RMA | Running Moving Average | +| T3 | Tillson T3 Moving Average | +| VWAP | Volume Weighted Average Price | +| VWMA | Volume Weighted Moving Average | +| BOP | Balance of Power | +| BBP | Bull and Bear Power | +| CI | Choppiness Index | +| DCP | Dominant Cycle Periods | +| PMO | Price Momentum Oscillator | +| PRS | Price Relative Strength | +| ROCB | ROC with Bands | +| RRA | Rescaled Range Analysis | +| UI | Ulcer Index | +| BC | Beta Coefficient | +| MAD | Mean absolute deviation | +| MAPE | Mean absolute percentage error | +| MSE | Mean square error | +| R2 | R-Squared (Coefficient of Determination) | +| SLR | Slope and Linear Regression | +| ZS | Z-Score | +| EMA | Exponential Moving Average | +| SMA | Simple Moving Average | +| LWMA | Linearly Weighted Moving Average | +| SMMA | Smoothed Moving Average | +| MMA | Modified Moving Average | +| KAMA | Kaufman Adaptive Moving Average | +| DEMA | Double Exponential Moving Average | +| TEMA | Triple Exponential Moving Average | +| MAMA | MESA Adaptive Moving Average | +| TRIMA | Triangular Moving Average | +| T3 | Triple Exponential Moving Average (T3) | +| PPMA | Pivot Point Moving Average | +| MAE | Moving Average Envelope | +| MACD | Moving Average Convergence Divergence | +| MACDEXT | MACD with controllable MA type | +| MACDFIX | Moving Average Convergence Divergence Fix 12/26 | +| OsMA | Moving Average of Oscillator | +| Regression | Regression Indicator | +| LINEARREG | Linear Regression | +| LINEARREG_ANGLE | Linear Regression Angle | +| LINEARREG_INTERCEPT | Linear Regression Intercept | +| LINEARREG_SLOPE | Linear Regression Slope | +| RSI | Relative Strength Index | +| CCI | Commodity Channel Index | +| MOM | Momentum | +| ROC | Rate of Change | +| PPO | Percentage Price Oscillator | +| AO | Awesome Oscillator | +| CMO | Chande Momentum Oscillator | +| TRIX | 1-day Rate-Of-Change (ROC) of a Triple Smooth EMA | +| ULTOSC | Ultimate Oscillator | +| AROONOSC | Aroon Oscillator | +| ADOSC | Chaikin A/D Oscillator | +| APO | Absolute Price Oscillator | +| STOCH | Stochastic | +| STOCHF | Stochastic Fast | +| STOCHRSI | Stochastic Relative Strength Index | +| Qstick | Qstick Indicator | +| RLW | %R Larry Williams | +| AC | Acceleration Oscillator | +| TSI | True Strength Index | +| AD | Chaikin A/D Line | +| AROON | Aroon Indicator | +| ADX | Average Directional Movement Index | +| ADXR | Average Directional Movement Index Rating | +| DX | Directional Movement Index | +| SAR | Parabolic SAR | +| SAREXT | Parabolic SAR - Extended | +| HT_TRENDLINE | Hilbert Transform - Instantaneous Trendline | +| HT_TRENDMODE | Hilbert Transform - Trend vs Cycle Mode | +| ZZ | ZigZag Indicator | +| DMI | Directional Movement Index | +| Alligator | Alligator Indicator | +| Regression | Regression Line Indicator | +| SI | Swing Index | +| ATR | Average True Range | +| NATR | Normalized Average True Range | +| TRANGE | True Range | +| STDDEV | Standard Deviation | +| HV | Historical Volatility | +| BB | Bollinger Bands | +| Keltner | Keltner Channel | +| BBF | Bollinger Bands Flat | +| Channel | Price Channel | +| MAE | Moving Average Envelope | +| PAZ | Price Action Zones | +| OBV | On Balance Volume | +| PVI | Positive Volume Index | +| Volume | Volume Indicator | +| MFI | Money Flow Index | +| CORREL | Pearson's Correlation Coefficient | +| BETA | Beta | +| VAR | Variance | +| AVGPRICE | Average Price | +| MEDPRICE | Median Price | +| TYPPRICE | Typical Price | +| WCLPRICE | Weighted Close Price | +| SUM | Summation | +| MAX | Highest value over a specified period | +| MIN | Lowest value over a specified period | +| MAXINDEX | Index of highest value over a specified period | +| MININDEX | Index of lowest value over a specified period | +| MINMAX | Lowest and highest values over a specified period | +| MINMAXINDEX | Indexes of lowest and highest values over a period | diff --git a/docs/_sidebar.md b/docs/_sidebar.md index cc1650b9..5df2cf71 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,7 +1,34 @@ -* [QuanTAlib](/) + +* [Home](/) +* Introduction + * [Overview]() + * [Features]() * [Historical vs Real-time analysis](essays/realtime.md) -* [Indicators](indicators/indicators.md) - * Averages & Trends + +* Core Concepts + * [Time Series Data Handling]() + * [Calculation classes]() + * [Presentation Classes]() + +* QuanTAlib C# Library + * [Installation]() + * [Quick Start Guide]() + * [Usage Examples]() + * [Tests and Validation]() + +* Quantower Charts + * [Installation]() + * [Quick Start Guide]() + * [Using VS Code for QuanTower coding](setup/vscode.md) + * [Using DotPeek](setup/dotpeek.md) + * [Creating Custom Indicators]() + * [Inspecting Quantower Internals]() + +* [Available Indicators](indicators/indicators.md) + * Basic Transforms + * Numerical Analysis + * Errors + * Moving Averages * [AFIRMA - Adaptive Filtering Integrated Recursive Moving Average](indicators/averages/afirma/afirma.md) * [Calculation](indicators/averages/afirma/calc.md) * [Analysis](indicators/averages/afirma/analysis.md) @@ -14,7 +41,6 @@ * [Calculation](indicators/averages/ama/calc.md) * [Analysis](indicators/averages/ama/analysis.md) * [Charts](indicators/averages/ama/charts.md) - * [Convolutiuon] * [DEMA - Double Exponential Moving Average](indicators/averages/dema/dema.md) * [Calculation](indicators/averages/dema/calc.md) * [Analysis](indicators/averages/dema/analysis.md) @@ -61,11 +87,9 @@ * VIDYA - Variable Index Dynamic Average * WMA - Weighted Moving Average * ZLEMA - Weighted Moving Average - * Basic Data Transforms - * [Statistics & Numerical Analysis](indicators/statistics/list.md) + * Trends + * Momentum + * Oscillators * Volatility * Volume - * Momentum & Oscillators -* Development - * [VS Code](setup/vscode.md) - * [DotPeek](setup/dotpeek.md) + diff --git a/docs/index.html b/docs/index.html index 4c1158b0..c98425ed 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,76 +2,38 @@ - Document + QuanTAlib Documentation - + - - - - +
- - - - - - - + + + + + + - \ No newline at end of file + diff --git a/docs/styles.css b/docs/styles.css new file mode 100644 index 00000000..7b7c45aa --- /dev/null +++ b/docs/styles.css @@ -0,0 +1,1602 @@ +:root { + /* Base Styles */ + box-sizing: border-box; + background-color: var(--base-background-color); + font-size: var(--base-font-size); + font-weight: var(--base-font-weight); + line-height: var(--base-line-height); + letter-spacing: var(--base-letter-spacing); + color: var(--base-color); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + /* Base Colors */ + --mono-hue: 201; + --mono-saturation: 18%; + --mono-shade3: hsl(var(--mono-hue), var(--mono-saturation), 13%); + --mono-shade2: hsl(var(--mono-hue), var(--mono-saturation), 15%); + --mono-shade1: hsl(var(--mono-hue), var(--mono-saturation), 17%); + --mono-base: hsl(var(--mono-hue), var(--mono-saturation), 19%); + --mono-tint1: hsl(var(--mono-hue), var(--mono-saturation), 25%); + --mono-tint2: hsl(var(--mono-hue), var(--mono-saturation), 35%); + --mono-tint3: hsl(var(--mono-hue), var(--mono-saturation), 43%); + + /* Theme Colors */ + --theme-hue: 204; + --theme-saturation: 90%; + --theme-lightness: 45%; + --theme-color: hsl(var(--theme-hue), var(--theme-saturation), var(--theme-lightness)); + + /* Base Variables */ + --base-background-color: var(--mono-base); + --base-color: #d3d3d3; + --base-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --base-font-size: 16px; + --base-font-weight: normal; + --base-line-height: 1.7; + + /* Modular Scale */ + --modular-scale: 1.333; + --modular-scale--2: calc(var(--modular-scale--1) / var(--modular-scale)); + --modular-scale--1: calc(var(--modular-scale-1) / var(--modular-scale)); + --modular-scale-1: 1rem; + --modular-scale-2: calc(var(--modular-scale-1) * var(--modular-scale)); + --modular-scale-3: calc(var(--modular-scale-2) * var(--modular-scale)); + --modular-scale-4: calc(var(--modular-scale-3) * var(--modular-scale)); + --modular-scale-5: calc(var(--modular-scale-4) * var(--modular-scale)); + + /* Font Sizes */ + --font-size-xxxl: var(--modular-scale-5); + --font-size-xxl: var(--modular-scale-4); + --font-size-xl: var(--modular-scale-3); + --font-size-l: var(--modular-scale-2); + --font-size-m: var(--modular-scale-1); + --font-size-s: var(--modular-scale--1); + --font-size-xs: var(--modular-scale--2); + + /* Headings */ + --heading-color: #fff; + --heading-font-weight: var(--strong-font-weight); + --heading-margin: 2.5rem 0 0; + --heading-h1-font-size: var(--font-size-xxl); + --heading-h2-font-size: var(--font-size-xl); + --heading-h3-font-size: var(--font-size-l); + --heading-h4-font-size: var(--font-size-m); + --heading-h5-font-size: var(--font-size-s); + --heading-h6-font-size: var(--font-size-xs); + + /* Blockquote */ + --blockquote-background: var(--mono-shade2); + --blockquote-border-color: var(--theme-color); + --blockquote-border-style: solid; + --blockquote-border-width: 0 0 0 4px; + --blockquote-border-radius: 0 var(--border-radius-m) var(--border-radius-m) 0; + --blockquote-padding: 1.5em; + + /* Code */ + --code-font-family: Inconsolata, Consolas, Menlo, Monaco, "Andale Mono WT", "Andale Mono", "Lucida Console", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; + --code-font-size: calc(var(--font-size-m) * 0.95); + --code-font-weight: normal; + --code-tab-size: 4; + --code-block-border-radius: var(--border-radius-m); + --code-block-padding: 1.75em 1.5em 1.5em 1.5em; + --code-inline-background: var(--mono-tint1); + --code-inline-border-radius: var(--border-radius-s); + --code-inline-padding: 0.125em 0.4em; + + /* Notice */ + --notice-background: var(--mono-shade2); + --notice-border-radius: 0 var(--border-radius-m) var(--border-radius-m) 0; + --notice-border-width: 0 0 0 4px; + --notice-padding: 1em 1.5em 1em 3em; + + /* Table */ + --table-cell-border-color: var(--mono-tint1); + --table-cell-border-width: 1px 0; + --table-cell-padding: 0.75em 0.5em; + --table-row-odd-background: var(--mono-shade2); + + /* Layout */ + --content-max-width: 55em; + + /* Cover */ + --cover-margin: 0 auto; + --cover-max-width: 40em; + --cover-background-color: var(--base-background-color); + --cover-background-image: radial-gradient(ellipse at center bottom, var(--mono-tint3), transparent); + + /* Navbar */ + --navbar-menu-background: var(--mono-tint1); + --navbar-menu-border-radius: var(--border-radius-m); + --navbar-menu-padding: 0.5em; + + /* Sidebar */ + --sidebar-background: var(--mono-shade1); + --sidebar-border-color: var(--mono-tint1); + --sidebar-border-width: 0 1px 0 0; + --sidebar-padding: 0 25px; + --sidebar-width: 17rem; + + /* Search */ + --search-margin: 1.5rem 0 0; + --search-input-background-color: var(--mono-shade2); + --search-input-border-color: var(--mono-tint1); + --search-input-padding: 0.5em; + + /* Misc UI */ + --border-radius-s: 2px; + --border-radius-m: 4px; + --duration-fast: 0.25s; + --duration-medium: 0.5s; + --duration-slow: 1s; +} + +.github-corner{ + position:absolute; + z-index:40; + top:0; + right:0; + border-bottom:0; + text-decoration:none +} +.github-corner svg{ + height:70px; + width:70px; + fill:var(--theme-color); + color:var(--base-background-color) +} +.github-corner:hover .octo-arm{ + -webkit-animation:octocat-wave 560ms ease-in-out; + animation:octocat-wave 560ms ease-in-out +} +@-webkit-keyframes octocat-wave{ + 0%,100%{ + transform:rotate(0) + } + 20%,60%{ + transform:rotate(-25deg) + } + 40%,80%{ + transform:rotate(10deg) + } +} +@keyframes octocat-wave{ + 0%,100%{ + transform:rotate(0) + } + 20%,60%{ + transform:rotate(-25deg) + } + 40%,80%{ + transform:rotate(10deg) + } +} +.progress{ + position:fixed; + z-index:2147483647; + top:0; + left:0; + right:0; + height:3px; + width:0; + background-color:var(--theme-color); + transition:width var(--duration-fast),opacity calc(var(--duration-fast)*2) +} +body.ready-transition:after,body.ready-transition>*:not(.progress){ + opacity:0; + transition:opacity var(--spinner-transition-duration) +} +body.ready-transition:after{ + content:""; + position:absolute; + z-index:1000; + top:calc(50% - var(--spinner-size)/2); + left:calc(50% - var(--spinner-size)/2); + height:var(--spinner-size); + width:var(--spinner-size); + border:var(--spinner-track-width, 0) solid var(--spinner-track-color); + border-left-color:var(--theme-color); + border-radius:50%; + -webkit-animation:spinner var(--duration-slow) infinite linear; + animation:spinner var(--duration-slow) infinite linear +} +body.ready-transition.ready-spinner:after{ + opacity:1 +} +body.ready-transition.ready-fix:after{ + opacity:0 +} +body.ready-transition.ready-fix>*:not(.progress){ + opacity:1; + transition-delay:var(--spinner-transition-duration) +} +@-webkit-keyframes spinner{ + 0%{ + transform:rotate(0deg) + } + 100%{ + transform:rotate(360deg) + } +} +@keyframes spinner{ + 0%{ + transform:rotate(0deg) + } + 100%{ + transform:rotate(360deg) + } +} +*,*:before,*:after{ + box-sizing:inherit; + font-size:inherit; + -webkit-overflow-scrolling:touch; + -webkit-tap-highlight-color:rgba(0,0,0,0); + -webkit-text-size-adjust:none; + -webkit-touch-callout:none +} +html,button,input,optgroup,select,textarea{ + font-family:var(--base-font-family) +} +button,input,optgroup,select,textarea{ + font-size:100%; + margin:0 +} +a{ + text-decoration:none; + -webkit-text-decoration-skip:ink; + text-decoration-skip-ink:auto +} +body{ + margin:0 +} +hr{ + height:0; + margin:2em 0; + border:none; + border-bottom:var(--hr-border, 0) +} +img{ + max-width:100%; + border:0 +} +main{ + display:block; + position:relative; + overflow-x:hidden; + min-height:100vh +} +main.hidden{ + display:none +} +mark{ + background:var(--mark-background); + color:var(--mark-color) +} +pre{ + font-family:var(--pre-font-family); + font-size:var(--pre-font-size); + font-weight:var(--pre-font-weight); + line-height:var(--pre-line-height) +} +small{ + display:inline-block; + font-size:var(--small-font-size) +} +strong{ + font-weight:var(--strong-font-weight); + color:var(--strong-color, currentColor) +} +sub,sup{ + font-size:var(--subsup-font-size); + line-height:0; + position:relative; + vertical-align:baseline +} +sub{ + bottom:-0.25em +} +sup{ + top:-0.5em +} +body:not([data-platform^=Mac]) *{ + scrollbar-color:hsla(var(--mono-hue), var(--mono-saturation), 50%, 0.3) hsla(var(--mono-hue), var(--mono-saturation), 50%, 0.1); + scrollbar-width:thin +} +body:not([data-platform^=Mac]) * ::-webkit-scrollbar{ + width:5px; + height:5px +} +body:not([data-platform^=Mac]) * ::-webkit-scrollbar-thumb{ + background:hsla(var(--mono-hue), var(--mono-saturation), 50%, 0.3) +} +body:not([data-platform^=Mac]) * ::-webkit-scrollbar-track{ + background:hsla(var(--mono-hue), var(--mono-saturation), 50%, 0.1) +} +::-moz-selection{ + background:var(--selection-color) +} +::selection{ + background:var(--selection-color) +} +.emoji{ + height:var(--emoji-size); + vertical-align:middle +} +.task-list-item{ + list-style:none +} +.task-list-item input{ + margin-right:.5em; + margin-left:0; + vertical-align:.075em +} +.markdown-section code[class*=lang-],.markdown-section pre[data-lang]{ + font-family:var(--code-font-family); + font-size:var(--code-font-size); + font-weight:var(--code-font-weight); + letter-spacing:normal; + line-height:var(--code-block-line-height); + -moz-tab-size:var(--code-tab-size); + -o-tab-size:var(--code-tab-size); + tab-size:var(--code-tab-size); + text-align:left; + white-space:pre; + word-spacing:normal; + word-wrap:normal; + word-break:normal; + -webkit-hyphens:none; + hyphens:none +} +.markdown-section pre[data-lang]{ + position:relative; + overflow:hidden; + margin:var(--code-block-margin); + padding:0; + border-radius:var(--code-block-border-radius) +} +.markdown-section pre[data-lang]::after{ + content:attr(data-lang); + position:absolute; + top:.75em; + right:.75em; + opacity:.6; + color:inherit; + font-size:var(--font-size-s); + line-height:1 +} +.markdown-section pre[data-lang] code{ + display:block; + overflow:auto; + padding:var(--code-block-padding) +} +code[class*=lang-],pre[data-lang]{ + color:var(--code-theme-text) +} +pre[data-lang]::-moz-selection,pre[data-lang] ::-moz-selection,code[class*=lang-]::-moz-selection,code[class*=lang-] ::-moz-selection{ + background:var(--code-theme-selection, var(--selection-color)) +} +pre[data-lang]::selection,pre[data-lang] ::selection,code[class*=lang-]::selection,code[class*=lang-] ::selection{ + background:var(--code-theme-selection, var(--selection-color)) +} +:not(pre)>code[class*=lang-],pre[data-lang]{ + background:var(--code-theme-background) +} +.namespace{ + opacity:.7 +} +.token.comment,.token.prolog,.token.doctype,.token.cdata{ + color:var(--code-theme-comment) +} +.token.punctuation{ + color:var(--code-theme-punctuation) +} +.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol,.token.deleted{ + color:var(--code-theme-tag) +} +.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{ + color:var(--code-theme-selector) +} +.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string{ + color:var(--code-theme-operator) +} +.token.atrule,.token.attr-value,.token.keyword{ + color:var(--code-theme-keyword) +} +.token.function{ + color:var(--code-theme-function) +} +.token.regex,.token.important,.token.variable{ + color:var(--code-theme-variable) +} +.token.important,.token.bold{ + font-weight:bold +} +.token.italic{ + font-style:italic +} +.token.entity{ + cursor:help +} +.markdown-section{ + position:relative; + max-width:var(--content-max-width); + margin:0 auto; + padding:2rem 45px +} +.app-nav:not(:empty)~main .markdown-section{ + padding-top:3.5rem +} +.markdown-section figure,.markdown-section p,.markdown-section ol,.markdown-section ul{ + margin:1em 0 +} +.markdown-section ol,.markdown-section ul{ + padding-left:1.5rem +} +.markdown-section ol ol,.markdown-section ol ul,.markdown-section ul ol,.markdown-section ul ul{ + margin-top:.15rem; + margin-bottom:.15rem +} +.markdown-section a{ + border-bottom:var(--link-border-bottom); + color:var(--link-color); + -webkit-text-decoration:var(--link-text-decoration); + text-decoration:var(--link-text-decoration); + -webkit-text-decoration-color:var(--link-text-decoration-color); + text-decoration-color:var(--link-text-decoration-color) +} +.markdown-section a:hover{ + border-bottom:var(--link-border-bottom--hover, var(--link-border-bottom, 0)); + color:var(--link-color--hover, var(--link-color)); + -webkit-text-decoration:var(--link-text-decoration--hover, var(--link-text-decoration)); + text-decoration:var(--link-text-decoration--hover, var(--link-text-decoration)); + -webkit-text-decoration-color:var(--link-text-decoration-color--hover, var(--link-text-decoration-color)); + text-decoration-color:var(--link-text-decoration-color--hover, var(--link-text-decoration-color)) +} +.markdown-section a.anchor{ + border-bottom:0; + color:inherit; + text-decoration:none +} +.markdown-section a.anchor:hover{ + text-decoration:underline +} +.markdown-section blockquote{ + overflow:visible; + margin:2em 0; + padding:var(--blockquote-padding); + border-width:var(--blockquote-border-width, 0); + border-style:var(--blockquote-border-style); + border-color:var(--blockquote-border-color); + border-radius:var(--blockquote-border-radius); + background:var(--blockquote-background); + color:var(--blockquote-color); + font-family:var(--blockquote-font-family); + font-size:var(--blockquote-font-size); + font-style:var(--blockquote-font-style); + font-weight:var(--blockquote-font-weight); + quotes:"“" "”" "‘" "’" +} +.markdown-section blockquote em{ + font-family:var(--blockquote-em-font-family); + font-size:var(--blockquote-em-font-size); + font-style:var(--blockquote-em-font-style); + font-weight:var(--blockquote-em-font-weight) +} +.markdown-section blockquote p:first-child{ + margin-top:0 +} +.markdown-section blockquote p:first-child:before,.markdown-section blockquote p:first-child:after{ + color:var(--blockquote-quotes-color); + font-family:var(--blockquote-quotes-font-family); + font-size:var(--blockquote-quotes-font-size); + line-height:0 +} +.markdown-section blockquote p:first-child:before{ + content:var(--blockquote-quotes-open); + margin-right:.15em; + vertical-align:-0.45em +} +.markdown-section blockquote p:first-child:after{ + content:var(--blockquote-quotes-close); + margin-left:.15em; + vertical-align:-0.55em +} +.markdown-section blockquote p:last-child{ + margin-bottom:0 +} +.markdown-section code{ + font-family:var(--code-font-family); + font-size:var(--code-font-size); + font-weight:var(--code-font-weight); + line-height:inherit +} +.markdown-section code:not([class*=lang-]):not([class*=language-]){ + margin:var(--code-inline-margin); + padding:var(--code-inline-padding); + border-radius:var(--code-inline-border-radius); + background:var(--code-inline-background); + color:var(--code-inline-color, currentColor); + white-space:nowrap +} +.markdown-section h1:first-child,.markdown-section h2:first-child,.markdown-section h3:first-child,.markdown-section h4:first-child,.markdown-section h5:first-child,.markdown-section h6:first-child{ + margin-top:0 +} +.markdown-section h1 a[data-id],.markdown-section h2 a[data-id],.markdown-section h3 a[data-id],.markdown-section h4 a[data-id],.markdown-section h5 a[data-id],.markdown-section h6 a[data-id]{ + display:inline-block +} +.markdown-section h1 code,.markdown-section h2 code,.markdown-section h3 code,.markdown-section h4 code,.markdown-section h5 code,.markdown-section h6 code{ + font-size:.875em +} +.markdown-section h1+h2,.markdown-section h1+h3,.markdown-section h1+h4,.markdown-section h1+h5,.markdown-section h1+h6,.markdown-section h2+h3,.markdown-section h2+h4,.markdown-section h2+h5,.markdown-section h2+h6,.markdown-section h3+h4,.markdown-section h3+h5,.markdown-section h3+h6,.markdown-section h4+h5,.markdown-section h4+h6,.markdown-section h5+h6{ + margin-top:1rem +} +.markdown-section h1{ + margin:var(--heading-h1-margin, var(--heading-margin)); + padding:var(--heading-h1-padding, var(--heading-padding)); + border-width:var(--heading-h1-border-width, 0); + border-style:var(--heading-h1-border-style); + border-color:var(--heading-h1-border-color); + font-family:var(--heading-h1-font-family, var(--heading-font-family)); + font-size:var(--heading-h1-font-size); + font-weight:var(--heading-h1-font-weight, var(--heading-font-weight)); + line-height:var(--base-line-height); + color:var(--heading-h1-color, var(--heading-color)) +} +.markdown-section h2{ + margin:var(--heading-h2-margin, var(--heading-margin)); + padding:var(--heading-h2-padding, var(--heading-padding)); + border-width:var(--heading-h2-border-width, 0); + border-style:var(--heading-h2-border-style); + border-color:var(--heading-h2-border-color); + font-family:var(--heading-h2-font-family, var(--heading-font-family)); + font-size:var(--heading-h2-font-size); + font-weight:var(--heading-h2-font-weight, var(--heading-font-weight)); + line-height:var(--base-line-height); + color:var(--heading-h2-color, var(--heading-color)) +} +.markdown-section h3{ + margin:var(--heading-h3-margin, var(--heading-margin)); + padding:var(--heading-h3-padding, var(--heading-padding)); + border-width:var(--heading-h3-border-width, 0); + border-style:var(--heading-h3-border-style); + border-color:var(--heading-h3-border-color); + font-family:var(--heading-h3-font-family, var(--heading-font-family)); + font-size:var(--heading-h3-font-size); + font-weight:var(--heading-h3-font-weight, var(--heading-font-weight)); + color:var(--heading-h3-color, var(--heading-color)) +} +.markdown-section h4{ + margin:var(--heading-h4-margin, var(--heading-margin)); + padding:var(--heading-h4-padding, var(--heading-padding)); + border-width:var(--heading-h4-border-width, 0); + border-style:var(--heading-h4-border-style); + border-color:var(--heading-h4-border-color); + font-family:var(--heading-h4-font-family, var(--heading-font-family)); + font-size:var(--heading-h4-font-size); + font-weight:var(--heading-h4-font-weight, var(--heading-font-weight)); + color:var(--heading-h4-color, var(--heading-color)) +} +.markdown-section h5{ + margin:var(--heading-h5-margin, var(--heading-margin)); + padding:var(--heading-h5-padding, var(--heading-padding)); + border-width:var(--heading-h5-border-width, 0); + border-style:var(--heading-h5-border-style); + border-color:var(--heading-h5-border-color); + font-family:var(--heading-h5-font-family, var(--heading-font-family)); + font-size:var(--heading-h5-font-size); + font-weight:var(--heading-h5-font-weight, var(--heading-font-weight)); + color:var(--heading-h5-color, var(--heading-color)) +} +.markdown-section h6{ + margin:var(--heading-h6-margin, var(--heading-margin)); + padding:var(--heading-h6-padding, var(--heading-padding)); + border-width:var(--heading-h6-border-width, 0); + border-style:var(--heading-h6-border-style); + border-color:var(--heading-h6-border-color); + font-family:var(--heading-h6-font-family, var(--heading-font-family)); + font-size:var(--heading-h6-font-size); + font-weight:var(--heading-h6-font-weight, var(--heading-font-weight)); + color:var(--heading-h6-color, var(--heading-color)) +} +.markdown-section iframe{ + margin:1em 0 +} +.markdown-section img{ + max-width:100% +} +.markdown-section kbd{ + display:inline-block; + min-width:var(--kbd-min-width); + margin:var(--kbd-margin); + padding:var(--kbd-padding); + border:var(--kbd-border); + border-radius:var(--kbd-border-radius); + background:var(--kbd-background); + font-family:inherit; + font-size:var(--kbd-font-size); + text-align:center; + letter-spacing:0; + line-height:1; + color:var(--kbd-color) +} +.markdown-section kbd+kbd{ + margin-left:-0.15em +} +.markdown-section table{ + display:block; + overflow:auto; + margin:1rem 0; + border-spacing:0; + border-collapse:collapse +} +.markdown-section th,.markdown-section td{ + padding:var(--table-cell-padding) +} +.markdown-section th:not([align]){ + text-align:left +} +.markdown-section thead{ + border-color:var(--table-head-border-color); + border-style:solid; + border-width:var(--table-head-border-width, 0); + background:var(--table-head-background) +} +.markdown-section th{ + font-weight:var(--table-head-font-weight); + color:var(--strong-color) +} +.markdown-section td{ + border-color:var(--table-cell-border-color); + border-style:solid; + border-width:var(--table-cell-border-width, 0) +} +.markdown-section tbody{ + border-color:var(--table-body-border-color); + border-style:solid; + border-width:var(--table-body-border-width, 0) +} +.markdown-section tbody tr:nth-child(odd){ + background:var(--table-row-odd-background) +} +.markdown-section tbody tr:nth-child(even){ + background:var(--table-row-even-background) +} +.markdown-section>ul .task-list-item{ + margin-left:-1.25em +} +.markdown-section>ul .task-list-item .task-list-item{ + margin-left:0 +} +.markdown-section .table-wrapper{ + overflow-x:auto +} +.markdown-section .table-wrapper table{ + display:table; + width:100% +} +.markdown-section .table-wrapper td::before{ + display:none +} +@media(max-width: 30em){ + .markdown-section .table-wrapper tbody,.markdown-section .table-wrapper tr,.markdown-section .table-wrapper td{ + display:block + } + .markdown-section .table-wrapper th,.markdown-section .table-wrapper td{ + border:none + } + .markdown-section .table-wrapper thead{ + display:none + } + .markdown-section .table-wrapper tr{ + border-color:var(--table-cell-border-color); + border-style:solid; + border-width:var(--table-cell-border-width, 0); + padding:var(--table-cell-padding) + } + .markdown-section .table-wrapper tr:not(:last-child){ + border-bottom:0 + } + .markdown-section .table-wrapper td{ + padding:.15em 0 .15em 8em + } + .markdown-section .table-wrapper td::before{ + display:inline-block; + width:8em; + margin-left:-8em; + font-weight:bold; + text-align:left + } +} +.markdown-section .tip,.markdown-section .warn{ + position:relative; + margin:2em 0; + padding:var(--notice-padding); + border-width:var(--notice-border-width, 0); + border-style:var(--notice-border-style); + border-color:var(--notice-border-color); + border-radius:var(--notice-border-radius); + background:var(--notice-background); + font-family:var(--notice-font-family); + font-weight:var(--notice-font-weight); + color:var(--notice-color) +} +.markdown-section .tip:before,.markdown-section .warn:before{ + display:inline-block; + position:var(--notice-before-position, relative); + top:var(--notice-before-top); + left:var(--notice-before-left); + height:var(--notice-before-height); + width:var(--notice-before-width); + margin:var(--notice-before-margin); + padding:var(--notice-before-padding); + border-radius:var(--notice-before-border-radius); + line-height:var(--notice-before-line-height); + font-family:var(--notice-before-font-family); + font-size:var(--notice-before-font-size); + font-weight:var(--notice-before-font-weight); + text-align:center +} +.markdown-section .tip{ + border-width:var(--notice-important-border-width, var(--notice-border-width, 0)); + border-style:var(--notice-important-border-style, var(--notice-border-style)); + border-color:var(--notice-important-border-color, var(--notice-border-color)); + background:var(--notice-important-background, var(--notice-background)); + color:var(--notice-important-color, var(--notice-color)) +} +.markdown-section .tip:before{ + content:var(--notice-important-before-content, var(--notice-before-content)); + background:var(--notice-important-before-background, var(--notice-before-background)); + color:var(--notice-important-before-color, var(--notice-before-color)) +} +.markdown-section .warn{ + border-width:var(--notice-tip-border-width, var(--notice-border-width, 0)); + border-style:var(--notice-tip-border-style, var(--notice-border-style)); + border-color:var(--notice-tip-border-color, var(--notice-border-color)); + background:var(--notice-tip-background, var(--notice-background)); + color:var(--notice-tip-color, var(--notice-color)) +} +.markdown-section .warn:before{ + content:var(--notice-tip-before-content, var(--notice-before-content)); + background:var(--notice-tip-before-background, var(--notice-before-background)); + color:var(--notice-tip-before-color, var(--notice-before-color)) +} +.cover{ + display:none; + position:relative; + z-index:20; + min-height:100vh; + flex-direction:column; + align-items:center; + justify-content:center; + padding:calc(var(--cover-border-inset, 0px) + var(--cover-border-width, 0px)); + color:var(--cover-color); + text-align:var(--cover-text-align) +} +@media screen and (-ms-high-contrast: active),screen and (-ms-high-contrast: none){ + .cover{ + height:100vh + } +} +.cover:before,.cover:after{ + content:""; + position:absolute +} +.cover:before{ + top:0; + bottom:0; + left:0; + right:0; + background-blend-mode:var(--cover-background-blend-mode); + background-color:var(--cover-background-color); + background-image:var(--cover-background-image); + background-position:var(--cover-background-position); + background-repeat:var(--cover-background-repeat); + background-size:var(--cover-background-size) +} +.cover:after{ + top:var(--cover-border-inset, 0); + bottom:var(--cover-border-inset, 0); + left:var(--cover-border-inset, 0); + right:var(--cover-border-inset, 0); + border-width:var(--cover-border-width, 0); + border-style:solid; + border-color:var(--cover-border-color) +} +.cover a{ + border-bottom:var(--cover-link-border-bottom); + color:var(--cover-link-color); + -webkit-text-decoration:var(--cover-link-text-decoration); + text-decoration:var(--cover-link-text-decoration); + -webkit-text-decoration-color:var(--cover-link-text-decoration-color); + text-decoration-color:var(--cover-link-text-decoration-color) +} +.cover a:hover{ + border-bottom:var(--cover-link-border-bottom--hover, var(--cover-link-border-bottom)); + color:var(--cover-link-color--hover, var(--cover-link-color)); + -webkit-text-decoration:var(--cover-link-text-decoration--hover, var(--cover-link-text-decoration)); + text-decoration:var(--cover-link-text-decoration--hover, var(--cover-link-text-decoration)); + -webkit-text-decoration-color:var(--cover-link-text-decoration-color--hover, var(--cover-link-text-decoration-color)); + text-decoration-color:var(--cover-link-text-decoration-color--hover, var(--cover-link-text-decoration-color)) +} +.cover h1{ + color:var(--cover-heading-color); + position:relative; + margin:0; + font-size:var(--cover-heading-font-size); + font-weight:var(--cover-heading-font-weight); + line-height:1.2 +} +.cover h1 a,.cover h1 a:hover{ + display:block; + border-bottom:none; + color:inherit; + text-decoration:none +} +.cover h1 small{ + position:absolute; + bottom:0; + margin-left:.5em +} +.cover h1 span{ + font-size:calc(var(--cover-heading-font-size-min)*1px) +} +@media(min-width: 26em){ + .cover h1 span{ + font-size:calc(var(--cover-heading-font-size-min)*1px + (var(--cover-heading-font-size-max) - var(--cover-heading-font-size-min))*(100vw - 420px)/604) + } +} +@media(min-width: 64em){ + .cover h1 span{ + font-size:calc(var(--cover-heading-font-size-max)*1px) + } +} +.cover blockquote{ + margin:0; + color:var(--cover-blockquote-color); + font-size:var(--cover-blockquote-font-size) +} +.cover blockquote a{ + color:inherit +} +.cover ul{ + padding:0; + list-style-type:none +} +.cover .cover-main{ + position:relative; + z-index:1; + max-width:var(--cover-max-width); + margin:var(--cover-margin); + padding:0 45px +} +.cover .cover-main>p:last-child{ + margin:1.25em -0.25em +} +.cover .cover-main>p:last-child a{ + display:block; + margin:.375em .25em; + padding:var(--cover-button-padding); + border:var(--cover-button-border); + border-radius:var(--cover-button-border-radius); + box-shadow:var(--cover-button-box-shadow); + background:var(--cover-button-background); + text-align:center; + -webkit-text-decoration:var(--cover-button-text-decoration); + text-decoration:var(--cover-button-text-decoration); + -webkit-text-decoration-color:var(--cover-button-text-decoration-color); + text-decoration-color:var(--cover-button-text-decoration-color); + color:var(--cover-button-color); + white-space:nowrap; + transition:var(--cover-button-transition) +} +.cover .cover-main>p:last-child a:hover{ + border:var(--cover-button-border--hover, var(--cover-button-border)); + box-shadow:var(--cover-button-box-shadow--hover, var(--cover-button-box-shadow)); + background:var(--cover-button-background--hover, var(--cover-button-background)); + -webkit-text-decoration:var(--cover-button-text-decoration--hover, var(--cover-button-text-decoration)); + text-decoration:var(--cover-button-text-decoration--hover, var(--cover-button-text-decoration)); + -webkit-text-decoration-color:var(--cover-button-text-decoration-color--hover, var(--cover-button-text-decoration-color)); + text-decoration-color:var(--cover-button-text-decoration-color--hover, var(--cover-button-text-decoration-color)); + color:var(--cover-button-color--hover, var(--cover-button-color)) +} +.cover .cover-main>p:last-child a:first-child{ + border:var(--cover-button-primary-border, var(--cover-button-border)); + box-shadow:var(--cover-button-primary-box-shadow, var(--cover-button-box-shadow)); + background:var(--cover-button-primary-background, var(--cover-button-background)); + -webkit-text-decoration:var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration)); + text-decoration:var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration)); + -webkit-text-decoration-color:var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color)); + text-decoration-color:var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color)); + color:var(--cover-button-primary-color, var(--cover-button-color)) +} +.cover .cover-main>p:last-child a:first-child:hover{ + border:var(--cover-button-primary-border--hover, var(--cover-button-border--hover, var(--cover-button-primary-border, var(--cover-button-border)))); + box-shadow:var(--cover-button-primary-box-shadow--hover, var(--cover-button-box-shadow--hover, var(--cover-button-primary-box-shadow, var(--cover-button-box-shadow)))); + background:var(--cover-button-primary-background--hover, var(--cover-button-background--hover, var(--cover-button-primary-background, var(--cover-button-background)))); + -webkit-text-decoration:var(--cover-button-primary-text-decoration--hover, var(--cover-button-text-decoration--hover, var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration)))); + text-decoration:var(--cover-button-primary-text-decoration--hover, var(--cover-button-text-decoration--hover, var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration)))); + -webkit-text-decoration-color:var(--cover-button-primary-text-decoration-color--hover, var(--cover-button-text-decoration-color--hover, var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color)))); + text-decoration-color:var(--cover-button-primary-text-decoration-color--hover, var(--cover-button-text-decoration-color--hover, var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color)))); + color:var(--cover-button-primary-color--hover, var(--cover-button-color--hover, var(--cover-button-primary-color, var(--cover-button-color)))) +} +@media(min-width: 30.01em){ + .cover .cover-main>p:last-child a{ + display:inline-block + } +} +.cover .mask{ + visibility:var(--cover-background-mask-visibility, hidden); + position:absolute; + top:0; + bottom:0; + left:0; + right:0; + background-color:var(--cover-background-mask-color); + opacity:var(--cover-background-mask-opacity) +} +.cover.has-mask .mask{ + visibility:visible +} +.cover.show{ + display:flex +} +.app-nav{ + position:absolute; + z-index:30; + top:calc(35px - .5em*var(--base-line-height)); + left:45px; + right:80px; + text-align:right +} +.app-nav.no-badge{ + right:45px +} +.app-nav li>img,.app-nav li>a>img{ + margin-top:-0.25em; + vertical-align:middle +} +.app-nav li>img:first-child,.app-nav li>a>img:first-child{ + margin-right:.5em +} +.app-nav ul,.app-nav li{ + margin:0; + padding:0; + list-style:none +} +.app-nav li{ + position:relative +} +.app-nav li a{ + display:block; + line-height:1; + transition:var(--navbar-root-transition) +} +.app-nav>ul>li{ + display:inline-block; + margin:var(--navbar-root-margin) +} +.app-nav>ul>li:first-child{ + margin-left:0 +} +.app-nav>ul>li:last-child{ + margin-right:0 +} +.app-nav>ul>li>a,.app-nav>ul>li>span{ + padding:var(--navbar-root-padding); + border-width:var(--navbar-root-border-width, 0); + border-style:var(--navbar-root-border-style); + border-color:var(--navbar-root-border-color); + border-radius:var(--navbar-root-border-radius); + background:var(--navbar-root-background); + color:var(--navbar-root-color); + -webkit-text-decoration:var(--navbar-root-text-decoration); + text-decoration:var(--navbar-root-text-decoration); + -webkit-text-decoration-color:var(--navbar-root-text-decoration-color); + text-decoration-color:var(--navbar-root-text-decoration-color) +} +.app-nav>ul>li>a:hover,.app-nav>ul>li>span:hover{ + background:var(--navbar-root-background--hover, var(--navbar-root-background)); + border-style:var(--navbar-root-border-style--hover, var(--navbar-root-border-style)); + border-color:var(--navbar-root-border-color--hover, var(--navbar-root-border-color)); + color:var(--navbar-root-color--hover, var(--navbar-root-color)); + -webkit-text-decoration:var(--navbar-root-text-decoration--hover, var(--navbar-root-text-decoration)); + text-decoration:var(--navbar-root-text-decoration--hover, var(--navbar-root-text-decoration)); + -webkit-text-decoration-color:var(--navbar-root-text-decoration-color--hover, var(--navbar-root-text-decoration-color)); + text-decoration-color:var(--navbar-root-text-decoration-color--hover, var(--navbar-root-text-decoration-color)) +} +.app-nav>ul>li>a:not(:last-child),.app-nav>ul>li>span:not(:last-child){ + padding:var(--navbar-menu-root-padding, var(--navbar-root-padding)); + background:var(--navbar-menu-root-background, var(--navbar-root-background)) +} +.app-nav>ul>li>a:not(:last-child):hover,.app-nav>ul>li>span:not(:last-child):hover{ + background:var(--navbar-menu-root-background--hover, var(--navbar-menu-root-background, var(--navbar-root-background--hover, var(--navbar-root-background)))) +} +.app-nav>ul>li>a.active{ + background:var(--navbar-root-background--active, var(--navbar-root-background)); + border-style:var(--navbar-root-border-style--active, var(--navbar-root-border-style)); + border-color:var(--navbar-root-border-color--active, var(--navbar-root-border-color)); + color:var(--navbar-root-color--active, var(--navbar-root-color)); + -webkit-text-decoration:var(--navbar-root-text-decoration--active, var(--navbar-root-text-decoration)); + text-decoration:var(--navbar-root-text-decoration--active, var(--navbar-root-text-decoration)); + -webkit-text-decoration-color:var(--navbar-root-text-decoration-color--active, var(--navbar-root-text-decoration-color)); + text-decoration-color:var(--navbar-root-text-decoration-color--active, var(--navbar-root-text-decoration-color)) +} +.app-nav>ul>li>a.active:not(:last-child):hover{ + background:var(--navbar-menu-root-background--active, var(--navbar-menu-root-background, var(--navbar-root-background--active, var(--navbar-root-background)))) +} +.app-nav>ul>li ul{ + visibility:hidden; + position:absolute; + top:100%; + right:50%; + overflow-y:auto; + box-sizing:border-box; + max-height:50vh; + padding:var(--navbar-menu-padding); + border-width:var(--navbar-menu-border-width, 0); + border-style:solid; + border-color:var(--navbar-menu-border-color); + border-radius:var(--navbar-menu-border-radius); + background:var(--navbar-menu-background); + box-shadow:var(--navbar-menu-box-shadow); + text-align:left; + white-space:nowrap; + opacity:0; + transform:translate(50%, -0.35em); + transition:var(--navbar-menu-transition) +} +.app-nav>ul>li ul li{ + white-space:nowrap +} +.app-nav>ul>li ul a{ + margin:var(--navbar-menu-link-margin); + padding:var(--navbar-menu-link-padding); + border-width:var(--navbar-menu-link-border-width, 0); + border-style:var(--navbar-menu-link-border-style); + border-color:var(--navbar-menu-link-border-color); + border-radius:var(--navbar-menu-link-border-radius); + background:var(--navbar-menu-link-background); + color:var(--navbar-menu-link-color); + -webkit-text-decoration:var(--navbar-menu-link-text-decoration); + text-decoration:var(--navbar-menu-link-text-decoration); + -webkit-text-decoration-color:var(--navbar-menu-link-text-decoration-color); + text-decoration-color:var(--navbar-menu-link-text-decoration-color) +} +.app-nav>ul>li ul a:hover{ + background:var(--navbar-menu-link-background--hover, var(--navbar-menu-link-background)); + border-style:var(--navbar-menu-link-border-style--hover, var(--navbar-menu-link-border-style)); + border-color:var(--navbar-menu-link-border-color--hover, var(--navbar-menu-link-border-color)); + color:var(--navbar-menu-link-color--hover, var(--navbar-menu-link-color)); + -webkit-text-decoration:var(--navbar-menu-link-text-decoration--hover, var(--navbar-menu-link-text-decoration)); + text-decoration:var(--navbar-menu-link-text-decoration--hover, var(--navbar-menu-link-text-decoration)); + -webkit-text-decoration-color:var(--navbar-menu-link-text-decoration-color--hover, var(--navbar-menu-link-text-decoration-color)); + text-decoration-color:var(--navbar-menu-link-text-decoration-color--hover, var(--navbar-menu-link-text-decoration-color)) +} +.app-nav>ul>li ul a.active{ + background:var(--navbar-menu-link-background--active, var(--navbar-menu-link-background)); + border-style:var(--navbar-menu-link-border-style--active, var(--navbar-menu-link-border-style)); + border-color:var(--navbar-menu-link-border-color--active, var(--navbar-menu-link-border-color)); + color:var(--navbar-menu-link-color--active, var(--navbar-menu-link-color)); + -webkit-text-decoration:var(--navbar-menu-link-text-decoration--active, var(--navbar-menu-link-text-decoration)); + text-decoration:var(--navbar-menu-link-text-decoration--active, var(--navbar-menu-link-text-decoration)); + -webkit-text-decoration-color:var(--navbar-menu-link-text-decoration-color--active, var(--navbar-menu-link-text-decoration-color)); + text-decoration-color:var(--navbar-menu-link-text-decoration-color--active, var(--navbar-menu-link-text-decoration-color)) +} +.app-nav>ul>li:hover ul,.app-nav>ul>li:focus ul,.app-nav>ul>li.focus-within ul{ + visibility:visible; + opacity:1; + transform:translate(50%, 0) +} +@media(min-width: 48em){ + nav.app-nav{ + margin-left:var(--sidebar-width) + } +} +.sidebar,.sidebar-toggle,.sidebar+.content{ + transition:all var(--sidebar-transition-duration) ease-out +} +@media(min-width: 48em){ + .sidebar+.content{ + margin-left:var(--sidebar-width) + } +} +.sidebar{ + display:flex; + flex-direction:column; + position:fixed; + z-index:10; + top:0; + right:100%; + overflow-x:hidden; + overflow-y:auto; + height:100vh; + width:var(--sidebar-width); + padding:var(--sidebar-padding); + border-width:var(--sidebar-border-width); + border-style:solid; + border-color:var(--sidebar-border-color); + background:var(--sidebar-background) +} +.sidebar>h1{ + margin:0; + margin:var(--sidebar-name-margin); + padding:var(--sidebar-name-padding); + background:var(--sidebar-name-background); + color:var(--sidebar-name-color); + font-family:var(--sidebar-name-font-family); + font-size:var(--sidebar-name-font-size); + font-weight:var(--sidebar-name-font-weight); + text-align:var(--sidebar-name-text-align) +} +.sidebar>h1 img{ + max-width:100% +} +.sidebar>h1 .app-name-link{ + color:var(--sidebar-name-color) +} +body:not([data-platform^=Mac]) .sidebar::-webkit-scrollbar{ + width:5px +} +body:not([data-platform^=Mac]) .sidebar::-webkit-scrollbar-thumb{ + border-radius:50vw +} +@media(min-width: 48em){ + .sidebar{ + position:absolute; + transform:translateX(var(--sidebar-width)) + } +} +@media print{ + .sidebar{ + display:none + } +} +.sidebar-nav,.sidebar nav{ + order:1; + margin:var(--sidebar-nav-margin); + padding:var(--sidebar-nav-padding); + background:var(--sidebar-nav-background) +} +.sidebar-nav ul,.sidebar nav ul{ + margin:0; + padding:0; + list-style:none +} +.sidebar-nav ul ul,.sidebar nav ul ul{ + margin-left:var(--sidebar-nav-indent) +} +.sidebar-nav a,.sidebar nav a{ + display:block; + overflow:hidden; + margin:var(--sidebar-nav-link-margin); + padding:var(--sidebar-nav-link-padding); + border-width:var(--sidebar-nav-link-border-width, 0); + border-style:var(--sidebar-nav-link-border-style); + border-color:var(--sidebar-nav-link-border-color); + border-radius:var(--sidebar-nav-link-border-radius); + background:var(--sidebar-nav-link-background); + color:var(--sidebar-nav-link-color); + font-weight:var(--sidebar-nav-link-font-weight); + white-space:nowrap; + -webkit-text-decoration:var(--sidebar-nav-link-text-decoration); + text-decoration:var(--sidebar-nav-link-text-decoration); + -webkit-text-decoration-color:var(--sidebar-nav-link-text-decoration-color); + text-decoration-color:var(--sidebar-nav-link-text-decoration-color); + text-overflow:ellipsis; + transition:var(--sidebar-nav-link-transition) +} +.sidebar-nav a img,.sidebar nav a img{ + margin-top:-0.25em; + vertical-align:middle +} +.sidebar-nav a img:first-child,.sidebar nav a img:first-child{ + margin-right:.5em +} +.sidebar-nav a:hover,.sidebar nav a:hover{ + border-width:var(--sidebar-nav-link-border-width--hover, var(--sidebar-nav-link-border-width, 0)); + border-style:var(--sidebar-nav-link-border-style--hover, var(--sidebar-nav-link-border-style)); + border-color:var(--sidebar-nav-link-border-color--hover, var(--sidebar-nav-link-border-color)); + background:var(--sidebar-nav-link-background--hover, var(--sidebar-nav-link-background)); + color:var(--sidebar-nav-link-color--hover, var(--sidebar-nav-link-color)); + font-weight:var(--sidebar-nav-link-font-weight--hover, var(--sidebar-nav-link-font-weight)); + -webkit-text-decoration:var(--sidebar-nav-link-text-decoration--hover, var(--sidebar-nav-link-text-decoration)); + text-decoration:var(--sidebar-nav-link-text-decoration--hover, var(--sidebar-nav-link-text-decoration)); + -webkit-text-decoration-color:var(--sidebar-nav-link-text-decoration-color); + text-decoration-color:var(--sidebar-nav-link-text-decoration-color) +} +.sidebar-nav ul>li>span,.sidebar-nav ul>li>strong,.sidebar nav ul>li>span,.sidebar nav ul>li>strong{ + display:block; + margin:var(--sidebar-nav-strong-margin); + padding:var(--sidebar-nav-strong-padding); + border-width:var(--sidebar-nav-strong-border-width, 0); + border-style:solid; + border-color:var(--sidebar-nav-strong-border-color); + color:var(--sidebar-nav-strong-color); + font-size:var(--sidebar-nav-strong-font-size); + font-weight:var(--sidebar-nav-strong-font-weight); + text-transform:var(--sidebar-nav-strong-text-transform) +} +.sidebar-nav ul>li>span+ul,.sidebar-nav ul>li>strong+ul,.sidebar nav ul>li>span+ul,.sidebar nav ul>li>strong+ul{ + margin-left:0 +} +.sidebar-nav ul>li:first-child>span,.sidebar-nav ul>li:first-child>strong,.sidebar nav ul>li:first-child>span,.sidebar nav ul>li:first-child>strong{ + margin-top:0 +} +.sidebar-nav::-webkit-scrollbar,.sidebar nav::-webkit-scrollbar{ + width:0 +} +@supports(width: env(safe-area-inset)){ + @media only screen and (orientation: landscape){ + .sidebar-nav,.sidebar nav{ + margin-left:calc(env(safe-area-inset-left)/2) + } + } +} +.sidebar-nav li>a:before,.sidebar-nav li>strong:before{ + display:inline-block +} +.sidebar-nav li>a{ + background-repeat:var(--sidebar-nav-pagelink-background-repeat); + background-size:var(--sidebar-nav-pagelink-background-size) +} +.sidebar-nav li>a[href^="/"]:not([href*="?id="]),.sidebar-nav li>a[href^="#/"]:not([href*="?id="]){ + transition:var(--sidebar-nav-pagelink-transition) +} +.sidebar-nav li>a[href^="/"]:not([href*="?id="]),.sidebar-nav li>a[href^="/"]:not([href*="?id="])~ul a,.sidebar-nav li>a[href^="#/"]:not([href*="?id="]),.sidebar-nav li>a[href^="#/"]:not([href*="?id="])~ul a{ + padding:var(--sidebar-nav-pagelink-padding, var(--sidebar-nav-link-padding)) +} +.sidebar-nav li>a[href^="/"]:not([href*="?id="]):only-child,.sidebar-nav li>a[href^="#/"]:not([href*="?id="]):only-child{ + background:var(--sidebar-nav-pagelink-background) +} +.sidebar-nav li>a[href^="/"]:not([href*="?id="]):not(:only-child),.sidebar-nav li>a[href^="#/"]:not([href*="?id="]):not(:only-child){ + background:var(--sidebar-nav-pagelink-background--loaded, var(--sidebar-nav-pagelink-background)) +} +.sidebar-nav li.active>a,.sidebar-nav li.collapse>a{ + border-width:var(--sidebar-nav-link-border-width--active, var(--sidebar-nav-link-border-width)); + border-style:var(--sidebar-nav-link-border-style--active, var(--sidebar-nav-link-border-style)); + border-color:var(--sidebar-nav-link-border-color--active, var(--sidebar-nav-link-border-color)); + background:var(--sidebar-nav-link-background--active, var(--sidebar-nav-link-background)); + color:var(--sidebar-nav-link-color--active, var(--sidebar-nav-link-color)); + font-weight:var(--sidebar-nav-link-font-weight--active, var(--sidebar-nav-link-font-weight)); + -webkit-text-decoration:var(--sidebar-nav-link-text-decoration--active, var(--sidebar-nav-link-text-decoration)); + text-decoration:var(--sidebar-nav-link-text-decoration--active, var(--sidebar-nav-link-text-decoration)); + -webkit-text-decoration-color:var(--sidebar-nav-link-text-decoration-color); + text-decoration-color:var(--sidebar-nav-link-text-decoration-color) +} +.sidebar-nav li.active>a[href^="/"]:not([href*="?id="]):not(:only-child),.sidebar-nav li.active>a[href^="#/"]:not([href*="?id="]):not(:only-child){ + background:var(--sidebar-nav-pagelink-background--active, var(--sidebar-nav-pagelink-background--loaded, var(--sidebar-nav-pagelink-background))) +} +.sidebar-nav li.collapse>a[href^="/"]:not([href*="?id="]):not(:only-child),.sidebar-nav li.collapse>a[href^="#/"]:not([href*="?id="]):not(:only-child){ + background:var(--sidebar-nav-pagelink-background--collapse, var(--sidebar-nav-pagelink-background--loaded, var(--sidebar-nav-pagelink-background))) +} +.sidebar-nav li.collapse .app-sub-sidebar{ + display:none +} +.sidebar-nav>ul>li>a:before{ + content:var(--sidebar-nav-link-before-content-l1, var(--sidebar-nav-link-before-content)); + margin:var(--sidebar-nav-link-before-margin-l1, var(--sidebar-nav-link-before-margin)); + color:var(--sidebar-nav-link-before-color-l1, var(--sidebar-nav-link-before-color)) +} +.sidebar-nav>ul>li.active>a:before{ + content:var(--sidebar-nav-link-before-content-l1--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l1, var(--sidebar-nav-link-before-content)))); + color:var(--sidebar-nav-link-before-color-l1--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l1, var(--sidebar-nav-link-before-color)))) +} +.sidebar-nav>ul>li>ul>li>a:before{ + content:var(--sidebar-nav-link-before-content-l2, var(--sidebar-nav-link-before-content)); + margin:var(--sidebar-nav-link-before-margin-l2, var(--sidebar-nav-link-before-margin)); + color:var(--sidebar-nav-link-before-color-l2, var(--sidebar-nav-link-before-color)) +} +.sidebar-nav>ul>li>ul>li.active>a:before{ + content:var(--sidebar-nav-link-before-content-l2--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l2, var(--sidebar-nav-link-before-content)))); + color:var(--sidebar-nav-link-before-color-l2--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l2, var(--sidebar-nav-link-before-color)))) +} +.sidebar-nav>ul>li>ul>li>ul>li>a:before{ + content:var(--sidebar-nav-link-before-content-l3, var(--sidebar-nav-link-before-content)); + margin:var(--sidebar-nav-link-before-margin-l3, var(--sidebar-nav-link-before-margin)); + color:var(--sidebar-nav-link-before-color-l3, var(--sidebar-nav-link-before-color)) +} +.sidebar-nav>ul>li>ul>li>ul>li.active>a:before{ + content:var(--sidebar-nav-link-before-content-l3--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l3, var(--sidebar-nav-link-before-content)))); + color:var(--sidebar-nav-link-before-color-l3--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l3, var(--sidebar-nav-link-before-color)))) +} +.sidebar-nav>ul>li>ul>li>ul>li>ul>li>a:before{ + content:var(--sidebar-nav-link-before-content-l4, var(--sidebar-nav-link-before-content)); + margin:var(--sidebar-nav-link-before-margin-l4, var(--sidebar-nav-link-before-margin)); + color:var(--sidebar-nav-link-before-color-l4, var(--sidebar-nav-link-before-color)) +} +.sidebar-nav>ul>li>ul>li>ul>li>ul>li.active>a:before{ + content:var(--sidebar-nav-link-before-content-l4--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l4, var(--sidebar-nav-link-before-content)))); + color:var(--sidebar-nav-link-before-color-l4--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l4, var(--sidebar-nav-link-before-color)))) +} +.sidebar-nav>:last-child{ + margin-bottom:2rem +} +.sidebar-toggle,.sidebar-toggle-button{ + width:var(--sidebar-toggle-width); + outline:none +} +.sidebar-toggle{ + position:fixed; + z-index:11; + top:0; + bottom:0; + left:0; + max-width:40px; + margin:0; + padding:0; + border:0; + background:rgba(0,0,0,0); + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; + cursor:pointer +} +.sidebar-toggle .sidebar-toggle-button{ + position:absolute; + top:var(--sidebar-toggle-offset-top); + left:var(--sidebar-toggle-offset-left); + height:var(--sidebar-toggle-height); + border-radius:var(--sidebar-toggle-border-radius); + border-width:var(--sidebar-toggle-border-width); + border-style:var(--sidebar-toggle-border-style); + border-color:var(--sidebar-toggle-border-color); + background:var(--sidebar-toggle-background, transparent); + color:var(--sidebar-toggle-icon-color) +} +.sidebar-toggle span{ + position:absolute; + top:calc(50% - var(--sidebar-toggle-icon-stroke-width)/2); + left:calc(50% - var(--sidebar-toggle-icon-width)/2); + height:var(--sidebar-toggle-icon-stroke-width); + width:var(--sidebar-toggle-icon-width); + background-color:currentColor +} +.sidebar-toggle span:nth-child(1){ + margin-top:calc(0px - var(--sidebar-toggle-icon-height)/2) +} +.sidebar-toggle span:nth-child(3){ + margin-top:calc(var(--sidebar-toggle-icon-height)/2) +} +@media(min-width: 48em){ + .sidebar-toggle{ + position:absolute; + overflow:visible; + top:var(--sidebar-toggle-offset-top); + bottom:auto; + left:0; + height:var(--sidebar-toggle-height); + transform:translateX(var(--sidebar-width)) + } + .sidebar-toggle .sidebar-toggle-button{ + top:0 + } +} +@media print{ + .sidebar-toggle{ + display:none + } +} +@media(max-width: 47.99em){ + body.close .sidebar,body.close .sidebar-toggle,body.close .sidebar+.content{ + transform:translateX(var(--sidebar-width)) + } +} +@media(min-width: 48em){ + body.close .sidebar+.content{ + transform:translateX(0) + } +} +@media(max-width: 47.99em){ + body.close nav.app-nav,body.close .github-corner{ + display:none + } +} +@media(min-width: 48em){ + body.close .sidebar,body.close .sidebar-toggle{ + transform:translateX(0) + } +} +@media(min-width: 48em){ + body.close nav.app-nav{ + margin-left:0 + } +} +@media(max-width: 47.99em){ + body.close .sidebar-toggle{ + width:100%; + max-width:none + } + body.close .sidebar-toggle span{ + margin-top:0 + } + body.close .sidebar-toggle span:nth-child(1){ + transform:rotate(45deg) + } + body.close .sidebar-toggle span:nth-child(2){ + display:none + } + body.close .sidebar-toggle span:nth-child(3){ + transform:rotate(-45deg) + } +} +@media(min-width: 48em){ + body.close .sidebar+.content{ + margin-left:0 + } +} +@media(min-width: 48em){ + body.sticky .sidebar,body.sticky .sidebar-toggle{ + position:fixed + } +} +body .docsify-copy-code-button,body .docsify-copy-code-button:after{ + border-radius:var(--border-radius-m, 0); + border-top-left-radius:0; + border-bottom-right-radius:0; + background:var(--copycode-background); + color:var(--copycode-color) +} +body .docsify-copy-code-button span{ + border-radius:var(--border-radius-s, 0) +} +body .docsify-pagination-container{ + border-top:var(--pagination-border-top); + color:var(--pagination-color) +} +body .pagination-item-label{ + font-size:var(--pagination-label-font-size) +} +body .pagination-item-label svg{ + color:var(--pagination-label-color); + height:var(--pagination-chevron-height); + stroke:var(--pagination-chevron-stroke); + stroke-linecap:var(--pagination-chevron-stroke-linecap); + stroke-linejoin:var(--pagination-chevron-stroke-linecap); + stroke-width:var(--pagination-chevron-stroke-width) +} +body .pagination-item-title{ + color:var(--pagination-title-color); + font-size:var(--pagination-title-font-size) +} +body .app-name.hide{ + display:block +} +body .sidebar{ + padding:var(--sidebar-padding) +} +.sidebar .search input{ + padding:0; + line-height:1; + font-size:inherit +} +.sidebar .search .clear-button svg{ + transform:scale(1) +} +.sidebar .search{ + order:var(--search-flex-order); + margin:var(--search-margin); + padding:var(--search-padding); + background:var(--search-background) +} +.sidebar .search a{ + color:inherit +} +.sidebar .search h2{ + margin:var(--search-result-heading-margin); + font-size:var(--search-result-heading-font-size); + font-weight:var(--search-result-heading-font-weight); + color:var(--search-result-heading-color) +} +.sidebar .search .input-wrap{ + align-items:stretch; + margin:var(--search-input-margin); + background-color:var(--search-input-background-color); + border-width:var(--search-input-border-width, 0); + border-style:solid; + border-color:var(--search-input-border-color); + border-radius:var(--search-input-border-radius) +} +.sidebar .search input[type=search]{ + min-width:0; + padding:var(--search-input-padding); + border:none; + background-color:rgba(0,0,0,0); + background-image:var(--search-input-background-image); + background-position:var(--search-input-background-position); + background-repeat:var(--search-input-background-repeat); + background-size:var(--search-input-background-size); + font-size:var(--search-input-font-size); + color:var(--search-input-color); + transition:var(--search-input-transition) +} +.sidebar .search input[type=search]::-ms-clear{ + display:none +} +.sidebar .search input[type=search]::-moz-placeholder{ + color:var(--search-input-placeholder-color, #808080) +} +.sidebar .search input[type=search]::placeholder{ + color:var(--search-input-placeholder-color, #808080) +} +.sidebar .search input[type=search]::-webkit-input-placeholder{ + line-height:normal +} +.sidebar .search input[type=search]:focus{ + background-color:var(--search-input-background-color--focus, var(--search-input-background-color)); + background-image:var(--search-input-background-image--focus, var(--search-input-background-image)); + background-position:var(--search-input-background-position--focus, var(--search-input-background-position)); + background-size:var(--search-input-background-size--focus, var(--search-input-background-size)) +} +@supports(width: env(safe-area-inset)){ + @media only screen and (orientation: landscape){ + .sidebar .search input[type=search]{ + margin-left:calc(env(safe-area-inset-left)/2) + } + } +} +.sidebar .search p{ + font-size:inherit; + overflow:hidden; + text-overflow:ellipsis; + -webkit-box-orient:vertical; + -webkit-line-clamp:2; + line-clamp:2 +} +.sidebar .search p:empty{ + text-align:center +} +.sidebar .search .clear-button{ + width:auto; + margin:0; + padding:0 10px; + border:none; + line-height:1; + background:rgba(0,0,0,0); + cursor:pointer +} +.sidebar .search .clear-button svg circle{ + fill:var(--search-clear-icon-color1, #808080) +} +.sidebar .search .clear-button svg path{ + stroke:var(--search-clear-icon-color2, #fff) +} +.sidebar .search.show~*:not(h1){ + display:none +} +.sidebar .search .results-panel{ + display:none; + color:var(--search-result-item-color); + font-size:var(--search-result-item-font-size); + font-weight:var(--search-result-item-font-weight) +} +.sidebar .search .results-panel.show{ + display:block +} +.sidebar .search .matching-post{ + border:none; + margin:var(--search-result-item-margin); + padding:var(--search-result-item-padding) +} +.sidebar .search .matching-post,.sidebar .search .matching-post:last-child{ + border-width:var(--search-result-item-border-width, 0) !important; + border-style:var(--search-result-item-border-style); + border-color:var(--search-result-item-border-color) +} +.sidebar .search .matching-post p{ + margin:0 +} +.sidebar .search .search-keyword{ + margin:var(--search-result-keyword-margin); + padding:var(--search-result-keyword-padding); + border-radius:var(--search-result-keyword-border-radius); + background-color:var(--search-result-keyword-background); + color:var(--search-result-keyword-color, currentColor); + font-style:normal; + font-weight:var(--search-result-keyword-font-weight) +} +.medium-zoom-overlay,.medium-zoom-image--open,.medium-zoom-image--opened{ + z-index:2147483646 !important +} +.medium-zoom-overlay{ + background:var(--zoomimage-overlay-background) !important +} diff --git a/lib/averages/Ema.cs b/lib/averages/Ema.cs index 9d53fa4e..6e7c7d9a 100644 --- a/lib/averages/Ema.cs +++ b/lib/averages/Ema.cs @@ -2,10 +2,11 @@ namespace QuanTAlib; /// /// EMA: Exponential Moving Average -/// EMA needs very short history buffer and calculates the EMA value using just the -/// previous EMA value. The weight of the new datapoint (alpha) is alpha = 2 / (period + 1) /// /// +/// EMA needs very short history buffer and calculates the EMA value using just the +/// previous EMA value. The weight of the new datapoint (alpha) is alpha = 2 / (period + 1) +/// /// Key characteristics: /// - Uses no buffer, relying only on the previous EMA value. /// - The weight of new data points is calculated as alpha = 2 / (period + 1). @@ -19,19 +20,52 @@ namespace QuanTAlib; /// - https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp /// - https://blog.fugue88.ws/archives/2017-01/The-correct-way-to-start-an-Exponential-Moving-Average-EMA /// - public class Ema : AbstractBase { // inherited _index // inherited _value + + /// + /// The period for the EMA calculation. + /// private readonly int _period; + + /// + /// Circular buffer for SMA calculation. + /// private CircularBuffer _sma; + + /// + /// The last calculated EMA value. + /// private double _lastEma, _p_lastEma; + + /// + /// Compensator for early EMA values. + /// private double _e, _p_e; + + /// + /// The smoothing factor for EMA calculation. + /// private readonly double _k; + + /// + /// Flags to track initialization status. + /// private bool _isInit, _p_isInit; + + /// + /// Flag to determine whether to use SMA for initial values. + /// private readonly bool _useSma; + /// + /// Initializes a new instance of the Ema class with a specified period. + /// + /// The period for EMA calculation. + /// Whether to use SMA for initial values. Default is true. + /// Thrown when period is less than 1. public Ema(int period, bool useSma = true) { if (period < 1) @@ -47,6 +81,10 @@ public class Ema : AbstractBase Init(); } + /// + /// Initializes a new instance of the Ema class with a specified alpha value. + /// + /// The smoothing factor for EMA calculation. public Ema(double alpha) { _k = alpha; @@ -57,13 +95,21 @@ public class Ema : AbstractBase Init(); } + /// + /// Initializes a new instance of the Ema class with a specified source and period. + /// + /// The source object for event subscription. + /// The period for EMA calculation. + /// Whether to use SMA for initial values. Default is true. public Ema(object source, int period, bool useSma = true) : this(period, useSma) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } - //inhereted public void Sub(object source, in ValueEventArgs args) + /// + /// Initializes the Ema instance. + /// public override void Init() { base.Init(); @@ -74,6 +120,10 @@ public class Ema : AbstractBase _sma = new(_period); } + /// + /// Manages the state of the Ema instance. + /// + /// Indicates whether the input is new. protected override void ManageState(bool isNew) { if (isNew) @@ -92,8 +142,9 @@ public class Ema : AbstractBase } /// - /// Core EMA calculation + /// Performs the EMA calculation. /// + /// The calculated EMA value. protected override double Calculation() { double result, _ema; @@ -118,7 +169,7 @@ public class Ema : AbstractBase _ema = _k * (Input.Value - _lastEma) + _lastEma; // _useSma decides if we use compensator or not - result = (_useSma || _e == 0) ? _ema : _ema / (1 - _e); + result = (_useSma || _e <= double.Epsilon) ? _ema : _ema / (1 - _e); } _lastEma = _ema; IsHot = _index >= WarmupPeriod; diff --git a/lib/averages/Jma.cs b/lib/averages/Jma.cs index 8a71cd01..142a47d4 100644 --- a/lib/averages/Jma.cs +++ b/lib/averages/Jma.cs @@ -1,68 +1,91 @@ +/// +/// Represents a Jurik Moving Average, based on known and reverse-engineered insights +/// + namespace QuanTAlib; -//TODO fails consistency test + public class Jma : AbstractBase { - public readonly int Period; + private readonly int _period; private readonly double _phase; - private readonly int _vshort, _vlong; - private readonly CircularBuffer _values; - private readonly CircularBuffer _voltyShort; private readonly CircularBuffer _vsumBuff; private readonly CircularBuffer _avoltyBuff; - private double _beta, _len1, _pow1; - private double _upperBand, _lowerBand, _prevMa1, _prevDet0, _prevDet1, _prevJma; - private double _p_UpperBand, _p_LowerBand, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma; + private double _len1; + private double _pow1; + private readonly double _beta; + private double _upperBand, _lowerBand, _p_upperBand, _p_lowerBand; + private double _prevMa1, _prevDet0, _prevDet1, _prevJma, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma; + private double _vSum, _p_vSum; - public Jma(int period, double phase = 0, int vshort = 10) + + public double UpperBand { get; set; } + public double LowerBand { get; set; } + public double Volty { get; set; } + + /// + /// Initializes a new instance of the Jma class with the specified parameters. + /// + /// The period over which to calculate the Jvolty. + /// The phase parameter for the JMA-style calculation. + /// + /// Thrown when period is less than 1. + /// + public Jma(int period, int phase = 0) { if (period < 1) { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } - Period = period; - _vshort = vshort; - _vlong = 65; + _period = period; _phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5); - _values = new CircularBuffer(period); - _voltyShort = new CircularBuffer(vshort); - _vsumBuff = new CircularBuffer(_vlong); - _avoltyBuff = new CircularBuffer(2); + _vsumBuff = new CircularBuffer(10); + _avoltyBuff = new CircularBuffer(65); + _beta = 0.45 * (period - 1) / (0.45 * (period - 1) + 2); - Name = "JMA"; WarmupPeriod = period * 2; - Init(); + Name = $"JMA({period})"; } - public Jma(object source, int period, double phase = 0, int vshort = 10) : this(period, phase, vshort) + /// + /// Initializes a new instance of the Jvolty class with the specified source and parameters. + /// + /// The source object to subscribe to for value updates. + /// The period over which to calculate the Jvolty. + /// The phase parameter for the JMA-style calculation. + public Jma(object source, int period, int phase = 0) : this(period, phase) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } + /// + /// Initializes the Jma instance by setting up the initial state. + /// public override void Init() { - _upperBand = _lowerBand = _prevMa1 = _prevDet0 = _prevDet1 = _prevJma = 0.0; - _p_UpperBand = _p_LowerBand = _p_prevMa1 = _p_prevDet0 = _p_prevDet1 = _p_prevJma = 0.0; - _beta = 0.45 * (Period - 1) / (0.45 * (Period - 1) + 2); - _len1 = Math.Max((Math.Log(Math.Sqrt(Period - 1)) / Math.Log(2.0)) + 2.0, 0); + base.Init(); + _upperBand = _lowerBand = 0.0; + _p_upperBand = _p_lowerBand = 0.0; + _len1 = Math.Max((Math.Log(Math.Sqrt(_period - 1)) / Math.Log(2.0)) + 2.0, 0); _pow1 = Math.Max(_len1 - 2.0, 0.5); _avoltyBuff.Clear(); - _avoltyBuff.Add(0, true); - _avoltyBuff.Add(0, true); - base.Init(); + _vsumBuff.Clear(); } + /// + /// Manages the state of the Jma instance based on whether a new value is being processed. + /// + /// Indicates whether the current input is a new value. protected override void ManageState(bool isNew) { if (isNew) { - _lastValidValue = Input.Value; _index++; - // Save current state - _p_UpperBand = _upperBand; - _p_LowerBand = _lowerBand; + _p_upperBand = _upperBand; + _p_lowerBand = _lowerBand; + _p_vSum = _vSum; _p_prevMa1 = _prevMa1; _p_prevDet0 = _prevDet0; _p_prevDet1 = _prevDet1; @@ -70,67 +93,68 @@ public class Jma : AbstractBase } else { - // Restore previous state - _upperBand = _p_UpperBand; - _lowerBand = _p_LowerBand; + _upperBand = _p_upperBand; + _lowerBand = _p_lowerBand; + _vSum = _p_vSum; _prevMa1 = _p_prevMa1; _prevDet0 = _p_prevDet0; _prevDet1 = _p_prevDet1; _prevJma = _p_prevJma; - } } + + /// + /// Performs the Jma calculation for the current value. + /// + /// + /// The calculated Jma value for the current input. + /// protected override double Calculation() { ManageState(Input.IsNew); - _values.Add(Input.Value, Input.IsNew); - + double price = Input.Value; if (_index == 1) { - _prevMa1 = _prevJma = Input.Value; - return Input.Value; + _upperBand = _lowerBand = price; } - double hprice = _values.Max(); - double lprice = _values.Min(); - - double del1 = hprice - _upperBand; - double del2 = lprice - _lowerBand; + double del1 = price - _upperBand; + double del2 = price - _lowerBand; double volty = Math.Max(Math.Abs(del1), Math.Abs(del2)); - _voltyShort.Add(volty, Input.IsNew); - double vsum = _vsumBuff.Newest() + 0.1 * (volty - _voltyShort.Oldest()); - _vsumBuff.Add(vsum, Input.IsNew); + _vsumBuff.Add(volty, Input.IsNew); + _vSum += (_vsumBuff[^1] - _vsumBuff[0]) / 10; + _avoltyBuff.Add(_vSum, Input.IsNew); + double avgvolty = _avoltyBuff.Average(); - double prevAvolty = _avoltyBuff.Newest(); - double avolty = prevAvolty + 2.0 / (Math.Max(4.0 * Period, 30) + 1.0) * (vsum - prevAvolty); - _avoltyBuff.Add(avolty, Input.IsNew); + double rvolty = (avgvolty > 0) ? volty / avgvolty : 1; + rvolty = Math.Min(Math.Max(rvolty, 1.0), Math.Pow(_len1, 1.0 / _pow1)); - double dVolty = (avolty > 0) ? volty / avolty : 0; - dVolty = Math.Min(Math.Max(dVolty, 1.0), Math.Pow(_len1, 1.0 / _pow1)); + double pow2 = Math.Pow(rvolty, _pow1); + double Kv = Math.Pow(_beta, Math.Sqrt(pow2)); - double pow2 = Math.Pow(dVolty, _pow1); - double len2 = Math.Sqrt(0.5 * (Period - 1)) * _len1; - double _Kv = Math.Pow(len2 / (len2 + 1), Math.Sqrt(pow2)); - - _upperBand = (del1 > 0) ? hprice : hprice - (_Kv * del1); - _lowerBand = (del2 < 0) ? lprice : lprice - (_Kv * del2); + _upperBand = (del1 >= 0) ? price : price - (Kv * del1); + _lowerBand = (del2 <= 0) ? price : price - (Kv * del2); double alpha = Math.Pow(_beta, pow2); double ma1 = (1 - alpha) * Input.Value + alpha * _prevMa1; _prevMa1 = ma1; - double det0 = (1 - _beta) * (Input.Value - ma1) + _beta * _prevDet0; + double det0 = (price - ma1) * (1 - _beta) + _beta * _prevDet0; _prevDet0 = det0; - double ma2 = ma1 + (_phase + 1) * det0; + double ma2 = ma1 + _phase * det0; - double det1 = ((1 - alpha) * (1 - alpha) * (ma2 - _prevJma)) + (alpha * alpha * _prevDet1); + double det1 = ((ma2 - _prevJma) * (1 - alpha) * (1 - alpha) ) + (alpha * alpha * _prevDet1); _prevDet1 = det1; double jma = _prevJma + det1; _prevJma = jma; + UpperBand = _upperBand; + LowerBand = _lowerBand; + Volty = volty; + IsHot = _index >= WarmupPeriod; return jma; } -} \ No newline at end of file +} diff --git a/lib/volatility/Jvolty.cs b/lib/volatility/Jvolty.cs index dba3208b..23f6ae97 100644 --- a/lib/volatility/Jvolty.cs +++ b/lib/volatility/Jvolty.cs @@ -7,17 +7,25 @@ namespace QuanTAlib; public class Jvolty : AbstractBase { private readonly int _period; - private readonly CircularBuffer _values; - private readonly CircularBuffer _voltyShort; + private readonly double _phase; private readonly CircularBuffer _vsumBuff; private readonly CircularBuffer _avoltyBuff; private double _len1; private double _pow1; - private double _upperBand; - private double _lowerBand; - private double _p_upperBand; - private double _p_lowerBand; + private readonly double _beta; + private double _upperBand, _lowerBand, _p_upperBand, _p_lowerBand; + private double _prevMa1, _prevDet0, _prevDet1, _prevJma, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma; + private double _vSum, _p_vSum; + + + public double UpperBand { get; set; } + public double LowerBand { get; set; } + public double Volty { get; set; } + public double VSum { get; set; } + public double Jma { get; set; } + public double AvgVolty { get; set; } + /// /// Initializes a new instance of the Jvolty class with the specified parameters. @@ -28,22 +36,21 @@ public class Jvolty : AbstractBase /// /// Thrown when period is less than 1. /// - public Jvolty(int period, int vshort = 10) + public Jvolty(int period, int phase = 0) { if (period < 1) { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } _period = period; - int _vlong = 65; + _phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5); - _values = new CircularBuffer(period); - _voltyShort = new CircularBuffer(vshort); - _vsumBuff = new CircularBuffer(_vlong); - _avoltyBuff = new CircularBuffer(2); + _vsumBuff = new CircularBuffer(10); + _avoltyBuff = new CircularBuffer(65); + _beta = 0.45 * (period - 1) / (0.45 * (period - 1) + 2); WarmupPeriod = period * 2; - Name = $"JVOLTY({period},{vshort})"; + Name = $"JVOLTY({period})"; } /// @@ -53,7 +60,7 @@ public class Jvolty : AbstractBase /// The period over which to calculate the Jvolty. /// The phase parameter for the JMA-style calculation. /// The short-term volatility period. - public Jvolty(object source, int period, int vshort = 10) : this(period, vshort) + public Jvolty(object source, int period, int phase = 0) : this(period, phase) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new BarSignal(Sub)); @@ -70,8 +77,7 @@ public class Jvolty : AbstractBase _len1 = Math.Max((Math.Log(Math.Sqrt(_period - 1)) / Math.Log(2.0)) + 2.0, 0); _pow1 = Math.Max(_len1 - 2.0, 0.5); _avoltyBuff.Clear(); - _avoltyBuff.Add(0, true); - _avoltyBuff.Add(0, true); + _vsumBuff.Clear(); } /// @@ -85,11 +91,21 @@ public class Jvolty : AbstractBase _index++; _p_upperBand = _upperBand; _p_lowerBand = _lowerBand; + _p_vSum = _vSum; + _p_prevMa1 = _prevMa1; + _p_prevDet0 = _prevDet0; + _p_prevDet1 = _prevDet1; + _p_prevJma = _prevJma; } else { _upperBand = _p_upperBand; _lowerBand = _p_lowerBand; + _vSum = _p_vSum; + _prevMa1 = _p_prevMa1; + _prevDet0 = _p_prevDet0; + _prevDet1 = _p_prevDet1; + _prevJma = _p_prevJma; } } @@ -101,41 +117,57 @@ public class Jvolty : AbstractBase /// protected override double Calculation() { - ManageState(BarInput.IsNew); - - _values.Add(BarInput.Close, BarInput.IsNew); + ManageState(Input.IsNew); + double price = Input.Value; if (_index == 1) { - return 0; + _upperBand = _lowerBand = price; } - double hprice = _values.Max(); - double lprice = _values.Min(); - - double del1 = hprice - _upperBand; - double del2 = lprice - _lowerBand; + double del1 = price - _upperBand; + double del2 = price - _lowerBand; double volty = Math.Max(Math.Abs(del1), Math.Abs(del2)); - _voltyShort.Add(volty, BarInput.IsNew); - double vsum = _vsumBuff.Newest() + 0.1 * (volty - _voltyShort.Oldest()); - _vsumBuff.Add(vsum, BarInput.IsNew); + _vsumBuff.Add(volty, Input.IsNew); + _vSum += (_vsumBuff[^1] - _vsumBuff[0]) / 10; + _avoltyBuff.Add(_vSum, Input.IsNew); + double avgvolty = _avoltyBuff.Average(); - double prevAvolty = _avoltyBuff.Newest(); - double avolty = prevAvolty + 2.0 / (Math.Max(4.0 * _period, 30) + 1.0) * (vsum - prevAvolty); - _avoltyBuff.Add(avolty, BarInput.IsNew); + double rvolty = (avgvolty > 0) ? volty / avgvolty : 1; + rvolty = Math.Min(Math.Max(rvolty, 1.0), Math.Pow(_len1, 1.0 / _pow1)); - double dVolty = (avolty > 0) ? volty / avolty : 0; - dVolty = Math.Min(Math.Max(dVolty, 1.0), Math.Pow(_len1, 1.0 / _pow1)); + double pow2 = Math.Pow(rvolty, _pow1); + double Kv = Math.Pow(_beta, Math.Sqrt(pow2)); - double pow2 = Math.Pow(dVolty, _pow1); - double len2 = Math.Sqrt(0.5 * (_period - 1)) * _len1; - double Kv = Math.Pow(len2 / (len2 + 1), Math.Sqrt(pow2)); + _upperBand = (del1 >= 0) ? price : price - (Kv * del1); + _lowerBand = (del2 <= 0) ? price : price - (Kv * del2); - _upperBand = (del1 > 0) ? hprice : hprice - (Kv * del1); - _lowerBand = (del2 < 0) ? lprice : lprice - (Kv * del2); + + + double alpha = Math.Pow(_beta, pow2); + double ma1 = (1 - alpha) * Input.Value + alpha * _prevMa1; + _prevMa1 = ma1; + + double det0 = (price - ma1) * (1 - _beta) + _beta * _prevDet0; + _prevDet0 = det0; + double ma2 = ma1 + _phase * det0; + + double det1 = ((ma2 - _prevJma) * (1 - alpha) * (1 - alpha) ) + (alpha * alpha * _prevDet1); + _prevDet1 = det1; + double jma = _prevJma + det1; + _prevJma = jma; + + UpperBand = _upperBand; + LowerBand = _lowerBand; + Volty = volty; + VSum = _vSum; + AvgVolty = avgvolty; + Jma = jma; IsHot = _index >= WarmupPeriod; return volty; } } + + diff --git a/lib/volatility/todo.md b/lib/volatility/todo.md index 5a4aa170..4a04ca32 100644 --- a/lib/volatility/todo.md +++ b/lib/volatility/todo.md @@ -18,11 +18,11 @@ ## OHLC Input (Open, High, Low, Close) +- **Average True Range (ATR) (High, Low, Close)** - Garman-Klass Volatility - Rogers-Satchell Volatility - Yang-Zhang Volatility - Parkinson Volatility (High, Low) -- Average True Range (ATR) (High, Low, Close) - Chaikin Volatility (High, Low) - Keltner Channels (typically Close, High, Low) - High-Low Volatility (High, Low) diff --git a/notebooks/jma.dib b/notebooks/jma.dib index ed284f0f..b6d42f87 100644 --- a/notebooks/jma.dib +++ b/notebooks/jma.dib @@ -82,58 +82,77 @@ TSeries MarketJMA = new() { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, #!csharp -public class Jma1 : AbstractBase +public class Jmaxx : AbstractBase { - public readonly int Period; + private readonly double _period; private readonly double _phase; - private readonly int _vshort, _vlong; - private CircularBuffer _voltyShort; - private CircularBuffer _vsumBuff; - private CircularBuffer _avoltyBuff; + private readonly CircularBuffer _vsumBuff; + private readonly CircularBuffer _avoltyBuff; - private double _beta, _len1, _pow1; - private double _upperBand, _lowerBand, _prevMa1, _prevDet0, _prevDet1, _prevJma; - private double _p_UpperBand, _p_LowerBand, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma; + private double _len1; + private double _pow1; + private readonly double _beta; + private double _upperBand, _lowerBand, _p_upperBand, _p_lowerBand; + private double _prevMa1, _prevDet0, _prevDet1, _prevJma, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma; + private double _vSum, _p_vSum; - public Jma1(int period, double phase = 0, int vshort = 10) : base() + + public double UpperBand { get; set; } + public double LowerBand { get; set; } + public double Volty { get; set; } + + /// + /// Initializes a new instance of the Jma class with the specified parameters. + /// + /// The period over which to calculate the Jvolty. + /// The phase parameter for the JMA-style calculation. + /// + /// Thrown when period is less than 1. + /// + public Jmaxx(int period, int phase = 0) { if (period < 1) { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } - Period = period; - _vshort = vshort; - _vlong = 65; + _period = period; + _phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5); - _voltyShort = new CircularBuffer(vshort); - _vsumBuff = new CircularBuffer(_vlong); - _avoltyBuff = new CircularBuffer(2); + _vsumBuff = new CircularBuffer(10); + _avoltyBuff = new CircularBuffer(65); + _beta = 0.45 * (_period - 1) / (0.45 * (_period - 1) + 2); - Name = "JMA"; - WarmupPeriod = period * 2; - Init(); + WarmupPeriod = (int)_period * 2; + Name = $"JMA({period})"; } + /// + /// Initializes the Jma instance by setting up the initial state. + /// public override void Init() { - _upperBand = _lowerBand = _prevMa1 = _prevDet0 = _prevDet1 = _prevJma = 0.0; - _p_UpperBand = _p_LowerBand = _p_prevMa1 = _p_prevDet0 = _p_prevDet1 = _p_prevJma = 0.0; - _avoltyBuff.Clear(); - _avoltyBuff.Add(0, true); - _avoltyBuff.Add(0, true); base.Init(); + _upperBand = _lowerBand = 0.0; + _p_upperBand = _p_lowerBand = 0.0; + _len1 = Math.Max((Math.Log(Math.Sqrt(_period - 1)) / Math.Log(2.0)) + 2.0, 0); + _pow1 = Math.Max(_len1 - 2.0, 0.5); + _avoltyBuff.Clear(); + _vsumBuff.Clear(); } + /// + /// Manages the state of the Jma instance based on whether a new value is being processed. + /// + /// Indicates whether the current input is a new value. protected override void ManageState(bool isNew) { if (isNew) { - _lastValidValue = Input.Value; _index++; - // Save current state - _p_UpperBand = _upperBand; - _p_LowerBand = _lowerBand; + _p_upperBand = _upperBand; + _p_lowerBand = _lowerBand; + _p_vSum = _vSum; _p_prevMa1 = _prevMa1; _p_prevDet0 = _prevDet0; _p_prevDet1 = _prevDet1; @@ -141,66 +160,73 @@ public class Jma1 : AbstractBase } else { - // Restore previous state - _upperBand = _p_UpperBand; - _lowerBand = _p_LowerBand; + _upperBand = _p_upperBand; + _lowerBand = _p_lowerBand; + _vSum = _p_vSum; _prevMa1 = _p_prevMa1; _prevDet0 = _p_prevDet0; _prevDet1 = _p_prevDet1; _prevJma = _p_prevJma; - } } + + /// + /// Performs the Jma calculation for the current value. + /// + /// + /// The calculated Jma value for the current input. + /// protected override double Calculation() { ManageState(Input.IsNew); + double price = Input.Value; if (_index == 1) { - _prevMa1 = _prevJma = Input.Value; - return Input.Value; + _upperBand = _lowerBand = price; } - double del1 = Input.Value - _upperBand; - double del2 = Input.Value - _lowerBand; + double del1 = price - _upperBand; + double del2 = price - _lowerBand; double volty = Math.Max(Math.Abs(del1), Math.Abs(del2)); - _voltyShort.Add(volty, Input.IsNew); - double vsum = _vsumBuff.Newest() + 0.1 * (volty - _voltyShort.Oldest()); - _vsumBuff.Add(vsum, Input.IsNew); + _vsumBuff.Add(volty, Input.IsNew); + _vSum += (_vsumBuff[^1] - _vsumBuff[0]) / 10; + _avoltyBuff.Add(_vSum, Input.IsNew); + double avgvolty = _avoltyBuff.Average(); - double avolty = 0; - for (int i = 0; i < _vsumBuff.Count; i++) { avolty += _vsumBuff[i]; } - avolty /= _vsumBuff.Count; + double rvolty = (avgvolty > 0) ? volty / avgvolty : 1; + rvolty = Math.Min(Math.Max(rvolty, 1.0), Math.Pow(_len1, 1.0 / _pow1)); - double rVolty = (avolty > 0) ? volty / avolty *20: 0; - double _len1 = Math.Max((Math.Log(Math.Sqrt(Period)) / Math.Log(2.0)) + 2.0, 0); - double _pow1 = Math.Max(_len1 - 2, 0.5); + double pow2 = Math.Pow(rvolty, _pow1); + double Kv = Math.Pow(_beta, Math.Sqrt(pow2)); - rVolty = Math.Clamp(rVolty, 1.0, Math.Pow(_len1, 1.0 / _pow1)); + _upperBand = (del1 >= 0) ? price : price - (Kv * del1); + _lowerBand = (del2 <= 0) ? price : price - (Kv * del2); - double _pow2 = Math.Pow(rVolty, _pow1); - double _beta = 0.45 * (Period - 1) / (0.45 * (Period - 1) + 2); - double len2 = Math.Sqrt(0.5 * (Period - 1)) * _len1; - double _Kv = Math.Pow (_beta, Math.Sqrt(_pow2)) *1.5; - _upperBand = (del1 > 0) ? Input.Value : Input.Value - (_Kv * del1); - _lowerBand = (del2 < 0) ? Input.Value : Input.Value - (_Kv * del2); - double alpha = Math.Pow(_beta, _pow2); - double ma1 = alpha * (_prevMa1 - Input.Value) + Input.Value; + + + + double alpha = Math.Pow(_beta, pow2); + double ma1 = (1 - alpha) * Input.Value + alpha * _prevMa1; _prevMa1 = ma1; - double det0 = _beta * (_prevDet0 - Input.Value + ma1) + Input.Value - ma1; + double det0 = (price - ma1) * (1 - _beta) + _beta * _prevDet0; _prevDet0 = det0; double ma2 = ma1 + _phase * det0; - double det1 = ((1 - alpha) * (1 - alpha) * (ma2 - _prevJma)) + (alpha * alpha * _prevDet1 ); + double det1 = ((ma2 - _prevJma) * (1 - alpha) * (1 - alpha) ) + (alpha * alpha * _prevDet1); _prevDet1 = det1; double jma = _prevJma + det1; _prevJma = jma; + UpperBand = _upperBand; + LowerBand = _lowerBand; + Volty = volty; + IsHot = _index >= WarmupPeriod; return jma; } @@ -208,22 +234,17 @@ public class Jma1 : AbstractBase #!csharp -TSeries ma = Triangle; -TSeries re = TriangleJMA; +TSeries ma = Complex; +TSeries re = ComplexJMA; TSeries out1 = new(); -TSeries out2 = new(); -Jma calc = new(10); -Jma1 calc1 = new(10); + +Jmaxx calc = new(10); + foreach (var value in ma) { out1.Add(calc.Calc(value)); } -foreach (var value in ma) { out2.Add(calc1.Calc(value)); } + Plot plt = new(); var sigplot = plt.Add.Signal(ma.v.ToArray()[60..80]); var jmaplot = plt.Add.Signal(re.v.ToArray()[60..80]); sigplot.Color = ScottPlot.Colors.Red; sigplot.LineWidth = 2; jmaplot.LineWidth = 3; -//var jma1plot = plt.Add.Signal(out1.v.ToArray()[60..80]); jma1plot.Color = ScottPlot.Colors.Purple; jma1plot.LineWidth = 3; -var jma2plot = plt.Add.Signal(out2.v.ToArray()[60..80]); jma2plot.Color = ScottPlot.Colors.Blue; jma2plot.LineWidth = 3; +var jma1plot = plt.Add.Signal(out1.v.ToArray()[60..80]); jma1plot.Color = ScottPlot.Colors.Purple; jma1plot.LineWidth = 3; + plt.Display(); - -#!csharp - -#r "nuget: Plotly.net.Interactive" -using Plotly.NET.Interactive; diff --git a/quantower/Averages/JmaIndicator.cs b/quantower/Averages/JmaIndicator.cs index 049aa19a..e661c9de 100644 --- a/quantower/Averages/JmaIndicator.cs +++ b/quantower/Averages/JmaIndicator.cs @@ -9,7 +9,7 @@ public class JmaIndicator : Indicator, IWatchlistIndicator public int Periods { get; set; } = 10; [InputParameter("Phase", sortIndex: 2, -100, 100, 1, 0)] - public double Phase { get; set; } = 0; + public int Phase { get; set; } = 0; [InputParameter("Data source", sortIndex: 4, variants: [ "Open", SourceType.Open, diff --git a/quantower/Averages/MacdIndicator.cs b/quantower/Averages/MacdIndicator.cs new file mode 100644 index 00000000..8dca5e31 --- /dev/null +++ b/quantower/Averages/MacdIndicator.cs @@ -0,0 +1,95 @@ +using System.Diagnostics.Metrics; +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class MacdIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Slow EMA", sortIndex: 1, 1, 1000, 1, 0)] + public int Slow { get; set; } = 26; + + [InputParameter("Fast EMA", sortIndex: 2, 1, 2000, 1, 0)] + public int Fast { get; set; } = 12; + + [InputParameter("Signal line", sortIndex: 3, 1, 2000, 1, 0)] + public int Signal { get; set; } = 9; + + [InputParameter("Use SMA for warmup period", sortIndex: 2)] + public bool UseSMA { get; set; } = false; + + [InputParameter("Data source", sortIndex: 3, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Ema? slow_ma; + private Ema? fast_ma; + private Ema? signal_ma; + protected LineSeries? MainSeries; + protected LineSeries? SignalSeries; + + protected string? SourceName; + public int MinHistoryDepths => Slow; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public override string ShortName => $"MACD {Slow}:{Fast}:{Signal}"; + + public MacdIndicator() + { + OnBackGround = true; + SeparateWindow = true; + SourceName = Source.ToString(); + Name = "MACD - Moving Average Convergence Divergence"; + Description = "MACD"; + MainSeries = new(name: $"MAIN", color: Color.Yellow, width: 2, style: LineStyle.Solid); + SignalSeries = new(name: $"SIGNAL", color: Color.Blue, width: 2, style: LineStyle.Solid); + + AddLineSeries(MainSeries); + AddLineSeries(SignalSeries); + } + + protected override void OnInit() + { + slow_ma = new(Slow, useSma: UseSMA); + fast_ma = new(Fast, useSma: UseSMA); + signal_ma = new(Signal, useSma: UseSMA); + SourceName = Source.ToString(); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + slow_ma!.Calc(input); + fast_ma!.Calc(input); + double main = fast_ma.Value - slow_ma.Value; + signal_ma!.Calc(main); + + MainSeries!.SetValue(main); + MainSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + SignalSeries!.SetValue(signal_ma.Value); + SignalSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + } + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, MainSeries!, slow_ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + this.PaintSmoothCurve(args, SignalSeries!, slow_ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + this.DrawText(args, Description); + } +} + diff --git a/quantower/Volatility/JbandsIndicator.cs b/quantower/Volatility/JbandsIndicator.cs new file mode 100644 index 00000000..608f93c1 --- /dev/null +++ b/quantower/Volatility/JbandsIndicator.cs @@ -0,0 +1,64 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class JbandsIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 14; + + [InputParameter("Data source", sortIndex: 5, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("vShort", sortIndex: 6, -100, 100, 1, 0)] + public int Phase { get; set; } = 10; + + private Jma? jma; + protected LineSeries? UbSeries; + protected LineSeries? LbSeries; + protected string? SourceName; + public static int MinHistoryDepths => 2; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public JbandsIndicator() + { + Name = "JBANDS - Mark Jurik's Bands"; + Description = "Upper and Lower Bands."; + SeparateWindow = false; + + UbSeries = new("UB", Color.Blue, 2, LineStyle.Solid); + LbSeries = new("LB", Color.Red, 2, LineStyle.Solid); + AddLineSeries(UbSeries); + AddLineSeries(LbSeries); + } + + protected override void OnInit() + { + jma = new(Periods, phase: Phase); + SourceName = Source.ToString(); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + jma!.Calc(input); + + UbSeries!.SetValue(jma.UpperBand); + LbSeries!.SetValue(jma.LowerBand); + } + + public override string ShortName => $"JBands ({Periods}:{Phase})"; +} diff --git a/quantower/Volatility/JvoltyIndicator.cs b/quantower/Volatility/JvoltyIndicator.cs index 9db23343..7a6436b5 100644 --- a/quantower/Volatility/JvoltyIndicator.cs +++ b/quantower/Volatility/JvoltyIndicator.cs @@ -6,11 +6,27 @@ namespace QuanTAlib; public class JvoltyIndicator : Indicator, IWatchlistIndicator { [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] - public int Periods { get; set; } = 20; + public int Periods { get; set; } = 14; - private Jvolty? jvolty; + [InputParameter("Data source", sortIndex: 5, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + private Jma? jma; protected LineSeries? JvoltySeries; public static int MinHistoryDepths => 2; + + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; public JvoltyIndicator() @@ -25,16 +41,17 @@ public class JvoltyIndicator : Indicator, IWatchlistIndicator protected override void OnInit() { - jvolty = new (Periods); + jma = new(Periods); base.OnInit(); } protected override void OnUpdate(UpdateArgs args) { - TBar input = IndicatorExtensions.GetInputBar(this, args); - TValue result = jvolty!.Calc(input); + TValue input = this.GetInputValue(args, Source); + jma!.Calc(input); + + JvoltySeries!.SetValue(jma.Volty); - JvoltySeries!.SetValue(result.Value); } public override string ShortName => $"JVOLTY ({Periods})"; From e3d7cd989644c670a789278bd058e6571b2e9596 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Mon, 21 Oct 2024 16:06:47 -0700 Subject: [PATCH 2/5] Atr, FlowIndicator and fixes --- lib/averages/Ema.cs | 1 + lib/averages/Jma.cs | 21 ++-- lib/averages/Rma.cs | 134 ++++++++++++++++------ lib/core/abstractBase.cs | 6 + lib/core/tbar.cs | 2 + lib/core/tvalue.cs | 4 +- lib/feeds/GbmFeed.cs | 38 +++---- lib/quantalib.csproj | 1 + lib/volatility/Atr.cs | 37 ++++--- notebooks/core.dib | 141 +++++++----------------- quantower/Averages/JmaIndicator.cs | 7 +- quantower/IndicatorExtensions.cs | 23 +++- quantower/Volatility/AtrIndicator.cs | 16 ++- quantower/Volatility/FlowIndicator.cs | 78 +++++++++++++ quantower/Volatility/JbandsIndicator.cs | 15 ++- 15 files changed, 335 insertions(+), 189 deletions(-) create mode 100644 quantower/Volatility/FlowIndicator.cs diff --git a/lib/averages/Ema.cs b/lib/averages/Ema.cs index 6e7c7d9a..3e38f745 100644 --- a/lib/averages/Ema.cs +++ b/lib/averages/Ema.cs @@ -90,6 +90,7 @@ public class Ema : AbstractBase _k = alpha; _useSma = false; _sma = new(1); + Name = "Ema"; _period = 1; WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - _k)); //95th percentile Init(); diff --git a/lib/averages/Jma.cs b/lib/averages/Jma.cs index 142a47d4..c8e8eba0 100644 --- a/lib/averages/Jma.cs +++ b/lib/averages/Jma.cs @@ -6,7 +6,7 @@ namespace QuanTAlib; public class Jma : AbstractBase { - private readonly int _period; + private readonly double _period; private readonly double _phase; private readonly CircularBuffer _vsumBuff; private readonly CircularBuffer _avoltyBuff; @@ -22,6 +22,7 @@ public class Jma : AbstractBase public double UpperBand { get; set; } public double LowerBand { get; set; } public double Volty { get; set; } + public double Factor { get; set; } /// /// Initializes a new instance of the Jma class with the specified parameters. @@ -31,18 +32,19 @@ public class Jma : AbstractBase /// /// Thrown when period is less than 1. /// - public Jma(int period, int phase = 0) + public Jma(int period, int phase = 0, double factor = 0.45) { if (period < 1) { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } + Factor = factor; _period = period; _phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5); _vsumBuff = new CircularBuffer(10); _avoltyBuff = new CircularBuffer(65); - _beta = 0.45 * (period - 1) / (0.45 * (period - 1) + 2); + _beta = factor * (_period - 1) / (factor * (_period - 1) + 2); WarmupPeriod = period * 2; Name = $"JMA({period})"; @@ -114,9 +116,10 @@ public class Jma : AbstractBase ManageState(Input.IsNew); double price = Input.Value; - if (_index == 1) + if (_index <= 1) { _upperBand = _lowerBand = price; + _prevMa1 = _prevJma = price; } double del1 = price - _upperBand; @@ -124,7 +127,7 @@ public class Jma : AbstractBase double volty = Math.Max(Math.Abs(del1), Math.Abs(del2)); _vsumBuff.Add(volty, Input.IsNew); - _vSum += (_vsumBuff[^1] - _vsumBuff[0]) / 10; + _vSum += (_vsumBuff[^1] - _vsumBuff[0]) / _vsumBuff.Count; _avoltyBuff.Add(_vSum, Input.IsNew); double avgvolty = _avoltyBuff.Average(); @@ -137,15 +140,15 @@ public class Jma : AbstractBase _upperBand = (del1 >= 0) ? price : price - (Kv * del1); _lowerBand = (del2 <= 0) ? price : price - (Kv * del2); - double alpha = Math.Pow(_beta, pow2); - double ma1 = (1 - alpha) * Input.Value + alpha * _prevMa1; + double _alpha = Math.Pow(_beta, pow2); + double ma1 = Input.Value + _alpha * (_prevMa1 - Input.Value); //original: (1 - _alpha) * Input.Value + _alpha * _prevMa1; _prevMa1 = ma1; - double det0 = (price - ma1) * (1 - _beta) + _beta * _prevDet0; + double det0 = price + _beta * (_prevDet0 - price + ma1) - ma1; //original: (price - ma1) * (1 - _beta) + _beta * _prevDet0; _prevDet0 = det0; double ma2 = ma1 + _phase * det0; - double det1 = ((ma2 - _prevJma) * (1 - alpha) * (1 - alpha) ) + (alpha * alpha * _prevDet1); + double det1 = ((ma2 - _prevJma) * (1 - _alpha) * (1 - _alpha) ) + (_alpha * _alpha * _prevDet1); _prevDet1 = det1; double jma = _prevJma + det1; _prevJma = jma; diff --git a/lib/averages/Rma.cs b/lib/averages/Rma.cs index f8077faa..628d8310 100644 --- a/lib/averages/Rma.cs +++ b/lib/averages/Rma.cs @@ -1,18 +1,18 @@ -using System; - namespace QuanTAlib; + /// /// RMA: Relative Moving Average (also known as Wilder's Moving Average) -/// RMA is similar to EMA but uses a different smoothing factor. /// /// +/// RMA is similar to EMA but uses a different smoothing factor. +/// /// Key characteristics: /// - Uses no buffer, relying only on the previous RMA value. /// - The weight of new data points (alpha) is calculated as 1 / period. /// - Provides a smoother curve compared to SMA and EMA, reacting more slowly to price changes. /// /// Calculation method: -/// RMA = (Previous RMA * (period - 1) + New Data) / period +/// This implementation can use SMA for the first Period bars as a seeding value for RMA when useSma is true. /// /// Sources: /// - https://www.tradingview.com/pine-script-reference/v5/#fun_ta{dot}rma @@ -20,75 +20,143 @@ namespace QuanTAlib; /// public class Rma : AbstractBase { - private readonly int _period; - private double _lastRma; - private readonly double _alpha; - private double _savedLastRma; + // inherited _index + // inherited _value - public Rma(int period) + /// + /// The period for the RMA calculation. + /// + private readonly int _period; + + /// + /// Circular buffer for SMA calculation. + /// + private CircularBuffer _sma; + + /// + /// The last calculated RMA value. + /// + private double _lastRma, _p_lastRma; + + /// + /// Compensator for early RMA values. + /// + private double _e, _p_e; + + /// + /// The smoothing factor for RMA calculation. + /// + private readonly double _k; + + /// + /// Flags to track initialization status. + /// + private bool _isInit, _p_isInit; + + /// + /// Flag to determine whether to use SMA for initial values. + /// + private readonly bool _useSma; + + /// + /// Initializes a new instance of the Rma class with a specified period. + /// + /// The period for RMA calculation. + /// Whether to use SMA for initial values. Default is true. + /// Thrown when period is less than 1. + public Rma(int period, bool useSma = true) { if (period < 1) { - throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); + throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } _period = period; - WarmupPeriod = period * 2; - _alpha = 1.0 / _period; // Wilder's smoothing factor - Name = $"Rma({_period})"; + _k = 1.0 / _period; // Wilder's smoothing factor + _useSma = useSma; + _sma = new(period); + Name = "Rma"; + WarmupPeriod = _period * 2; // RMA typically needs more warmup periods Init(); } - public Rma(object source, int period) : this(period) + /// + /// Initializes a new instance of the Rma class with a specified source and period. + /// + /// The source object for event subscription. + /// The period for RMA calculation. + /// Whether to use SMA for initial values. Default is true. + public Rma(object source, int period, bool useSma = true) : this(period, useSma) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } + /// + /// Initializes the Rma instance. + /// public override void Init() { base.Init(); + _e = 1.0; _lastRma = 0; - _savedLastRma = 0; + _isInit = false; + _p_isInit = false; + _sma = new(_period); } + /// + /// Manages the state of the Rma instance. + /// + /// Indicates whether the input is new. protected override void ManageState(bool isNew) { if (isNew) { - _savedLastRma = _lastRma; - _lastValidValue = Input.Value; + _p_lastRma = _lastRma; + _p_isInit = _isInit; + _p_e = _e; _index++; } else { - _lastRma = _savedLastRma; + _lastRma = _p_lastRma; + _isInit = _p_isInit; + _e = _p_e; } } + /// + /// Performs the RMA calculation. + /// + /// The calculated RMA value. protected override double Calculation() { + double result, _rma; ManageState(Input.IsNew); - double rma; - - if (_index == 1) + // when _UseSma == true, use SMA calculation until we have enough data points + if (!_isInit && _useSma) { - rma = Input.Value; - } - else if (_index <= _period) - { - // Simple average during initial period - rma = (_lastRma * (_index - 1) + Input.Value) / _index; + _sma.Add(Input.Value, Input.IsNew); + _rma = _sma.Average(); + result = _rma; + if (_index >= _period) + { + _isInit = true; + } } else { - // Wilder's smoothing method - rma = _alpha * (_lastRma - Input.Value) + _lastRma; + // compensator for early rma values + _e = (_e > 1e-10) ? (1 - _k) * _e : 0; + + _rma = _k * Input.Value + (1 - _k) * _lastRma; + + // _useSma decides if we use compensator or not + result = (_useSma || _e <= double.Epsilon) ? _rma : _rma / (1 - _e); } - - _lastRma = rma; + _lastRma = _rma; IsHot = _index >= WarmupPeriod; - - return rma; + return result; } } diff --git a/lib/core/abstractBase.cs b/lib/core/abstractBase.cs index 3859473a..3ac3d973 100644 --- a/lib/core/abstractBase.cs +++ b/lib/core/abstractBase.cs @@ -57,6 +57,12 @@ public abstract class AbstractBase : ITValue Input2 = new(Time: Input.Time, Value: double.NaN, IsNew: Input.IsNew, IsHot: Input.IsHot); return Process(input.Value, input.Time, input.IsNew); } + public virtual TValue Calc(double value, bool IsNew) + { + Input = new(this.Time, Value: value, IsNew: IsNew, IsHot: false); + Input2 = new(this.Time, double.NaN, false, false); + return Process(Input.Value, Input.Time, Input.IsNew); + } public virtual TValue Calc(TBar barInput) { diff --git a/lib/core/tbar.cs b/lib/core/tbar.cs index 441994ea..9b72bc88 100644 --- a/lib/core/tbar.cs +++ b/lib/core/tbar.cs @@ -32,6 +32,8 @@ public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { } public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { } public TBar(double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) { } public TBar(TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) { } +public TBar(TBar v) : this(Time: v.Time, Open: v.Open, High: v.High, Low: v.Low, Close: v.Close, Volume: v.Volume, IsNew: true) { } + public static implicit operator double(TBar bar) => bar.Close; public static implicit operator DateTime(TBar tv) => tv.Time; diff --git a/lib/core/tvalue.cs b/lib/core/tvalue.cs index f7c83cff..843768d6 100644 --- a/lib/core/tvalue.cs +++ b/lib/core/tvalue.cs @@ -52,13 +52,13 @@ public class TSeries : List var pubEvent = source.GetType().GetEvent("Pub"); if (pubEvent != null) { - +/* var nameProperty = source.GetType().GetProperty("Name"); if (nameProperty != null) { Name = nameProperty.GetValue(nameProperty)?.ToString()!; } - +*/ pubEvent.AddEventHandler(source, new ValueSignal(Sub)); } } diff --git a/lib/feeds/GbmFeed.cs b/lib/feeds/GbmFeed.cs index 5b2edd2f..566ee8db 100644 --- a/lib/feeds/GbmFeed.cs +++ b/lib/feeds/GbmFeed.cs @@ -6,11 +6,11 @@ public class GbmFeed : TBarSeries { private readonly double _mu, _sigma; private readonly RandomNumberGenerator _rng; - private double _lastClose, _lastHigh, _lastLow; + private double _lastClose; public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2) { - _lastClose = _lastHigh = _lastLow = initialPrice; + _lastClose = initialPrice; _mu = mu; _sigma = sigma; _rng = RandomNumberGenerator.Create(); @@ -24,9 +24,7 @@ public class GbmFeed : TBarSeries DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count); for (int i = 0; i < count; i++) { - Add(startTime, true); - Add(startTime, false); - Add(startTime, false); + Add(startTime, isNew: true); startTime = startTime.AddHours(1); } } @@ -36,27 +34,29 @@ public class GbmFeed : TBarSeries double dt = 1.0 / 252; double drift = (_mu - 0.5 * _sigma * _sigma) * dt; double diffusion = _sigma * Math.Sqrt(dt) * GenerateNormalRandom(); - double newClose = _lastClose * Math.Exp(drift + diffusion); double open = _lastClose; - double high = Math.Max(_lastHigh, Math.Max(open, newClose) * (1 + GenerateRandomDouble() * 0.01)); - double low = Math.Min(_lastLow, Math.Min(open, newClose) * (1 - GenerateRandomDouble() * 0.01)); + double close = open * Math.Exp(drift + diffusion); + + // Generate intra-bar price movements + double maxMove = Math.Abs(close - open) * 1.5; // Allow for some extra movement within the bar + double high = Math.Max(open, close) + maxMove * GenerateRandomDouble(); + double low = Math.Min(open, close) - maxMove * GenerateRandomDouble(); + + // Ensure high is always greater than or equal to both open and close + high = Math.Max(high, Math.Max(open, close)); + + // Ensure low is always less than or equal to both open and close + low = Math.Min(low, Math.Min(open, close)); + double volume = 1000 + GenerateRandomDouble() * 1000; if (isNew) { - _lastClose = newClose; + _lastClose = close; } - else - { - high = Math.Max(_lastHigh, high); - low = Math.Min(_lastLow, low); - } - _lastHigh = high; - _lastLow = low; - TBar bar = new(time, open, high, low, newClose, volume, isNew); - return bar; + return new TBar(time, open, high, low, close, volume, isNew); } private double GenerateNormalRandom() @@ -73,4 +73,4 @@ public class GbmFeed : TBarSeries _rng.GetBytes(bytes); return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue; } -} \ No newline at end of file +} diff --git a/lib/quantalib.csproj b/lib/quantalib.csproj index 716dc1ad..3f706bc7 100644 --- a/lib/quantalib.csproj +++ b/lib/quantalib.csproj @@ -31,6 +31,7 @@ https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png True false + $(NoWarn);NU1903;NU5104 diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs index e6c20845..3ea3e5f0 100644 --- a/lib/volatility/Atr.cs +++ b/lib/volatility/Atr.cs @@ -4,13 +4,14 @@ namespace QuanTAlib; /// Represents an Average True Range (ATR) calculator, a measure of market volatility. /// /// -/// The ATR class calculates the average true range using an Exponential Moving Average (EMA) +/// The ATR class calculates the average true range using a Relative Moving Average (RMA) /// of the true range. The true range is the greatest of: current high - current low, /// absolute value of current high - previous close, or absolute value of current low - previous close. /// public class Atr : AbstractBase { - private readonly Ema _ma; + public double Tr { get; private set; } + private readonly Rma _ma; private double _prevClose, _p_prevClose; /// @@ -26,7 +27,7 @@ public class Atr : AbstractBase { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } - _ma = new(1.0 / period); + _ma = new(period, useSma: true); WarmupPeriod = _ma.WarmupPeriod; Name = $"ATR({period})"; } @@ -50,6 +51,7 @@ public class Atr : AbstractBase base.Init(); _ma.Init(); _prevClose = double.NaN; + Tr = 0; } /// @@ -76,7 +78,7 @@ public class Atr : AbstractBase /// The calculated ATR value for the current bar. /// /// - /// This method calculates the true range for the current bar and then uses an EMA + /// This method calculates the true range for the current bar and then uses an RMA /// to smooth the true range values. For the first bar, it uses the high-low range /// as the true range. /// @@ -84,22 +86,25 @@ public class Atr : AbstractBase { ManageState(BarInput.IsNew); - double trueRange = Math.Max( - Math.Max( - BarInput.High - BarInput.Low, - Math.Abs(BarInput.High - _prevClose) - ), - Math.Abs(BarInput.Low - _prevClose) - ); - if (_index < 2) + if (_index == 1) { - trueRange = BarInput.High - BarInput.Low; + Tr = BarInput.High - BarInput.Low; + _prevClose = BarInput.Close; } + else + { + Tr = Math.Max( + BarInput.High - BarInput.Low, + Math.Max( + Math.Abs(BarInput.High - _prevClose), + Math.Abs(BarInput.Low - _prevClose) + ) + ); + } + _ma.Calc(new TValue(Input.Time, Tr, BarInput.IsNew)); - TValue emaTrueRange = _ma.Calc(new TValue(Input.Time, trueRange, Input.IsNew)); IsHot = _ma.IsHot; _prevClose = BarInput.Close; - - return emaTrueRange.Value; + return _ma.Value; } } diff --git a/notebooks/core.dib b/notebooks/core.dib index 2f997b49..1fa02006 100644 --- a/notebooks/core.dib +++ b/notebooks/core.dib @@ -4,7 +4,7 @@ #!csharp -#r "..\src\obj\Debug\QuanTAlib.dll" +#r "..\lib\obj\Debug\QuanTAlib.dll" #r "nuget:Skender.Stock.Indicators" using Skender.Stock.Indicators; @@ -13,113 +13,56 @@ QuanTAlib.Formatters.Initialize(); #!csharp +Atr ma = new(10); GbmFeed gbm = new(); -EmaCalc ema1 = new(gbm.Close, 10, useSma: false); -EmaCalc ema2 = new(gbm.Close, 10, useSma: true); -TValSeries res1 = new(ema1); -TValSeries res2 = new(ema2); -gbm.Add(50); -List mse1 = new(); -List mse2 = new(); - - +gbm.Add(30); +IEnumerable quotes = gbm.Select(item => new Quote { Date = item.Time, Open = (decimal)item.Open, High = (decimal)item.High, Low = (decimal)item.Low, Close = (decimal)item.Close, Volume = (decimal)item.Volume }); +var SkResults = quotes.GetAtr(10).Select(i => i.Atr.Null2NaN()!); for (int i=0; i< gbm.Length; i++) { - double v= gbm.Close[i].Value; - double e1 = res1[i].Value; - mse1.Add((e1-v)*(e1-v)); - double e2 = res2[i].Value; - mse2.Add((e2-v)*(e2-v)); - - //Console.WriteLine($"{i,3} {mse1.Average(),10:F4} {mse2.Average(),10:F4}"); + ma.Calc(gbm[i]); + Console.WriteLine($"{i,3} {ma.Value,10:F3} \t {SkResults.ElementAt(i):F3}"); } - Console.WriteLine($"{mse2.Average()-mse1.Average(),10:F8}"); - -#!csharp - -display(res1); - #!csharp +Atr ma = new(10); GbmFeed gbm = new(); -EmaCalc ema1 = new(gbm.Close, 10, useSma: false); -EmaCalc ema2 = new(gbm.Close, 10, useSma: true); -TValSeries res1 = new(ema1); -TValSeries res2 = new(ema2); +gbm.Add(30); +IEnumerable quotes = gbm.Select(item => new Quote { Date = item.Time, Open = (decimal)item.Open, High = (decimal)item.High, Low = (decimal)item.Low, Close = (decimal)item.Close, Volume = (decimal)item.Volume }); +var SkResults = quotes.GetTr().Select(i => i.Tr.Null2NaN()!); +for (int i=0; i< gbm.Length; i++) { + ma.Calc(new TBar(gbm[i])); + + Console.WriteLine($"{gbm.High[i].Value,6:F4} \t{gbm.Low[i].Value,6:F4} \t{gbm.Close[i].Value,6:F4} \t{ma.Tr,10:F4} \t{SkResults.ElementAt(i),10:F4}"); +} + +#!csharp + +//ATR test +GbmFeed gbm = new(); +TBarSeries feed = new(gbm); + +Atr ma1 = new(gbm, 10); +TSeries res1 = new(ma1); +gbm.Add(30); +IEnumerable quotes = gbm.Select(item => new Quote { Date = item.Time, Open = (decimal)item.Open, High = (decimal)item.High, Low = (decimal)item.Low, Close = (decimal)item.Close, Volume = (decimal)item.Volume }); +var SkResults = quotes.GetAtr(10).Select(i => i.Atr.Null2NaN()!); +for (int i=0; i< gbm.Length; i++) { + double delta = Math.Round(res1[i].Value, 10) - Math.Round(SkResults.ElementAt(i), 10); + //Console.WriteLine($"{i,3} {gbm.High[i].Value,6:F2} {gbm.Low[i].Value,6:F2} {gbm.Close[i].Value,6:F2} {res1[i].Value,10:F4} {SkResults.ElementAt(i),10:F4}\t{delta}"); + Console.WriteLine($"{i,3} h:{gbm.High[i].Value,6:F2} l:{gbm.Low[i].Value,6:F2} c:{gbm.Close[i].Value,6:F2} {res1[i].Atr,10:F4} {SkResults.ElementAt(i),10:F4}\t{delta}"); +} + +#!csharp + +//EMA test +GbmFeed gbm = new(); +Ema ema1 = new(gbm.Close, 10, useSma: true); +TSeries res1 = new(ema1); gbm.Add(30); IEnumerable quotes = gbm.Close.Select(item => new Quote { Date = item.Time, Close = (decimal)item.Value }); var SkResults = quotes.GetEma(10).Select(i => i.Ema.Null2NaN()!); for (int i=0; i< gbm.Length; i++) { - Console.WriteLine($"{i,3} {gbm.Close[i].Value,6:F2} {res1[i].Value,10:F4} {res2[i].Value,10:F4} {SkResults.ElementAt(i),10:F4}"); + double delta = Math.Round(res1[i].Value, 10) - Math.Round(SkResults.ElementAt(i), 10); + Console.WriteLine($"{i,3} {gbm.Close[i].Value,6:F2} {res1[i].Value,10:F4} {SkResults.ElementAt(i),10:F4}\t{delta}"); } - -#!csharp - -TValSeries test = new(); - -EmaCalc ma1 = new(test, 7, true); -TValSeries res1 = new(ma1); - -EmaCalc ma2 = new(test, 7, false); -TValSeries res2 = new(ma2); - -test.Add(new[]{1.0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0}); - -for (int i=0; i Math.Max(65,Periods * 2); int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; - public override string ShortName => $"JMA {Periods}:{Phase}:{SourceName}"; + public override string ShortName => $"JMA {Periods}:{Phase}:{Factor:F2}:{SourceName}"; public JmaIndicator() { @@ -49,7 +52,7 @@ public class JmaIndicator : Indicator, IWatchlistIndicator protected override void OnInit() { - ma = new Jma(Periods, Phase); + ma = new Jma(period: Periods, phase: Phase, factor: Factor); SourceName = Source.ToString(); base.OnInit(); } diff --git a/quantower/IndicatorExtensions.cs b/quantower/IndicatorExtensions.cs index 522c4a30..90d37e1f 100644 --- a/quantower/IndicatorExtensions.cs +++ b/quantower/IndicatorExtensions.cs @@ -59,14 +59,35 @@ public static class IndicatorExtensions #pragma warning disable CA1416 // Validate platform compatibility + public static void PaintHLine(this Indicator indicator, PaintChartEventArgs args, double value, Pen pen) + { + if (indicator.CurrentChart == null) + return; + + Graphics gr = args.Graphics; + var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + gr.SetClip(clientRect); + int leftX = clientRect.Left; + int rightX = clientRect.Right; + int Y = (int)converter.GetChartY(value); + using (pen) + { + gr.DrawLine(pen, new Point(leftX, Y), new Point(rightX, Y)); + } + } + public static void PaintSmoothCurve(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true, double tension = 0.2) { if (!series.Visible || indicator.CurrentChart == null) return; Graphics gr = args.Graphics; - var mainWindow = indicator.CurrentChart.MainWindow; + gr.SmoothingMode = SmoothingMode.AntiAlias; + var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex]; var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; gr.SetClip(clientRect); diff --git a/quantower/Volatility/AtrIndicator.cs b/quantower/Volatility/AtrIndicator.cs index de288567..a2f03aa9 100644 --- a/quantower/Volatility/AtrIndicator.cs +++ b/quantower/Volatility/AtrIndicator.cs @@ -8,9 +8,12 @@ public class AtrIndicator : Indicator, IWatchlistIndicator [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] public int Periods { get; set; } = 20; + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + private Atr? atr; protected LineSeries? AtrSeries; - public static int MinHistoryDepths => 2; + public int MinHistoryDepths => Math.Max(5, Periods * 2); int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; public AtrIndicator() @@ -19,7 +22,7 @@ public class AtrIndicator : Indicator, IWatchlistIndicator Description = "Measures market volatility by calculating the average range between high and low prices."; SeparateWindow = true; - AtrSeries = new("ATR", Color.Blue, 2, LineStyle.Solid); + AtrSeries = new($"ATR {Periods}", Color.Blue, 2, LineStyle.Solid); AddLineSeries(AtrSeries); } @@ -35,7 +38,16 @@ public class AtrIndicator : Indicator, IWatchlistIndicator TValue result = atr!.Calc(input); AtrSeries!.SetValue(result.Value); + AtrSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + } public override string ShortName => $"ATR ({Periods})"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintHLine(args, 0.05, new Pen(Color.DarkRed, width: 2)); + this.PaintSmoothCurve(args, AtrSeries!, atr!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } } diff --git a/quantower/Volatility/FlowIndicator.cs b/quantower/Volatility/FlowIndicator.cs new file mode 100644 index 00000000..d7695497 --- /dev/null +++ b/quantower/Volatility/FlowIndicator.cs @@ -0,0 +1,78 @@ +using System.Drawing; +using System.Drawing.Drawing2D; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class FlowIndicator : Indicator, IWatchlistIndicator +{ + protected string? SourceName; + public static int MinHistoryDepths => 2; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public FlowIndicator() + { + Name = "Flow Visualization"; + SeparateWindow = false; + } + + protected override void OnInit() + { + // placeholder + } + + protected override void OnUpdate(UpdateArgs args) + { + // placeholder + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + Graphics gr = args.Graphics; + gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + var mainWindow = this.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + gr.SetClip(clientRect); + DateTime leftTime = new[] { converter.GetTime(clientRect.Left), this.HistoricalData.Time(this!.Count - 1) }.Max(); + DateTime rightTime = new[] { converter.GetTime(clientRect.Right), this.HistoricalData.Time(0) }.Min(); + + int leftIndex = (int)this.HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; + int rightIndex = (int)this.HistoricalData.GetIndexByTime(rightTime.Ticks); + int width = this.CurrentChart.BarsWidth; + + for (int i = rightIndex; i < leftIndex; i++) + { + int barX1 = (int)converter.GetChartX(this.HistoricalData.Time(i)); + int barY1 = (int)converter.GetChartY(this.HistoricalData.Open(i)); + int barYHigh = (int)converter.GetChartY(this.HistoricalData.High(i)); + int barYLow = (int)converter.GetChartY(this.HistoricalData.Low(i)); + int barX2 = barX1 + width; + int barY2 = (int)converter.GetChartY(this.HistoricalData.Close(i)); + using (Brush transparentBrush = new SolidBrush(Color.FromArgb(250, 70, 70, 70))) + gr.FillRectangle(transparentBrush, barX1, barYHigh - 1, CurrentChart.BarsWidth, Math.Abs(barYLow - barYHigh) + 2); + using (Pen defaultPen = new(Color.Yellow, 3)) + { + defaultPen.StartCap = LineCap.Round; + defaultPen.EndCap = LineCap.Round; + gr.DrawLine(defaultPen, barX1, barY1, barX2, barY2); + } + if (i > 0) + { + int barX0 = (int)converter.GetChartX(this.HistoricalData.Time(i - 1)); + int barY0 = (int)converter.GetChartY(this.HistoricalData.Open(i - 1)); + using (Pen dottedPen = new(Color.Yellow, 1)) + { + dottedPen.DashStyle = DashStyle.Dot; + gr.DrawLine(dottedPen, barX2, barY2, barX0, barY0); + } + + } + + } + } + +} diff --git a/quantower/Volatility/JbandsIndicator.cs b/quantower/Volatility/JbandsIndicator.cs index 608f93c1..a77312c8 100644 --- a/quantower/Volatility/JbandsIndicator.cs +++ b/quantower/Volatility/JbandsIndicator.cs @@ -25,7 +25,8 @@ public class JbandsIndicator : Indicator, IWatchlistIndicator [InputParameter("vShort", sortIndex: 6, -100, 100, 1, 0)] public int Phase { get; set; } = 10; - private Jma? jma; + private Jma? jmaUp; + private Jma? jmaLo; protected LineSeries? UbSeries; protected LineSeries? LbSeries; protected string? SourceName; @@ -46,18 +47,20 @@ public class JbandsIndicator : Indicator, IWatchlistIndicator protected override void OnInit() { - jma = new(Periods, phase: Phase); + jmaUp = new(Periods, phase: Phase); + jmaLo = new(Periods, phase: Phase); SourceName = Source.ToString(); base.OnInit(); } protected override void OnUpdate(UpdateArgs args) { - TValue input = this.GetInputValue(args, Source); - jma!.Calc(input); + TBar input = IndicatorExtensions.GetInputBar(this, args); + jmaUp!.Calc(input.High); + jmaLo!.Calc(input.Low); - UbSeries!.SetValue(jma.UpperBand); - LbSeries!.SetValue(jma.LowerBand); + UbSeries!.SetValue(jmaUp.UpperBand); + LbSeries!.SetValue(jmaLo.LowerBand); } public override string ShortName => $"JBands ({Periods}:{Phase})"; From 7748bdd101d82f9e142bf245e2b1a3334abfffe5 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Tue, 22 Oct 2024 05:46:48 -0700 Subject: [PATCH 3/5] CMO - Chande Momentum Oscillator --- Tests/test_skender.stock.cs | 2 +- docs/indicators/indicators.md | 3 +- docs/styles.css | 2 +- lib/volatility/Cmo.cs | 78 +++++++++++++++++++++++++++ quantower/Volatility/AtrIndicator.cs | 1 + quantower/Volatility/CmoIndicator.cs | 72 +++++++++++++++++++++++++ quantower/Volatility/FlowIndicator.cs | 10 +++- 7 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 lib/volatility/Cmo.cs create mode 100644 quantower/Volatility/CmoIndicator.cs diff --git a/Tests/test_skender.stock.cs b/Tests/test_skender.stock.cs index 3dbf6a2a..38a87f20 100644 --- a/Tests/test_skender.stock.cs +++ b/Tests/test_skender.stock.cs @@ -345,7 +345,7 @@ public class SkenderTests var atrValues = quotes.GetAtr(lookbackPeriods: period).Select(i => i.Atr.Null2NaN()!); const int AdditionalPeriods = 500; - for (int i = QL.Length - 1; i > period + AdditionalPeriods; i--) + for (int i = QL.Length - 1; i > 1000 + AdditionalPeriods; i--) { Assert.InRange(atrValues.ElementAt(i) - QL[i].Value, -range, range); } diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index 7b28a378..b37f75d1 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -19,7 +19,7 @@ |CURVATURE - Rate of Change in Direction or Slope|`Curvature`|||| |ENTROPY - Measure of Uncertainty or Disorder|`Entropy`|||| |KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`|||| -|HUBER - Huber Loss||||| +|HUBER - Huber Loss|`Huberloss`|||| |MAX - Maximum with exponential decay|`Max`|||| |MAE - Mean Absolute Error|`Mae`|||| |MAPD - Mean Absolute Percentage Deviation|`Mapd`|||| @@ -80,7 +80,6 @@ |SMMA - Smoothed Moving Average|`Smma`|`✔️`||| |SSF - Ehler's Super Smoother Filter||||| |SUPERTREND - Supertrend||`✔️`||| -|SWMA - Symmetric Weighted Moving Average||||| |T3 - Tillson T3 Moving Average|`T3`|`✔️`|`✔️`|| |TEMA - Triple EMA Average|`Tema`|`✔️`|`✔️`|`✔️`| |TRIMA - Triangular Moving Average|`Trima`|`✔️`||`✔️`| diff --git a/docs/styles.css b/docs/styles.css index 7b7c45aa..f8c34949 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -97,7 +97,7 @@ --table-row-odd-background: var(--mono-shade2); /* Layout */ - --content-max-width: 55em; + --content-max-width: 100em; /* Cover */ --cover-margin: 0 auto; diff --git a/lib/volatility/Cmo.cs b/lib/volatility/Cmo.cs new file mode 100644 index 00000000..dff9c329 --- /dev/null +++ b/lib/volatility/Cmo.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; + +namespace QuanTAlib; + +/// +/// Represents a Chande Momentum Oscillator (CMO) calculator. +/// +public class Cmo : AbstractBase +{ + private readonly int _period; + private readonly CircularBuffer _diffBuffer; + private readonly CircularBuffer _sumH; + private readonly CircularBuffer _sumL; + private double _prevValue, _p_prevValue; + + public Cmo(int period) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + + _period = period; + _diffBuffer = new(period); + _sumH = new(period); + _sumL = new(period); + + WarmupPeriod = period+1; + Name = $"CMO({period})"; + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_prevValue = _prevValue; + } + else + { + _prevValue = _p_prevValue; + } + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + if (_index == 0) + { + _prevValue = Input.Value; + } + + double diff = Input.Value - _prevValue; + _prevValue = Input.Value; + + if (diff > 0) + { + _sumH.Add(diff, Input.IsNew); + _sumL.Add(0, Input.IsNew); + } + else + { + _sumH.Add(0, Input.IsNew); + _sumL.Add(-diff, Input.IsNew); + + } + + // Calculate sums for the specified period only + double sumH = _sumH.Sum(); + double sumL = _sumL.Sum(); + double divisor = sumH + sumL; + + return (Math.Abs(divisor) > double.Epsilon) ? + 100.0 * ((sumH - sumL) / divisor) : + 0.0; + } +} + diff --git a/quantower/Volatility/AtrIndicator.cs b/quantower/Volatility/AtrIndicator.cs index a2f03aa9..72c95e9c 100644 --- a/quantower/Volatility/AtrIndicator.cs +++ b/quantower/Volatility/AtrIndicator.cs @@ -41,6 +41,7 @@ public class AtrIndicator : Indicator, IWatchlistIndicator AtrSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here } +#pragma warning disable CA1416 // Validate platform compatibility public override string ShortName => $"ATR ({Periods})"; diff --git a/quantower/Volatility/CmoIndicator.cs b/quantower/Volatility/CmoIndicator.cs new file mode 100644 index 00000000..c539aeec --- /dev/null +++ b/quantower/Volatility/CmoIndicator.cs @@ -0,0 +1,72 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class CmoIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 9; + + [InputParameter("Data source", sortIndex: 5, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Cmo? cmo; + protected string? SourceName; + protected LineSeries? CmoSeries; + public int MinHistoryDepths => Periods + 1; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + + public CmoIndicator() + { + Name = "CMO - Chande Momentum Oscillator"; + Description = "Measures the momentum of price changes using the difference between the sum of recent gains and the sum of recent losses."; + SeparateWindow = true; + SourceName = Source.ToString(); + CmoSeries = new($"CMO {Periods}", Color.Blue, 2, LineStyle.Solid); + AddLineSeries(CmoSeries); + } + + protected override void OnInit() + { + cmo = new Cmo(Periods); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + cmo!.Calc(input); + + CmoSeries!.SetValue(cmo.Value); + CmoSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + + } + + public override string ShortName => $"CMO ({Periods}:{SourceName})"; + +#pragma warning disable CA1416 // Validate platform compatibility + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintHLine(args, 0, new Pen(Color.DarkGray, width: 1)); + this.PaintHLine(args, 50, new Pen(Color.DarkRed, width: 1)); + this.PaintHLine(args, -50, new Pen(Color.DarkGreen, width: 1)); + this.PaintSmoothCurve(args, CmoSeries!, cmo!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Volatility/FlowIndicator.cs b/quantower/Volatility/FlowIndicator.cs index d7695497..07dc9c2d 100644 --- a/quantower/Volatility/FlowIndicator.cs +++ b/quantower/Volatility/FlowIndicator.cs @@ -53,7 +53,15 @@ public class FlowIndicator : Indicator, IWatchlistIndicator int barX2 = barX1 + width; int barY2 = (int)converter.GetChartY(this.HistoricalData.Close(i)); using (Brush transparentBrush = new SolidBrush(Color.FromArgb(250, 70, 70, 70))) + { gr.FillRectangle(transparentBrush, barX1, barYHigh - 1, CurrentChart.BarsWidth, Math.Abs(barYLow - barYHigh) + 2); + } + using (Brush circ = new SolidBrush(Color.FromArgb(100, 255, 255, 0))) + { + int size = 3; + gr.FillEllipse(circ, barX1 - size, barY1 - size, 2 * size, 2 * size); + gr.FillEllipse(circ, barX2 - size, barY2 - size, 2 * size, 2 * size); + } using (Pen defaultPen = new(Color.Yellow, 3)) { defaultPen.StartCap = LineCap.Round; @@ -69,9 +77,9 @@ public class FlowIndicator : Indicator, IWatchlistIndicator dottedPen.DashStyle = DashStyle.Dot; gr.DrawLine(dottedPen, barX2, barY2, barX0, barY0); } - } + } } From 707d81ff72b269e4e9a15ef070a1fa2df9a8263d Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Wed, 23 Oct 2024 22:04:21 -0700 Subject: [PATCH 4/5] MACD histogram --- docs/indicators/indicators.md | 179 +++++++++++++-------------- docs/styles.css | 33 +++-- lib/volatility/Cmo.cs | 5 - lib/volatility/todo.md | 11 +- quantower/Averages/MacdIndicator.cs | 70 +++++++++-- quantower/IndicatorExtensions.cs | 53 ++++++++ quantower/Volatility/CmoIndicator.cs | 4 +- 7 files changed, 235 insertions(+), 120 deletions(-) diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index b37f75d1..897a417a 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -4,95 +4,11 @@ ✔️= Validation tests passed
❌= Issue -|**BASIC TRANSFORMS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady| -|--|:--:|:--:|:--:|:--:|:--:| -|OC2 - Midpoint price|️`.OC2`|CandlePart.OC2|MidPoint|| -|HL2 - Median Price|️`.HL2`|CandlePart.HL2|MedPrice|| -|HLC3 - Typical Price|️`.HLC3`|CandlePart.HLC3|TypPrice|| -|OHL3 - Mean Price|`️.OHL3`|CandlePart.OHL3`||| -|OHLC4 - Average Price|`️.OHLC4`|CandlePart.OHLC4|AvgPrice|| -|HLCC4 - Weighted Price|`️.HLCC4`||WclPrice|| -|
|||| -|**STATISTICS, ERRORS AND NUMERICAL ANALYSIS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady| -|BETA - Beta coefficient||||| -|CORR - Correlation Coefficient||||| -|CURVATURE - Rate of Change in Direction or Slope|`Curvature`|||| -|ENTROPY - Measure of Uncertainty or Disorder|`Entropy`|||| -|KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`|||| -|HUBER - Huber Loss|`Huberloss`|||| -|MAX - Maximum with exponential decay|`Max`|||| -|MAE - Mean Absolute Error|`Mae`|||| -|MAPD - Mean Absolute Percentage Deviation|`Mapd`|||| -|MAPE - Mean Absolute Percentage Error|`Mape`|||| -|MASE - Mean Absolute Scaled Error|`Mase`|||| -|MDA - Mean Directional Accuracy||||| -|ME - Mean Error|`Me`|||| -|MEDIAN - Middle value|`Median`|||| -|MIN - Minimum with exponential decay|`Min`|||| -|MODE - Most Frequent Value|`Mode`|||| -|MPE - Pean Percentage Error|`Mpe`|||| -|MSE - Mean Squared Error|`Mse`|||| -|MSLE - Mean Squared Logarithmic Error|`Msle`|||| -|PERCENTILE - Rank Order|`Percentile`|||| -|RSQUARED - Coefficient of Determination R-Squared||||| -|RAE - Relative Absolute Error|`Rae`|||| -|RMSE - Root Mean Squared Error|`Rmse`|||| -|RSE - Relateive Squared Error|`Rse`|||| -|RMSLE - Root Mean Squared Logarithmic Error|`Rmsle`|||| -|SKEW - Skewness, asymmetry of distribution|`Skew`|||| -|SLOPE - Rate of Change, Linear Regression|`Slope`|||| -|SMAPE - Symmetric Mean Absolute Percentage Error|`Smape`|||| -|STDDEV - Standard Deviation, Measure of Spread|`Stddev`|||| -|THEIL - Theil's U Statistics||||| -|VARIANCE - Average of Squared Deviations|`Variance`|||| -|ZSCORE - Standardized Score|`Zscore`|||| -|
||||| -|**AVERAGES & TRENDS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady| -|AFIRMA - Autoregressive Finite Impulse Response Moving Average|`Afirma`|||| -|ALMA - Arnaud Legoux Moving Average|`Alma`|`✔️`||| -|⭐DEMA - Double EMA Average|`Dema`|`⭐`|`⭐`|`⭐`|| -|DSMA - Deviation Scaled Moving Average|`Dsma`|||| -|DWMA - Double WMA Average|`Dwma`|||| -|⭐EMA - Exponential Moving Average|`Ema`|`⭐`|`⭐`|`⭐`|`⭐`| -|EPMA - Endpoint Moving Average|`Epma`|`✔️`||| -|FRAMA - Fractal Adaptive Moving Average|`Frama`|||| -|FWMA - Fibonacci Weighted Moving Average|`Fwma`|||| -|HILO - Gann High-Low Activator||||| -|HTIT - Hilbert Transform Instantaneous Trendline|`Htit`|`✔️`|`✔️`|| -|GMA - Gaussian-Weighted Moving Average|`Gma`|||| -|HMA - Hull Moving Average|`Hma`|`✔️`||`✔️`| -|HWMA - Holt-Winter Moving Average|`Hwma`|||| -|JMA - Jurik Moving Average|`Jma`|||| -|KAMA - Kaufman's Adaptive Moving Average|`Kama`|`✔️`|`✔️`|`✔️`| -|KDJ - KDJ Indicator (trend reversal)||||| -|LTMA - Laguerre Transform Moving Average|`Ltma`|||| -|MAAF - Median-Average Adaptive Filter|`Maaf`|||| -|MACD - Movign Average Convergence/Divergence||`✔️`|`✔️`|| -|MAMA - MESA Adaptive Moving Average|`Mama`|`✔️`|`✔️`|| -|MGDI - McGinley Dynamic Indicator|`Mgdi`|`✔️`||| -|MMA - Modified Moving Average|`Mma`|||| -|PPMA - Pivot Point Moving Average||||| -|PWMA - Pascal's Weighted Moving Average|`Pwma`|||| -|QEMA - Quad Exponential Moving Average|`Qema`|||| -|RMA - WildeR's Moving Average|`Rma`|||| -|SINEMA - Sine Weighted Moving Average|`Sinema`|||| -|⭐SMA - Simple Moving Average|`Sma`|`⭐`|`⭐`|`⭐`|`⭐`| -|SMMA - Smoothed Moving Average|`Smma`|`✔️`||| -|SSF - Ehler's Super Smoother Filter||||| -|SUPERTREND - Supertrend||`✔️`||| -|T3 - Tillson T3 Moving Average|`T3`|`✔️`|`✔️`|| -|TEMA - Triple EMA Average|`Tema`|`✔️`|`✔️`|`✔️`| -|TRIMA - Triangular Moving Average|`Trima`|`✔️`||`✔️`| -|TSF - Time Series Forecast|||`✔️`|`✔️`| -|VIDYA - Variable Index Dynamic Average|`Vidya`|||`✔️`| -|VORTEX - Vortex Indicator||`✔️`||| -|WMA - Weighted Moving Average|`Wma`|`✔️`||`✔️`| -|ZLEMA - Zero Lag EMA Average|`Zlema`|||`✔️`| -|
|||| -|**VOLATILITY INDICATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady| +|**VOLATILITY INDICATORS**|QuanTALib|Skender.Stock|TALib.NETCore| +|--|:--:|:--:|:--:| |ADL - Chaikin Accumulation Distribution Line||GetAdl|Ad|| |ADOSC - Chaikin Accumulation Distribution Oscillator||GetChaikinOsc|AdOsc|| -|ATR - Average True Range||GetAtr|Atr|| +|ATR - Average True Range|`Atr`|GetAtr|Atr|| |ATRP - Average True Range Percent||||| |ATRSTOP - ATR Trailing Stop ||GetAtrStop||| |BBANDS - Bollinger Bands®||BollingerBands||| @@ -114,8 +30,82 @@ |TR - True Range||||| |UI - Ulcer Index||GetUlcerIndex||| |VSTOP - Volatility Stop||GetVolatilityStop||| -|
|||| -|**MOMENTUM INDICATORS & OSCILLATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady| +|**NUMERICAL ANALYSIS**|QuanTALib|Skender.Stock|TALib.NETCore| +|BETA - Beta coefficient||||| +|CORR - Correlation Coefficient||||| +|CURVATURE - Rate of Change in Direction or Slope|`Curvature`|||| +|ENTROPY - Measure of Uncertainty or Disorder|`Entropy`|||| +|KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`|||| +|HUBER - Huber Loss|`Huberloss`|||| +|MAX - Maximum with exponential decay|`Max`|||| +|MEDIAN - Middle value|`Median`|||| +|MIN - Minimum with exponential decay|`Min`|||| +|MODE - Most Frequent Value|`Mode`|||| +|PERCENTILE - Rank Order|`Percentile`|||| +|RSQUARED - Coefficient of Determination R-Squared||||| +|SKEW - Skewness, asymmetry of distribution|`Skew`|||| +|SLOPE - Rate of Change, Linear Regression|`Slope`|||| +|STDDEV - Standard Deviation, Measure of Spread|`Stddev`|||| +|THEIL - Theil's U Statistics||||| +|VARIANCE - Average of Squared Deviations|`Variance`|||| +|ZSCORE - Standardized Score|`Zscore`|||| +|**ERRORS**|QuanTALib|Skender.Stock|TALib.NETCore| +|MAE - Mean Absolute Error|`Mae`|||| +|MAPD - Mean Absolute Percentage Deviation|`Mapd`|||| +|MAPE - Mean Absolute Percentage Error|`Mape`|||| +|MASE - Mean Absolute Scaled Error|`Mase`|||| +|MDA - Mean Directional Accuracy||||| +|ME - Mean Error|`Me`|||| +|MPE - Pean Percentage Error|`Mpe`|||| +|MSE - Mean Squared Error|`Mse`|||| +|MSLE - Mean Squared Logarithmic Error|`Msle`|||| +|RAE - Relative Absolute Error|`Rae`|||| +|RMSE - Root Mean Squared Error|`Rmse`|||| +|RSE - Relateive Squared Error|`Rse`|||| +|RMSLE - Root Mean Squared Logarithmic Error|`Rmsle`|||| +|SMAPE - Symmetric Mean Absolute Percentage Error|`Smape`|||| +|**AVERAGES & TRENDS**QuanTALib|Skender.Stock|TALib.NETCore| +|AFIRMA - Autoregressive Finite Impulse Response Moving Average|`Afirma`|||| +|ALMA - Arnaud Legoux Moving Average|`Alma`|`✔️`||| +|⭐DEMA - Double EMA Average|`Dema`|`⭐`|`⭐`|`⭐`|| +|DSMA - Deviation Scaled Moving Average|`Dsma`|||| +|DWMA - Double WMA Average|`Dwma`|||| +|⭐EMA - Exponential Moving Average|`Ema`|`⭐`|`⭐`|`⭐`|`⭐`| +|EPMA - Endpoint Moving Average|`Epma`|`✔️`||| +|FRAMA - Fractal Adaptive Moving Average|`Frama`|||| +|FWMA - Fibonacci Weighted Moving Average|`Fwma`|||| +|HILO - Gann High-Low Activator||||| +|HTIT - Hilbert Transform Instantaneous Trendline|`Htit`|`✔️`|`✔️`|| +|GMA - Gaussian-Weighted Moving Average|`Gma`|||| +|HMA - Hull Moving Average|`Hma`|`✔️`||`✔️`| +|HWMA - Holt-Winter Moving Average|`Hwma`|||| +|JMA - Jurik Moving Average|`Jma`|||| +|KAMA - Kaufman's Adaptive Moving Average|`Kama`|`✔️`|`✔️`|`✔️`| +|KDJ - KDJ Indicator (trend reversal)||||| +|LTMA - Laguerre Transform Moving Average|`Ltma`|||| +|MAAF - Median-Average Adaptive Filter|`Maaf`|||| +|MAMA - MESA Adaptive Moving Average|`Mama`|`✔️`|`✔️`|| +|MGDI - McGinley Dynamic Indicator|`Mgdi`|`✔️`||| +|MMA - Modified Moving Average|`Mma`|||| +|PPMA - Pivot Point Moving Average||||| +|PWMA - Pascal's Weighted Moving Average|`Pwma`|||| +|QEMA - Quad Exponential Moving Average|`Qema`|||| +|RMA - WildeR's Moving Average|`Rma`|||| +|SINEMA - Sine Weighted Moving Average|`Sinema`|||| +|⭐SMA - Simple Moving Average|`Sma`|`⭐`|`⭐`|`⭐`|`⭐`| +|SMMA - Smoothed Moving Average|`Smma`|`✔️`||| +|SSF - Ehler's Super Smoother Filter||||| +|SUPERTREND - Supertrend||`✔️`||| +|T3 - Tillson T3 Moving Average|`T3`|`✔️`|`✔️`|| +|TEMA - Triple EMA Average|`Tema`|`✔️`|`✔️`|`✔️`| +|TRIMA - Triangular Moving Average|`Trima`|`✔️`||`✔️`| +|TSF - Time Series Forecast|||`✔️`|`✔️`| +|VIDYA - Variable Index Dynamic Average|`Vidya`|||`✔️`| +|VORTEX - Vortex Indicator||`✔️`||| +|WMA - Weighted Moving Average|`Wma`|`✔️`||`✔️`| +|ZLEMA - Zero Lag EMA Average|`Zlema`|||`✔️`| + +|**MOMENTUM INDICATORS & OSCILLATORS**|QuanTALib|Skender.Stock|TALib.NETCore| |AC - Acceleration Oscillator||||| |ADX - Average Directional Movement Index||GetAdx|Adx|| |ADXR - Average Directional Movement Index||Rating|Adxr|| @@ -138,6 +128,7 @@ |HURST - Hurst Exponent||GetHurst||| |KRI - Kairi Relative Index||||| |KVO - Klinger Volume Oscillator||GetKvo|||| +|MACD - Movign Average Convergence/Divergence||`✔️`|`✔️`|| |MFI - Money Flow Index||GetMfi||| |MOM - Momentum||||| |NVI - Negative Volume Index||||| @@ -155,8 +146,7 @@ |UO - Ultimate Oscillator||GetUltimate||| |WILLR - Larry Williams' %R||GetWilliamsR||| |WGAT - Williams Alligator||GetAlligator||| -|
|||| -|**VOLUME INDICATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady| +|**VOLUME INDICATORS**|QuanTALib|Skender.Stock|TALib.NETCore| |AOBV - Archer On-Balance Volume||||| |CMF - Chaikin Money Flow||GetCmf||| |EOM - Ease of Movement||||| @@ -170,3 +160,10 @@ |VP - Volume Profile||||| |VWAP - Volume Weighted Average Price||GetVwap||| |VWMA - Volume Weighted Moving Average||GetVwma|||| +|**BASIC TRANSFORMS**|QuanTALib|Skender.Stock|TALib.NETCore| +|OC2 - Midpoint price|️`.OC2`|CandlePart.OC2|MidPoint| +|HL2 - Median Price|️`.HL2`|CandlePart.HL2|MedPrice| +|HLC3 - Typical Price|️`.HLC3`|CandlePart.HLC3|TypPrice| +|OHL3 - Mean Price|`️.OHL3`|CandlePart.OHL3| +|OHLC4 - Average Price|`️.OHLC4`|CandlePart.OHLC4|AvgPrice| +|HLCC4 - Weighted Price|`️.HLCC4`||WclPrice| \ No newline at end of file diff --git a/docs/styles.css b/docs/styles.css index f8c34949..143773fa 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -31,12 +31,12 @@ --base-background-color: var(--mono-base); --base-color: #d3d3d3; --base-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - --base-font-size: 16px; + --base-font-size: 14px; --base-font-weight: normal; --base-line-height: 1.7; /* Modular Scale */ - --modular-scale: 1.333; + --modular-scale: 1.0; --modular-scale--2: calc(var(--modular-scale--1) / var(--modular-scale)); --modular-scale--1: calc(var(--modular-scale-1) / var(--modular-scale)); --modular-scale-1: 1rem; @@ -313,10 +313,12 @@ body:not([data-platform^=Mac]) * ::-webkit-scrollbar-track{ background:hsla(var(--mono-hue), var(--mono-saturation), 50%, 0.1) } ::-moz-selection{ - background:var(--selection-color) + background: var(--selection-color, #0074d9); + color: #ffffff; } ::selection{ - background:var(--selection-color) + background: var(--selection-color, #0074d9); + color: #ffffff; } .emoji{ height:var(--emoji-size); @@ -629,9 +631,6 @@ pre[data-lang]::selection,pre[data-lang] ::selection,code[class*=lang-]::selecti border-spacing:0; border-collapse:collapse } -.markdown-section th,.markdown-section td{ - padding:var(--table-cell-padding) -} .markdown-section th:not([align]){ text-align:left } @@ -642,14 +641,30 @@ pre[data-lang]::selection,pre[data-lang] ::selection,code[class*=lang-]::selecti background:var(--table-head-background) } .markdown-section th{ - font-weight:var(--table-head-font-weight); - color:var(--strong-color) + font-weight:var(--table-head-font-weight, 700); + color:var(--strong-color); } .markdown-section td{ border-color:var(--table-cell-border-color); border-style:solid; border-width:var(--table-cell-border-width, 0) } +.markdown-section td, +.markdown-section th { + padding:var(--table-cell-padding); + line-height: 1.1; /* Reduced from default 1.7 */ +} +.markdown-section th strong, +.markdown-section th b, +.markdown-section th em, +.markdown-section td strong, +.markdown-section td b, +.markdown-section td em { + font-size: 1.2em; + font-weight: var(--table-head-font-weight, 700); + color: var(--strong-color); + line-height: 2.0; +} .markdown-section tbody{ border-color:var(--table-body-border-color); border-style:solid; diff --git a/lib/volatility/Cmo.cs b/lib/volatility/Cmo.cs index dff9c329..df64d3a9 100644 --- a/lib/volatility/Cmo.cs +++ b/lib/volatility/Cmo.cs @@ -8,8 +8,6 @@ namespace QuanTAlib; ///
public class Cmo : AbstractBase { - private readonly int _period; - private readonly CircularBuffer _diffBuffer; private readonly CircularBuffer _sumH; private readonly CircularBuffer _sumL; private double _prevValue, _p_prevValue; @@ -18,9 +16,6 @@ public class Cmo : AbstractBase { if (period < 1) throw new ArgumentOutOfRangeException(nameof(period)); - - _period = period; - _diffBuffer = new(period); _sumH = new(period); _sumL = new(period); diff --git a/lib/volatility/todo.md b/lib/volatility/todo.md index 4a04ca32..32636c3f 100644 --- a/lib/volatility/todo.md +++ b/lib/volatility/todo.md @@ -4,7 +4,12 @@ - **Jurik Volatility (Volty)** - **Standard Deviation** -- **Relative Volatility Index (RVI)** +- **RVI Relative Volatility Index** +- **CMO Chande Momentum Oscillator** +- **Historical Volatility** +- **Average True Range (ATR) (High, Low, Close)** + +- Normalized ATR - Ulcer Index - ARCH/GARCH Models - Exponential Weighted Moving Average (EWMA) Volatility @@ -15,10 +20,6 @@ - Volatility Cone - Bollinger Bands - Stochastic Volatility: Typically modeled using closing prices, but can incorporate other price information - -## OHLC Input (Open, High, Low, Close) - -- **Average True Range (ATR) (High, Low, Close)** - Garman-Klass Volatility - Rogers-Satchell Volatility - Yang-Zhang Volatility diff --git a/quantower/Averages/MacdIndicator.cs b/quantower/Averages/MacdIndicator.cs index 8dca5e31..b8eb4f0b 100644 --- a/quantower/Averages/MacdIndicator.cs +++ b/quantower/Averages/MacdIndicator.cs @@ -1,5 +1,6 @@ using System.Diagnostics.Metrics; using System.Drawing; +using System.Drawing.Drawing2D; using TradingPlatform.BusinessLayer; namespace QuanTAlib; @@ -38,8 +39,11 @@ public class MacdIndicator : Indicator, IWatchlistIndicator private Ema? slow_ma; private Ema? fast_ma; private Ema? signal_ma; + private Slope? histSlope; protected LineSeries? MainSeries; - protected LineSeries? SignalSeries; + protected LineSeries? SignalSeries; + protected LineSeries? HistogramSeries; + protected LineSeries? HistSlopeSeries; protected string? SourceName; public int MinHistoryDepths => Slow; @@ -54,11 +58,16 @@ public class MacdIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "MACD - Moving Average Convergence Divergence"; Description = "MACD"; - MainSeries = new(name: $"MAIN", color: Color.Yellow, width: 2, style: LineStyle.Solid); - SignalSeries = new(name: $"SIGNAL", color: Color.Blue, width: 2, style: LineStyle.Solid); + MainSeries = new(name: $"MAIN", color: Color.Blue, width: 2, style: LineStyle.Solid); + SignalSeries = new(name: $"SIGNAL", color: Color.Yellow, width: 2, style: LineStyle.Solid); + HistogramSeries = new(name: $"HISTOGRAM", color: Color.White, width: 2, style: LineStyle.Solid); + HistSlopeSeries = new(name: $"SLOPE", color: Color.Transparent, width: 2, style: LineStyle.Solid); + AddLineSeries(MainSeries); AddLineSeries(SignalSeries); + AddLineSeries(HistogramSeries); + AddLineSeries(HistSlopeSeries); } protected override void OnInit() @@ -66,6 +75,7 @@ public class MacdIndicator : Indicator, IWatchlistIndicator slow_ma = new(Slow, useSma: UseSMA); fast_ma = new(Fast, useSma: UseSMA); signal_ma = new(Signal, useSma: UseSMA); + histSlope = new(2); SourceName = Source.ToString(); base.OnInit(); } @@ -76,20 +86,64 @@ public class MacdIndicator : Indicator, IWatchlistIndicator slow_ma!.Calc(input); fast_ma!.Calc(input); double main = fast_ma.Value - slow_ma.Value; - signal_ma!.Calc(main); + double signal = signal_ma!.Calc(main); + double histogram = main - signal; + histSlope!.Calc(histogram); MainSeries!.SetValue(main); MainSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here - SignalSeries!.SetValue(signal_ma.Value); + SignalSeries!.SetValue(signal); SignalSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + HistogramSeries!.SetValue(histogram); + HistogramSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + HistSlopeSeries!.SetValue(histSlope.Value); + HistSlopeSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here } +#pragma warning disable CA1416 // Validate platform compatibility public override void OnPaintChart(PaintChartEventArgs args) { - base.OnPaintChart(args); - this.PaintSmoothCurve(args, MainSeries!, slow_ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + + Graphics gr = args.Graphics; + gr.SmoothingMode = SmoothingMode.AntiAlias; + var mainWindow = this.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + + gr.SetClip(clientRect); + DateTime leftTime = new[] { converter.GetTime(clientRect.Left), this.HistoricalData.Time(this!.Count - 1) }.Max(); + DateTime rightTime = new[] { converter.GetTime(clientRect.Right), this.HistoricalData.Time(0) }.Min(); + int leftIndex = (int)this.HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; + int rightIndex = (int)this.HistoricalData.GetIndexByTime(rightTime.Ticks); + + for (int i = rightIndex; i < leftIndex; i++) + { + int barX = (int)converter.GetChartX(this.HistoricalData.Time(i)); + int barY = (int)converter.GetChartY(HistogramSeries![i]); + int barY0 = (int)converter.GetChartY(0); + int HistBarWidth = this.CurrentChart.BarsWidth - 2; + + Brush lowGreen = new SolidBrush(Color.FromArgb(255, 0, 100, 0)); + Brush highGreen = new SolidBrush(Color.FromArgb(255, 50, 255, 50)); + Brush lowRed = new SolidBrush(Color.FromArgb(255, 100, 0, 0)); + Brush highRed = new SolidBrush(Color.FromArgb(255, 255, 50, 50)); + + if (HistogramSeries[i] > 0) + { + Brush col = HistSlopeSeries![i] > 0 ? highGreen : lowGreen; + gr.FillRectangle(col, barX, barY, HistBarWidth, Math.Abs(barY - barY0)); + } + else + { + Brush col = HistSlopeSeries![i] < 0 ? highRed : lowRed; + gr.FillRectangle(col, barX, barY0, HistBarWidth, Math.Abs(barY0 - barY)); + } + + } + + this.PaintSmoothCurve(args, MainSeries!, slow_ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.3); this.PaintSmoothCurve(args, SignalSeries!, slow_ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); - this.DrawText(args, Description); + base.OnPaintChart(args); } } diff --git a/quantower/IndicatorExtensions.cs b/quantower/IndicatorExtensions.cs index 90d37e1f..5c9a79dc 100644 --- a/quantower/IndicatorExtensions.cs +++ b/quantower/IndicatorExtensions.cs @@ -8,6 +8,12 @@ public enum SourceType { Open, High, Low, Close, HL2, OC2, OHL3, HLC3, OHLC4, HLCC4 } + +public enum MaType +{ + Alma, Dema, Dsma, Dwma, Ema, Epma, Frama, Fwma, Gma, Hma, Hwma, Jma, Kama, Maaf, Mgdi, MMa, Pwma, Rema, Rma, Sinema, Sma, Smma, T3, Tema, Trima, Vidya, Wma, Zlema +} + public static class IndicatorExtensions { public static TValue GetInputValue(this Indicator indicator, UpdateArgs args, SourceType source) @@ -131,6 +137,53 @@ public static class IndicatorExtensions } } } + + public static void PaintHistogram(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true) + { + if (!series.Visible || indicator.CurrentChart == null) + return; + + Graphics gr = args.Graphics; + gr.SmoothingMode = SmoothingMode.AntiAlias; + var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + + gr.SetClip(clientRect); + DateTime leftTime = new[] { converter.GetTime(clientRect.Left), indicator.HistoricalData.Time(indicator!.Count - 1) }.Max(); + DateTime rightTime = new[] { converter.GetTime(clientRect.Right), indicator.HistoricalData.Time(0) }.Min(); + int leftIndex = (int)indicator.HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; + int rightIndex = (int)indicator.HistoricalData.GetIndexByTime(rightTime.Ticks); + + for (int i = rightIndex; i < leftIndex; i++) + { + int barX = (int)converter.GetChartX(indicator.HistoricalData.Time(i)); + int barY = (int)converter.GetChartY(series[i]); + int barY0 = (int)converter.GetChartY(0); + int HistBarWidth = indicator.CurrentChart.BarsWidth - 2; + + if (series[i] > 0) + { + using (Brush hist = new SolidBrush(Color.FromArgb(150, 0, 255, 0))) + { + gr.FillRectangle(hist, barX, barY, HistBarWidth, Math.Abs(barY - barY0)); + + } + } + else + { + using (Brush hist = new SolidBrush(Color.FromArgb(150, 255, 0, 0))) + { + gr.FillRectangle(hist, barX, barY0, HistBarWidth, Math.Abs(barY0 - barY)); + + } + } + + } + + } + + public static void DrawText(this Indicator indicator, PaintChartEventArgs args, string text) { if (indicator.CurrentChart == null) diff --git a/quantower/Volatility/CmoIndicator.cs b/quantower/Volatility/CmoIndicator.cs index c539aeec..6a7a069d 100644 --- a/quantower/Volatility/CmoIndicator.cs +++ b/quantower/Volatility/CmoIndicator.cs @@ -65,8 +65,8 @@ public class CmoIndicator : Indicator, IWatchlistIndicator { base.OnPaintChart(args); this.PaintHLine(args, 0, new Pen(Color.DarkGray, width: 1)); - this.PaintHLine(args, 50, new Pen(Color.DarkRed, width: 1)); - this.PaintHLine(args, -50, new Pen(Color.DarkGreen, width: 1)); + this.PaintHLine(args, 50, new Pen(Color.Blue, width: 1)); + this.PaintHLine(args, -50, new Pen(Color.Blue, width: 1)); this.PaintSmoothCurve(args, CmoSeries!, cmo!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); } } From efc8e553dbf1b73c23dbff5b641c0e7ed87e6e9b Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Thu, 24 Oct 2024 18:30:59 -0700 Subject: [PATCH 5/5] Rsi and Rsx --- docs/indicators/indicators.md | 2 +- lib/averages/Jma.cs | 8 ++-- lib/volatility/Rsi.cs | 62 +++++++++++++++++++++++++ lib/volatility/Rsx.cs | 64 ++++++++++++++++++++++++++ quantower/Averages/MacdIndicator.cs | 4 +- quantower/Volatility/RsiIndicator.cs | 67 ++++++++++++++++++++++++++++ quantower/Volatility/RsxIndicator.cs | 67 ++++++++++++++++++++++++++++ 7 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 lib/volatility/Rsi.cs create mode 100644 lib/volatility/Rsx.cs create mode 100644 quantower/Volatility/RsiIndicator.cs create mode 100644 quantower/Volatility/RsxIndicator.cs diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index 897a417a..4ef85e51 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -23,7 +23,7 @@ |KEL - Keltner Channels||GetKeltner||| |NATR - Normalized Average True Range||GetAtr||| |CHN - Price Channel Indicator||||| -|RSI - Relative Strength Index||GetRsi||| +|RSI - Relative Strength Index|`Rsi`|GetRsi||| |SAR - Parabolic Stop and Reverse||GetParabolicSar||| |SRSI - Stochastic RSI||GetStochRsi||| |STARC - Starc Bands||GetStarcBands||| diff --git a/lib/averages/Jma.cs b/lib/averages/Jma.cs index c8e8eba0..0d3777cb 100644 --- a/lib/averages/Jma.cs +++ b/lib/averages/Jma.cs @@ -32,7 +32,7 @@ public class Jma : AbstractBase /// /// Thrown when period is less than 1. /// - public Jma(int period, int phase = 0, double factor = 0.45) + public Jma(int period, int phase = 0, double factor = 0.45, int buffer = 10) { if (period < 1) { @@ -42,7 +42,7 @@ public class Jma : AbstractBase _period = period; _phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5); - _vsumBuff = new CircularBuffer(10); + _vsumBuff = new CircularBuffer(buffer); _avoltyBuff = new CircularBuffer(65); _beta = factor * (_period - 1) / (factor * (_period - 1) + 2); @@ -56,7 +56,7 @@ public class Jma : AbstractBase /// The source object to subscribe to for value updates. /// The period over which to calculate the Jvolty. /// The phase parameter for the JMA-style calculation. - public Jma(object source, int period, int phase = 0) : this(period, phase) + public Jma(object source, int period, int phase = 0, double factor = 0.45, int buffer = 10) : this(period, phase, factor, buffer) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); @@ -148,7 +148,7 @@ public class Jma : AbstractBase _prevDet0 = det0; double ma2 = ma1 + _phase * det0; - double det1 = ((ma2 - _prevJma) * (1 - _alpha) * (1 - _alpha) ) + (_alpha * _alpha * _prevDet1); + double det1 = ((ma2 - _prevJma) * (1 - _alpha) * (1 - _alpha)) + (_alpha * _alpha * _prevDet1); _prevDet1 = det1; double jma = _prevJma + det1; _prevJma = jma; diff --git a/lib/volatility/Rsi.cs b/lib/volatility/Rsi.cs new file mode 100644 index 00000000..9252281d --- /dev/null +++ b/lib/volatility/Rsi.cs @@ -0,0 +1,62 @@ +using System; + +namespace QuanTAlib; + +/// +/// Represents a Relative Strength Index (RSI) calculator following Wilder's algorithm. +/// +public class Rsi : AbstractBase +{ + private readonly Rma _avgGain; + private readonly Rma _avgLoss; + private double _prevValue, _p_prevValue; + + public Rsi(int period = 14) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + _avgGain = new(period, useSma: true); + _avgLoss = new(period, useSma: true); + _index = 0; + WarmupPeriod = period + 1; + Name = $"RSI({period})"; + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_prevValue = _prevValue; + } + else + { + _prevValue = _p_prevValue; + } + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + if (_index == 1) + { + _prevValue = Input.Value; + } + + double change = Input.Value - _prevValue; + double gain = Math.Max(change, 0); + double loss = Math.Max(-change, 0); + _prevValue = Input.Value; + + _avgGain.Calc(gain, IsNew: Input.IsNew); + _avgLoss.Calc(loss, IsNew: Input.IsNew); + + double rsi = (_avgLoss.Value > 0) ? 100 - (100 / (1 + (_avgGain.Value / _avgLoss.Value))) : 100; + + + return rsi; + + + } +} diff --git a/lib/volatility/Rsx.cs b/lib/volatility/Rsx.cs new file mode 100644 index 00000000..6d44c26d --- /dev/null +++ b/lib/volatility/Rsx.cs @@ -0,0 +1,64 @@ +using System; + +namespace QuanTAlib; + +/// +/// Jurik's superior replacement for RSI +/// +public class Rsx : AbstractBase +{ + private readonly Rma _avgGain; + private readonly Rma _avgLoss; + private readonly Jma _rsx; + private double _prevValue, _p_prevValue; + + public Rsx(int period = 14, int phase = 0, double factor = 0.55) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + _avgGain = new(period); + _avgLoss = new(period); + _rsx = new(8, 100, 0.25, 3); + _index = 0; + WarmupPeriod = period + 1; + Name = $"RSX({period})"; + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_prevValue = _prevValue; + } + else + { + _prevValue = _p_prevValue; + } + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + if (_index == 1) + { + _prevValue = Input.Value; + } + + double change = Input.Value - _prevValue; + double gain = Math.Max(change, 0); + double loss = Math.Max(-change, 0); + _prevValue = Input.Value; + + _avgGain.Calc(gain, IsNew: Input.IsNew); + _avgLoss.Calc(loss, IsNew: Input.IsNew); + + double rsi = (_avgLoss.Value > 0) ? 100 - (100 / (1 + (_avgGain.Value / _avgLoss.Value))) : 100; + double rsx = _rsx.Calc(rsi, Input.IsNew); + + return rsx; + + + } +} diff --git a/quantower/Averages/MacdIndicator.cs b/quantower/Averages/MacdIndicator.cs index b8eb4f0b..56365f3f 100644 --- a/quantower/Averages/MacdIndicator.cs +++ b/quantower/Averages/MacdIndicator.cs @@ -62,7 +62,7 @@ public class MacdIndicator : Indicator, IWatchlistIndicator SignalSeries = new(name: $"SIGNAL", color: Color.Yellow, width: 2, style: LineStyle.Solid); HistogramSeries = new(name: $"HISTOGRAM", color: Color.White, width: 2, style: LineStyle.Solid); HistSlopeSeries = new(name: $"SLOPE", color: Color.Transparent, width: 2, style: LineStyle.Solid); - + HistSlopeSeries.Visible = false; AddLineSeries(MainSeries); AddLineSeries(SignalSeries); @@ -119,7 +119,7 @@ public class MacdIndicator : Indicator, IWatchlistIndicator for (int i = rightIndex; i < leftIndex; i++) { int barX = (int)converter.GetChartX(this.HistoricalData.Time(i)); - int barY = (int)converter.GetChartY(HistogramSeries![i]); + int barY = (int)converter.GetChartY(HistogramSeries![i]*2.0); int barY0 = (int)converter.GetChartY(0); int HistBarWidth = this.CurrentChart.BarsWidth - 2; diff --git a/quantower/Volatility/RsiIndicator.cs b/quantower/Volatility/RsiIndicator.cs new file mode 100644 index 00000000..fd0a4eec --- /dev/null +++ b/quantower/Volatility/RsiIndicator.cs @@ -0,0 +1,67 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class RsiIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 14; + + [InputParameter("Data source", sortIndex: 5, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Rsi? rsi; + protected string? SourceName; + protected LineSeries? RsiSeries; + public int MinHistoryDepths => Periods + 1; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public RsiIndicator() + { + Name = "RSI - Relative Strength Index"; + Description = "Measures the speed and magnitude of recent price changes to evaluate overbought or oversold conditions."; + SeparateWindow = true; + SourceName = Source.ToString(); + RsiSeries = new($"RSI {Periods}", Color.Blue, 2, LineStyle.Solid); + AddLineSeries(RsiSeries); + } + + protected override void OnInit() + { + rsi = new Rsi(Periods); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + rsi!.Calc(input); + + RsiSeries!.SetValue(rsi.Value); + RsiSeries!.SetMarker(0, Color.Transparent); + } + + public override string ShortName => $"RSI ({Periods}:{SourceName})"; + +#pragma warning disable CA1416 // Validate platform compatibility + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, RsiSeries!, rsi!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Volatility/RsxIndicator.cs b/quantower/Volatility/RsxIndicator.cs new file mode 100644 index 00000000..5158bc77 --- /dev/null +++ b/quantower/Volatility/RsxIndicator.cs @@ -0,0 +1,67 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class RsxIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Rsi Period", sortIndex: 1, 1, 2000, 1, 0)] + public int Period { get; set; } = 14; + + [InputParameter("Data source", sortIndex: 5, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Rsx? rsx; + protected string? SourceName; + protected LineSeries? RsxSeries; + public int MinHistoryDepths => Period + 1; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public RsxIndicator() + { + Name = "RSX - Jurik Trend Strengt Index"; + Description = "Measures the speed and magnitude of recent price changes to evaluate overbought or oversold conditions."; + SeparateWindow = true; + SourceName = Source.ToString(); + RsxSeries = new($"RSX {Period}", Color.Blue, 2, LineStyle.Solid); + AddLineSeries(RsxSeries); + } + + protected override void OnInit() + { + rsx = new(Period); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + rsx!.Calc(input); + + RsxSeries!.SetValue(rsx.Value); + RsxSeries!.SetMarker(0, Color.Transparent); + } + + public override string ShortName => $"RSX ({Period}:{SourceName})"; + +#pragma warning disable CA1416 // Validate platform compatibility + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, RsxSeries!, rsx!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +}