next: ai part 11

This commit is contained in:
nym21
2026-07-29 10:50:14 +02:00
parent eccbda34ee
commit 1cdee36f2e
56 changed files with 2269 additions and 1037 deletions
@@ -0,0 +1,186 @@
import { brk } from "../../utils/client.js";
import { colors } from "../../utils/colors.js";
import { Unit } from "../../utils/units.js";
import { ageRanges } from "../age-ranges.js";
import { line, price } from "../series.js";
import { satsBtcUsd } from "../shared.js";
/**
* @typedef {Object} CoinflowAgeRange
* @property {string} name
* @property {Color} color
* @property {{
* mobility: AnySeriesPattern,
* spendingRate: AnySeriesPattern,
* spendingExposure: AnySeriesPattern,
* supply: { mobile: AnyValuePattern, immobile: AnyValuePattern },
* }} tree
*/
/**
* @param {readonly CoinflowAgeRange[]} ranges
* @param {"mobility" | "spendingRate" | "spendingExposure"} key
* @param {string} name
* @returns {PartialChartOption}
*/
function ageRangeRatioChart(ranges, key, name) {
return {
name,
title: `${name} by UTXO Age`,
bottom: ranges.map((range) =>
line({
series: range.tree[key],
name: range.name,
color: range.color,
unit: Unit.ratio,
}),
),
};
}
/**
* @param {readonly CoinflowAgeRange[]} ranges
* @param {"mobile" | "immobile"} key
* @param {string} name
* @returns {PartialChartOption}
*/
function ageRangeSupplyChart(ranges, key, name) {
return {
name,
title: `${name} Supply by UTXO Age`,
bottom: ranges.flatMap((range) =>
satsBtcUsd({
pattern: range.tree.supply[key],
name: range.name,
color: range.color,
}),
),
};
}
/**
* Create Coinflow section.
* @returns {PartialOptionsGroup}
*/
export function createCoinflowSection() {
const { coinflow } = brk.series;
const ranges = ageRanges.map(({ key, ...range }) => ({
...range,
tree: coinflow.ageRange[key],
}));
const horizons = /** @type {const} */ ([
{ key: "_8y", name: "8Y" },
{ key: "_4y", name: "4Y" },
{ key: "_2y", name: "2Y" },
{ key: "_1y", name: "1Y" },
{ key: "_6m", name: "6M" },
{ key: "_3m", name: "3M" },
{ key: "_1m", name: "1M" },
]).map((horizon, index, all) => ({
...horizon,
color: colors.at(index, all.length),
}));
return {
name: "Coinflow",
tree: [
{
name: "Supply",
tree: [
{
name: "Breakdown",
title: "Mobile vs Immobile Supply",
bottom: [
...satsBtcUsd({
pattern: coinflow.supply.mobile,
name: "Mobile",
color: colors.mobile,
}),
...satsBtcUsd({
pattern: coinflow.supply.immobile,
name: "Immobile",
color: colors.immobile,
}),
],
},
{
name: "Mobile in Loss",
title: "Mobile Supply in Loss",
bottom: [
line({
series: coinflow.supply.mobile.inLoss.share,
name: "Lifetime",
color: colors.loss,
unit: Unit.ratio,
}),
],
},
{
name: "In Loss by Horizon",
title: "Mobile Supply in Loss by Horizon",
bottom: horizons.map((horizon) =>
line({
series:
coinflow.horizon[horizon.key].supply.inLoss.share,
name: horizon.name,
color: horizon.color,
unit: Unit.ratio,
}),
),
},
],
},
{
name: "Cap",
title: "Coinflow Cap",
bottom: [
line({
series: coinflow.cap.usd,
name: "Coinflow",
color: colors.coinflow,
unit: Unit.usd,
}),
],
},
{
name: "Price",
title: "Coinflow Price",
top: [
price({
series: coinflow.price,
name: "Coinflow",
color: colors.coinflow,
}),
],
bottom: [
line({
series: coinflow.price.ratio,
name: "Spot / Coinflow",
color: colors.coinflow,
unit: Unit.ratio,
}),
],
},
{
name: "Age Range",
tree: [
ageRangeRatioChart(ranges, "mobility", "Mobility"),
ageRangeRatioChart(ranges, "spendingRate", "Spending Rate"),
ageRangeRatioChart(
ranges,
"spendingExposure",
"Spending Exposure",
),
{
name: "Supply",
tree: [
ageRangeSupplyChart(ranges, "mobile", "Mobile"),
ageRangeSupplyChart(ranges, "immobile", "Immobile"),
],
},
],
},
],
};
}
@@ -0,0 +1,162 @@
import { Unit } from "../../../utils/units.js";
import { line, ROLLING_WINDOWS } from "../../series.js";
import { satsBtcUsd } from "../../shared.js";
/**
* @typedef {{
* average: Record<"_24h" | "_1w" | "_1m" | "_1y", AnySeriesPattern>,
* sum: Record<"_24h" | "_1w" | "_1m" | "_1y", AnySeriesPattern>,
* cumulative: AnySeriesPattern,
* }} CointimeAgeRangeCoindays
*
* @typedef {Object} CointimeAgeRange
* @property {string} name
* @property {Color} color
* @property {{
* coindaysCreated: CointimeAgeRangeCoindays,
* coindaysConsumed: CointimeAgeRangeCoindays,
* coindaysStored: CointimeAgeRangeCoindays,
* liveliness: AnySeriesPattern,
* vaultedness: AnySeriesPattern,
* ratio: AnySeriesPattern,
* supply: { active: AnyValuePattern, vaulted: AnyValuePattern },
* }} tree
*/
/**
* @param {readonly CointimeAgeRange[]} ranges
* @param {"liveliness" | "vaultedness" | "ratio"} key
* @param {string} name
* @param {string} legend
* @returns {PartialChartOption}
*/
function activityChart(ranges, key, name, legend) {
return {
name,
title: `${legend} by UTXO Age`,
bottom: ranges.map((range) =>
line({
series: range.tree[key],
name: range.name,
color: range.color,
unit: Unit.ratio,
}),
),
};
}
/**
* @param {readonly CointimeAgeRange[]} ranges
* @param {"active" | "vaulted"} key
* @param {string} name
* @returns {PartialChartOption}
*/
function supplyChart(ranges, key, name) {
return {
name,
title: `${name} Supply by UTXO Age`,
bottom: ranges.flatMap((range) =>
satsBtcUsd({
pattern: range.tree.supply[key],
name: range.name,
color: range.color,
}),
),
};
}
/**
* @param {readonly CointimeAgeRange[]} ranges
* @param {"coindaysCreated" | "coindaysConsumed" | "coindaysStored"} key
* @param {string} name
* @returns {PartialOptionsGroup}
*/
function coindaysTree(ranges, key, name) {
return {
name,
tree: [
{
name: "Average",
tree: ROLLING_WINDOWS.map((window) => ({
name: window.name,
title: `${window.title} Average ${name} by UTXO Age`,
bottom: ranges.map((range) =>
line({
series: range.tree[key].average[window.key],
name: range.name,
color: range.color,
unit: Unit.coindays,
}),
),
})),
},
{
name: "Sum",
tree: ROLLING_WINDOWS.map((window) => ({
name: window.name,
title: `${window.title} ${name} by UTXO Age`,
bottom: ranges.map((range) =>
line({
series: range.tree[key].sum[window.key],
name: range.name,
color: range.color,
unit: Unit.coindays,
}),
),
})),
},
{
name: "Cumulative",
title: `Cumulative ${name} by UTXO Age`,
bottom: ranges.map((range) =>
line({
series: range.tree[key].cumulative,
name: range.name,
color: range.color,
unit: Unit.coindays,
}),
),
},
],
};
}
/**
* @param {readonly CointimeAgeRange[]} ranges
* @returns {PartialOptionsGroup}
*/
export function createCointimeAgeRangeSection(ranges) {
return {
name: "Age Range",
tree: [
{
name: "Supply",
tree: [
supplyChart(ranges, "active", "Active"),
supplyChart(ranges, "vaulted", "Vaulted"),
],
},
{
name: "Activity",
tree: [
activityChart(ranges, "liveliness", "Liveliness", "Liveliness"),
activityChart(ranges, "vaultedness", "Vaultedness", "Vaultedness"),
activityChart(
ranges,
"ratio",
"Activity Ratio",
"Liveliness / Vaultedness",
),
],
},
{
name: "Coindays",
tree: [
coindaysTree(ranges, "coindaysCreated", "Coindays Created"),
coindaysTree(ranges, "coindaysConsumed", "Coindays Consumed"),
coindaysTree(ranges, "coindaysStored", "Coindays Stored"),
],
},
],
};
}
@@ -0,0 +1,472 @@
import { colors } from "../../../utils/colors.js";
import { brk } from "../../../utils/client.js";
import { Unit } from "../../../utils/units.js";
import {
dots,
line,
price,
multiSeriesTree,
percentRatioDots,
sumsAndAveragesCumulative,
} from "../../series.js";
import { ageRanges } from "../../age-ranges.js";
import { satsBtcUsd, priceRatioPercentilesTree } from "../../shared.js";
import { createCointimeAgeRangeSection } from "./age-range.js";
/**
* Create Cointime section
* @returns {PartialOptionsGroup}
*/
export function createCointimeSection() {
const { cointime, cohorts, supply } = brk.series;
const {
prices: cointimePrices,
cap,
activity,
supply: cointimeSupply,
adjusted,
reserveRisk,
value,
} = cointime;
const { all } = cohorts.utxo;
const cointimeAgeRanges = ageRanges.map(({ key, ...range }) => ({
...range,
tree: cointime.ageRange[key],
}));
// Reference lines for cap comparisons
const capReferenceLines = /** @type {const} */ ([
{ series: supply.marketCap.usd, name: "Market", color: colors.default },
{
series: all.realized.cap.usd,
name: "Realized",
color: colors.realized,
},
]);
/** @type {readonly { pattern: PriceRatioPercentilesPattern, name: string, title: (name: string) => string, color: Color, defaultActive: boolean }[]} */
const prices = [
{
pattern: cointimePrices.trueMarketMean,
name: "True Market Mean",
title: (name) => name,
color: colors.trueMarketMean,
defaultActive: true,
},
{
pattern: cointimePrices.vaulted,
name: "Vaulted",
title: (name) => `${name} Price`,
color: colors.vaulted,
defaultActive: true,
},
{
pattern: cointimePrices.active,
name: "Active",
title: (name) => `${name} Price`,
color: colors.active,
defaultActive: true,
},
{
pattern: cointimePrices.cointime,
name: "Cointime",
title: (name) => `${name} Price`,
color: colors.cointime,
defaultActive: true,
},
];
const caps = /** @type {const} */ ([
{
series: cap.vaulted.usd,
name: "Vaulted",
color: colors.vaulted,
defaultActive: true,
},
{
series: cap.active.usd,
name: "Active",
color: colors.active,
defaultActive: true,
},
{
series: cap.cointime.usd,
name: "Cointime",
color: colors.cointime,
defaultActive: true,
},
{
series: cap.investor.usd,
name: "Investor",
color: colors.investor,
defaultActive: false,
},
{
series: cap.thermo.usd,
name: "Thermo",
color: colors.thermo,
defaultActive: false,
},
]);
const supplyBreakdown = /** @type {const} */ ([
{ pattern: all.supply.total, name: "Total", color: colors.bitcoin },
{
pattern: cointimeSupply.vaulted,
name: "Vaulted",
color: colors.vaulted,
},
{
pattern: cointimeSupply.active,
name: "Active",
color: colors.active,
},
]);
const coinblocks = /** @type {const} */ ([
{
pattern: activity.coinblocksDestroyed,
name: "Destroyed",
title: "Coinblocks Destroyed",
color: colors.destroyed,
},
{
pattern: activity.coinblocksCreated,
name: "Created",
title: "Coinblocks Created",
color: colors.created,
},
{
pattern: activity.coinblocksStored,
name: "Stored",
title: "Coinblocks Stored",
color: colors.stored,
},
]);
// Colors aligned with coinblocks: Destroyed=red, Created=orange, Stored=green
const cointimeValues = /** @type {const} */ ([
{
pattern: value.created,
name: "Created",
title: "Cointime Value Created",
color: colors.created,
},
{
pattern: value.destroyed,
name: "Destroyed",
title: "Cointime Value Destroyed",
color: colors.destroyed,
},
{
pattern: value.stored,
name: "Stored",
title: "Cointime Value Stored",
color: colors.stored,
},
]);
const vocdd = /** @type {const} */ ({
pattern: value.vocdd,
name: "VOCDD",
title: "Value of Coin Days Destroyed",
color: colors.vocdd,
});
return {
name: "Cointime",
tree: [
{
name: "Prices",
tree: [
{
name: "Compare",
title: "Cointime Prices",
top: [
price({
series: all.realized.price,
name: "Realized",
color: colors.realized,
}),
price({
series: all.realized.capitalized.price,
name: "Capitalized",
color: colors.capitalized,
}),
...prices.map(({ pattern, name, color, defaultActive }) =>
price({ series: pattern, name, color, defaultActive }),
),
],
},
...prices.map(({ pattern, name, title, color }) => ({
name,
tree: priceRatioPercentilesTree({
pattern,
title: title(name),
legend: name,
color,
priceReferences: [
price({
series: all.realized.price,
name: "Realized",
color: colors.realized,
defaultActive: false,
}),
],
}),
})),
],
},
{
name: "Caps",
tree: [
{
name: "Compare",
title: "Cointime Caps",
bottom: [
...capReferenceLines.map(({ series, name, color }) =>
line({ series, name, color, unit: Unit.usd }),
),
...caps.map(({ series, name, color, defaultActive }) =>
line({ series, name, color, defaultActive, unit: Unit.usd }),
),
],
},
...caps.map(({ series, name, color }) => ({
name,
title: `${name} Cap`,
bottom: [
line({ series, name, color, unit: Unit.usd }),
...capReferenceLines.map((ref) =>
line({
series: ref.series,
name: ref.name,
color: ref.color,
unit: Unit.usd,
}),
),
],
})),
],
},
{
name: "Supply",
tree: [
{
name: "Breakdown",
title: "Active vs Vaulted Supply",
bottom: supplyBreakdown.flatMap(({ pattern, name, color }) =>
satsBtcUsd({ pattern, name, color }),
),
},
{
name: "Active in Loss",
title: "Active Supply in Loss",
bottom: [
line({
series: cointimeSupply.active.inLoss.share,
name: "Share",
color: colors.loss,
unit: Unit.ratio,
}),
],
},
],
},
createCointimeAgeRangeSection(cointimeAgeRanges),
{
name: "Activity",
title: "Liveliness & Vaultedness",
bottom: [
line({
series: activity.liveliness,
name: "Liveliness",
color: colors.liveliness,
unit: Unit.ratio,
}),
line({
series: activity.vaultedness,
name: "Vaultedness",
color: colors.vaulted,
unit: Unit.ratio,
}),
line({
series: activity.ratio,
name: "Liveliness / Vaultedness",
color: colors.activity,
unit: Unit.ratio,
defaultActive: false,
}),
],
},
{
name: "Coinblocks",
tree: [
...multiSeriesTree({
entries: coinblocks.map(({ pattern, name, color }) => ({
name,
color,
average: pattern.average,
sum: pattern.sum,
cumulative: pattern.cumulative,
})),
metric: "Coinblocks",
unit: Unit.coinblocks,
}),
...coinblocks.map(({ pattern, name, title: metric, color }) => ({
name,
tree: sumsAndAveragesCumulative({
sum: pattern.sum,
average: pattern.average,
cumulative: pattern.cumulative,
metric,
unit: Unit.coinblocks,
color,
}),
})),
],
},
{
name: "Value",
tree: [
...multiSeriesTree({
entries: [
...cointimeValues.map(({ pattern, name, color }) => ({
name,
color,
average: pattern.average,
sum: pattern.sum,
cumulative: pattern.cumulative,
})),
{
name: vocdd.name,
color: vocdd.color,
average: vocdd.pattern.average,
sum: vocdd.pattern.sum,
cumulative: vocdd.pattern.cumulative,
},
],
metric: "Cointime Value",
unit: Unit.usd,
}),
...cointimeValues.map(({ pattern, name, title: metric, color }) => ({
name,
tree: sumsAndAveragesCumulative({
sum: pattern.sum,
average: pattern.average,
cumulative: pattern.cumulative,
metric,
unit: Unit.usd,
color,
}),
})),
{
name: vocdd.name,
tree: sumsAndAveragesCumulative({
sum: vocdd.pattern.sum,
average: vocdd.pattern.average,
cumulative: vocdd.pattern.cumulative,
metric: vocdd.title,
unit: Unit.usd,
color: vocdd.color,
}),
},
],
},
{
name: "Indicators",
tree: [
{
name: "AVIV",
title: "AVIV Ratio",
bottom: [
line({
series: cap.aviv.ratio,
name: "AVIV",
unit: Unit.ratio,
}),
],
},
{
name: "Reserve Risk",
title: "Reserve Risk",
bottom: [
line({
series: reserveRisk.value,
name: "Ratio",
color: colors.reserveRisk,
unit: Unit.ratio,
}),
],
},
],
},
{
name: "Adjusted",
tree: [
{
name: "Inflation",
title: "Cointime-Adjusted Inflation",
bottom: [
dots({
series: supply.inflationRate.percent,
name: "Base",
color: colors.base,
unit: Unit.percentage,
}),
...percentRatioDots({
pattern: adjusted.inflationRate,
name: "Cointime-Adjusted",
color: colors.adjusted,
}),
],
},
{
name: "BTC Velocity",
title: "Cointime-Adjusted BTC Velocity",
bottom: [
line({
series: supply.velocity.native,
name: "Base",
color: colors.base,
unit: Unit.ratio,
}),
line({
series: adjusted.txVelocityNative,
name: "Cointime-Adjusted",
color: colors.adjusted,
unit: Unit.ratio,
}),
],
},
{
name: "USD Velocity",
title: "Cointime-Adjusted USD Velocity",
bottom: [
line({
series: supply.velocity.fiat,
name: "Base",
color: colors.thermo,
unit: Unit.ratio,
}),
line({
series: adjusted.txVelocityFiat,
name: "Cointime-Adjusted",
color: colors.vaulted,
unit: Unit.ratio,
}),
],
},
],
},
],
};
}