diff --git a/.refactoring/base.cs b/.refactoring/base.cs index e3e84b86..7e61500b 100644 --- a/.refactoring/base.cs +++ b/.refactoring/base.cs @@ -1,44 +1,46 @@ using System; -public struct TValue -{ - public DateTime Timestamp { get; set; } - public double Value { get; set; } +public readonly struct TValue { + public DateTime Timestamp { get; } + public double Value { get; } - public TValue(DateTime timestamp, double value) - { + public TValue(DateTime timestamp, double value) { Timestamp = timestamp; Value = value; } + public TValue() : this(DateTime.Now, 0) { } + public TValue(double value) : this(DateTime.Now, value) { } + public static implicit operator double(TValue tv) => tv.Value; + public static implicit operator DateTime(TValue tv) => tv.Timestamp; + public static implicit operator TValue(double value) => new TValue(DateTime.Now, value); - public override string ToString() - { - return $"[{this.Timestamp:yyyy-MM-dd HH:mm:ss}: {this.Value:F2}]"; - } - public override bool Equals(object obj) - { - if (obj is TValue other) - { - return Timestamp == other.Timestamp && Value == other.Value; - } - return false; + + public override string ToString() { + return $"[{Timestamp:yyyy-MM-dd HH:mm:ss}: {Value:F2}]"; } - public override int GetHashCode() - { + public override bool Equals(object obj) { + return obj is TValue other && Equals(in other); + } + + public bool Equals(in TValue other) { + return Timestamp == other.Timestamp && Value == other.Value; + } + + public override int GetHashCode() { return HashCode.Combine(Timestamp, Value); } } -public struct TBar +public readonly struct TBar { - public DateTime Timestamp { get; set; } - public double Open { get; set; } - public double High { get; set; } - public double Low { get; set; } - public double Close { get; set; } - public double Volume { get; set; } + public DateTime Timestamp { get; } + public double Open { get; } + public double High { get; } + public double Low { get; } + public double Close { get; } + public double Volume { get; } public TBar(DateTime timestamp, double open, double high, double low, double close, double volume) { @@ -52,21 +54,22 @@ public struct TBar public override string ToString() { - return $"[{this.Timestamp:yyyy-MM-dd HH:mm:ss}: O={this.Open:F2}, H={this.High:F2}, L={this.Low:F2}, C={this.Close:F2}, V={this.Volume:F2}]"; + return $"[{Timestamp:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; } public override bool Equals(object obj) { - if (obj is TBar other) - { - return Timestamp == other.Timestamp && - Open == other.Open && - High == other.High && - Low == other.Low && - Close == other.Close && - Volume == other.Volume; - } - return false; + return obj is TBar other && Equals(in other); + } + + public bool Equals(in TBar other) + { + return Timestamp == other.Timestamp && + Open == other.Open && + High == other.High && + Low == other.Low && + Close == other.Close && + Volume == other.Volume; } public override int GetHashCode() @@ -77,13 +80,13 @@ public struct TBar -public class TValueEventArg : EventArgs +public class EventArg : EventArgs { public T Data { get; } public bool IsClosed { get; } public bool IsHot { get; } - public TValueEventArg(T data, bool isClosed, bool isHot) + public EventArg(T data, bool isClosed, bool isHot) { Data = data; IsClosed = isClosed; diff --git a/.refactoring/test.dib b/.refactoring/test.dib index 6c442688..7a0a681d 100644 --- a/.refactoring/test.dib +++ b/.refactoring/test.dib @@ -8,7 +8,7 @@ #!csharp -TValue vv = new(DateTime.Now, 100); +TValue vv = new(10); display(vv.ToString()); #!csharp @@ -20,16 +20,16 @@ display(bb.ToString()); public class Emitter { private Random random = new Random(); - public event EventHandler> Pub; + public event EventHandler> Pub; public void Emit() { DateTime now = DateTime.Now; double randomValue = random.NextDouble() * 100; // Generates a random number between 0 and 100 TValue value = new TValue(now, randomValue); - TValueEventArg eventArg = new TValueEventArg(value, true, true); + EventArg eventArg = new EventArg(value, true, true); OnValuePub(eventArg); } - protected virtual void OnValuePub(TValueEventArg eventArg) { + protected virtual void OnValuePub(EventArg eventArg) { Pub?.Invoke(this, eventArg); } } @@ -37,7 +37,7 @@ public class Emitter { public class BarEmitter { private Random random = new Random(); - public event EventHandler> Pub; + public event EventHandler> Pub; private double lastClose = 100.0; // Starting price public void Emit() @@ -51,11 +51,11 @@ public class BarEmitter TBar bar = new TBar(DateTime.Now, open, high, low, close, volume); lastClose = close; - TValueEventArg eventArg = new TValueEventArg(bar, true, true); + EventArg eventArg = new EventArg(bar, true, true); OnBarPub(eventArg); } - protected virtual void OnBarPub(TValueEventArg eventArg) + protected virtual void OnBarPub(EventArg eventArg) { Pub?.Invoke(this, eventArg); } @@ -66,9 +66,9 @@ public class Listener { public void Sub(object sender, EventArgs e) { - if (e is TValueEventArg tValueArg) { + if (e is EventArg tValueArg) { Console.WriteLine($"TValue: {tValueArg.Data.Value:F2}"); - } else if (e is TValueEventArg tBarArg) { + } else if (e is EventArg tBarArg) { Console.WriteLine($"TBar: o={tBarArg.Data.Open:F2}, v={tBarArg.Data.Volume:F2}"); } else { Console.WriteLine($"Unknown type: {e.GetType().Name}"); @@ -90,3 +90,57 @@ for (int i = 0; i < 3; i++) { em1.Emit(); em2.Emit(); } + +#!csharp + +public abstract class Indicator { + protected Indicator() { + Init(); } + public virtual void Init() {} + public virtual TValue Calc(TValue input, bool isNew=true, bool isHot=true) { + return new TValue(); + } +} + +public class EMA : Indicator +{ + private double lastEma, lastEmaCandidate, k; + private int period, i; + + public EMA(int period) { + Init(period); + } + + public void Init(int period) + { + this.period = period; + this.k = 2.0 / (period + 1); + this.lastEma = this.lastEmaCandidate = double.NaN; + this.i = 0; + } + + public override TValue Calc(TValue input, bool isNew = true, bool isHot = true) { + double ema; + + if (double.IsNaN(lastEma)) { lastEma = lastEmaCandidate = input.Value; } + + if (isNew) { + lastEma = lastEmaCandidate; + i++; + } + + double kk = (i>=period)?k:(2.0/(i+1)); + ema = lastEma + kk * (input.Value - lastEma); + lastEmaCandidate = ema; + + return new TValue(input.Timestamp, ema); + } +} + +#!csharp + +EMA ema = new(3); +display(ema.Calc(100)); +display(ema.Calc(0,false)); +display(ema.Calc(100,false)); +display(ema.Calc(0));