19 using System.Collections.Generic;
20 using System.Runtime.CompilerServices;
32 protected TimeSpan
Step {
get;
set; }
60 var dataDiff = series.
Values[1].Time - series.
Values[0].Time;
64 return GetIdentitySeries(series.
Clone(empty:
true), series, start, stop, truncateValues:
false);
68 if (series is
Series seriesToSample)
70 return SampleSeries(seriesToSample, start, stop, truncateValues);
75 return SampleCandlestickSeries(candlestickSeries, start, stop, truncateValues);
78 throw new ArgumentException($
"SeriesSampler.Sample(): Sampling only supports {typeof(Series)} and {typeof(CandlestickSeries)}");
88 public Dictionary<string, Chart>
SampleCharts(IDictionary<string, Chart> charts, DateTime start, DateTime stop)
90 var sampledCharts =
new Dictionary<string, Chart>();
91 foreach (var chart
in charts.Values)
93 sampledCharts[chart.Name] =
SampleChart(chart, start, stop);
108 foreach (var series
in chart.Series.Values)
110 var sampledSeries =
Sample(series, start, stop);
111 sampledChart.AddSeries(sampledSeries);
124 private Series SampleSeries(
Series series, DateTime start, DateTime stop,
bool truncateValues)
128 var nextSampleTime = start;
134 return GetIdentitySeries(sampled, series, start, stop, truncateValues);
137 var enumerator = series.
Values.Cast<ChartPoint>().GetEnumerator();
140 enumerator.MoveNext();
141 var previous = enumerator.Current;
142 enumerator.MoveNext();
143 var current = enumerator.Current;
146 if (nextSampleTime < previous.Time)
148 nextSampleTime = previous.Time;
152 while (current.Time < nextSampleTime && enumerator.MoveNext())
155 current = enumerator.Current;
161 while (nextSampleTime <= current.Time && nextSampleTime <= stop)
163 ISeriesPoint sampledPoint;
167 sampledPoint =
new ChartPoint(nextSampleTime, (nextSampleTime +
Step) > current.Time ? current.Y : previous.Y);
171 sampledPoint = TruncateValue(
Interpolate(previous, current, nextSampleTime, (decimal)
Step.TotalSeconds), truncateValues, clone:
false);
174 nextSampleTime +=
Step;
177 sampled.Values.Add(sampledPoint);
181 if (current.Time < nextSampleTime)
183 sampled.Values.Add(sampledPoint);
189 if (nextSampleTime > current.Time)
191 if (enumerator.MoveNext())
194 current = enumerator.Current;
203 while (nextSampleTime <= stop);
205 enumerator.DisposeSafely();
217 private CandlestickSeries SampleCandlestickSeries(CandlestickSeries series, DateTime start, DateTime stop,
bool truncateValues)
219 var sampledSeries = (CandlestickSeries)series.Clone(empty:
true);
221 var candlesticks = series.Values;
222 var seriesSize = candlesticks.Count;
227 return GetIdentitySeries(sampledSeries, series, start, stop, truncateValues);
231 var nextSampleTime = start;
232 if (start < candlesticks[0].Time)
234 nextSampleTime = candlesticks[0].Time;
239 var startIndex = candlesticks.FindIndex(x => x.Time > nextSampleTime) - 1;
243 return GetIdentitySeries(sampledSeries, series, start, stop, truncateValues);
245 if (candlesticks[startIndex].Time == nextSampleTime && nextSampleTime <= stop)
247 sampledSeries.Values.Add(candlesticks[startIndex].Clone());
248 nextSampleTime +=
Step;
253 for (var i = startIndex; i < seriesSize && nextSampleTime <= stop; i++)
255 var current = (Candlestick)candlesticks[i];
256 Candlestick next =
null;
257 if (i + 1 < candlesticks.Count)
259 next = (Candlestick)candlesticks[i + 1];
261 if (nextSampleTime > current.Time)
268 var aggregated = startIndex != i;
269 var sampledCandlestick = AggregateCandlesticks(candlesticks, startIndex, i + 1, nextSampleTime, truncateValues);
271 var first = (Candlestick)candlesticks[startIndex];
272 var firstOpenTime = startIndex > 0
273 ? candlesticks[startIndex - 1].Time
274 : first.Time - (candlesticks[startIndex + 1].Time - candlesticks[startIndex].Time);
275 Candlestick previous =
null;
279 var interpolated =
Interpolate(sampledCandlestick, first, current, firstOpenTime, nextSampleTime, (decimal)
Step.TotalSeconds);
280 nextSampleTime +=
Step;
284 if (previous !=
null)
286 interpolated.Open = previous.Close;
288 sampledSeries.Values.Add(interpolated);
290 else if (current.Time < nextSampleTime)
292 sampledSeries.Values.Add(interpolated);
294 previous = interpolated;
299 interpolated.High = interpolated.Close;
300 interpolated.Low = interpolated.Close;
301 if (interpolated.Open.HasValue)
303 if (!interpolated.Close.HasValue || interpolated.Open > interpolated.Close.Value)
305 interpolated.High = interpolated.Open.Value;
307 if (!interpolated.Close.HasValue || interpolated.Open < interpolated.Close.Value)
309 interpolated.Low = interpolated.Open.Value;
314 if (next !=
null && (nextSampleTime +
Step) < next.Time && interpolated.Open ==
null)
323 while ((nextSampleTime <= current.Time || isNull) && nextSampleTime <= stop);
329 return sampledSeries;
336 private static Candlestick AggregateCandlesticks(List<ISeriesPoint> candlesticks,
int start,
int end, DateTime time,
bool truncateValues)
338 var aggregatedCandlestick =
new Candlestick
343 for (var j = start; j < end; j++)
345 var current = (Candlestick)candlesticks[j];
346 aggregatedCandlestick.Update(current.Open);
347 aggregatedCandlestick.Update(current.High);
348 aggregatedCandlestick.Update(current.Low);
349 aggregatedCandlestick.Update(current.Close);
352 return (Candlestick)TruncateValue(aggregatedCandlestick, truncateValues, clone:
false);
358 protected static decimal?
Interpolate(decimal x0, decimal? y0, decimal x1, decimal? y1, decimal xTarget, decimal step)
363 if (xTarget - x0 <= step)
373 if (x1 - xTarget <= step)
381 return (y1 - y0) * (xTarget - x0) / (x1 - x0) + y0;
389 if (current.
X == previous.
X)
394 var targetUnixTime = Time.DateTimeToUnixTimeStamp(targetTime).SafeDecimalCast();
396 return new ChartPoint(targetTime,
Interpolate(previous.
X, previous.
Y, current.
X, current.
Y, targetUnixTime, step));
402 private static Candlestick
Interpolate(Candlestick
template, Candlestick first, Candlestick current,
403 DateTime firstOpenTime, DateTime targetTime, decimal step)
406 if (firstOpenTime == current.Time)
408 result = (Candlestick)current.Clone();
409 result.Time = targetTime;
413 result = (Candlestick)
template.Clone();
414 result.Time = targetTime;
416 var targetUnixTime = Time.DateTimeToUnixTimeStamp(targetTime).SafeDecimalCast();
417 var firstOpenUnitTime = Time.DateTimeToUnixTimeStamp(firstOpenTime).SafeDecimalCast();
418 result.Close =
Interpolate(firstOpenUnitTime, first.Open, current.LongTime, current.Close, targetUnixTime, step);
426 [MethodImpl(MethodImplOptions.AggressiveInlining)]
427 private static ISeriesPoint TruncateValue(ISeriesPoint point,
bool truncate,
bool clone =
false)
434 var truncatedPoint = clone ? point.Clone() : point;
436 if (truncatedPoint is ChartPoint chartPoint)
438 chartPoint.y = SafeTruncate(chartPoint.y);
440 else if (truncatedPoint is Candlestick candlestick)
442 candlestick.Open = SafeTruncate(candlestick.Open);
443 candlestick.High = SafeTruncate(candlestick.High);
444 candlestick.Low = SafeTruncate(candlestick.Low);
445 candlestick.Close = SafeTruncate(candlestick.Close);
448 return truncatedPoint;
454 protected static T
GetIdentitySeries<T>(T sampled, T series, DateTime start, DateTime stop,
bool truncateValues)
458 foreach (var point
in series.Values)
460 if (point.Time >= start && point.Time <= stop)
462 sampled.
Values.Add(TruncateValue(point, truncateValues, clone:
true));
468 private static decimal? SafeTruncate(decimal? value)
472 return Math.Truncate(value.Value);