From 968dd3e7e683f97ae7d48bcbe1b587daaaef4a09 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Fri, 22 Nov 2024 11:18:49 -0800 Subject: [PATCH] pindexer: implement symmetrization of charts Now charts implement price information from both directions of a pair. --- crates/bin/pindexer/src/dex_ex/mod.rs | 49 +++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/crates/bin/pindexer/src/dex_ex/mod.rs b/crates/bin/pindexer/src/dex_ex/mod.rs index d430ef7150..ab2dde3b7c 100644 --- a/crates/bin/pindexer/src/dex_ex/mod.rs +++ b/crates/bin/pindexer/src/dex_ex/mod.rs @@ -24,6 +24,10 @@ mod candle { use penumbra_dex::CandlestickData; use std::fmt::Display; + fn geo_mean(a: f64, b: f64) -> f64 { + (a * b).sqrt() + } + /// Candlestick data, unmoored from the prison of a particular block height. /// /// In other words, this can represent candlesticks which span arbitrary windows, @@ -57,6 +61,31 @@ mod candle { self.direct_volume += that.direct_volume; self.swap_volume += that.swap_volume; } + + /// Mix this candle with a candle going in the opposite direction of the pair. + pub fn mix(&mut self, op: &Self) { + // We use the geometric mean, resulting in all the prices in a.mix(b) being + // the inverse of the prices in b.mix(a), and the volumes being equal. + self.close /= geo_mean(self.close, op.close); + self.open /= geo_mean(self.open, op.open); + self.low = self.low.min(1.0 / op.low); + self.high = self.high.min(1.0 / op.high); + // Using the closing price to look backwards at volume. + self.direct_volume += op.direct_volume / self.close; + self.swap_volume += op.swap_volume / self.close; + } + + /// Flip this candle to get the equivalent in the other direction. + pub fn flip(&self) -> Self { + Self { + open: 1.0 / self.open, + close: 1.0 / self.close, + low: 1.0 / self.low, + high: 1.0 / self.high, + direct_volume: self.direct_volume / self.close, + swap_volume: self.swap_volume / self.close, + } + } } impl From for Candle { @@ -616,14 +645,20 @@ impl Events { } fn with_candle(&mut self, pair: DirectedTradingPair, candle: Candle) { - match self.candles.get_mut(&pair) { - None => { - self.candles.insert(pair, candle); - } - Some(current) => { - current.merge(&candle); + // Popular both this pair and the flipped pair, and if the flipped pair + // is already populated, we need to mix the two candles together. + let flip = pair.flip(); + let new_candle = match self.candles.get(&flip).cloned() { + None => candle, + Some(flipped) => { + let mut out = candle; + out.mix(&flipped); + out } - } + }; + + self.candles.insert(pair, new_candle); + self.candles.insert(flip, new_candle.flip()); } fn metric(&mut self, pair: &DirectedTradingPair) -> &mut PairMetrics {