|
5 | 5 | package one.chartsy.data;
|
6 | 6 |
|
7 | 7 | import one.chartsy.Candle;
|
| 8 | +import one.chartsy.SymbolResource; |
| 9 | +import one.chartsy.data.packed.PackedCandleSeries; |
| 10 | +import one.chartsy.data.packed.PackedDataset; |
| 11 | +import one.chartsy.time.AbstractTimeline; |
8 | 12 | import one.chartsy.time.Chronological;
|
| 13 | +import one.chartsy.time.Timeline; |
9 | 14 |
|
10 | 15 | import java.time.LocalDateTime;
|
| 16 | +import java.util.ArrayList; |
| 17 | +import java.util.Collection; |
| 18 | +import java.util.Iterator; |
| 19 | +import java.util.List; |
| 20 | +import java.util.function.Consumer; |
11 | 21 |
|
12 | 22 | /**
|
13 | 23 | * This class provides utility methods for working with candle series data.
|
@@ -55,5 +65,99 @@ public static <E extends Candle> Dataset<E> getBarsBefore(Series<? extends E> ba
|
55 | 65 | return dataset;
|
56 | 66 | }
|
57 | 67 |
|
| 68 | + /** |
| 69 | + * Computes a unified timeline of the given series. |
| 70 | + * |
| 71 | + * @param seriesCollection the collection of series |
| 72 | + * @return a unified timeline |
| 73 | + */ |
| 74 | + public static Timeline getUnifiedTimeline(Collection<? extends Series<? extends Chronological>> seriesCollection) { |
| 75 | + var order = Chronological.Order.REVERSE_CHRONOLOGICAL; |
| 76 | + var times = seriesCollection.stream() |
| 77 | + .mapMulti((Series<? extends Chronological> series, Consumer<Chronological> result) -> series.forEach(result)) |
| 78 | + .sorted(order.comparator()) |
| 79 | + .distinct() |
| 80 | + .toList(); |
| 81 | + |
| 82 | + return new AbstractTimeline(order) { |
| 83 | + @Override public int length() { return times.size(); } |
| 84 | + @Override public long getTimeAt(int x) { return times.get(x).getTime(); } |
| 85 | + }; |
| 86 | + } |
| 87 | + |
| 88 | + /** |
| 89 | + * Synchronizes the timelines of multiple candle series to a common timeline. Missing |
| 90 | + * data points in the original series are filled with candles having the previous |
| 91 | + * candle's closing price and zero volume. |
| 92 | + * |
| 93 | + * @param seriesCollection a collection of candle series to synchronize |
| 94 | + * @return a new collection of candle series with synchronized timelines |
| 95 | + */ |
| 96 | + public static <E extends Candle> List<Series<Candle>> synchronizeTimelines(Collection<? extends Series<? extends E>> seriesCollection) { |
| 97 | + Timeline timeline = getUnifiedTimeline(seriesCollection); |
| 98 | + |
| 99 | + return seriesCollection.stream() |
| 100 | + .map(series -> synchronizeTimeline(series, timeline)) |
| 101 | + .toList(); |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Synchronizes the timeline of a given candle series with a provided desired timeline. |
| 106 | + * If no candle exists for a particular timestamp in the unified timeline, a new candle |
| 107 | + * is created with the previous candle's closing price and zero volume. |
| 108 | + * |
| 109 | + * @param series the candle series to synchronize |
| 110 | + * @param timeline the timeline to synchronize the series with |
| 111 | + * @return a new candle series with the desired timeline |
| 112 | + * @throws AssertionError if the series cannot be aligned, for example if it contains |
| 113 | + * timestamps which doesn't exist in the {@code timeline} |
| 114 | + */ |
| 115 | + public static Series<Candle> synchronizeTimeline(Series<? extends Candle> series, Timeline timeline) { |
| 116 | + Iterator<? extends Candle> iter = series.iterator(); |
| 117 | + if (iter.hasNext()) { |
| 118 | + Candle curr = iter.next(), prev = null; |
| 119 | + int timeIndex = timeline.getTimeLocation(curr.getTime()); |
| 120 | + if (timeIndex < 0) { |
| 121 | + timeIndex = -timeIndex - 1; |
| 122 | + } |
| 123 | + List<Candle> bars = new ArrayList<>(timeIndex + 1); |
| 124 | + |
| 125 | + // Iterate through the timeline |
| 126 | + for (; timeIndex >= 0; timeIndex--) { |
| 127 | + long time = timeline.getTimeAt(timeIndex); |
| 128 | + |
| 129 | + if (curr.getTime() > time && prev != null) { |
| 130 | + bars.add(Candle.of(time, prev.close())); |
| 131 | + } |
| 132 | + else if (curr.getTime() == time) { |
| 133 | + bars.add(curr); |
| 134 | + if (iter.hasNext()) { |
| 135 | + prev = curr; |
| 136 | + curr = iter.next(); |
| 137 | + } else if (curr != MAX) { |
| 138 | + prev = curr; |
| 139 | + curr = MAX; |
| 140 | + } |
| 141 | + } |
| 142 | + else |
| 143 | + throw new AssertionError("Cannot synchronize with this Timeline"); |
| 144 | + } |
| 145 | + return createSeries(series, bars, timeline); |
| 146 | + } |
| 147 | + return createSeries(series, List.of(), timeline); |
| 148 | + } |
| 149 | + |
| 150 | + @SuppressWarnings("unchecked") |
| 151 | + private static PackedCandleSeries createSeries(Series<? extends Candle> series, List<Candle> bars, Timeline timeline) { |
| 152 | + return new PackedCandleSeries((SymbolResource<Candle>) series.getResource(), PackedDataset.of(bars, true)) { |
| 153 | + @Override |
| 154 | + public Timeline getTimeline() { |
| 155 | + return timeline; |
| 156 | + } |
| 157 | + }; |
| 158 | + } |
| 159 | + |
| 160 | + private static final Candle MAX = Candle.of(Long.MAX_VALUE, 0); |
| 161 | + |
58 | 162 | private CandleSeriesSupport() {} // cannot instantiate
|
59 | 163 | }
|
0 commit comments