-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Fisher Transform indicator #49
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -321,4 +321,19 @@ public void Dosc_Update() | |
|
||
Assert.Equal(initialValue, finalValue, precision); | ||
} | ||
|
||
[Fact] | ||
public void Fisher_Update() | ||
{ | ||
var indicator = new Fisher(period: 10); | ||
double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); | ||
|
||
for (int i = 0; i < RandomUpdates; i++) | ||
{ | ||
indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); | ||
} | ||
double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); | ||
|
||
Assert.Equal(initialValue, finalValue, precision); | ||
} | ||
Comment on lines
+162
to
+174
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Add documentation for the Fisher Transform test. Consider adding a brief XML documentation comment explaining:
Example: /// <summary>
/// Verifies that the Fisher Transform indicator maintains consistency
/// when receiving repeated reference values after random updates.
/// Period of 10 is used as it's the commonly recommended value for this indicator.
/// </summary>
[Fact]
public void Fisher_Update() |
||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,95 @@ | ||||||||||||||||||||
using System.Runtime.CompilerServices; | ||||||||||||||||||||
namespace QuanTAlib; | ||||||||||||||||||||
|
||||||||||||||||||||
/// <summary> | ||||||||||||||||||||
/// FISHER: Fisher Transform | ||||||||||||||||||||
/// A technical indicator that converts prices into a Gaussian normal distribution. | ||||||||||||||||||||
/// </summary> | ||||||||||||||||||||
/// <remarks> | ||||||||||||||||||||
/// The Fisher Transform calculation process: | ||||||||||||||||||||
/// 1. Calculate the value of the price relative to its high-low range. | ||||||||||||||||||||
/// 2. Apply the Fisher Transform formula to the normalized price. | ||||||||||||||||||||
/// 3. Smooth the result using an exponential moving average. | ||||||||||||||||||||
/// | ||||||||||||||||||||
/// Key characteristics: | ||||||||||||||||||||
/// - Oscillates between -1 and 1 | ||||||||||||||||||||
/// - Emphasizes price reversals | ||||||||||||||||||||
/// - Can be used to identify overbought and oversold conditions | ||||||||||||||||||||
/// | ||||||||||||||||||||
/// Formula: | ||||||||||||||||||||
/// Fisher Transform = 0.5 * log((1 + x) / (1 - x)) | ||||||||||||||||||||
/// where: | ||||||||||||||||||||
/// x = 2 * ((price - min) / (max - min) - 0.5) | ||||||||||||||||||||
/// | ||||||||||||||||||||
/// Sources: | ||||||||||||||||||||
/// John F. Ehlers - "Rocket Science for Traders" (2001) | ||||||||||||||||||||
/// https://www.investopedia.com/terms/f/fisher-transform.asp | ||||||||||||||||||||
/// </remarks> | ||||||||||||||||||||
[SkipLocalsInit] | ||||||||||||||||||||
public sealed class Fisher : AbstractBase | ||||||||||||||||||||
{ | ||||||||||||||||||||
private readonly int _period; | ||||||||||||||||||||
private readonly double[] _prices; | ||||||||||||||||||||
private double _prevFisher; | ||||||||||||||||||||
private double _prevValue; | ||||||||||||||||||||
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / CodeQL
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
Check warning on line 34 in lib/oscillators/Fisher.cs GitHub Actions / Code_Coverage
|
||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Remove unused field '_prevValue' The field Apply this diff to remove the unused field: private readonly int _period;
private readonly double[] _prices;
-private double _prevValue;
private double _prevFisher; 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: CodeQL[warning] 34-34: [warning] 34-34: [warning] 34-34: [warning] 34-34: [warning] 34-34: [warning] 34-34: [warning] 34-34: [warning] 34-34: |
||||||||||||||||||||
|
||||||||||||||||||||
/// <param name="source">The data source object that publishes updates.</param> | ||||||||||||||||||||
/// <param name="period">The calculation period (default: 10)</param> | ||||||||||||||||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||||||||||||||||
public Fisher(object source, int period = 10) : this(period) | ||||||||||||||||||||
{ | ||||||||||||||||||||
var pubEvent = source.GetType().GetEvent("Pub"); | ||||||||||||||||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||||||||||||||||
public Fisher(int period = 10) | ||||||||||||||||||||
{ | ||||||||||||||||||||
_period = period; | ||||||||||||||||||||
_prices = new double[period]; | ||||||||||||||||||||
WarmupPeriod = period; | ||||||||||||||||||||
Name = "FISHER"; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||||||||||||||||
protected override void ManageState(bool isNew) | ||||||||||||||||||||
{ | ||||||||||||||||||||
if (isNew) | ||||||||||||||||||||
{ | ||||||||||||||||||||
_index++; | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||||||||||||||||
private double NormalizePrice(double price, double min, double max) | ||||||||||||||||||||
{ | ||||||||||||||||||||
return 2 * ((price - min) / (max - min) - 0.5); | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Clarify arithmetic precedence with additional parentheses Adding parentheses can make the arithmetic operations clearer and enhance code readability, even if the current expression is technically correct. Apply this diff to add parentheses for clarity: return 2 * ((price - min) / (max - min) - 0.5);
+return 2 * (((price - min) / (max - min)) - 0.5);
🧰 Tools🪛 GitHub Check: CodeFactor[notice] 66-66: lib/oscillators/Fisher.cs#L66 Handle potential division by zero when 'max' equals 'min' in 'NormalizePrice' In the Apply this diff to handle the scenario where private double NormalizePrice(double price, double min, double max)
{
+ var range = max - min;
+ if (range == 0)
+ return 0;
+ else
+ return 2 * ((price - min) / range - 0.5);
- return 2 * ((price - min) / (max - min) - 0.5);
} 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: CodeFactor[notice] 66-66: lib/oscillators/Fisher.cs#L66 |
||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||||||||||||||||
private double FisherTransform(double value) | ||||||||||||||||||||
{ | ||||||||||||||||||||
return 0.5 * System.Math.Log((1 + value) / (1 - value)); | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent division by zero or invalid logarithm in 'FisherTransform' In the Apply this diff to clamp private double FisherTransform(double value)
{
+ const double limit = 0.999;
+ if (value >= limit) value = limit;
+ if (value <= -limit) value = -limit;
return 0.5 * System.Math.Log((1 + value) / (1 - value));
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||||||||||||||||
protected override double Calculation() | ||||||||||||||||||||
{ | ||||||||||||||||||||
ManageState(Input.IsNew); | ||||||||||||||||||||
|
||||||||||||||||||||
var idx = _index % _period; | ||||||||||||||||||||
_prices[idx] = Input.Value; | ||||||||||||||||||||
|
||||||||||||||||||||
if (_index < _period - 1) return double.NaN; | ||||||||||||||||||||
|
||||||||||||||||||||
var min = _prices.Min(); | ||||||||||||||||||||
var max = _prices.Max(); | ||||||||||||||||||||
var normalizedPrice = NormalizePrice(Input.Value, min, max); | ||||||||||||||||||||
var fisherValue = FisherTransform(normalizedPrice); | ||||||||||||||||||||
|
||||||||||||||||||||
var smoothedFisher = 0.5 * (fisherValue + _prevFisher); | ||||||||||||||||||||
_prevFisher = smoothedFisher; | ||||||||||||||||||||
|
||||||||||||||||||||
return smoothedFisher; | ||||||||||||||||||||
} | ||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,5 +1,5 @@ | ||||||
# Oscillators indicators | ||||||
Done: 21, Todo: 8 | ||||||
Done: 22, Todo: 7 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Fix formatting in the count summary. The counts are correctly updated to reflect the Fisher Transform completion. However, there's a minor formatting issue. -Done: 22, Todo: 7
+Done: 22, To-do: 7 📝 Committable suggestion
Suggested change
🧰 Tools🪛 LanguageTool[grammar] ~2-~2: It appears that a hyphen is missing in the noun “To-do” (= task) or did you mean the verb “to do”? (TO_DO_HYPHEN) |
||||||
|
||||||
✔️ AC - Acceleration Oscillator | ||||||
✔️ AO - Awesome Oscillator | ||||||
|
@@ -15,7 +15,7 @@ Done: 21, Todo: 8 | |||||
CTI - Ehler's Correlation Trend Indicator | ||||||
✔️ DOSC - Derivative Oscillator | ||||||
EFI - Elder Ray's Force Index | ||||||
FISHER - Fisher Transform | ||||||
✔️ FISHER - Fisher Transform | ||||||
FOSC - Forecast Oscillator | ||||||
*GATOR - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth) | ||||||
*KDJ - KDJ Indicator (K, D, J lines) | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider adding edge case tests.
While the random update testing is good, consider adding specific test cases for:
Example addition: