v3.0.0 preview | Streaming indicators and quotes #1018
Replies: 5 comments 10 replies
-
The latest preview is 3.0.0-preview.1014.25 for evaluation and community feedback. This preview version has this usage model: // initialize quoteProvider provider,
// can be extended to integrate with your
// quote stream or WebSocket source
QuoteProvider quoteProvider = new();
// subscribe individual indicators as observers
EmaObserver ema = quoteProvider.GetEma(20);
SmaObserver sma = quoteProvider.GetSma(13);
[..]
// these results are automatically updated
IEnumerable<EmaResult> emaResults = ema.Results;
IEnumerable<SmaResult> smaResults = sma.Results;
// when quotes are added to the provider (in separate process)
quoteProvider.Add(quote);
// and also works with `.Use` chain
EmaObserver emaAlt = quoteProvider
.Use(CandlePart.HL2)
.GetEma(20);
// stop streaming
// by unsubscribing each observer
ema.Unsubscribe();
// stop providing quoteProvider to everyone
// will unsubscribe all gracefully
quoteProvider.EndTransmission();
// feedback appreciated |
Beta Was this translation helpful? Give feedback.
-
What you think about using Rx.net for streaming data and processing indicators? |
Beta Was this translation helpful? Give feedback.
-
Hi, I'm right now trying out v3.0.0 preview and wanted to ask whether there is or will be an option to limit the size of the data collections held by both, observable and observer, as I didn't find any yet. Thanks! |
Beta Was this translation helpful? Give feedback.
-
Just wanted to share a note, for "determine best approach for handling out of sequence quotes", or the "auto-aggregator", you may need a parameter to determine what to do with missing candles. I found that in premarket or sometimes just after open, some candles will be missing because of extremely limited volume. I believe the correct way to do it is to use the last available value as that candle's value. For instance, if you have candles for 10:01, 10:02, 10:04, 10:05, the missing 10:03 could potentially mess up any calculations. So 10:03 can just have the same value as 10:02. Or, the avg between 10:02 and 10:04 is another approach ive seen. great library! and great work! |
Beta Was this translation helpful? Give feedback.
-
Let me share some modeling with you, I did for series of data I had before This design should help you write indicators for real time series very easy and quickly, and should handle series size, and improve performance by reducing arrays allocation Cache methods results, this interface for caching methods using their names and an object that represent the method parameterspublic interface IMethodCacher
{
bool TryGetFromCache<TRet>(string key, object parameters, out TRet result);
void SetCache<TRet>(string key, object parameters, TRet value);
TRet ExecuteAndCache<TRet>(object obj, Func<TRet> func, [CallerMemberName] string methodName = "");
Task<TRet> ExecuteAndCacheAsync<TRet>(object parameters, Func<Task<TRet>> func, [CallerMemberName] string methodName = "");
} Implement it, I chose concurrent dictionary hereTwo dictionaries one for the method name, the other inside it is for that method parameters public abstract class MethodCacher : IMethodCacher
{
readonly ConcurrentDictionary<string, ConcurrentDictionary<object, object>> _c =
new ConcurrentDictionary<string, ConcurrentDictionary<object, object>>();
public bool TryGetFromCache<TRet>(string key, object parameters, out TRet result)
{
result = default;
if (!_c.TryGetValue(key, out var dic))
return false;
if (!dic.TryGetValue(parameters, out var obj))
return false;
result = (TRet)obj;
return true;
}
public void SetCache<TRet>(string key, object parameters, TRet value)
{
if (!_c.TryGetValue(key, out var dic))
dic = _c.AddOrUpdate(key, new ConcurrentDictionary<object, object>(), (k, v) => v);
dic.AddOrUpdate(parameters, value, (k, v) => v);
}
public async Task<TRet> ExecuteAndCacheAsync<TRet>(object parameters, Func<Task<TRet>> func,
[CallerMemberName] string caller = "")
{
if (!_c.TryGetValue(caller, out var dic))
{
dic = _c.AddOrUpdate(caller, new ConcurrentDictionary<object, object>(), (k, v) => v);
}
if (!dic.TryGetValue(parameters, out var result))
{
result = await func();
dic.AddOrUpdate(parameters, result, (k, v) => v);
}
return (TRet)result;
}
public TRet ExecuteAndCache<TRet>(object obj, Func<TRet> func, [CallerMemberName] string caller = "")
{
if(!_c.TryGetValue(caller, out var dic))
{
dic = _c.AddOrUpdate(caller, new ConcurrentDictionary<object, object>(), (k, v) => v);
}
if(!dic.TryGetValue(obj, out var result))
{
result = func();
dic.AddOrUpdate(obj, result, (k, v) => v);
}
return (TRet)result;
}
public bool TryGetFromCache<TRet>(string key, out TRet result) =>
TryGetFromCache(key, new MethodCacheParams(), out result);
public void SetCache<TRet>(string key, TRet value) =>
SetCache(key, new MethodCacheParams());
} Now extend it, so it looks like method callDefine IEquatable to compare parameters and work as dictionary key for the method parameters, go for up to 6 parametersrecord struct MethodCacheParams();
record struct MethodCacheParams<T>(T a) where T : struct, INumber<T>;
record struct MethodCacheParams<T1, T2>(T1 a1, T2 a2)
where T1 : struct, INumber<T1>
where T2 : struct, INumber<T2>;
record struct MethodCacheParams<T1, T2, T3>(T1 a1, T2 a2, T3 a3)
where T1 : struct, INumber<T1>
where T2 : struct, INumber<T2>
where T3 : struct, INumber<T3>;
// and so on until T6 Write the extensionspublic static class MethodCacherExtensions
{
public static TRet ExecuteAndCache<TRet>(this IMethodCacher mc, Func<TRet> func, [CallerMemberName] string caller = "")
=> mc.ExecuteAndCache(new MethodCacheParams(), func, caller);
public static TRet ExecuteAndCache<TRet, T1>(this IMethodCacher mc, T1 a1, Func<TRet> func, [CallerMemberName] string caller = "") where T1 : struct, INumber<T1>
=> mc.ExecuteAndCache(new MethodCacheParams<T1>(a1), func, caller);
public static TRet ExecuteAndCache<TRet, T1, T2>(this IMethodCacher mc, T1 a1, T2 a2, Func<TRet> func, [CallerMemberName] string caller = "")
where T1 : struct, INumber<T1>
where T2 : struct, INumber<T2>
=> mc.ExecuteAndCache(new MethodCacheParams<T1, T2>(a1, a2), func, caller);
// and so on until T6
. . . Usage example, this is how the results holder will look likestatic void Main(string[] args)
{
var obj = new MyObj();
obj.X = "Hello world!";
var x = obj.GetX(1, 2, 3);
Console.WriteLine(x);
obj.X = "Good bye";
x = obj.GetX(2, 3, 4);
Console.WriteLine(x);
x = obj.GetX(1, 2, 3);
Console.WriteLine(x);
Console.ReadLine();
}
public class MyObj : MethodCacher
{
public string X { get; set; }
public MyResult GetX(int a, int b, int c)
{
var func = () => new MyResult(X);
return this.ExecuteAndCache(a, b, c, func);
}
} Implement series navigationDefine seriesThis model will have
public interface IReadOnlySeries<T> : IReadOnlyList<T>
{
IEnumerable<T> EnumeratePrevious(T item, bool includeItem);
IEnumerable<T> EnumerateNext(T item, bool includeItem);
T Prev(T item);
T Next(T item);
int IndexOf(T item);
}
public interface ISeries<T> : IReadOnlySeries<T>
{
void Add(T item);
} public class Series<T> : ISeries<T>
{
readonly int _maxItems;
readonly List<T> _items;
readonly bool _isCapped;
readonly object _lock;
public Series(int max = -1)
{
_maxItems = max;
_isCapped = max > 0;
if(_isCapped)
{
_items = new List<T>(max);
_lock = new object();
}
else
{
_items = new List<T>();
}
}
public T this[int index] => _items[index];
public int Count => _items.Count;
public void Add(T item)
{
if(_lock != null)
Monitor.Enter(_lock);
try
{
if (_isCapped && _items.Count == _maxItems)
{
var first = _items[0];
_items.RemoveAt(0);
OnItemOutdated(first);
}
_items.Add(item);
OnItemAdded(item);
}
finally
{
if(_lock != null)
Monitor.Exit(_lock);
}
}
public int IndexOf(T item) => _items.IndexOf(item);
public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
public IEnumerable<T> EnumeratePrevious(T item, bool includeItem)
{
var index = _items.IndexOf(item);
index += includeItem ? 1 : 0;
return _items.Take(index);
}
public IEnumerable<T> EnumerateNext(T item, bool includeItem)
{
var index = _items.IndexOf(item);
index += includeItem ? 0 : 1;
return _items.Skip(index);
}
public T Prev(T item)
{
var index = _items.IndexOf(item);
if (index <= 0)
return default;
return _items[index - 1];
}
public T Next(T item)
{
var index = _items.IndexOf(item);
if (index < 0 || index >= _items.Count - 1)
return default;
return _items[index + 1];
}
protected virtual void OnItemAdded(T item) { }
protected virtual void OnItemOutdated(T item) { }
} Define the candle modelThis model will have
public interface ICandle
{
public int Index { get; } // => _series.IndexOf(this);
public ICandle Prev(); // => _series.Prev(this);
public ICandle Next(); // => _series.Next(this);
public IEnumerable<ICandle> EnumeratePrev(bool includeSelf = true); // => _series.EnumeratePrevious(this, includeSelf);
public IEnumerable<ICandle> EnumerateNext(bool includeSelf = true); // => _series.EnumerateNext(this, includeSelf);
public void SetSeries(ISeriece<ICandle> series); Create the candles series, this will hold candles, set a cap so old ones will be removed, and allow them to navigate by Next() and Prev() methodspublic interface ICandleSeries : ISeries<ICandle>
{
}
public class CandleSeries : Series<ICandle>, ICandleSeries
{
public CandleSeries(int max) : base(max)
{
}
protected override void OnItemAdded(ICandle c)
{
c.SetSeries(this); // so in the Indicators we can do c.Prev() and c.Next()
. . . Implement the indicators using cachable methods that return indicators results, and implement the navigation in the candle as well. All in the Candle model.public class Candle : MethodCacher, ICandle
{
ICandleSeries _series;
//cached method
public EmaResult GetEma(int period)
{
var func = () => EmaCalculation(period);
//use method cacher
return ExecuteAndCache(period, func);
}
private EmaResult EmaCalculation(int perdiod)
{
//get previous item
var prev = this.Prev();
if(prev == null)
. . .
var prevEma = prev.GetEma(period) // This will come from memory, not calculated
//Calculate
}
//series methods
public void SetSeries(ICandleSeries series) => _series = series;
public ICandle Prev() => _series.Prev(this);
public ICandle Next() => _series.Next(this);
public IEnumerable<ICandle> EnumeratePrev(bool includeSelf = true) => _series.EnumeratePrevious(this, includeSelf);
} Or write an Indicatorpublic static class SomeIndicator
{
public static SomeResult Calculate(ICandle c, int p1, double p2, ...)
{
//prev candles
var prevCandles = c.EnumeratePrev().TakeLast(p1) ...
//prev
var prev = c.Prev();
//prev of prev
var prev_prev = c.Prev().Prev();
//cached result
var r = prev.GetResult(p1, p2, ...) //cached according to method name and parameters.
// and so on.
}
} Hope it helps, I might have copied inconsistent pieces, let me know if it is not clear. |
Beta Was this translation helpful? Give feedback.
-
We’ll be releasing a series of progressive pre-release preview versions to evaluate and stabilize features for streaming indicators from live WebSockets and other incremental quote sources. As we evolve our approach, your feedback is appreciated. Runnable examples are available.
Beta Was this translation helpful? Give feedback.
All reactions