diff --git a/crates/brk_bindgen/src/generators/javascript/client.rs b/crates/brk_bindgen/src/generators/javascript/client.rs index 173c21b44..dbdca434d 100644 --- a/crates/brk_bindgen/src/generators/javascript/client.rs +++ b/crates/brk_bindgen/src/generators/javascript/client.rs @@ -310,6 +310,9 @@ class BrkClientBase {{ this.timeout = isString ? 5000 : (options.timeout ?? 5000); /** @type {{Promise}} */ this._cachePromise = _openCache(isString ? undefined : options.cache); + /** @type {{Cache | null}} */ + this._cache = null; + this._cachePromise.then(c => this._cache = c); }} /** @@ -325,35 +328,57 @@ class BrkClientBase {{ }} /** - * Make a GET request with stale-while-revalidate caching + * Make a GET request - races cache vs network, first to resolve calls onUpdate * @template T * @param {{string}} path - * @param {{(value: T) => void}} [onUpdate] - Called when data is available + * @param {{(value: T) => void}} [onUpdate] - Called when data is available (may be called twice: cache then network) * @returns {{Promise}} */ async getJson(path, onUpdate) {{ const base = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl; const url = `${{base}}${{path}}`; - const cache = await this._cachePromise; - const cachedRes = await cache?.match(url); - const cachedJson = cachedRes ? await cachedRes.json() : null; + const cache = this._cache ?? await this._cachePromise; - if (cachedJson) onUpdate?.(cachedJson); - if (globalThis.navigator?.onLine === false) {{ - if (cachedJson) return cachedJson; - throw new BrkError('Offline and no cached data available'); - }} + let resolved = false; + /** @type {{Response | null}} */ + let cachedRes = null; - try {{ - const res = await this.get(path); - if (cachedRes?.headers.get('ETag') === res.headers.get('ETag')) return cachedJson; + // Race cache vs network - first to resolve calls onUpdate + const cachePromise = cache?.match(url).then(async (res) => {{ + cachedRes = res ?? null; + if (!res) return null; + const json = await res.json(); + if (!resolved && onUpdate) {{ + resolved = true; + onUpdate(json); + }} + return json; + }}); + const networkPromise = this.get(path).then(async (res) => {{ const cloned = res.clone(); const json = await res.json(); - onUpdate?.(json); + // Skip update if ETag matches and cache already delivered + if (cachedRes?.headers.get('ETag') === res.headers.get('ETag')) {{ + if (!resolved && onUpdate) {{ + resolved = true; + onUpdate(json); + }} + return json; + }} + resolved = true; + if (onUpdate) {{ + onUpdate(json); + }} if (cache) _runIdle(() => cache.put(url, cloned)); return json; + }}); + + try {{ + return await networkPromise; }} catch (e) {{ + // Network failed - wait for cache + const cachedJson = await cachePromise?.catch(() => null); if (cachedJson) return cachedJson; throw e; }} diff --git a/crates/brk_computer/src/distribution/cohorts/address/groups.rs b/crates/brk_computer/src/distribution/cohorts/address/groups.rs index 718164bda..6104acfde 100644 --- a/crates/brk_computer/src/distribution/cohorts/address/groups.rs +++ b/crates/brk_computer/src/distribution/cohorts/address/groups.rs @@ -56,10 +56,10 @@ impl AddressCohorts { })) } - /// Apply a function to each aggregate cohort with its source cohorts. - fn for_each_aggregate(&mut self, mut f: F) -> Result<()> + /// Apply a function to each aggregate cohort with its source cohorts (in parallel). + fn for_each_aggregate(&mut self, f: F) -> Result<()> where - F: FnMut(&mut AddressCohortVecs, Vec<&AddressCohortVecs>) -> Result<()>, + F: Fn(&mut AddressCohortVecs, Vec<&AddressCohortVecs>) -> Result<()> + Sync, { let by_amount_range = &self.0.amount_range; @@ -80,10 +80,9 @@ impl AddressCohorts { }) .collect(); - for (vecs, sources) in pairs { - f(vecs, sources)?; - } - Ok(()) + pairs + .into_par_iter() + .try_for_each(|(vecs, sources)| f(vecs, sources)) } /// Compute overlapping cohorts from component amount_range cohorts. @@ -105,22 +104,24 @@ impl AddressCohorts { starting_indexes: &ComputeIndexes, exit: &Exit, ) -> Result<()> { + // 1. Compute all metrics except net_sentiment self.par_iter_mut() - .try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit)) - } + .try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit))?; - /// Recompute net_sentiment for aggregate cohorts as weighted average of source cohorts. - pub fn compute_aggregate_net_sentiment( - &mut self, - indexes: &indexes::Vecs, - starting_indexes: &ComputeIndexes, - exit: &Exit, - ) -> Result<()> { + // 2. Compute net_sentiment.height for separate cohorts (greed - pain) + self.par_iter_separate_mut() + .try_for_each(|v| v.metrics.compute_net_sentiment_height(starting_indexes, exit))?; + + // 3. Compute net_sentiment.height for aggregate cohorts (weighted average) self.for_each_aggregate(|vecs, sources| { let metrics: Vec<_> = sources.iter().map(|v| &v.metrics).collect(); vecs.metrics - .compute_net_sentiment_from_others(starting_indexes, &metrics, indexes, exit) - }) + .compute_net_sentiment_from_others(starting_indexes, &metrics, exit) + })?; + + // 4. Compute net_sentiment dateindex for ALL cohorts + self.par_iter_mut() + .try_for_each(|v| v.metrics.compute_net_sentiment_rest(indexes, starting_indexes, exit)) } /// Second phase of post-processing: compute relative metrics. diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs index b7424bc29..2c8e9d6d1 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs @@ -152,10 +152,10 @@ impl UTXOCohorts { })) } - /// Apply a function to each aggregate cohort with its source cohorts. - fn for_each_aggregate(&mut self, mut f: F) -> Result<()> + /// Apply a function to each aggregate cohort with its source cohorts (in parallel). + fn for_each_aggregate(&mut self, f: F) -> Result<()> where - F: FnMut(&mut UTXOCohortVecs, Vec<&UTXOCohortVecs>) -> Result<()>, + F: Fn(&mut UTXOCohortVecs, Vec<&UTXOCohortVecs>) -> Result<()> + Sync, { let by_age_range = &self.0.age_range; let by_amount_range = &self.0.amount_range; @@ -215,10 +215,9 @@ impl UTXOCohorts { })) .collect(); - for (vecs, sources) in pairs { - f(vecs, sources)?; - } - Ok(()) + pairs + .into_par_iter() + .try_for_each(|(vecs, sources)| f(vecs, sources)) } /// Compute overlapping cohorts from component age/amount range cohorts. @@ -240,26 +239,24 @@ impl UTXOCohorts { starting_indexes: &ComputeIndexes, exit: &Exit, ) -> Result<()> { + // 1. Compute all metrics except net_sentiment self.par_iter_mut() - .try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit)) - } + .try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit))?; - /// Recompute net_sentiment for aggregate cohorts as weighted average of source cohorts. - pub fn compute_aggregate_net_sentiment( - &mut self, - indexes: &indexes::Vecs, - starting_indexes: &ComputeIndexes, - exit: &Exit, - ) -> Result<()> { + // 2. Compute net_sentiment.height for separate cohorts (greed - pain) + self.par_iter_separate_mut() + .try_for_each(|v| v.metrics.compute_net_sentiment_height(starting_indexes, exit))?; + + // 3. Compute net_sentiment.height for aggregate cohorts (weighted average) self.for_each_aggregate(|vecs, sources| { let metrics: Vec<_> = sources.iter().map(|v| &v.metrics).collect(); - vecs.metrics.compute_net_sentiment_from_others( - starting_indexes, - &metrics, - indexes, - exit, - ) - }) + vecs.metrics + .compute_net_sentiment_from_others(starting_indexes, &metrics, exit) + })?; + + // 4. Compute net_sentiment dateindex for ALL cohorts + self.par_iter_mut() + .try_for_each(|v| v.metrics.compute_net_sentiment_rest(indexes, starting_indexes, exit)) } /// Second phase of post-processing: compute relative metrics. diff --git a/crates/brk_computer/src/distribution/compute/aggregates.rs b/crates/brk_computer/src/distribution/compute/aggregates.rs index 045c85ad2..b23f1d5cf 100644 --- a/crates/brk_computer/src/distribution/compute/aggregates.rs +++ b/crates/brk_computer/src/distribution/compute/aggregates.rs @@ -42,10 +42,6 @@ pub fn compute_rest_part1( utxo_cohorts.compute_rest_part1(indexes, price, starting_indexes, exit)?; address_cohorts.compute_rest_part1(indexes, price, starting_indexes, exit)?; - // Recompute net_sentiment for aggregate cohorts as weighted average - utxo_cohorts.compute_aggregate_net_sentiment(indexes, starting_indexes, exit)?; - address_cohorts.compute_aggregate_net_sentiment(indexes, starting_indexes, exit)?; - Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/mod.rs b/crates/brk_computer/src/distribution/metrics/mod.rs index 720efac02..26913e7c5 100644 --- a/crates/brk_computer/src/distribution/metrics/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/mod.rs @@ -296,16 +296,17 @@ impl CohortMetrics { Ok(()) } - /// Compute net_sentiment as capital-weighted average of component cohorts. + /// Compute net_sentiment.height as capital-weighted average of component cohorts. /// /// For aggregate cohorts, the simple greed-pain formula produces values outside - /// the range of components due to asymmetric weighting. This recomputes net_sentiment + /// the range of components due to asymmetric weighting. This computes net_sentiment /// as a proper weighted average using realized_cap as weight. + /// + /// Only computes height; dateindex derivation is done separately via compute_net_sentiment_rest. pub fn compute_net_sentiment_from_others( &mut self, starting_indexes: &ComputeIndexes, others: &[&Self], - indexes: &indexes::Vecs, exit: &Exit, ) -> Result<()> { let Some(unrealized) = self.unrealized.as_mut() else { @@ -325,14 +326,10 @@ impl CohortMetrics { return Ok(()); } - unrealized + Ok(unrealized .net_sentiment .height - .compute_weighted_average_of_others(starting_indexes.height, &weights, &values, exit)?; - - unrealized - .net_sentiment - .compute_rest(indexes, starting_indexes, exit) + .compute_weighted_average_of_others(starting_indexes.height, &weights, &values, exit)?) } /// First phase of computed metrics (indexes from height). @@ -389,4 +386,31 @@ impl CohortMetrics { Ok(()) } + + /// Compute net_sentiment.height for separate cohorts (greed - pain). + /// Called only for separate cohorts; aggregates compute via weighted average in compute_from_stateful. + pub fn compute_net_sentiment_height( + &mut self, + starting_indexes: &ComputeIndexes, + exit: &Exit, + ) -> Result<()> { + if let Some(unrealized) = self.unrealized.as_mut() { + unrealized.compute_net_sentiment_height(starting_indexes, exit)?; + } + Ok(()) + } + + /// Compute net_sentiment dateindex derivation from height. + /// Called for ALL cohorts after height is computed. + pub fn compute_net_sentiment_rest( + &mut self, + indexes: &indexes::Vecs, + starting_indexes: &ComputeIndexes, + exit: &Exit, + ) -> Result<()> { + if let Some(unrealized) = self.unrealized.as_mut() { + unrealized.compute_net_sentiment_rest(indexes, starting_indexes, exit)?; + } + Ok(()) + } } diff --git a/crates/brk_computer/src/distribution/metrics/unrealized.rs b/crates/brk_computer/src/distribution/metrics/unrealized.rs index 6d6d6fe10..622563d8f 100644 --- a/crates/brk_computer/src/distribution/metrics/unrealized.rs +++ b/crates/brk_computer/src/distribution/metrics/unrealized.rs @@ -598,17 +598,32 @@ impl UnrealizedMetrics { )?) })?; - // Net sentiment: greed - pain - self.net_sentiment - .compute_all(indexes, starting_indexes, exit, |vec| { - Ok(vec.compute_subtract( - starting_indexes.height, - &self.greed_index.height, - &self.pain_index.height, - exit, - )?) - })?; + // Net sentiment height (greed - pain) computed separately for separate cohorts only + // Aggregate cohorts compute it via weighted average in compute_from_stateful + // Dateindex derivation for ALL cohorts happens in compute_net_sentiment_rest Ok(()) } + + /// Compute net_sentiment.height for separate cohorts (greed - pain). + /// Aggregate cohorts skip this - their height is computed via weighted average in compute_from_stateful. + pub fn compute_net_sentiment_height(&mut self, starting_indexes: &ComputeIndexes, exit: &Exit) -> Result<()> { + Ok(self.net_sentiment.height.compute_subtract( + starting_indexes.height, + &self.greed_index.height, + &self.pain_index.height, + exit, + )?) + } + + /// Compute net_sentiment dateindex derivation from height. + /// Called for ALL cohorts after height is computed (either via greed-pain or weighted avg). + pub fn compute_net_sentiment_rest( + &mut self, + indexes: &indexes::Vecs, + starting_indexes: &ComputeIndexes, + exit: &Exit, + ) -> Result<()> { + self.net_sentiment.compute_rest(indexes, starting_indexes, exit) + } } diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 723a9b9d8..d3a788143 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -1125,6 +1125,9 @@ class BrkClientBase { this.timeout = isString ? 5000 : (options.timeout ?? 5000); /** @type {Promise} */ this._cachePromise = _openCache(isString ? undefined : options.cache); + /** @type {Cache | null} */ + this._cache = null; + this._cachePromise.then(c => this._cache = c); } /** @@ -1140,35 +1143,57 @@ class BrkClientBase { } /** - * Make a GET request with stale-while-revalidate caching + * Make a GET request - races cache vs network, first to resolve calls onUpdate * @template T * @param {string} path - * @param {(value: T) => void} [onUpdate] - Called when data is available + * @param {(value: T) => void} [onUpdate] - Called when data is available (may be called twice: cache then network) * @returns {Promise} */ async getJson(path, onUpdate) { const base = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl; const url = `${base}${path}`; - const cache = await this._cachePromise; - const cachedRes = await cache?.match(url); - const cachedJson = cachedRes ? await cachedRes.json() : null; + const cache = this._cache ?? await this._cachePromise; - if (cachedJson) onUpdate?.(cachedJson); - if (globalThis.navigator?.onLine === false) { - if (cachedJson) return cachedJson; - throw new BrkError('Offline and no cached data available'); - } + let resolved = false; + /** @type {Response | null} */ + let cachedRes = null; - try { - const res = await this.get(path); - if (cachedRes?.headers.get('ETag') === res.headers.get('ETag')) return cachedJson; + // Race cache vs network - first to resolve calls onUpdate + const cachePromise = cache?.match(url).then(async (res) => { + cachedRes = res ?? null; + if (!res) return null; + const json = await res.json(); + if (!resolved && onUpdate) { + resolved = true; + onUpdate(json); + } + return json; + }); + const networkPromise = this.get(path).then(async (res) => { const cloned = res.clone(); const json = await res.json(); - onUpdate?.(json); + // Skip update if ETag matches and cache already delivered + if (cachedRes?.headers.get('ETag') === res.headers.get('ETag')) { + if (!resolved && onUpdate) { + resolved = true; + onUpdate(json); + } + return json; + } + resolved = true; + if (onUpdate) { + onUpdate(json); + } if (cache) _runIdle(() => cache.put(url, cloned)); return json; + }); + + try { + return await networkPromise; } catch (e) { + // Network failed - wait for cache + const cachedJson = await cachePromise?.catch(() => null); if (cachedJson) return cachedJson; throw e; } diff --git a/scripts/js-publish.sh b/scripts/js-publish.sh index 32bba92bc..3d08793c2 100755 --- a/scripts/js-publish.sh +++ b/scripts/js-publish.sh @@ -16,6 +16,10 @@ cd "$ROOT_DIR/modules/brk-client" sed -i '' 's/"version": "[^"]*"/"version": "'"$VERSION"'"/' package.json echo "Updated package.json to $VERSION" +# Update VERSION in index.js +sed -i '' 's/VERSION = "v[^"]*"/VERSION = "v'"$VERSION"'"/' index.js +echo "Updated index.js VERSION to v$VERSION" + # Determine npm tag based on version if [[ "$VERSION" == *"-alpha"* ]]; then NPM_TAG="alpha" diff --git a/scripts/python-publish.sh b/scripts/python-publish.sh index e639e0c07..54b296c18 100755 --- a/scripts/python-publish.sh +++ b/scripts/python-publish.sh @@ -16,6 +16,10 @@ cd "$ROOT_DIR/packages/brk_client" sed -i '' 's/^version = "[^"]*"/version = "'"$VERSION"'"/' pyproject.toml echo "Updated pyproject.toml to $VERSION" +# Update VERSION in __init__.py +sed -i '' 's/VERSION = "v[^"]*"/VERSION = "v'"$VERSION"'"/' brk_client/__init__.py +echo "Updated __init__.py VERSION to v$VERSION" + # Clean old build artifacts rm -rf dist diff --git a/website/scripts/chart/index.js b/website/scripts/chart/index.js index 24153f824..2d720136c 100644 --- a/website/scripts/chart/index.js +++ b/website/scripts/chart/index.js @@ -114,6 +114,12 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { // Used to detect and ignore stale operations (in-flight fetches, etc.) let generation = 0; + // Shared time - fetched once per rebuild, all series register callbacks + /** @type {number[] | null} */ + let sharedTimeData = null; + /** @type {Set<(data: number[]) => void>} */ + let timeCallbacks = new Set(); + // Range state: localStorage stores all ranges per-index, URL stores current range only /** @typedef {{ from: number, to: number }} Range */ const ranges = createPersistedValue({ @@ -519,13 +525,16 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { active.value ? show() : hide(); const seriesGeneration = generation; - let hasData = false; - let lastTime = -Infinity; - /** @type {string | null} */ - let lastStamp = null; - - /** @type {VoidFunction | null} */ - let _fetch = null; + const state = { + hasData: false, + lastTime: -Infinity, + /** @type {string | null} */ + lastStamp: null, + /** @type {VoidFunction | null} */ + fetch: null, + /** @type {((data: number[]) => void) | null} */ + onTime: null, + }; /** @type {AnySeries} */ const series = { @@ -535,7 +544,7 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { active.set(value); value ? show() : hide(); if (value && !wasActive) { - _fetch?.(); + state.fetch?.(); } panes.updateVisibility(); }, @@ -544,14 +553,15 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { hide, highlight, tame, - hasData: () => hasData, - fetch: () => _fetch?.(), + hasData: () => state.hasData, + fetch: () => state.fetch?.(), id, paneIndex, url: null, getData, update, remove() { + if (state.onTime) timeCallbacks.delete(state.onTime); onRemove(); serieses.all.delete(series); panes.seriesByHome.get(paneIndex)?.delete(series); @@ -563,9 +573,9 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { /** @param {ChartableIndex} idx */ function setupIndexEffect(idx) { // Reset data state for new index - hasData = false; - lastTime = -Infinity; - _fetch = null; + state.hasData = false; + state.lastTime = -Infinity; + state.fetch = null; const _valuesEndpoint = metric.by[idx]; // Gracefully skip - series may be about to be removed by option change @@ -590,13 +600,13 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { // Find start index for processing let startIdx = 0; - if (hasData) { - // Binary search to find first index where time >= lastTime + if (state.hasData) { + // Binary search to find first index where time >= state.lastTime let lo = 0; let hi = length; while (lo < hi) { const mid = (lo + hi) >>> 1; - if (indexes[mid] < lastTime) { + if (indexes[mid] < state.lastTime) { lo = mid + 1; } else { hi = mid; @@ -625,7 +635,7 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { } } - if (!hasData) { + if (!state.hasData) { // Initial load: build full array const data = /** @type {LineData[] | CandlestickData[]} */ ( Array.from({ length }) @@ -655,8 +665,9 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { data.length -= timeOffset; setData(data); - hasData = true; - lastTime = /** @type {number} */ (data.at(-1)?.time) ?? -Infinity; + state.hasData = true; + state.lastTime = + /** @type {number} */ (data.at(-1)?.time) ?? -Infinity; // Restore saved range or use defaults const savedRange = getRange(); @@ -685,26 +696,45 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { for (let i = startIdx; i < length; i++) { const point = buildDataPoint(i); update(point); - lastTime = /** @type {number} */ (point.time); + state.lastTime = /** @type {number} */ (point.time); } } } async function fetchAndProcess() { - const [timeResult, valuesResult] = await Promise.all([ - getTimeEndpoint(idx).slice(-10000).fetch(), - valuesEndpoint.slice(-10000).fetch(), - ]); - // Ignore stale fetches from series that have been replaced - if (seriesGeneration !== generation) return; - if (valuesResult.stamp === lastStamp) return; - lastStamp = valuesResult.stamp; - if (timeResult.data.length && valuesResult.data.length) { - processData(timeResult.data, valuesResult.data); + /** @type {number[] | null} */ + let timeData = null; + /** @type {(number | null | [number, number, number, number])[] | null} */ + let valuesData = null; + /** @type {string | null} */ + let valuesStamp = null; + + function tryProcess() { + if (seriesGeneration !== generation) return; + if (!timeData || !valuesData) return; + if (valuesStamp === state.lastStamp) return; + state.lastStamp = valuesStamp; + if (timeData.length && valuesData.length) { + processData(timeData, valuesData); + } } + + // Register for shared time data (fetched once in rebuild) + state.onTime = (data) => { + timeData = data; + tryProcess(); + }; + timeCallbacks.add(state.onTime); + if (sharedTimeData) state.onTime(sharedTimeData); + + await valuesEndpoint.slice(-10000).fetch((result) => { + valuesData = result.data; + valuesStamp = result.stamp; + tryProcess(); + }); } - _fetch = fetchAndProcess; + state.fetch = fetchAndProcess; // Initial fetch if active if (active.value) { @@ -1597,6 +1627,17 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { rebuild() { generation++; + const currentGen = generation; + const idx = index.get(); + sharedTimeData = null; + timeCallbacks = new Set(); + getTimeEndpoint(idx) + .slice(-10000) + .fetch((result) => { + if (currentGen !== generation) return; // Ignore stale fetch + sharedTimeData = result.data; + timeCallbacks.forEach((cb) => cb(result.data)); + }); this.rebuildPane(0); this.rebuildPane(1); }, diff --git a/website/scripts/options/distribution/activity.js b/website/scripts/options/distribution/activity.js index 7ea1beeaa..a6260d2a9 100644 --- a/website/scripts/options/distribution/activity.js +++ b/website/scripts/options/distribution/activity.js @@ -132,7 +132,7 @@ function valueBreakdownTree(list, all, title) { { name: "Created", title: title("Profit Value Created"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.profitValueCreated, name, @@ -144,7 +144,7 @@ function valueBreakdownTree(list, all, title) { { name: "Destroyed", title: title("Profit Value Destroyed"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.profitValueDestroyed, name, @@ -161,7 +161,7 @@ function valueBreakdownTree(list, all, title) { { name: "Created", title: title("Loss Value Created"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.lossValueCreated, name, @@ -173,7 +173,7 @@ function valueBreakdownTree(list, all, title) { { name: "Destroyed", title: title("Loss Value Destroyed"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.lossValueDestroyed, name, @@ -200,7 +200,7 @@ function coinsDestroyedTree(list, all, title) { { name: "Sum", title: title("Coins Destroyed"), - bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [ + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [ line({ metric: tree.activity.coinblocksDestroyed.sum, name, @@ -218,7 +218,7 @@ function coinsDestroyedTree(list, all, title) { { name: "Cumulative", title: title("Cumulative Coins Destroyed"), - bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [ + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [ line({ metric: tree.activity.coinblocksDestroyed.cumulative, name, @@ -594,7 +594,7 @@ function groupedFlowsTree(list, all, title) { { name: "Profit", title: title("Profit Flow"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.profitFlow, name, @@ -606,7 +606,7 @@ function groupedFlowsTree(list, all, title) { { name: "Capitulation", title: title("Capitulation Flow"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.capitulationFlow, name, @@ -632,7 +632,7 @@ function createGroupedValueTree(list, all, title) { { name: "Created", title: title("Value Created"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.valueCreated, name, @@ -644,7 +644,7 @@ function createGroupedValueTree(list, all, title) { { name: "Destroyed", title: title("Value Destroyed"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.valueDestroyed, name, @@ -678,14 +678,14 @@ export function createGroupedActivitySection({ { name: "14d EMA", title: title("Sent Volume 14d EMA"), - bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }), ), }, { name: "Sum", title: title("Sent Volume"), - bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: { sats: tree.activity.sent.sats.sum, @@ -733,7 +733,7 @@ function createGroupedValueTreeWithAdjusted(list, all, title) { { name: "Created", title: title("Value Created"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.valueCreated, name, @@ -745,7 +745,7 @@ function createGroupedValueTreeWithAdjusted(list, all, title) { { name: "Destroyed", title: title("Value Destroyed"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.valueDestroyed, name, @@ -762,7 +762,7 @@ function createGroupedValueTreeWithAdjusted(list, all, title) { { name: "Created", title: title("Adjusted Value Created"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.adjustedValueCreated, name, @@ -774,7 +774,7 @@ function createGroupedValueTreeWithAdjusted(list, all, title) { { name: "Destroyed", title: title("Adjusted Value Destroyed"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.adjustedValueDestroyed, name, @@ -839,7 +839,7 @@ function createSingleSellSideRiskSeries(tree) { * @returns {AnyFetchedSeriesBlueprint[]} */ function createGroupedSellSideRiskSeries(list, all) { - return flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [ + return flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [ line({ metric: tree.realized.sellSideRiskRatio, name, diff --git a/website/scripts/options/distribution/profitability.js b/website/scripts/options/distribution/profitability.js index f3d40e16d..1b4d1c9f6 100644 --- a/website/scripts/options/distribution/profitability.js +++ b/website/scripts/options/distribution/profitability.js @@ -1205,7 +1205,7 @@ function groupedPnlCharts(list, all, title) { { name: "Profit", title: title("Unrealized Profit"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.unrealizedProfit, name, @@ -1217,7 +1217,7 @@ function groupedPnlCharts(list, all, title) { { name: "Loss", title: title("Unrealized Loss"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.negUnrealizedLoss, name, @@ -1229,7 +1229,7 @@ function groupedPnlCharts(list, all, title) { { name: "Net P&L", title: title("Net Unrealized P&L"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.unrealized.netUnrealizedPnl, name, @@ -1254,7 +1254,7 @@ function groupedPnlChartsWithMarketCap(list, all, title) { name: "Profit", title: title("Unrealized Profit"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.unrealizedProfit, name, @@ -1262,7 +1262,7 @@ function groupedPnlChartsWithMarketCap(list, all, title) { unit: Unit.usd, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.unrealizedProfitRelToMarketCap, name, @@ -1276,7 +1276,7 @@ function groupedPnlChartsWithMarketCap(list, all, title) { name: "Loss", title: title("Unrealized Loss"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.negUnrealizedLoss, name, @@ -1284,7 +1284,7 @@ function groupedPnlChartsWithMarketCap(list, all, title) { unit: Unit.usd, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.negUnrealizedLossRelToMarketCap, name, @@ -1298,7 +1298,7 @@ function groupedPnlChartsWithMarketCap(list, all, title) { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.unrealized.netUnrealizedPnl, name, @@ -1306,7 +1306,7 @@ function groupedPnlChartsWithMarketCap(list, all, title) { unit: Unit.usd, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, @@ -1332,7 +1332,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { name: "Profit", title: title("Unrealized Profit"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.unrealizedProfit, name, @@ -1341,7 +1341,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { }), ), // OwnMarketCap properties don't exist on CohortAll - use mapCohorts - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.unrealizedProfitRelToOwnMarketCap, name, @@ -1349,7 +1349,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.unrealizedProfitRelToOwnTotalUnrealizedPnl, name, @@ -1363,7 +1363,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { name: "Loss", title: title("Unrealized Loss"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.negUnrealizedLoss, name, @@ -1372,7 +1372,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { }), ), // OwnMarketCap properties don't exist on CohortAll - use mapCohorts - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.negUnrealizedLossRelToOwnMarketCap, name, @@ -1380,7 +1380,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.negUnrealizedLossRelToOwnTotalUnrealizedPnl, name, @@ -1394,7 +1394,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.unrealized.netUnrealizedPnl, name, @@ -1403,7 +1403,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { }), ), // OwnMarketCap properties don't exist on CohortAll - use mapCohorts - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, name, @@ -1411,7 +1411,7 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, @@ -1437,7 +1437,7 @@ function groupedPnlChartsLongTerm(list, all, title) { name: "Profit", title: title("Unrealized Profit"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.unrealizedProfit, name, @@ -1446,7 +1446,7 @@ function groupedPnlChartsLongTerm(list, all, title) { }), ), // OwnMarketCap properties don't exist on CohortAll - use mapCohorts - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.unrealizedProfitRelToOwnMarketCap, name, @@ -1454,7 +1454,7 @@ function groupedPnlChartsLongTerm(list, all, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.unrealizedProfitRelToOwnTotalUnrealizedPnl, name, @@ -1468,7 +1468,7 @@ function groupedPnlChartsLongTerm(list, all, title) { name: "Loss", title: title("Unrealized Loss"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.negUnrealizedLoss, name, @@ -1476,7 +1476,7 @@ function groupedPnlChartsLongTerm(list, all, title) { unit: Unit.usd, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.unrealizedLossRelToMarketCap, name, @@ -1485,7 +1485,7 @@ function groupedPnlChartsLongTerm(list, all, title) { }), ), // OwnMarketCap properties don't exist on CohortAll - use mapCohorts - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.negUnrealizedLossRelToOwnMarketCap, name, @@ -1493,7 +1493,7 @@ function groupedPnlChartsLongTerm(list, all, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.negUnrealizedLossRelToOwnTotalUnrealizedPnl, name, @@ -1507,7 +1507,7 @@ function groupedPnlChartsLongTerm(list, all, title) { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.unrealized.netUnrealizedPnl, name, @@ -1516,7 +1516,7 @@ function groupedPnlChartsLongTerm(list, all, title) { }), ), // OwnMarketCap properties don't exist on CohortAll - use mapCohorts - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, name, @@ -1524,7 +1524,7 @@ function groupedPnlChartsLongTerm(list, all, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, @@ -1549,7 +1549,7 @@ function groupedInvestedCapitalAbsolute(list, all, title) { { name: "In Profit", title: title("Invested Capital In Profit"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.investedCapitalInProfit, name, @@ -1561,7 +1561,7 @@ function groupedInvestedCapitalAbsolute(list, all, title) { { name: "In Loss", title: title("Invested Capital In Loss"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.investedCapitalInLoss, name, @@ -1586,7 +1586,7 @@ function groupedInvestedCapital(list, all, title) { name: "In Profit", title: title("Invested Capital In Profit"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.investedCapitalInProfit, name, @@ -1594,7 +1594,7 @@ function groupedInvestedCapital(list, all, title) { unit: Unit.usd, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.investedCapitalInProfitPct, name, @@ -1609,7 +1609,7 @@ function groupedInvestedCapital(list, all, title) { name: "In Loss", title: title("Invested Capital In Loss"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.investedCapitalInLoss, name, @@ -1617,7 +1617,7 @@ function groupedInvestedCapital(list, all, title) { unit: Unit.usd, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.investedCapitalInLossPct, name, @@ -1643,7 +1643,7 @@ function groupedRealizedPnlSum(list, all, title) { { name: "Profit", title: title("Realized Profit"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.realizedProfit.sum, name, @@ -1655,7 +1655,7 @@ function groupedRealizedPnlSum(list, all, title) { { name: "Loss", title: title("Realized Loss"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.negRealizedLoss.sum, name, @@ -1667,7 +1667,7 @@ function groupedRealizedPnlSum(list, all, title) { { name: "Total", title: title("Total Realized P&L"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.totalRealizedPnl, name, @@ -1679,7 +1679,7 @@ function groupedRealizedPnlSum(list, all, title) { { name: "Value", title: title("Realized Value"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.realizedValue, name, @@ -1704,7 +1704,7 @@ function groupedRealizedPnlSumWithExtras(list, all, title) { { name: "P/L Ratio", title: title("Realized Profit/Loss Ratio"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.realizedProfitToLossRatio, name, @@ -1728,7 +1728,7 @@ function groupedRealizedPnlCumulative(list, all, title) { { name: "Profit", title: title("Cumulative Realized Profit"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.realizedProfit.cumulative, name, @@ -1740,7 +1740,7 @@ function groupedRealizedPnlCumulative(list, all, title) { { name: "Loss", title: title("Cumulative Realized Loss"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.negRealizedLoss.cumulative, name, @@ -1768,7 +1768,7 @@ function groupedSentInPnl(list, all, title) { name: "In Profit", title: title("Sent In Profit"), bottom: [ - ...flatMapCohortsWithAll(list, all, ({ color, name, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name: `${name} 14d EMA`, @@ -1776,7 +1776,7 @@ function groupedSentInPnl(list, all, title) { defaultActive: false, }), ), - ...flatMapCohortsWithAll(list, all, ({ color, name, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsdFrom({ source: tree.realized.sentInProfit, key: "sum", @@ -1790,7 +1790,7 @@ function groupedSentInPnl(list, all, title) { name: "In Loss", title: title("Sent In Loss"), bottom: [ - ...flatMapCohortsWithAll(list, all, ({ color, name, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name: `${name} 14d EMA`, @@ -1798,7 +1798,7 @@ function groupedSentInPnl(list, all, title) { defaultActive: false, }), ), - ...flatMapCohortsWithAll(list, all, ({ color, name, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsdFrom({ source: tree.realized.sentInLoss, key: "sum", @@ -1816,7 +1816,7 @@ function groupedSentInPnl(list, all, title) { { name: "In Profit", title: title("Cumulative Sent In Profit"), - bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsdFrom({ source: tree.realized.sentInProfit, key: "cumulative", @@ -1828,7 +1828,7 @@ function groupedSentInPnl(list, all, title) { { name: "In Loss", title: title("Cumulative Sent In Loss"), - bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsdFrom({ source: tree.realized.sentInLoss, key: "cumulative", @@ -1856,7 +1856,7 @@ function groupedSentiment(list, all, title) { { name: "Net", title: title("Net Sentiment"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.unrealized.netSentiment, name, @@ -1868,7 +1868,7 @@ function groupedSentiment(list, all, title) { { name: "Greed", title: title("Greed Index"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.greedIndex, name, @@ -1880,7 +1880,7 @@ function groupedSentiment(list, all, title) { { name: "Pain", title: title("Pain Index"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.painIndex, name, @@ -1908,7 +1908,7 @@ function groupedRealizedSubfolder(list, all, title) { { name: "Net", title: title("Net Realized P&L"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.netRealizedPnl.sum, name, @@ -1920,7 +1920,7 @@ function groupedRealizedSubfolder(list, all, title) { { name: "30d Change", title: title("Realized P&L 30d Change"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.netRealizedPnlCumulative30dDelta, name, @@ -1936,7 +1936,7 @@ function groupedRealizedSubfolder(list, all, title) { { name: "Net", title: title("Cumulative Net Realized P&L"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.netRealizedPnl.cumulative, name, @@ -1966,7 +1966,7 @@ function groupedRealizedSubfolderWithExtras(list, all, title) { { name: "Net", title: title("Net Realized P&L"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.netRealizedPnl.sum, name, @@ -1978,7 +1978,7 @@ function groupedRealizedSubfolderWithExtras(list, all, title) { { name: "30d Change", title: title("Realized P&L 30d Change"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.netRealizedPnlCumulative30dDelta, name, @@ -1994,7 +1994,7 @@ function groupedRealizedSubfolderWithExtras(list, all, title) { { name: "Net", title: title("Cumulative Net Realized P&L"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.netRealizedPnl.cumulative, name, @@ -2076,7 +2076,7 @@ export function createGroupedProfitabilitySectionWithInvestedCapitalPct({ { name: "Peak Regret", title: title("Unrealized Peak Regret"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.peakRegret, name, @@ -2111,7 +2111,7 @@ export function createGroupedProfitabilitySectionWithNupl({ list, all, title }) { name: "NUPL", title: title("NUPL"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.nupl, name, @@ -2146,7 +2146,7 @@ export function createGroupedProfitabilitySectionLongTerm({ list, all, title }) { name: "NUPL", title: title("NUPL"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.nupl, name, @@ -2159,7 +2159,7 @@ export function createGroupedProfitabilitySectionLongTerm({ list, all, title }) name: "Peak Regret", title: title("Unrealized Peak Regret"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.peakRegret, name, @@ -2167,7 +2167,7 @@ export function createGroupedProfitabilitySectionLongTerm({ list, all, title }) unit: Unit.usd, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.unrealizedPeakRegretRelToMarketCap, name, @@ -2207,7 +2207,7 @@ export function createGroupedProfitabilitySectionWithPeakRegret({ { name: "NUPL", title: title("NUPL"), - bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.nupl, name, @@ -2220,7 +2220,7 @@ export function createGroupedProfitabilitySectionWithPeakRegret({ name: "Peak Regret", title: title("Unrealized Peak Regret"), bottom: [ - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.unrealized.peakRegret, name, @@ -2228,7 +2228,7 @@ export function createGroupedProfitabilitySectionWithPeakRegret({ unit: Unit.usd, }), ), - ...mapCohortsWithAll(list, all, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.relative.unrealizedPeakRegretRelToMarketCap, name,