SIMD Refactor: Merge simd-dev into dev (#55)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: aider (openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
Co-authored-by: Warp <agent@warp.dev>
This commit is contained in:
Miha Kralj
2026-01-18 19:02:03 -08:00
committed by GitHub
co-authored by Claude Opus 4.5 aider Warp
parent 5bcdf8d614
commit 86fe32a682
1750 changed files with 198235 additions and 80539 deletions
@@ -0,0 +1,119 @@
using Xunit;
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Tests;
public class ExptransIndicatorTests
{
[Fact]
public void ExptransIndicator_Constructor_SetsDefaults()
{
var indicator = new ExptransIndicator();
Assert.Equal(SourceType.Close, indicator.Source);
Assert.True(indicator.ShowColdValues);
Assert.Equal("EXPTRANS - Exponential Function", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void ExptransIndicator_MinHistoryDepths_IsOne()
{
var indicator = new ExptransIndicator();
Assert.Equal(1, indicator.MinHistoryDepths);
}
[Fact]
public void ExptransIndicator_ShortName_IsCorrect()
{
var indicator = new ExptransIndicator();
Assert.Equal("Exptrans", indicator.ShortName);
}
[Fact]
public void ExptransIndicator_Initialize_CreatesLineSeries()
{
var indicator = new ExptransIndicator();
indicator.Initialize();
Assert.Single(indicator.LinesSeries);
Assert.Equal("Exptrans", indicator.LinesSeries[0].Name);
}
[Fact]
public void ExptransIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new ExptransIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 0, 1, -1, 0);
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Exp of 0 is 1.0
Assert.Equal(1.0, indicator.LinesSeries[0].GetValue(0), 1e-10);
}
[Fact]
public void ExptransIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new ExptransIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 0, 1, -1, 1);
indicator.HistoricalData.AddBar(now.AddMinutes(1), 0, 1, -1, 1);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(2, indicator.LinesSeries[0].Count);
// Exp of 1 is e (~2.718)
Assert.Equal(Math.E, indicator.LinesSeries[0].GetValue(0), 1e-10);
}
[Fact]
public void ExptransIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new ExptransIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 0, 1, -1, 0);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
Assert.Equal(2, indicator.LinesSeries[0].Count);
}
[Fact]
public void ExptransIndicator_DifferentSourceTypes_Work()
{
var sources = new[]
{
SourceType.Open,
SourceType.High,
SourceType.Low,
SourceType.Close,
SourceType.HL2,
SourceType.HLC3,
};
foreach (var source in sources)
{
var indicator = new ExptransIndicator { Source = source };
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 1, 2, 0, 1);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
Assert.Equal(1, indicator.LinesSeries[0].Count);
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
}
}
}
@@ -0,0 +1,56 @@
using System.Drawing;
using TradingPlatform.BusinessLayer;
using static QuanTAlib.IndicatorExtensions;
namespace QuanTAlib;
/// <summary>
/// EXPTRANS (Exponential Function) Quantower indicator.
/// Transforms values using the natural exponential function e^x.
/// </summary>
public class ExptransIndicator : Indicator, IWatchlistIndicator
{
[DataSourceInput]
public SourceType Source { get; set; } = SourceType.Close;
[InputParameter("Show Cold Values", sortIndex: 100)]
public bool ShowColdValues { get; set; } = true;
private Exptrans? _exptrans;
private Func<IHistoryItem, double>? _selector;
public int MinHistoryDepths => 1;
public override string ShortName => "Exptrans";
public ExptransIndicator()
{
Name = "EXPTRANS - Exponential Function";
Description = "Transforms values using the natural exponential function e^x";
SeparateWindow = true;
OnBackGround = true;
}
protected override void OnInit()
{
_exptrans = new Exptrans();
_selector = Source.GetPriceSelector();
AddLineSeries(new LineSeries("Exptrans", Color.Green, 2, LineStyle.Solid));
}
protected override void OnUpdate(UpdateArgs args)
{
if (_exptrans == null || _selector == null) return;
var item = HistoricalData[0, SeekOriginHistory.End];
double value = _selector(item);
bool isNew = args.IsNewBar();
TValue input = new(item.TimeLeft, value);
_exptrans.Update(input, isNew);
bool isHot = _exptrans.IsHot;
LinesSeries[0].SetValue(_exptrans.Last.Value, isHot, ShowColdValues);
}
}
+277
View File
@@ -0,0 +1,277 @@
using Xunit;
namespace QuanTAlib.Tests;
public class ExptransTests
{
private const double Tolerance = 1e-10;
[Fact]
public void Exptrans_Constructor_SetsProperties()
{
var indicator = new Exptrans();
Assert.Equal("Exptrans", indicator.Name);
Assert.Equal(0, indicator.WarmupPeriod);
Assert.True(indicator.IsHot); // Always hot (no warmup)
}
[Fact]
public void Exptrans_Update_ReturnsExponential()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
indicator.Update(new TValue(time, 0.0));
Assert.Equal(1.0, indicator.Last.Value, Tolerance); // exp(0) = 1
indicator.Update(new TValue(time.AddMinutes(1), 1.0));
Assert.Equal(Math.E, indicator.Last.Value, Tolerance); // exp(1) = e
indicator.Update(new TValue(time.AddMinutes(2), 2.0));
Assert.Equal(Math.E * Math.E, indicator.Last.Value, Tolerance); // exp(2) = e^2
indicator.Update(new TValue(time.AddMinutes(3), -1.0));
Assert.Equal(1.0 / Math.E, indicator.Last.Value, Tolerance); // exp(-1) = 1/e
}
[Fact]
public void Exptrans_Update_KnownValues()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
// exp(0) = 1
indicator.Update(new TValue(time, 0.0));
Assert.Equal(1.0, indicator.Last.Value, Tolerance);
// exp(ln(10)) = 10
indicator.Update(new TValue(time.AddMinutes(1), Math.Log(10.0)));
Assert.Equal(10.0, indicator.Last.Value, Tolerance);
// exp(ln(0.5)) = 0.5
indicator.Update(new TValue(time.AddMinutes(2), Math.Log(0.5)));
Assert.Equal(0.5, indicator.Last.Value, Tolerance);
}
[Fact]
public void Exptrans_Update_IsNewFalse_CorrectsPreviousValue()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
indicator.Update(new TValue(time, 1.0));
indicator.Update(new TValue(time.AddMinutes(1), 2.0));
Assert.Equal(Math.Exp(2.0), indicator.Last.Value, Tolerance);
// Correct last value
indicator.Update(new TValue(time.AddMinutes(1), 3.0), isNew: false);
Assert.Equal(Math.Exp(3.0), indicator.Last.Value, Tolerance);
}
[Fact]
public void Exptrans_Update_IterativeCorrection_RestoresState()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
double[] values = { 0.5, 1.0, 0.8, 1.2, 0.7, 1.5, 1.1 };
// Process all values
foreach (var v in values)
{
indicator.Update(new TValue(time, v));
time = time.AddMinutes(1);
}
double finalResult = indicator.Last.Value;
// Reset and process with corrections
indicator.Reset();
time = DateTime.UtcNow;
foreach (var v in values)
{
// Submit wrong value first
indicator.Update(new TValue(time, 0.0));
// Correct it
indicator.Update(new TValue(time, v), isNew: false);
time = time.AddMinutes(1);
}
Assert.Equal(finalResult, indicator.Last.Value, Tolerance);
}
[Fact]
public void Exptrans_Update_NaN_UsesLastValidValue()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
indicator.Update(new TValue(time, 2.0));
double beforeNaN = indicator.Last.Value;
indicator.Update(new TValue(time.AddMinutes(1), double.NaN));
Assert.Equal(beforeNaN, indicator.Last.Value, Tolerance);
}
[Fact]
public void Exptrans_Update_Infinity_UsesLastValidValue()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
indicator.Update(new TValue(time, 1.5));
double beforeInf = indicator.Last.Value;
indicator.Update(new TValue(time.AddMinutes(1), double.PositiveInfinity));
Assert.Equal(beforeInf, indicator.Last.Value, Tolerance);
}
[Fact]
public void Exptrans_Update_LargeInput_HandlesOverflow()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
indicator.Update(new TValue(time, 5.0));
double validResult = indicator.Last.Value;
// exp(1000) overflows to infinity
indicator.Update(new TValue(time.AddMinutes(1), 1000.0));
// Should use last valid value
Assert.Equal(validResult, indicator.Last.Value, Tolerance);
}
[Fact]
public void Exptrans_Reset_ClearsState()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
indicator.Update(new TValue(time.AddMinutes(i), i * 0.1));
}
Assert.True(indicator.IsHot);
indicator.Reset();
Assert.True(indicator.IsHot); // Still hot (no warmup)
Assert.Equal(default, indicator.Last);
}
[Fact]
public void Exptrans_Pub_EventFires()
{
var indicator = new Exptrans();
int eventCount = 0;
indicator.Pub += (object? sender, in TValueEventArgs args) => eventCount++;
indicator.Update(new TValue(DateTime.UtcNow, 1.0));
Assert.Equal(1, eventCount);
}
[Fact]
public void Exptrans_Chaining_Constructor_Works()
{
var source = new TSeries();
var indicator = new Exptrans(source);
source.Add(new TValue(DateTime.UtcNow, 0.0), true);
Assert.Equal(1.0, indicator.Last.Value, Tolerance); // exp(0) = 1
source.Add(new TValue(DateTime.UtcNow.AddMinutes(1), 1.0), true);
Assert.Equal(Math.E, indicator.Last.Value, Tolerance); // exp(1) = e
}
[Fact]
public void Exptrans_Calculate_TSeries_MatchesStreaming()
{
int count = 50;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 40000);
var bars = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Use log of close prices to stay in reasonable exp range
var logSource = Logtrans.Calculate(bars.Close);
// Streaming
var streaming = new Exptrans();
var streamingResults = new List<double>();
for (int i = 0; i < logSource.Count; i++)
{
streaming.Update(logSource[i]);
streamingResults.Add(streaming.Last.Value);
}
// Batch
var batch = Exptrans.Calculate(logSource);
// Compare all values
for (int i = 0; i < logSource.Count; i++)
{
Assert.Equal(streamingResults[i], batch[i].Value, Tolerance);
}
}
[Fact]
public void Exptrans_Calculate_Span_MatchesTSeries()
{
int count = 50;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 40001);
var bars = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var logSource = Logtrans.Calculate(bars.Close);
// TSeries batch
var batchResult = Exptrans.Calculate(logSource);
// Span calculation
var values = logSource.Values.ToArray();
var output = new double[count];
Exptrans.Calculate(values, output);
for (int i = 0; i < logSource.Count; i++)
{
Assert.Equal(batchResult[i].Value, output[i], Tolerance);
}
}
[Fact]
public void Exptrans_Calculate_Span_ValidatesArguments()
{
Assert.Throws<ArgumentException>(() =>
{
Span<double> output = stackalloc double[10];
Exptrans.Calculate(ReadOnlySpan<double>.Empty, output);
});
Assert.Throws<ArgumentException>(() =>
{
ReadOnlySpan<double> source = stackalloc double[10];
Span<double> output = stackalloc double[5];
Exptrans.Calculate(source, output);
});
}
[Fact]
public void Exptrans_LogInverse_ReturnsOriginal()
{
var exp = new Exptrans();
var time = DateTime.UtcNow;
double logValue = 3.5;
exp.Update(new TValue(time, logValue));
double expResult = exp.Last.Value;
// log(exp(x)) should equal x
Assert.Equal(logValue, Math.Log(expResult), Tolerance);
}
[Fact]
public void Exptrans_Negative_ReturnsPositive()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
// exp(x) is always positive for any finite x
for (int i = -10; i <= 10; i++)
{
indicator.Update(new TValue(time.AddMinutes(i + 10), i));
Assert.True(indicator.Last.Value > 0);
}
}
}
@@ -0,0 +1,173 @@
using Xunit;
namespace QuanTAlib.Tests;
/// <summary>
/// EXPTRANS validation tests - validates against Math.Exp (standard library)
/// </summary>
public class ExptransValidationTests
{
private const double Tolerance = 1e-14;
[Fact]
public void Exptrans_Batch_MatchesMathExp()
{
int count = 100;
// Use log-transformed prices to keep exp in reasonable range
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 50000);
var bars = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var logSource = Logtrans.Calculate(bars.Close);
var result = Exptrans.Calculate(logSource);
for (int i = 0; i < logSource.Count; i++)
{
double expected = Math.Exp(logSource[i].Value);
Assert.Equal(expected, result[i].Value, Tolerance);
}
}
[Fact]
public void Exptrans_Streaming_MatchesMathExp()
{
int count = 100;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 50001);
var bars = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var logSource = Logtrans.Calculate(bars.Close);
var indicator = new Exptrans();
for (int i = 0; i < logSource.Count; i++)
{
indicator.Update(logSource[i]);
double expected = Math.Exp(logSource[i].Value);
Assert.Equal(expected, indicator.Last.Value, Tolerance);
}
}
[Fact]
public void Exptrans_Span_MatchesMathExp()
{
int count = 100;
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 50002);
var bars = gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var logSource = Logtrans.Calculate(bars.Close);
var values = logSource.Values.ToArray();
var output = new double[count];
Exptrans.Calculate(values, output);
for (int i = 0; i < count; i++)
{
double expected = Math.Exp(values[i]);
Assert.Equal(expected, output[i], Tolerance);
}
}
[Fact]
public void Exptrans_KnownIdentities()
{
var indicator = new Exptrans();
var time = DateTime.UtcNow;
// exp(0) = 1
indicator.Update(new TValue(time, 0.0));
Assert.Equal(1.0, indicator.Last.Value, Tolerance);
// exp(1) = e
indicator.Update(new TValue(time.AddMinutes(1), 1.0));
Assert.Equal(Math.E, indicator.Last.Value, Tolerance);
// exp(n) = e^n
for (int n = 2; n <= 5; n++)
{
indicator.Update(new TValue(time.AddMinutes(n), n));
Assert.Equal(Math.Exp(n), indicator.Last.Value, Tolerance);
}
}
[Fact]
public void Exptrans_InverseOfLog()
{
// exp(ln(x)) = x for all x > 0
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 50003);
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
var source = bars.Close;
var logResult = Logtrans.Calculate(source);
var expResult = Exptrans.Calculate(logResult);
for (int i = 0; i < source.Count; i++)
{
Assert.Equal(source[i].Value, expResult[i].Value, 1e-10);
}
}
[Fact]
public void Exptrans_ProductRule()
{
// exp(a + b) = exp(a) * exp(b)
double a = 1.5;
double b = 2.3;
var indicator = new Exptrans();
var time = DateTime.UtcNow;
indicator.Update(new TValue(time, a));
double expA = indicator.Last.Value;
indicator.Reset();
indicator.Update(new TValue(time, b));
double expB = indicator.Last.Value;
indicator.Reset();
indicator.Update(new TValue(time, a + b));
double expAB = indicator.Last.Value;
Assert.Equal(expA * expB, expAB, Tolerance);
}
[Fact]
public void Exptrans_QuotientRule()
{
// exp(a - b) = exp(a) / exp(b)
double a = 3.0;
double b = 1.5;
var indicator = new Exptrans();
var time = DateTime.UtcNow;
indicator.Update(new TValue(time, a));
double expA = indicator.Last.Value;
indicator.Reset();
indicator.Update(new TValue(time, b));
double expB = indicator.Last.Value;
indicator.Reset();
indicator.Update(new TValue(time, a - b));
double expAMinusB = indicator.Last.Value;
Assert.Equal(expA / expB, expAMinusB, Tolerance);
}
[Fact]
public void Exptrans_PowerRule()
{
// exp(n * a) = exp(a)^n
double a = 1.2;
int n = 3;
var indicator = new Exptrans();
var time = DateTime.UtcNow;
indicator.Update(new TValue(time, a));
double expA = indicator.Last.Value;
indicator.Reset();
indicator.Update(new TValue(time, n * a));
double expNA = indicator.Last.Value;
Assert.Equal(Math.Pow(expA, n), expNA, 1e-12);
}
}
+152
View File
@@ -0,0 +1,152 @@
// EXPTRANS: Exponential Transformer
// Transforms values using the exponential function e^x
using System.Runtime.CompilerServices;
using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace QuanTAlib;
/// <summary>
/// EXPTRANS: Exponential Transformer
/// Applies e^x transformation to input values.
/// </summary>
/// <remarks>
/// Key properties:
/// - Inverse of natural logarithm: exp(ln(x)) = x
/// - Maps additive relationships to multiplicative
/// - Always positive output for any finite input
/// - Useful for converting log returns to price ratios
/// </remarks>
[SkipLocalsInit]
public sealed class Exptrans : AbstractBase
{
private record struct State(double LastValid = 1.0); // exp(0) = 1
private State _state = new(1.0), _p_state = new(1.0);
public override bool IsHot => true; // No warmup needed
public Exptrans()
{
Name = "Exptrans";
WarmupPeriod = 0;
}
/// <param name="source">Source indicator for chaining</param>
public Exptrans(ITValuePublisher source) : this()
{
source.Pub += HandleUpdate;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void HandleUpdate(object? sender, in TValueEventArgs e) => Update(e.Value, e.IsNew);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override TValue Update(TValue input, bool isNew = true)
{
if (isNew)
_p_state = _state;
else
_state = _p_state;
double value = input.Value;
double result;
if (double.IsFinite(value))
{
result = Math.Exp(value);
// Check for overflow (exp can produce infinity for large inputs)
if (double.IsFinite(result))
{
_state = new State(result);
}
else
{
result = _state.LastValid;
}
}
else
{
result = _state.LastValid;
}
Last = new TValue(input.Time, result);
PubEvent(Last, isNew);
return Last;
}
public override TSeries Update(TSeries source)
{
var result = new TSeries(source.Count);
ReadOnlySpan<double> values = source.Values;
ReadOnlySpan<long> times = source.Times;
for (int i = 0; i < source.Count; i++)
{
var tv = Update(new TValue(new DateTime(times[i], DateTimeKind.Utc), values[i]), true);
result.Add(tv, true);
}
return result;
}
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
{
TimeSpan interval = step ?? TimeSpan.FromSeconds(1);
DateTime time = DateTime.UtcNow - (interval * source.Length);
for (int i = 0; i < source.Length; i++)
{
Update(new TValue(time, source[i]), true);
time += interval;
}
}
public static TSeries Calculate(TSeries source)
{
var indicator = new Exptrans();
return indicator.Update(source);
}
/// <summary>
/// Calculates exponential over a span of values.
/// </summary>
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
{
if (source.Length == 0)
throw new ArgumentException("Source cannot be empty", nameof(source));
if (output.Length < source.Length)
throw new ArgumentException("Output length must be >= source length", nameof(output));
double lastValid = 1.0; // exp(0) = 1
for (int i = 0; i < source.Length; i++)
{
double val = source[i];
if (double.IsFinite(val))
{
double result = Math.Exp(val);
if (double.IsFinite(result))
{
lastValid = result;
output[i] = result;
}
else
{
output[i] = lastValid;
}
}
else
{
output[i] = lastValid;
}
}
}
public override void Reset()
{
_state = new(1.0);
_p_state = new(1.0);
Last = default;
}
}
+184
View File
@@ -0,0 +1,184 @@
# EXPTRANS: Exponential Function
> "The exponential function is the only function that is its own derivative—a mathematical curiosity that makes it indispensable for modeling growth, decay, and everything compounding."
The Exponential (EXP) transformer applies the natural exponential function $e^x$ to each value in a time series. As the inverse of the natural logarithm, it converts additive relationships back to multiplicative ones, making it essential for reconstructing price levels from log-returns and implementing models that assume log-normal distributions.
## Mathematical Foundation
### Core Formula
$$
\text{EXP}_t = e^{x_t}
$$
where:
- $x_t$ is the input value at time $t$
- $e \approx 2.71828...$ is Euler's number
### Key Properties
| Property | Formula | Description |
|:---------|:--------|:------------|
| **Inverse of Log** | $e^{\ln(x)} = x$ | Undoes natural logarithm |
| **Product Rule** | $e^{a+b} = e^a \cdot e^b$ | Additive inputs → multiplicative outputs |
| **Quotient Rule** | $e^{a-b} = e^a / e^b$ | Differences → ratios |
| **Power Rule** | $e^{n \cdot x} = (e^x)^n$ | Scaling in exponent → power |
| **Identity** | $e^0 = 1$ | Zero maps to unity |
| **Base Value** | $e^1 = e \approx 2.71828$ | Unit exponent gives $e$ |
### Domain and Range
| | Value |
|:--|:--|
| **Domain** | $(-\infty, +\infty)$ |
| **Range** | $(0, +\infty)$ |
The exponential function accepts any real number but always produces strictly positive outputs.
## Financial Applications
### Log-Return to Price Reconstruction
Given cumulative log-returns, reconstruct price levels:
$$
P_t = P_0 \cdot e^{\sum_{i=1}^{t} r_i}
$$
where $r_i$ are log-returns.
### Volatility Scaling
Convert log-volatility to multiplicative factors:
$$
\text{VolFactor} = e^{\sigma \sqrt{T}}
$$
### Compound Growth
Model continuous compounding:
$$
A = P \cdot e^{rt}
$$
where $r$ is the continuous rate and $t$ is time.
### Option Pricing
The exponential appears throughout Black-Scholes:
$$
C = S \cdot N(d_1) - K \cdot e^{-rT} \cdot N(d_2)
$$
## Implementation Details
### Overflow Handling
For large positive inputs, $e^x$ can overflow to infinity:
- $e^{709}$ ≈ $8.2 \times 10^{307}$ (near double max)
- $e^{710}$ → overflow
The implementation substitutes the last valid value when overflow occurs.
### Precision Considerations
| Input Range | Relative Precision |
|:------------|:-------------------|
| $|x| < 1$ | Full 15-16 digits |
| $|x| < 20$ | Full precision |
| $|x| > 700$ | Overflow risk |
### Streaming Characteristics
| Metric | Value |
|:-------|:------|
| **Warmup Period** | 0 |
| **Memory** | O(1) |
| **Complexity** | O(1) per update |
## Performance Profile
### Operation Count (Scalar)
| Operation | Count | Notes |
|:----------|:-----:|:------|
| EXP | 1 | Hardware instruction |
| **Total** | ~20 cycles | Platform dependent |
### Quality Metrics
| Metric | Score | Notes |
|:-------|:-----:|:------|
| **Accuracy** | 10/10 | IEEE 754 compliant |
| **Timeliness** | 10/10 | Zero lag |
| **Smoothness** | N/A | Transform preserves input characteristics |
## Usage Examples
### Basic Usage
```csharp
// Create EXP transformer
var exp = new Exptrans();
// Transform log-returns back to growth factors
var logReturn = new TValue(DateTime.UtcNow, 0.05);
var growthFactor = exp.Update(logReturn); // ≈ 1.0513
```
### Reconstructing Prices from Log-Returns
```csharp
var logReturns = new TSeries();
// ... populate with cumulative log-returns
var cumulativeExp = new Exptrans();
var priceRatios = cumulativeExp.Update(logReturns);
// Multiply by initial price to get price levels
var initialPrice = 100.0;
var prices = priceRatios.Select(v => v * initialPrice);
```
### Undoing Log Transform
```csharp
var log = new Logtrans();
var exp = new Exptrans();
// Round-trip: price → log → exp → price
var price = new TValue(DateTime.UtcNow, 150.0);
var logPrice = log.Update(price); // ≈ 5.0106
var recovered = exp.Update(logPrice); // ≈ 150.0
```
## Common Pitfalls
1. **Overflow Risk**: Input values above ~709 cause overflow. Monitor input ranges when working with cumulative sums.
2. **Magnitude Explosion**: Small additive changes in the exponent create large multiplicative changes in output. A change of 1.0 in the exponent multiplies the output by $e$ ≈ 2.72.
3. **Inverse Relationship**: EXP undoes LOG, but only if the original values were positive. Negative prices cannot be recovered through log-exp round-trip.
4. **Scale Sensitivity**: Unlike LOG which compresses ranges, EXP expands them dramatically. Ensure downstream consumers can handle the output magnitudes.
## Validation
| Test | Status |
|:-----|:------:|
| **Math.Exp Parity** | ✅ |
| **Known Values (e⁰=1, e¹=e)** | ✅ |
| **Inverse of Log** | ✅ |
| **Product Rule** | ✅ |
| **Quotient Rule** | ✅ |
| **Power Rule** | ✅ |
## References
- Euler, L. (1748). *Introductio in analysin infinitorum*.
- Maor, E. (1994). *e: The Story of a Number*. Princeton University Press.
- Hull, J. (2018). *Options, Futures, and Other Derivatives*. Pearson. (Black-Scholes applications)
+25
View File
@@ -0,0 +1,25 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Exponential Transformation (EXP)", "Exptrans", overlay=false)
//@function Applies an exponential transformation (y = e^x) to the input series.
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/numerics/exp.md
//@param source series float The input series to transform.
//@returns series float The exponentially transformed series.
//@optimized for performance and dirty data
expT(series float source) =>
if na(source)
runtime.error("Parameter 'source' cannot be na.")
math.exp(source)
// ---------- Main loop ----------
// Inputs
i_source = input(close, "Source")
// Calculation
transformedSource = expT(i_source)
// Plot
plot(transformedSource, "Exponential Transformation", color=color.yellow, linewidth=2)