mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 07:09:59 -07:00
3560 lines
110 KiB
JavaScript
3560 lines
110 KiB
JavaScript
// @ts-check
|
||
|
||
/**
|
||
* @typedef {Object} BaseSeriesBlueprint
|
||
* @property {string} title
|
||
* @property {boolean} [defaultActive]
|
||
*
|
||
* @typedef {Object} BaselineSeriesBlueprintSpecific
|
||
* @property {"Baseline"} type
|
||
* @property {Color} [color]
|
||
* @property {[Color, Color]} [colors]
|
||
* @property {PartialBaselineStyleOptions} [options]
|
||
* @property {Accessor<BaselineData[]>} [data]
|
||
* @typedef {BaseSeriesBlueprint & BaselineSeriesBlueprintSpecific} BaselineSeriesBlueprint
|
||
*
|
||
* @typedef {Object} CandlestickSeriesBlueprintSpecific
|
||
* @property {"Candlestick"} type
|
||
* @property {Color} [color]
|
||
* @property {PartialCandlestickStyleOptions} [options]
|
||
* @property {Accessor<CandlestickData[]>} [data]
|
||
* @typedef {BaseSeriesBlueprint & CandlestickSeriesBlueprintSpecific} CandlestickSeriesBlueprint
|
||
*
|
||
* @typedef {Object} LineSeriesBlueprintSpecific
|
||
* @property {"Line"} [type]
|
||
* @property {Color} [color]
|
||
* @property {PartialLineStyleOptions} [options]
|
||
* @property {Accessor<LineData[]>} [data]
|
||
* @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint
|
||
*
|
||
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint
|
||
*
|
||
* @typedef {AnySeriesBlueprint["type"]} SeriesType
|
||
*
|
||
* @typedef {{ key: VecId, unit?: Unit | Unit[] }} FetchedAnySeriesOptions
|
||
*
|
||
* @typedef {BaselineSeriesBlueprint & FetchedAnySeriesOptions} FetchedBaselineSeriesBlueprint
|
||
* @typedef {CandlestickSeriesBlueprint & FetchedAnySeriesOptions} FetchedCandlestickSeriesBlueprint
|
||
* @typedef {LineSeriesBlueprint & FetchedAnySeriesOptions} FetchedLineSeriesBlueprint
|
||
* @typedef {AnySeriesBlueprint & FetchedAnySeriesOptions} AnyFetchedSeriesBlueprint
|
||
*
|
||
* @typedef {Object} PartialOption
|
||
* @property {string} name
|
||
*
|
||
* @typedef {Object} ProcessedOptionAddons
|
||
* @property {string} id
|
||
* @property {string} title
|
||
* @property {string[]} path
|
||
*
|
||
* @typedef {Object} PartialChartOptionSpecific
|
||
* @property {"chart"} [kind]
|
||
* @property {string} title
|
||
* @property {AnyFetchedSeriesBlueprint[]} [top]
|
||
* @property {AnyFetchedSeriesBlueprint[]} [bottom]
|
||
*
|
||
* @typedef {PartialOption & PartialChartOptionSpecific} PartialChartOption
|
||
*
|
||
* @typedef {Object} ProcessedChartOptionAddons
|
||
* @property {Record<Unit, AnyFetchedSeriesBlueprint[]>} top
|
||
* @property {Record<Unit, AnyFetchedSeriesBlueprint[]>} bottom
|
||
*
|
||
* @typedef {Required<Omit<PartialChartOption, "top" | "bottom">> & ProcessedChartOptionAddons & ProcessedOptionAddons} ChartOption
|
||
*
|
||
* @typedef {Object} PartialTableOptionSpecific
|
||
* @property {"table"} kind
|
||
* @property {string} title
|
||
*
|
||
* @typedef {PartialOption & PartialTableOptionSpecific} PartialTableOption
|
||
*
|
||
* @typedef {Required<PartialTableOption> & ProcessedOptionAddons} TableOption
|
||
*
|
||
* @typedef {Object} PartialSimulationOptionSpecific
|
||
* @property {"simulation"} kind
|
||
* @property {string} title
|
||
*
|
||
* @typedef {PartialOption & PartialSimulationOptionSpecific} PartialSimulationOption
|
||
*
|
||
* @typedef {Required<PartialSimulationOption> & ProcessedOptionAddons} SimulationOption
|
||
*
|
||
* @typedef {Object} PartialUrlOptionSpecific
|
||
* @property {"url"} [kind]
|
||
* @property {() => string} url
|
||
* @property {boolean} [qrcode]
|
||
*
|
||
* @typedef {PartialOption & PartialUrlOptionSpecific} PartialUrlOption
|
||
*
|
||
* @typedef {Required<PartialUrlOption> & ProcessedOptionAddons} UrlOption
|
||
*
|
||
* @typedef {PartialChartOption | PartialTableOption | PartialSimulationOption | PartialUrlOption} AnyPartialOption
|
||
*
|
||
* @typedef {ChartOption | TableOption | SimulationOption | UrlOption} Option
|
||
*
|
||
* @typedef {Object} PartialOptionsGroup
|
||
* @property {string} name
|
||
* @property {PartialOptionsTree} tree
|
||
*
|
||
* @typedef {Object} OptionsGroup
|
||
* @property {string} id
|
||
* @property {string} name
|
||
* @property {OptionsTree} tree
|
||
*
|
||
* @typedef {(AnyPartialOption | PartialOptionsGroup)[]} PartialOptionsTree
|
||
*
|
||
* @typedef {(Option | OptionsGroup)[]} OptionsTree
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* @param {Colors} colors
|
||
* @returns {PartialOptionsTree}
|
||
*/
|
||
function createPartialOptions(colors) {
|
||
/**
|
||
* @template {string} S
|
||
* @typedef {Extract<VecId, `${S}${string}`>} StartsWith
|
||
*/
|
||
/**
|
||
* @template {string} S
|
||
* @typedef {Extract<VecId, `${string}${S}`>} EndsWith
|
||
*/
|
||
/**
|
||
* @template {string} K
|
||
* @template {string} S
|
||
* @typedef {K extends `${S}${infer Rest}` ? Rest : never} WithoutPrefix
|
||
*/
|
||
/**
|
||
* @template {string} K
|
||
* @template {string} S
|
||
* @typedef {K extends `${infer Rest}${S}` ? Rest : never} WithoutSuffix
|
||
*/
|
||
|
||
/**
|
||
* @typedef {"cumulative-"} CumulativePrefix
|
||
* @typedef {StartsWith<CumulativePrefix>} CumulativeVecId
|
||
* @typedef {WithoutPrefix<CumulativeVecId, CumulativePrefix>} CumulativeVecIdBase
|
||
* @typedef {"-average"} AverageSuffix
|
||
* @typedef {EndsWith<AverageSuffix>} VecIdAverage
|
||
* @typedef {WithoutSuffix<VecIdAverage, AverageSuffix>} VecIdAverageBase
|
||
* @typedef {"-median"} MedianSuffix
|
||
* @typedef {EndsWith<MedianSuffix>} VecIdMedian
|
||
* @typedef {WithoutSuffix<VecIdMedian, MedianSuffix>} VecIdMedianBase
|
||
* @typedef {"-90p"} _90pSuffix
|
||
* @typedef {EndsWith<_90pSuffix>} VecId90p
|
||
* @typedef {WithoutSuffix<VecId90p, _90pSuffix>} VecId90pBase
|
||
* @typedef {"-75p"} _75pSuffix
|
||
* @typedef {EndsWith<_75pSuffix>} VecId75p
|
||
* @typedef {WithoutSuffix<VecId75p, _75pSuffix>} VecId75pBase
|
||
* @typedef {"-25p"} _25pSuffix
|
||
* @typedef {EndsWith<_25pSuffix>} VecId25p
|
||
* @typedef {WithoutSuffix<VecId25p, _25pSuffix>} VecId25pBase
|
||
* @typedef {"-10p"} _10pSuffix
|
||
* @typedef {EndsWith<_10pSuffix>} VecId10p
|
||
* @typedef {WithoutSuffix<VecId10p, _10pSuffix>} VecId10pBase
|
||
* @typedef {"-max"} MaxSuffix
|
||
* @typedef {EndsWith<MaxSuffix>} VecIdMax
|
||
* @typedef {WithoutSuffix<VecIdMax, MaxSuffix>} VecIdMaxBase
|
||
* @typedef {"-min"} MinSuffix
|
||
* @typedef {EndsWith<MinSuffix>} VecIdMin
|
||
* @typedef {WithoutSuffix<VecIdMin, MinSuffix>} VecIdMinBase
|
||
*/
|
||
|
||
const averages = /** @type {const} */ ([
|
||
{ name: "1 Week", key: "1w", days: 7, color: colors.red },
|
||
{ name: "8 Day", key: "8d", days: 8, color: colors.orange },
|
||
{ name: "13 Day", key: "13d", days: 13, color: colors.amber },
|
||
{ name: "21 Day", key: "21d", days: 21, color: colors.yellow },
|
||
{ name: "1 Month", key: "1m", days: 30, color: colors.lime },
|
||
{ name: "34 Day", key: "34d", days: 34, color: colors.green },
|
||
{ name: "55 Day", key: "55d", days: 55, color: colors.emerald },
|
||
{ name: "89 Day", key: "89d", days: 89, color: colors.teal },
|
||
{ name: "144 Day", key: "144d", days: 144, color: colors.cyan },
|
||
{ name: "200 Day", key: "200d", days: 200, color: colors.sky },
|
||
{ name: "1 Year", key: "1y", days: 365, color: colors.blue },
|
||
{ name: "2 Year", key: "2y", days: 2 * 365, color: colors.indigo },
|
||
{ name: "200 Week", key: "200w", days: 200 * 7, color: colors.violet },
|
||
{ name: "4 Year", key: "4y", days: 4 * 365, color: colors.purple },
|
||
]);
|
||
|
||
const dcaClasses = /** @type {const} */ ([
|
||
{ year: 2015, color: colors.pink, defaultActive: false },
|
||
{ year: 2016, color: colors.red, defaultActive: false },
|
||
{ year: 2017, color: colors.orange, defaultActive: true },
|
||
{ year: 2018, color: colors.yellow, defaultActive: true },
|
||
{ year: 2019, color: colors.green, defaultActive: true },
|
||
{ year: 2020, color: colors.teal, defaultActive: true },
|
||
{ year: 2021, color: colors.sky, defaultActive: true },
|
||
{ year: 2022, color: colors.blue, defaultActive: true },
|
||
{ year: 2023, color: colors.purple, defaultActive: true },
|
||
{ year: 2024, color: colors.fuchsia, defaultActive: true },
|
||
{ year: 2025, color: colors.pink, defaultActive: true },
|
||
]);
|
||
|
||
const terms = /** @type {const} */ ([
|
||
{
|
||
key: "sth",
|
||
name: "short",
|
||
title: "Short Term Holders",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "lth",
|
||
name: "long",
|
||
title: "Long Term Holders",
|
||
color: colors.fuchsia,
|
||
},
|
||
]);
|
||
|
||
const upTo = /** @type {const} */ ([
|
||
{
|
||
key: "up-to-1d",
|
||
name: "1d",
|
||
title: "Up to 1 Day",
|
||
color: colors.pink,
|
||
},
|
||
{
|
||
key: "up-to-1w",
|
||
name: "1w",
|
||
title: "Up to 1 Week",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "up-to-1m",
|
||
name: "1m",
|
||
title: "Up to 1 Month",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "up-to-2m",
|
||
name: "2m",
|
||
title: "Up to 2 Months",
|
||
color: colors.amber,
|
||
},
|
||
{
|
||
key: "up-to-3m",
|
||
name: "3m",
|
||
title: "Up to 3 Months",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "up-to-4m",
|
||
name: "4m",
|
||
title: "Up to 4 Months",
|
||
color: colors.lime,
|
||
},
|
||
{
|
||
key: "up-to-5m",
|
||
name: "5m",
|
||
title: "Up to 5 Months",
|
||
color: colors.green,
|
||
},
|
||
{
|
||
key: "up-to-6m",
|
||
name: "6m",
|
||
title: "Up to 6 Months",
|
||
color: colors.teal,
|
||
},
|
||
{
|
||
key: "up-to-1y",
|
||
name: "1y",
|
||
title: "Up to 1 Year",
|
||
color: colors.sky,
|
||
},
|
||
{
|
||
key: "up-to-2y",
|
||
name: "2y",
|
||
title: "Up to 2 Years",
|
||
color: colors.indigo,
|
||
},
|
||
{
|
||
key: "up-to-3y",
|
||
name: "3y",
|
||
title: "Up to 3 Years",
|
||
color: colors.violet,
|
||
},
|
||
{
|
||
key: "up-to-4y",
|
||
name: "4y",
|
||
title: "Up to 4 Years",
|
||
color: colors.purple,
|
||
},
|
||
{
|
||
key: "up-to-5y",
|
||
name: "5y",
|
||
title: "Up to 5 Years",
|
||
color: colors.fuchsia,
|
||
},
|
||
{
|
||
key: "up-to-6y",
|
||
name: "6y",
|
||
title: "Up to 6 Years",
|
||
color: colors.pink,
|
||
},
|
||
{
|
||
key: "up-to-7y",
|
||
name: "7y",
|
||
title: "Up to 7 Years",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "up-to-8y",
|
||
name: "8y",
|
||
title: "Up to 8 Years",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "up-to-10y",
|
||
name: "10y",
|
||
title: "Up to 10 Years",
|
||
color: colors.amber,
|
||
},
|
||
{
|
||
key: "up-to-15y",
|
||
name: "15y",
|
||
title: "Up to 15 Years",
|
||
color: colors.yellow,
|
||
},
|
||
]);
|
||
|
||
const from = /** @type {const} */ ([
|
||
{
|
||
key: "from-1d",
|
||
name: "1d",
|
||
title: "From 1 Day",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "from-1w",
|
||
name: "1w",
|
||
title: "From 1 Week",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "from-1m",
|
||
name: "1m",
|
||
title: "From 1 Month",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "from-2m",
|
||
name: "2m",
|
||
title: "From 2 Months",
|
||
color: colors.lime,
|
||
},
|
||
{
|
||
key: "from-3m",
|
||
name: "3m",
|
||
title: "From 3 Months",
|
||
color: colors.green,
|
||
},
|
||
{
|
||
key: "from-4m",
|
||
name: "4m",
|
||
title: "From 4 Months",
|
||
color: colors.teal,
|
||
},
|
||
{
|
||
key: "from-5m",
|
||
name: "5m",
|
||
title: "From 5 Months",
|
||
color: colors.cyan,
|
||
},
|
||
{
|
||
key: "from-6m",
|
||
name: "6m",
|
||
title: "From 6 Months",
|
||
color: colors.blue,
|
||
},
|
||
{
|
||
key: "from-1y",
|
||
name: "1y",
|
||
title: "From 1 Year",
|
||
color: colors.indigo,
|
||
},
|
||
{
|
||
key: "from-2y",
|
||
name: "2y",
|
||
title: "From 2 Years",
|
||
color: colors.violet,
|
||
},
|
||
{
|
||
key: "from-3y",
|
||
name: "3y",
|
||
title: "From 3 Years",
|
||
color: colors.purple,
|
||
},
|
||
{
|
||
key: "from-4y",
|
||
name: "4y",
|
||
title: "From 4 Years",
|
||
color: colors.fuchsia,
|
||
},
|
||
{
|
||
key: "from-5y",
|
||
name: "5y",
|
||
title: "From 5 Years",
|
||
color: colors.pink,
|
||
},
|
||
{
|
||
key: "from-6y",
|
||
name: "6y",
|
||
title: "From 6 Years",
|
||
color: colors.rose,
|
||
},
|
||
{
|
||
key: "from-7y",
|
||
name: "7y",
|
||
title: "From 7 Years",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "from-8y",
|
||
name: "8y",
|
||
title: "From 8 Years",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "from-10y",
|
||
name: "10y",
|
||
title: "From 10 Years",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "from-15y",
|
||
name: "15y",
|
||
title: "From 15 Years",
|
||
color: colors.lime,
|
||
},
|
||
]);
|
||
|
||
const range = /** @type {const} */ ([
|
||
{
|
||
key: "start-to-1d",
|
||
name: "24h",
|
||
title: "Last 24 hours",
|
||
color: colors.pink,
|
||
},
|
||
{
|
||
key: "from-1d-to-1w",
|
||
name: "1d..1w",
|
||
title: "From 1 Day ago to 1 Week ago",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "from-1w-to-1m",
|
||
name: "1w..1m",
|
||
title: "From 1 Week ago to 1 Month ago",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "from-1m-to-2m",
|
||
name: "1m..2m",
|
||
title: "From 1 Month ago to 2 Months ago",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "from-2m-to-3m",
|
||
name: "2m..3m",
|
||
title: "From 2 Month ago to 3 Months ago",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "from-3m-to-4m",
|
||
name: "3m..4m",
|
||
title: "From 3 Month ago to 4 Months ago",
|
||
color: colors.lime,
|
||
},
|
||
{
|
||
key: "from-4m-to-5m",
|
||
name: "4m..5m",
|
||
title: "From 4 Month ago to 5 Months ago",
|
||
color: colors.lime,
|
||
},
|
||
{
|
||
key: "from-5m-to-6m",
|
||
name: "5m..6m",
|
||
title: "From 5 Month ago to 6 Months ago",
|
||
color: colors.lime,
|
||
},
|
||
{
|
||
key: "from-6m-to-1y",
|
||
name: "6m..1y",
|
||
title: "From 6 Months ago to 1 Year ago",
|
||
color: colors.green,
|
||
},
|
||
{
|
||
key: "from-1y-to-2y",
|
||
name: "1y..2y",
|
||
title: "From 1 Year ago to 2 Years ago",
|
||
color: colors.cyan,
|
||
},
|
||
{
|
||
key: "from-2y-to-3y",
|
||
name: "2y..3y",
|
||
title: "From 2 Years ago to 3 Years ago",
|
||
color: colors.blue,
|
||
},
|
||
{
|
||
key: "from-3y-to-4y",
|
||
name: "3y..4y",
|
||
title: "From 3 Years ago to 4 Years ago",
|
||
color: colors.indigo,
|
||
},
|
||
{
|
||
key: "from-4y-to-5y",
|
||
name: "4y..5y",
|
||
title: "From 4 Years ago to 5 Years ago",
|
||
color: colors.violet,
|
||
},
|
||
{
|
||
key: "from-5y-to-6y",
|
||
name: "5y..6y",
|
||
title: "From 5 Years ago to 6 Years ago",
|
||
color: colors.purple,
|
||
},
|
||
{
|
||
key: "from-6y-to-7y",
|
||
name: "6y..7y",
|
||
title: "From 6 Years ago to 7 Years ago",
|
||
color: colors.purple,
|
||
},
|
||
{
|
||
key: "from-7y-to-8y",
|
||
name: "7y..8y",
|
||
title: "From 7 Years ago to 8 Years ago",
|
||
color: colors.fuchsia,
|
||
},
|
||
{
|
||
key: "from-8y-to-10y",
|
||
name: "8y..10y",
|
||
title: "From 8 Years ago to 10 Years ago",
|
||
color: colors.fuchsia,
|
||
},
|
||
{
|
||
key: "from-10y-to-15y",
|
||
name: "10y..15y",
|
||
title: "From 10 Years ago to 15 Years ago",
|
||
color: colors.pink,
|
||
},
|
||
{
|
||
key: "from-15y-to-end",
|
||
name: "15y+",
|
||
title: "From 15 Years ago to genesis (2009-01-03)",
|
||
color: colors.red,
|
||
},
|
||
]);
|
||
|
||
const epoch = /** @type {const} */ ([
|
||
{
|
||
key: "epoch-0",
|
||
name: "0",
|
||
title: "Epoch 0",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "epoch-1",
|
||
name: "1",
|
||
title: "Epoch 1",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "epoch-2",
|
||
name: "2",
|
||
title: "Epoch 2",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "epoch-3",
|
||
name: "3",
|
||
title: "Epoch 3",
|
||
color: colors.lime,
|
||
},
|
||
{
|
||
key: "epoch-4",
|
||
name: "4",
|
||
title: "Epoch 4",
|
||
color: colors.green,
|
||
},
|
||
]);
|
||
|
||
const fromSize = /** @type {const} */ ([
|
||
{
|
||
key: "from-1-000sats",
|
||
name: "1K sats",
|
||
title: "From 1K sats",
|
||
color: colors.cyan,
|
||
},
|
||
{
|
||
key: "from-1btc",
|
||
name: "1btc",
|
||
title: "From 1 BTC",
|
||
color: colors.violet,
|
||
},
|
||
{
|
||
key: "from-10btc",
|
||
name: "10btc",
|
||
title: "From 10 BTC",
|
||
color: colors.purple,
|
||
},
|
||
{
|
||
key: "from-100btc",
|
||
name: "100btc",
|
||
title: "From 100 BTC",
|
||
color: colors.pink,
|
||
},
|
||
]);
|
||
|
||
const upToSize = /** @type {const} */ ([
|
||
{
|
||
key: "up-to-1-000sats",
|
||
name: "1K sats",
|
||
title: "Up to 1K sats",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "up-to-10-000sats",
|
||
name: "10K sats",
|
||
title: "Up to 10K sats",
|
||
color: colors.green,
|
||
},
|
||
{
|
||
key: "up-to-1btc",
|
||
name: "1btc",
|
||
title: "Up to 1 btc",
|
||
color: colors.cyan,
|
||
},
|
||
{
|
||
key: "up-to-10btc",
|
||
name: "10btc",
|
||
title: "Up to 10 btc",
|
||
color: colors.blue,
|
||
},
|
||
{
|
||
key: "up-to-100btc",
|
||
name: "100btc",
|
||
title: "Up to 100 btc",
|
||
color: colors.violet,
|
||
},
|
||
]);
|
||
|
||
const sizeRanges = /** @type {const} */ ([
|
||
{
|
||
key: "0sats",
|
||
name: "0 sats",
|
||
title: "0 sats",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "from-1sat-to-10sats",
|
||
name: "1 sat..10 sats",
|
||
title: "From 1 sat to 10 sats",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "from-10sats-to-100sats",
|
||
name: "10 sats..100 sats",
|
||
title: "From 10 sats to 100 sats",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "from-100sats-to-1-000sats",
|
||
name: "100 sats..1K sats",
|
||
title: "From 100 sats to 1K sats",
|
||
color: colors.lime,
|
||
},
|
||
{
|
||
key: "from-1-000sats-to-10-000sats",
|
||
name: "1K sats..10K sats",
|
||
title: "From 1K sats to 10K sats",
|
||
color: colors.green,
|
||
},
|
||
{
|
||
key: "from-10-000sats-to-100-000sats",
|
||
name: "10K sats..100K sats",
|
||
title: "From 10K sats to 100K sats",
|
||
color: colors.cyan,
|
||
},
|
||
{
|
||
key: "from-100-000sats-to-1-000-000sats",
|
||
name: "100K sats .. 1M sats",
|
||
title: "From 100K sats to 1M sats",
|
||
color: colors.blue,
|
||
},
|
||
{
|
||
key: "from-1-000-000sats-to-10-000-000sats",
|
||
name: "1M sats..10M sats",
|
||
title: "From 1M sats to 10M sats",
|
||
color: colors.indigo,
|
||
},
|
||
{
|
||
key: "from-10-000-000sats-to-1btc",
|
||
name: "10M sats..1 btc",
|
||
title: "From 10M sats to 1 BTC",
|
||
color: colors.purple,
|
||
},
|
||
{
|
||
key: "from-1btc-to-10btc",
|
||
name: "1 btc..10 btc",
|
||
title: "From 1 BTC to 10 BTC",
|
||
color: colors.violet,
|
||
},
|
||
{
|
||
key: "from-10btc-to-100btc",
|
||
name: "10 btc..100 btc",
|
||
title: "From 10 BTC to 100 BTC",
|
||
color: colors.fuchsia,
|
||
},
|
||
{
|
||
key: "from-100btc-to-1-000btc",
|
||
name: "100 btc..1K btc",
|
||
title: "From 100 BTC to 1K BTC",
|
||
color: colors.pink,
|
||
},
|
||
{
|
||
key: "from-1-000btc-to-10-000btc",
|
||
name: "1K btc..10K btc",
|
||
title: "From 1K BTC to 10K BTC",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "from-10-000btc-to-100-000btc",
|
||
name: "10K btc..100K btc",
|
||
title: "From 10K BTC to 100K BTC",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "from-100-000btc",
|
||
name: "100K btc+",
|
||
title: "From 100K BTC",
|
||
color: colors.yellow,
|
||
},
|
||
]);
|
||
|
||
const type = /** @type {const} */ ([
|
||
{
|
||
key: "p2pk65",
|
||
name: "p2pk65",
|
||
title: "Pay To Long Public Key",
|
||
color: colors.red,
|
||
},
|
||
{
|
||
key: "p2pk33",
|
||
name: "p2pk33",
|
||
title: "Pay To Short Public Key",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: "p2pkh",
|
||
name: "p2pkh",
|
||
title: "Pay To Public Key Hash",
|
||
color: colors.yellow,
|
||
},
|
||
{
|
||
key: "p2ms",
|
||
name: "p2ms",
|
||
title: "Pay To Bare Multisig",
|
||
color: colors.lime,
|
||
},
|
||
{
|
||
key: "p2sh",
|
||
name: "p2sh",
|
||
title: "Pay To Script Hash",
|
||
color: colors.green,
|
||
},
|
||
{
|
||
key: "p2wpkh",
|
||
name: "p2wpkh",
|
||
title: "Pay To Witness Public Key Hash",
|
||
color: colors.teal,
|
||
},
|
||
{
|
||
key: "p2wsh",
|
||
name: "p2wsh",
|
||
title: "Pay To Witness Script Hash",
|
||
color: colors.blue,
|
||
},
|
||
{
|
||
key: "p2tr",
|
||
name: "p2tr",
|
||
title: "Pay To Taproot",
|
||
color: colors.indigo,
|
||
},
|
||
{
|
||
key: "p2a",
|
||
name: "p2a",
|
||
title: "Pay To Anchor",
|
||
color: colors.purple,
|
||
},
|
||
{
|
||
key: "unknown",
|
||
name: "unknown",
|
||
title: "Pay To Unknown",
|
||
color: colors.violet,
|
||
},
|
||
{
|
||
key: "empty",
|
||
name: "empty",
|
||
title: "Pay To Empty",
|
||
color: colors.fuchsia,
|
||
},
|
||
]);
|
||
|
||
/**
|
||
* @param {Object} args
|
||
* @param {VecId} args.key
|
||
* @param {string} args.name
|
||
* @param {Color} [args.color]
|
||
* @param {boolean} [args.defaultActive]
|
||
* @param {PartialLineStyleOptions} [args.options]
|
||
*/
|
||
function createBaseSeries({ key, name, color, defaultActive, options }) {
|
||
return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({
|
||
key,
|
||
title: name,
|
||
color,
|
||
defaultActive,
|
||
options,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param {Object} args
|
||
* @param {VecIdAverageBase} args.concat
|
||
*/
|
||
function createAverageSeries({ concat }) {
|
||
return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({
|
||
key: `${concat}-average`,
|
||
title: "Average",
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param {Object} args
|
||
* @param {CumulativeVecIdBase} args.concat
|
||
* @param {string} [args.name]
|
||
*/
|
||
function createSumCumulativeSeries({ concat, name }) {
|
||
return /** @satisfies {AnyFetchedSeriesBlueprint[]} */ ([
|
||
{
|
||
key: concat,
|
||
title: name ? `${name} Sum` : "Sum",
|
||
color: colors.orange,
|
||
},
|
||
{
|
||
key: `cumulative-${concat}`,
|
||
title: name ? `Cumulative ${name}` : "Cumulative",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
},
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* @param {Object} args
|
||
* @param {VecIdMinBase & VecIdMaxBase & VecId90pBase & VecId75pBase & VecIdMedianBase & VecId25pBase & VecId10pBase} args.concat
|
||
*/
|
||
function createMinMaxPercentilesSeries({ concat }) {
|
||
return /** @satisfies {AnyFetchedSeriesBlueprint[]} */ ([
|
||
{
|
||
key: `${concat}-max`,
|
||
title: "Max",
|
||
color: colors.pink,
|
||
defaultActive: false,
|
||
},
|
||
{
|
||
key: `${concat}-min`,
|
||
title: "Min",
|
||
color: colors.green,
|
||
defaultActive: false,
|
||
},
|
||
{
|
||
key: `${concat}-median`,
|
||
title: "Median",
|
||
color: colors.amber,
|
||
defaultActive: false,
|
||
},
|
||
{
|
||
key: `${concat}-75p`,
|
||
title: "75p",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
},
|
||
{
|
||
key: `${concat}-25p`,
|
||
title: "25p",
|
||
color: colors.yellow,
|
||
defaultActive: false,
|
||
},
|
||
{
|
||
key: `${concat}-90p`,
|
||
title: "90p",
|
||
color: colors.rose,
|
||
defaultActive: false,
|
||
},
|
||
{
|
||
key: `${concat}-10p`,
|
||
title: "10p",
|
||
color: colors.lime,
|
||
defaultActive: false,
|
||
},
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* @param {VecIdAverageBase & CumulativeVecIdBase & VecIdMinBase & VecIdMaxBase & VecId90pBase & VecId75pBase & VecIdMedianBase & VecId25pBase & VecId10pBase} key
|
||
*/
|
||
function createAverageSumCumulativeMinMaxPercentilesSeries(key) {
|
||
return [
|
||
createAverageSeries({ concat: key }),
|
||
...createSumCumulativeSeries({ concat: key }),
|
||
...createMinMaxPercentilesSeries({ concat: key }),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param {Object} args
|
||
* @param {VecId & VecIdAverageBase & CumulativeVecIdBase & VecIdMinBase & VecIdMaxBase & VecId90pBase & VecId75pBase & VecIdMedianBase & VecId25pBase & VecId10pBase} args.key
|
||
* @param {string} args.name
|
||
*/
|
||
function createBaseAverageSumCumulativeMinMaxPercentilesSeries({
|
||
key,
|
||
name,
|
||
}) {
|
||
return [
|
||
createBaseSeries({
|
||
key,
|
||
name,
|
||
}),
|
||
...createAverageSumCumulativeMinMaxPercentilesSeries(key),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param {Object} args
|
||
* @param {VecId & CumulativeVecIdBase} args.key
|
||
* @param {string} args.name
|
||
*/
|
||
function createBaseSumCumulativeSeries({ key, name }) {
|
||
return [
|
||
createBaseSeries({
|
||
key,
|
||
name,
|
||
}),
|
||
...createSumCumulativeSeries({
|
||
concat: key,
|
||
}),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @typedef {"-ratio-zscore"} RatioZScoreCapSuffix
|
||
* @typedef {EndsWith<RatioZScoreCapSuffix>} VecIdRatioZScoreCap
|
||
* @typedef {WithoutSuffix<VecIdRatioZScoreCap, RatioZScoreCapSuffix>} VecIdRatioZScoreCapBase
|
||
*/
|
||
|
||
/**
|
||
*
|
||
* @param {Object} args
|
||
* @param {string} args.name
|
||
* @param {string} args.legend
|
||
* @param {string} args.title
|
||
* @param {VecIdRatioZScoreCapBase} args.key
|
||
* @param {Color} [args.color]
|
||
*/
|
||
function createPriceWithRatio({ name, title, legend, key, color }) {
|
||
return {
|
||
name,
|
||
title,
|
||
top: [
|
||
createBaseSeries({
|
||
key,
|
||
name: legend,
|
||
color,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p1sd-as-price`,
|
||
name: "+1σ",
|
||
color: colors.orange,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p2sd-as-price`,
|
||
name: "+2σ",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p3sd-as-price`,
|
||
name: "+3σ",
|
||
color: colors.pink,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-m1sd-as-price`,
|
||
name: "−1σ",
|
||
color: colors.cyan,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-m2sd-as-price`,
|
||
name: "−2σ",
|
||
color: colors.blue,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-m3sd-as-price`,
|
||
name: "−3σ",
|
||
color: colors.violet,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p99-as-price`,
|
||
name: "p99",
|
||
color: colors.orange,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p99-5-as-price`,
|
||
name: "p99.5",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p99-9-as-price`,
|
||
name: "p99.9",
|
||
color: colors.pink,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p1-as-price`,
|
||
name: "p1",
|
||
color: colors.cyan,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p0-5-as-price`,
|
||
name: "p0.5",
|
||
color: colors.blue,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p0-1-as-price`,
|
||
name: "p0.1",
|
||
color: colors.violet,
|
||
defaultActive: false,
|
||
}),
|
||
],
|
||
bottom: [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-ratio`,
|
||
title: "Ratio",
|
||
type: "Baseline",
|
||
options: {
|
||
baseValue: { price: 1 },
|
||
createPriceLine: {
|
||
value: 1,
|
||
},
|
||
},
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p1sd`,
|
||
name: "+1σ",
|
||
color: colors.orange,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p2sd`,
|
||
name: "+2σ",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p3sd`,
|
||
name: "+3σ",
|
||
color: colors.pink,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-m1sd`,
|
||
name: "−1σ",
|
||
color: colors.cyan,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-m2sd`,
|
||
name: "−2σ",
|
||
color: colors.blue,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-m3sd`,
|
||
name: "−3σ",
|
||
color: colors.violet,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p99`,
|
||
name: "p99",
|
||
color: colors.orange,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p99-5`,
|
||
name: "p99.5",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p99-9`,
|
||
name: "p99.9",
|
||
color: colors.pink,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p1`,
|
||
name: "p1",
|
||
color: colors.cyan,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p0-5`,
|
||
name: "p0.5",
|
||
color: colors.blue,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-p0-1`,
|
||
name: "p0.1",
|
||
color: colors.violet,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-1w-sma`,
|
||
name: "1w sma",
|
||
color: colors.fuchsia,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-1m-sma`,
|
||
name: "1m sma",
|
||
color: colors.pink,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-1y-sma`,
|
||
name: "1y sma",
|
||
color: colors.rose,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-4y-sma`,
|
||
name: "4y-sma",
|
||
color: colors.violet,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}-ratio-sma`,
|
||
name: "sma",
|
||
color: colors.yellow,
|
||
defaultActive: false,
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-ratio-1y-sma-momentum-oscillator`,
|
||
title: "1Y Momentum",
|
||
type: "Baseline",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-ratio-zscore`,
|
||
title: "All time",
|
||
type: "Baseline",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-ratio-4y-zscore`,
|
||
title: "4y",
|
||
type: "Baseline",
|
||
colors: [colors.yellow, colors.pink],
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
],
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @typedef {"-supply-in-profit"} SupplyInProfitSuffix
|
||
* @typedef {EndsWith<SupplyInProfitSuffix>} VecIdSupplyInProfit
|
||
* @typedef {WithoutSuffix<VecIdSupplyInProfit, SupplyInProfitSuffix>} CohortId
|
||
*/
|
||
|
||
/**
|
||
* @typedef {Object} UTXOGroupObject
|
||
* @property {string} args.name
|
||
* @property {string} args.title
|
||
* @property {Color} args.color
|
||
* @property {"" | CohortId} args.key
|
||
*/
|
||
|
||
/**
|
||
* @typedef {Object} UTXOGroupsObject
|
||
* @property {string} args.name
|
||
* @property {string} args.title
|
||
* @property {readonly UTXOGroupObject[]} args.list
|
||
*/
|
||
|
||
/**
|
||
* @param {UTXOGroupObject | UTXOGroupsObject} args
|
||
*/
|
||
function createUTXOGroupFolder(args) {
|
||
/**
|
||
* @template {"" | CohortId} T
|
||
* @param {T} _key
|
||
*/
|
||
const fixKey = (_key) =>
|
||
_key !== ""
|
||
? /** @type {Exclude<"" | `${T}-`, "-">} */ (`${_key}-`)
|
||
: /** @type {const} */ ("");
|
||
0;
|
||
|
||
const list = "list" in args ? args.list : [args];
|
||
const useGroupName = "list" in args;
|
||
|
||
return /** @satisfies {PartialOptionsGroup} */ ({
|
||
name: args.name || "all",
|
||
tree: [
|
||
{
|
||
name: "supply",
|
||
title: `${args.title} Supply`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}supply`,
|
||
name: useGroupName ? name : "Supply",
|
||
color: "list" in args ? color : colors.default,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-btc`,
|
||
name: useGroupName ? name : "Supply",
|
||
color: "list" in args ? color : colors.default,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-usd`,
|
||
name: useGroupName ? name : "Supply",
|
||
color: "list" in args ? color : colors.default,
|
||
}),
|
||
...(key
|
||
? [
|
||
createBaseSeries({
|
||
key: `${key}supply-relative-to-circulating-supply`,
|
||
name: useGroupName ? name : "Supply",
|
||
color: "list" in args ? color : colors.default,
|
||
}),
|
||
]
|
||
: []),
|
||
...(!("list" in args)
|
||
? [
|
||
createBaseSeries({
|
||
key: `${key}halved-supply`,
|
||
name: useGroupName ? name : "Halved",
|
||
color: "list" in args ? color : colors.gray,
|
||
options: {
|
||
lineStyle: 4,
|
||
},
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-profit`,
|
||
name: useGroupName ? name : "In Profit",
|
||
color: colors.green,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-loss`,
|
||
name: useGroupName ? name : "In Loss",
|
||
color: colors.red,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-even`,
|
||
name: useGroupName ? name : "Even",
|
||
color: colors.yellow,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}halved-supply-in-btc`,
|
||
name: useGroupName ? name : "Halved",
|
||
color: "list" in args ? color : colors.gray,
|
||
options: {
|
||
lineStyle: 4,
|
||
},
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-profit-in-btc`,
|
||
name: useGroupName ? name : "In Profit",
|
||
color: colors.green,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-loss-in-btc`,
|
||
name: useGroupName ? name : "In Loss",
|
||
color: colors.red,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-even-in-btc`,
|
||
name: useGroupName ? name : "Even",
|
||
color: colors.yellow,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}halved-supply-in-usd`,
|
||
name: useGroupName ? name : "Halved",
|
||
color: "list" in args ? color : colors.gray,
|
||
options: {
|
||
lineStyle: 4,
|
||
},
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-profit-in-usd`,
|
||
name: useGroupName ? name : "In Profit",
|
||
color: colors.green,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-loss-in-usd`,
|
||
name: useGroupName ? name : "In Loss",
|
||
color: colors.red,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-even-in-usd`,
|
||
name: useGroupName ? name : "Even",
|
||
color: colors.yellow,
|
||
}),
|
||
...(key
|
||
? [
|
||
createBaseSeries({
|
||
key: `${key}supply-in-profit-relative-to-circulating-supply`,
|
||
name: useGroupName ? name : "In Profit",
|
||
color: colors.green,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-loss-relative-to-circulating-supply`,
|
||
name: useGroupName ? name : "In Loss",
|
||
color: colors.red,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-even-relative-to-circulating-supply`,
|
||
name: useGroupName ? name : "Even",
|
||
color: colors.yellow,
|
||
}),
|
||
]
|
||
: []),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-profit-relative-to-own-supply`,
|
||
name: useGroupName ? name : "In Profit",
|
||
color: colors.green,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-in-loss-relative-to-own-supply`,
|
||
name: useGroupName ? name : "In Loss",
|
||
color: colors.red,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}supply-even-relative-to-own-supply`,
|
||
name: useGroupName ? name : "Even",
|
||
color: colors.yellow,
|
||
}),
|
||
]
|
||
: []),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "utxo count",
|
||
title: `${args.title} UTXO Count`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}utxo-count`,
|
||
name: useGroupName ? name : "Count",
|
||
color,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "Realized",
|
||
tree: [
|
||
{
|
||
name: "cap",
|
||
title: `${args.title} Realized Capitalization`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}realized-cap`,
|
||
name: useGroupName ? name : "Cap",
|
||
color,
|
||
}),
|
||
...(!("list" in args)
|
||
? [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${key}realized-cap-30d-change`,
|
||
title: "30d change",
|
||
defaultActive: false,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
]
|
||
: []),
|
||
]);
|
||
}),
|
||
},
|
||
"list" in args
|
||
? {
|
||
name: "Price",
|
||
title: `${args.title} Realized Prices`,
|
||
top: args.list.map(({ color, name, key }) =>
|
||
createBaseSeries({
|
||
key: `${fixKey(key)}realized-price`,
|
||
name,
|
||
color,
|
||
})
|
||
),
|
||
}
|
||
: createPriceWithRatio({
|
||
title: `${args.title} Realized Price`,
|
||
key: `${fixKey(args.key)}realized-price`,
|
||
name: "price",
|
||
legend: "realized",
|
||
color: args.color,
|
||
}),
|
||
...(!("list" in args)
|
||
? [
|
||
{
|
||
name: "pnl",
|
||
title: `${args.title} Realized Profit And Loss`,
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}realized-profit`,
|
||
name: "Profit",
|
||
color: colors.green,
|
||
}),
|
||
createBaseSeries({
|
||
key: `cumulative-${fixKey(args.key)}realized-profit`,
|
||
name: "Cumulative Profit",
|
||
color: colors.green,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}realized-loss`,
|
||
name: "Loss",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `cumulative-${fixKey(args.key)}realized-loss`,
|
||
name: "Cumulative Loss",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}negative-realized-loss`,
|
||
name: "Negative Loss",
|
||
color: colors.red,
|
||
}),
|
||
createBaseSeries({
|
||
key: `cumulative-${fixKey(
|
||
args.key
|
||
)}negative-realized-loss`,
|
||
name: "Cumulative Negative Loss",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(
|
||
args.key
|
||
)}realized-profit-relative-to-realized-cap`,
|
||
title: "Profit",
|
||
color: colors.green,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(
|
||
args.key
|
||
)}realized-loss-relative-to-realized-cap`,
|
||
title: "Loss",
|
||
color: colors.red,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Net pnl",
|
||
title: `${args.title} Net Realized Profit And Loss`,
|
||
bottom: list.flatMap(({ color, name, key }) => [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(key)}net-realized-profit-and-loss`,
|
||
title: "Net",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `cumulative-${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss`,
|
||
title: "Cumulative net",
|
||
defaultActive: false,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `cumulative-${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss-30d-change`,
|
||
title: "cum net 30d change",
|
||
defaultActive: false,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss-relative-to-realized-cap`,
|
||
title: "Net",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `cumulative-${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss-30d-change-relative-to-realized-cap`,
|
||
title: "cum net 30d change",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `cumulative-${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss-30d-change-relative-to-market-cap`,
|
||
title: "cum net 30d change",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
]),
|
||
},
|
||
{
|
||
name: "sopr",
|
||
title: `${args.title} Spent Output Profit Ratio`,
|
||
bottom: list.flatMap(({ color, name, key }) => [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(key)}spent-output-profit-ratio`,
|
||
title: "sopr",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 1,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(key)}adjusted-spent-output-profit-ratio`,
|
||
title: "asopr",
|
||
colors: [colors.yellow, colors.pink],
|
||
options: {
|
||
createPriceLine: {
|
||
value: 1,
|
||
},
|
||
},
|
||
}),
|
||
]),
|
||
},
|
||
]
|
||
: [
|
||
{
|
||
name: "profit",
|
||
title: `${args.title} Realized Profit`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}realized-profit`,
|
||
name,
|
||
color,
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${key}realized-profit-relative-to-realized-cap`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "loss",
|
||
title: `${args.title} Realized Loss`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}realized-loss`,
|
||
name,
|
||
color,
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${key}realized-loss-relative-to-realized-cap`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "Net pnl",
|
||
title: `${args.title} Net Realized Profit And Loss`,
|
||
bottom: list.flatMap(({ color, name, key }) => [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(key)}net-realized-profit-and-loss`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss-relative-to-realized-cap`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
]),
|
||
},
|
||
{
|
||
name: "cumulative",
|
||
tree: [
|
||
{
|
||
name: "profit",
|
||
title: `Cumulative ${args.title} Realized Profit`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `cumulative-${key}realized-profit`,
|
||
name,
|
||
color,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "loss",
|
||
title: `Cumulative ${args.title} Realized Loss`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `cumulative-${key}realized-loss`,
|
||
name,
|
||
color,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "Net pnl",
|
||
title: `Cumulative ${args.title} Net Realized Profit And Loss`,
|
||
bottom: list.flatMap(({ color, name, key }) => [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `cumulative-${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss`,
|
||
title: name,
|
||
color,
|
||
defaultActive: false,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
]),
|
||
},
|
||
{
|
||
name: "Net pnl 30d change",
|
||
title: `Cumulative ${args.title} Net Realized Profit And Loss 30 Day Change`,
|
||
bottom: list.flatMap(({ color, name, key }) => [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `cumulative-${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss-30d-change`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `cumulative-${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss-30d-change-relative-to-realized-cap`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `cumulative-${fixKey(
|
||
key
|
||
)}net-realized-profit-and-loss-30d-change-relative-to-market-cap`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
]),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "sopr",
|
||
tree: [
|
||
{
|
||
name: "Normal",
|
||
title: `${args.title} Spent Output Profit Ratio`,
|
||
bottom: list.flatMap(({ color, name, key }) => [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(key)}spent-output-profit-ratio`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 1,
|
||
},
|
||
},
|
||
}),
|
||
]),
|
||
},
|
||
{
|
||
name: "Adjusted",
|
||
title: `${args.title} Adjusted Spent Output Profit Ratio`,
|
||
bottom: list.flatMap(({ color, name, key }) => [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(
|
||
key
|
||
)}adjusted-spent-output-profit-ratio`,
|
||
title: name,
|
||
color,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 1,
|
||
},
|
||
},
|
||
}),
|
||
]),
|
||
},
|
||
],
|
||
},
|
||
]),
|
||
{
|
||
name: "Sell Side Risk Ratio",
|
||
title: `${args.title} Sell Side Risk Ratio`,
|
||
bottom: list.flatMap(({ color, name, key }) =>
|
||
createBaseSeries({
|
||
key: `${fixKey(key)}sell-side-risk-ratio`,
|
||
name: useGroupName ? name : "Risk",
|
||
color: color,
|
||
})
|
||
),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Unrealized",
|
||
tree: [
|
||
...(!("list" in args)
|
||
? [
|
||
{
|
||
name: "pnl",
|
||
title: `${args.title} Unrealized Profit And Loss`,
|
||
bottom: [
|
||
// createBaseSeries({
|
||
// key: `0`,
|
||
// name: "Base",
|
||
// color: colors.gray,
|
||
// options: {
|
||
// lineStyle: 4,
|
||
// },
|
||
// }),
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}unrealized-profit`,
|
||
name: "Profit",
|
||
color: colors.green,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}unrealized-loss`,
|
||
name: "Loss",
|
||
color: colors.red,
|
||
defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}negative-unrealized-loss`,
|
||
name: "Negative Loss",
|
||
color: colors.red,
|
||
}),
|
||
],
|
||
},
|
||
]
|
||
: [
|
||
{
|
||
name: "profit",
|
||
title: `${args.title} Unrealized Profit`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}unrealized-profit`,
|
||
name: useGroupName ? name : "Profit",
|
||
color: useGroupName ? color : colors.green,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "loss",
|
||
title: `${args.title} Unrealized Loss`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}unrealized-loss`,
|
||
name: useGroupName ? name : "Loss",
|
||
color: useGroupName ? color : colors.red,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
]),
|
||
{
|
||
name: "Net pnl",
|
||
title: `${args.title} Net Unrealized Profit And Loss`,
|
||
bottom: list.flatMap(({ color, name, key }) => [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(key)}net-unrealized-profit-and-loss`,
|
||
title: useGroupName ? name : "Net",
|
||
color: useGroupName ? color : undefined,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
type: "Baseline",
|
||
key: `${fixKey(
|
||
key
|
||
)}net-unrealized-profit-and-loss-relative-to-market-cap`,
|
||
title: useGroupName ? name : "Net",
|
||
color: useGroupName ? color : undefined,
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
]),
|
||
},
|
||
],
|
||
},
|
||
...("list" in args
|
||
? [
|
||
{
|
||
name: "Price paid",
|
||
tree: [
|
||
{
|
||
name: "Average",
|
||
title: `${args.title} Average Price Paid`,
|
||
top: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}realized-price`,
|
||
name,
|
||
color: color,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "Min",
|
||
title: `${args.title} Min Price Paid`,
|
||
top: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}min-price-paid`,
|
||
name,
|
||
color: color,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
{
|
||
name: "Max",
|
||
title: `${args.title} Max Price Paid`,
|
||
top: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}max-price-paid`,
|
||
name,
|
||
color: color,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
],
|
||
},
|
||
]
|
||
: [
|
||
{
|
||
name: "Price paid",
|
||
title: `${args.title} Prices Paid`,
|
||
top: [
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}realized-price`,
|
||
name: "Average",
|
||
color: args.color,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}min-price-paid`,
|
||
name: "Min",
|
||
color: colors.green,
|
||
// defaultActive: false,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${fixKey(args.key)}max-price-paid`,
|
||
name: "Max",
|
||
color: colors.red,
|
||
// defaultActive: false,
|
||
}),
|
||
],
|
||
},
|
||
]),
|
||
{
|
||
name: "Coins Destroyed",
|
||
title: `${args.title} Coins Destroyed`,
|
||
bottom: list.flatMap(({ color, name, key: _key }) => {
|
||
const key = fixKey(_key);
|
||
return /** @type {const} */ ([
|
||
createBaseSeries({
|
||
key: `${key}coinblocks-destroyed`,
|
||
name: useGroupName ? name : "destroyed",
|
||
color,
|
||
}),
|
||
createBaseSeries({
|
||
key: `${key}coindays-destroyed`,
|
||
name: useGroupName ? name : "destroyed",
|
||
color,
|
||
}),
|
||
]);
|
||
}),
|
||
},
|
||
],
|
||
});
|
||
}
|
||
|
||
return [
|
||
{
|
||
name: "Charts",
|
||
tree: [
|
||
{
|
||
name: "Price",
|
||
title: "Bitcoin Price",
|
||
},
|
||
{
|
||
name: "Market",
|
||
tree: [
|
||
{
|
||
name: "Capitalization",
|
||
title: "Market Capitalization",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "marketcap",
|
||
name: "Capitalization",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "All Time High",
|
||
title: "All Time High",
|
||
// tree: [
|
||
// {
|
||
// name: "Value",
|
||
top: [
|
||
createBaseSeries({
|
||
key: "ath",
|
||
name: "ath",
|
||
}),
|
||
],
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "drawdown",
|
||
name: "Drawdown",
|
||
color: colors.red,
|
||
}),
|
||
createBaseSeries({
|
||
key: "days-since-ath",
|
||
name: "Days since",
|
||
}),
|
||
createBaseSeries({
|
||
key: "max-days-between-aths",
|
||
name: "Max",
|
||
color: colors.red,
|
||
}),
|
||
createBaseSeries({
|
||
key: "max-years-between-aths",
|
||
name: "Max",
|
||
color: colors.red,
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Average",
|
||
tree: [
|
||
{
|
||
name: "Compare",
|
||
title: "Market Price Moving Averages",
|
||
top: averages.map(({ days, key, name, color }) =>
|
||
createBaseSeries({
|
||
key: `${key}-sma`,
|
||
name: key,
|
||
color,
|
||
})
|
||
),
|
||
},
|
||
...averages.map(({ key, name, color }) =>
|
||
createPriceWithRatio({
|
||
key: `${key}-sma`,
|
||
name,
|
||
title: `${name} Market Price Moving Average`,
|
||
legend: "average",
|
||
color,
|
||
})
|
||
),
|
||
],
|
||
},
|
||
{
|
||
name: "Performance",
|
||
tree: /** @type {const} */ ([
|
||
{ name: "1 Day", key: "1d" },
|
||
{ name: "1 Week", key: "1w" },
|
||
{ name: "1 Month", key: "1m" },
|
||
{ name: "3 Month", key: "3m" },
|
||
{ name: "6 Month", key: "6m" },
|
||
{ name: "1 Year", key: "1y" },
|
||
{ name: "2 Year", key: "2y" },
|
||
{ name: "3 Year", key: "3y" },
|
||
{ name: "4 Year", key: "4y" },
|
||
{ name: "5 Year", key: "5y" },
|
||
{ name: "6 Year", key: "6y" },
|
||
{ name: "8 Year", key: "8y" },
|
||
{ name: "10 Year", key: "10y" },
|
||
]).map(({ name, key }) => ({
|
||
name,
|
||
title: `${name} Performance`,
|
||
bottom: [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-returns`,
|
||
title: "Returns",
|
||
type: "Baseline",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
],
|
||
})),
|
||
},
|
||
{
|
||
name: "Indicators",
|
||
tree: [
|
||
{
|
||
name: "Mayer multiple",
|
||
title: "Mayer multiple",
|
||
top: [
|
||
createBaseSeries({
|
||
key: `200d-sma`,
|
||
name: "200d sma",
|
||
color: colors.yellow,
|
||
}),
|
||
createBaseSeries({
|
||
key: `200d-sma-x2-4`,
|
||
name: "200d sma x2.4",
|
||
color: colors.green,
|
||
}),
|
||
createBaseSeries({
|
||
key: `200d-sma-x0-8`,
|
||
name: "200d sma x0.8",
|
||
color: colors.red,
|
||
}),
|
||
],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Investing",
|
||
tree: [
|
||
{
|
||
name: "DCA vs Lump sum",
|
||
tree: [
|
||
.../** @type {const} */ ([
|
||
{ name: "1 Week", key: "1w" },
|
||
{ name: "1 Month", key: "1m" },
|
||
{ name: "3 Month", key: "3m" },
|
||
{ name: "6 Month", key: "6m" },
|
||
{ name: "1 Year", key: "1y" },
|
||
]).map(
|
||
({ name, key }) =>
|
||
/** @satisfies {PartialChartOption} */ ({
|
||
name,
|
||
title: `${name} DCA vs Lump Sum Returns`,
|
||
top: [
|
||
createBaseSeries({
|
||
key: `${key}-dca-avg-price`,
|
||
name: `dca`,
|
||
color: colors.orange,
|
||
}),
|
||
createBaseSeries({
|
||
key: `price-${key}-ago`,
|
||
name: `lump sum`,
|
||
color: colors.cyan,
|
||
}),
|
||
],
|
||
bottom: [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-dca-returns`,
|
||
title: "dca",
|
||
type: "Baseline",
|
||
colors: [colors.yellow, colors.pink],
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-returns`,
|
||
title: "lump sum",
|
||
type: "Baseline",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
],
|
||
})
|
||
),
|
||
.../** @type {const} */ ([
|
||
{ name: "2 Year", key: "2y" },
|
||
{ name: "3 Year", key: "3y" },
|
||
{ name: "4 Year", key: "4y" },
|
||
{ name: "5 Year", key: "5y" },
|
||
{ name: "6 Year", key: "6y" },
|
||
{ name: "8 Year", key: "8y" },
|
||
{ name: "10 Year", key: "10y" },
|
||
]).map(
|
||
({ name, key }) =>
|
||
/** @satisfies {PartialChartOption} */ ({
|
||
name,
|
||
title: `${name} DCA vs Lump Sum Returns`,
|
||
top: [
|
||
createBaseSeries({
|
||
key: `${key}-dca-avg-price`,
|
||
name: `dca`,
|
||
color: colors.orange,
|
||
}),
|
||
createBaseSeries({
|
||
key: `price-${key}-ago`,
|
||
name: `lump sum price`,
|
||
color: colors.cyan,
|
||
}),
|
||
],
|
||
bottom: [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-dca-returns`,
|
||
title: "dca",
|
||
type: "Baseline",
|
||
colors: [colors.yellow, colors.pink],
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-dca-cagr`,
|
||
title: "dca",
|
||
type: "Baseline",
|
||
colors: [colors.yellow, colors.pink],
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-returns`,
|
||
title: "lump sum",
|
||
type: "Baseline",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `${key}-cagr`,
|
||
title: "lump sum",
|
||
type: "Baseline",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
],
|
||
})
|
||
),
|
||
],
|
||
},
|
||
{
|
||
name: "DCA Class",
|
||
tree: [
|
||
{
|
||
name: "Compare",
|
||
title: "DCA Classes",
|
||
top: dcaClasses.map(({ year, color, defaultActive }) =>
|
||
createBaseSeries({
|
||
key: `dca-class-${year}-avg-price`,
|
||
name: `${year}`,
|
||
color,
|
||
defaultActive,
|
||
})
|
||
),
|
||
},
|
||
...dcaClasses.map(
|
||
({ year, color }) =>
|
||
/** @satisfies {PartialChartOption} */ ({
|
||
name: `${year}`,
|
||
title: `DCA Since ${year}`,
|
||
top: [
|
||
createBaseSeries({
|
||
key: `dca-class-${year}-avg-price`,
|
||
name: `avg. price`,
|
||
color,
|
||
}),
|
||
],
|
||
bottom: [
|
||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||
key: `dca-class-${year}-returns`,
|
||
title: "dca",
|
||
type: "Baseline",
|
||
options: {
|
||
createPriceLine: {
|
||
value: 0,
|
||
},
|
||
},
|
||
}),
|
||
],
|
||
})
|
||
),
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Mining",
|
||
tree: [
|
||
{
|
||
name: "Supply",
|
||
title: "Circulating Supply",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "cumulative-subsidy-in-btc",
|
||
name: "Mined",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Coinbase",
|
||
title: "Coinbase",
|
||
bottom: [
|
||
...createBaseAverageSumCumulativeMinMaxPercentilesSeries({
|
||
key: "coinbase",
|
||
name: "Coinbase",
|
||
}),
|
||
...createBaseAverageSumCumulativeMinMaxPercentilesSeries({
|
||
key: "coinbase-in-btc",
|
||
name: "Coinbase",
|
||
}),
|
||
...createBaseAverageSumCumulativeMinMaxPercentilesSeries({
|
||
key: "coinbase-in-usd",
|
||
name: "Coinbase",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Subsidy",
|
||
title: "Subsidy",
|
||
bottom: [
|
||
...createBaseAverageSumCumulativeMinMaxPercentilesSeries({
|
||
key: "subsidy",
|
||
name: "Subsidy",
|
||
}),
|
||
...createBaseAverageSumCumulativeMinMaxPercentilesSeries({
|
||
key: "subsidy-in-btc",
|
||
name: "Subsidy",
|
||
}),
|
||
...createBaseAverageSumCumulativeMinMaxPercentilesSeries({
|
||
key: "subsidy-in-usd",
|
||
name: "Subsidy",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Fee",
|
||
title: "Transaction Fee",
|
||
bottom: [
|
||
...createAverageSumCumulativeMinMaxPercentilesSeries("fee"),
|
||
...createAverageSumCumulativeMinMaxPercentilesSeries(
|
||
"fee-in-btc"
|
||
),
|
||
...createAverageSumCumulativeMinMaxPercentilesSeries(
|
||
"fee-in-usd"
|
||
),
|
||
],
|
||
},
|
||
{
|
||
name: "Unclaimed Rewards",
|
||
title: "Unclaimed Rewards",
|
||
bottom: [
|
||
...createBaseSumCumulativeSeries({
|
||
key: "unclaimed-rewards",
|
||
name: "unclaimed",
|
||
}),
|
||
...createBaseSumCumulativeSeries({
|
||
key: "unclaimed-rewards-in-btc",
|
||
name: "unclaimed",
|
||
}),
|
||
...createBaseSumCumulativeSeries({
|
||
key: "unclaimed-rewards-in-usd",
|
||
name: "unclaimed",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Feerate",
|
||
title: "Transaction Fee Rate",
|
||
bottom: [
|
||
createAverageSeries({ concat: "feerate" }),
|
||
...createMinMaxPercentilesSeries({
|
||
concat: "feerate",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Difficulty",
|
||
title: "Difficulty",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "difficulty",
|
||
name: "Value",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Difficulty Epoch",
|
||
title: "Difficulty Epoch",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "difficultyepoch",
|
||
name: "Epoch",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Halving Epoch",
|
||
title: "Halving Epoch",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "halvingepoch",
|
||
name: "Epoch",
|
||
}),
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Block",
|
||
tree: [
|
||
{
|
||
name: "Count",
|
||
title: "Block Count",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "block-count",
|
||
name: "Count",
|
||
}),
|
||
...createSumCumulativeSeries({ concat: "block-count" }),
|
||
],
|
||
},
|
||
{
|
||
name: "Interval",
|
||
title: "Block Interval",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "interval",
|
||
name: "Interval",
|
||
}),
|
||
createAverageSeries({ concat: "block-interval" }),
|
||
...createMinMaxPercentilesSeries({
|
||
concat: "block-interval",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Size",
|
||
title: "Block Size",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "total-size",
|
||
name: "Size",
|
||
}),
|
||
...createSumCumulativeSeries({ concat: "block-size" }),
|
||
],
|
||
},
|
||
{
|
||
name: "Weight",
|
||
title: "Block Weight",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "weight",
|
||
name: "Weight",
|
||
}),
|
||
...createSumCumulativeSeries({ concat: "block-weight" }),
|
||
],
|
||
},
|
||
{
|
||
name: "Vbytes",
|
||
title: "Block Virtual Bytes",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "vbytes",
|
||
name: "Vbytes",
|
||
}),
|
||
...createSumCumulativeSeries({ concat: "block-vbytes" }),
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Transaction",
|
||
tree: [
|
||
{
|
||
name: "Count",
|
||
title: "Transaction Count",
|
||
bottom: createBaseAverageSumCumulativeMinMaxPercentilesSeries({
|
||
key: "tx-count",
|
||
name: "Count",
|
||
}),
|
||
},
|
||
{
|
||
name: "Weight",
|
||
title: "Transaction Weight",
|
||
bottom: [
|
||
createAverageSeries({ concat: "tx-weight" }),
|
||
...createMinMaxPercentilesSeries({
|
||
concat: "tx-weight",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "vsize",
|
||
title: "Transaction Virtual Size",
|
||
bottom: [
|
||
createAverageSeries({ concat: "tx-vsize" }),
|
||
...createMinMaxPercentilesSeries({
|
||
concat: "tx-vsize",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "Versions",
|
||
title: "Transaction Versions",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "tx-v1",
|
||
name: "v1 Count",
|
||
}),
|
||
...createSumCumulativeSeries({ concat: "tx-v1", name: "v1" }),
|
||
createBaseSeries({
|
||
key: "tx-v2",
|
||
name: "v2 Count",
|
||
}),
|
||
...createSumCumulativeSeries({ concat: "tx-v2", name: "v2" }),
|
||
createBaseSeries({
|
||
key: "tx-v3",
|
||
name: "v3 Count",
|
||
}),
|
||
...createSumCumulativeSeries({ concat: "tx-v3", name: "v3" }),
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Input",
|
||
tree: [
|
||
{
|
||
name: "Count",
|
||
title: "Transaction Input Count",
|
||
bottom: [
|
||
createAverageSeries({ concat: "input-count" }),
|
||
...createSumCumulativeSeries({ concat: "input-count" }),
|
||
...createMinMaxPercentilesSeries({
|
||
concat: "input-count",
|
||
}),
|
||
],
|
||
},
|
||
// {
|
||
// name: "Value",
|
||
// title: "Transaction Input Value",
|
||
// bottom: [
|
||
// createAverageSeries({ concat: "input-value" }),
|
||
// ...createSumCumulativeSeries({ concat: "input-value" }),
|
||
// ],
|
||
// },
|
||
],
|
||
},
|
||
{
|
||
name: "Output",
|
||
tree: [
|
||
{
|
||
name: "Count",
|
||
title: "Transaction Output Count",
|
||
bottom: [
|
||
createAverageSeries({ concat: "output-count" }),
|
||
...createSumCumulativeSeries({ concat: "output-count" }),
|
||
...createMinMaxPercentilesSeries({
|
||
concat: "output-count",
|
||
}),
|
||
],
|
||
},
|
||
// {
|
||
// name: "Unspent Count",
|
||
// title: "Unspent Transaction Output Count",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "exact-utxo-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "Value",
|
||
// title: "Transaction Output Value",
|
||
// bottom: [
|
||
// createAverageSeries({ concat: "output-value" }),
|
||
// ...createSumCumulativeSeries({ concat: "output-value" }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "types",
|
||
// tree: [
|
||
// {
|
||
// name: "p2pk",
|
||
// title: "Pay To Public Key Outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "p2pk33-count",
|
||
// name: "33B Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2pk33-count-sum",
|
||
// name: "33B sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2pk33-count",
|
||
// name: "33B cumulative",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2pk65-count",
|
||
// name: "65B Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2pk65-count-sum",
|
||
// name: "65B sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2pk65-count",
|
||
// name: "65B cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "p2pkh",
|
||
// title: "Pay To Public Key Hash Outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "p2pkh-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2pkh-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2pkh-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "p2ms",
|
||
// title: "Pay To Multisig Outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "p2ms-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2ms-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2ms-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "p2sh",
|
||
// title: "Pay To Script Hash Outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "p2sh-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2sh-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2sh-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "op_return",
|
||
// title: "op_return outputs",
|
||
// bottom: [
|
||
// createBaseSeries({ key: "opreturn-count", name: "Count" }),
|
||
// createBaseSeries({
|
||
// key: "opreturn-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-opreturn-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "p2wpkh",
|
||
// title: "Pay To Witness Public Key Hash Outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "p2wpkh-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2wpkh-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2wpkh-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "p2wsh",
|
||
// title: "Pay To Witness Script Hash Outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "p2wsh-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2wsh-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2wsh-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "p2tr",
|
||
// title: "Pay To Taproot Outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "p2tr-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2tr-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2tr-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "p2a",
|
||
// title: "Pay To Anchor outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "p2a-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "p2a-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-p2a-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "empty",
|
||
// title: "empty outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "emptyoutput-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "emptyoutput-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-emptyoutput-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// {
|
||
// name: "unknown",
|
||
// title: "unknown outputs",
|
||
// bottom: [
|
||
// createBaseSeries({
|
||
// key: "unknownoutput-count",
|
||
// name: "Count",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "unknownoutput-count-sum",
|
||
// name: "sum",
|
||
// }),
|
||
// createBaseSeries({
|
||
// key: "cumulative-unknownoutput-count",
|
||
// name: "cumulative",
|
||
// }),
|
||
// ],
|
||
// },
|
||
// ],
|
||
// // title: "Transaction Output Value",
|
||
// // bottom: [
|
||
// // createAverageSeries({ concat: "output-value" }),
|
||
// // ...createSumCumulativeSeries({ concat: "output-value" }),
|
||
// // ],
|
||
// },
|
||
],
|
||
},
|
||
{
|
||
name: "UTXOs",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
key: "",
|
||
name: "",
|
||
title: "",
|
||
color: colors.orange,
|
||
}),
|
||
{
|
||
name: "term",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By Term",
|
||
list: terms,
|
||
}),
|
||
...terms.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
{
|
||
name: "Up to date",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By Up To",
|
||
list: upTo,
|
||
}),
|
||
...upTo.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
{
|
||
name: "From Date",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By From",
|
||
list: from,
|
||
}),
|
||
...from.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
{
|
||
name: "Date Range",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By Range",
|
||
list: range,
|
||
}),
|
||
...range.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
{
|
||
name: "Epoch",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By Epoch",
|
||
list: epoch,
|
||
}),
|
||
...epoch.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
{
|
||
name: "Up to size",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By Up To Size",
|
||
list: upToSize,
|
||
}),
|
||
...upToSize.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
{
|
||
name: "From size",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By From Size",
|
||
list: fromSize,
|
||
}),
|
||
...fromSize.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
{
|
||
name: "Size range",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By Size Range",
|
||
list: sizeRanges,
|
||
}),
|
||
...sizeRanges.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
{
|
||
name: "type",
|
||
tree: [
|
||
createUTXOGroupFolder({
|
||
name: "Compare",
|
||
title: "Compare By Type",
|
||
list: type,
|
||
}),
|
||
...type.map(createUTXOGroupFolder),
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Unspendable",
|
||
tree: [
|
||
{
|
||
name: "supply",
|
||
title: "Unspendable Supply",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "unspendable-supply",
|
||
name: "Supply",
|
||
}),
|
||
createBaseSeries({
|
||
key: "unspendable-supply-in-btc",
|
||
name: "Supply",
|
||
}),
|
||
createBaseSeries({
|
||
key: "unspendable-supply-in-usd",
|
||
name: "Supply",
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "op_return",
|
||
tree: [
|
||
{
|
||
name: "outputs",
|
||
title: "op_return outputs",
|
||
bottom: [
|
||
createBaseSeries({ key: "opreturn-count", name: "Count" }),
|
||
createBaseSeries({
|
||
key: "opreturn-count",
|
||
name: "sum",
|
||
}),
|
||
createBaseSeries({
|
||
key: "cumulative-opreturn-count",
|
||
name: "cumulative",
|
||
color: colors.red,
|
||
}),
|
||
],
|
||
},
|
||
{
|
||
name: "supply",
|
||
title: "OP-return Supply",
|
||
bottom: [
|
||
createBaseSeries({
|
||
key: "opreturn-supply",
|
||
name: "Supply",
|
||
}),
|
||
createBaseSeries({
|
||
key: "opreturn-supply-in-btc",
|
||
name: "Supply",
|
||
}),
|
||
createBaseSeries({
|
||
key: "opreturn-supply-in-usd",
|
||
name: "Supply",
|
||
}),
|
||
],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
kind: "table",
|
||
title: "Table",
|
||
name: "Table",
|
||
},
|
||
{
|
||
name: "Simulations",
|
||
tree: [
|
||
{
|
||
kind: "simulation",
|
||
title: "Save In Bitcoin",
|
||
name: "Save In Bitcoin",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Social",
|
||
tree: [
|
||
{
|
||
name: "Nostr",
|
||
url: () =>
|
||
"https://primal.net/p/npub1jagmm3x39lmwfnrtvxcs9ac7g300y3dusv9lgzhk2e4x5frpxlrqa73v44",
|
||
},
|
||
{
|
||
name: "Discord",
|
||
url: () => "https://discord.com/invite/HaR3wpH3nr",
|
||
},
|
||
{
|
||
name: "Bluesky",
|
||
url: () => "https://bsky.app/profile/bitcoinresearchkit.org",
|
||
},
|
||
{
|
||
name: "x",
|
||
url: () => "https://x.com/brkdotorg",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Hosting",
|
||
tree: [
|
||
{
|
||
name: "Self",
|
||
url: () => "https://crates.io/crates/brk_cli",
|
||
},
|
||
{
|
||
name: "As a service",
|
||
url: () =>
|
||
"https://github.com/bitcoinresearchkit/brk?tab=readme-ov-file#hosting-as-a-service",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Developers",
|
||
tree: [
|
||
{
|
||
name: "API",
|
||
url: () => "/api",
|
||
},
|
||
{
|
||
name: "Source",
|
||
url: () => "https://github.com/bitcoinresearchkit/brk",
|
||
},
|
||
{
|
||
name: "Status",
|
||
url: () => "https://status.bitcoinresearchkit.org/",
|
||
},
|
||
{
|
||
name: "Crates",
|
||
url: () => "https://crates.io/crates/brk",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Donate",
|
||
tree: [
|
||
{
|
||
name: "Bitcoin",
|
||
qrcode: true,
|
||
url: () => "bitcoin:bc1q098zsm89m7kgyze338vfejhpdt92ua9p3peuve",
|
||
},
|
||
{
|
||
name: "Lightning",
|
||
qrcode: true,
|
||
url: () =>
|
||
"lightning:lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhkxmmww3jkuar8d35kgetj8yuq363hv4",
|
||
},
|
||
{
|
||
name: "Geyser",
|
||
url: () => "https://geyser.fund/project/brk",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "Share",
|
||
qrcode: true,
|
||
url: () => window.location.href,
|
||
},
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param {Object} args
|
||
* @param {Colors} args.colors
|
||
* @param {Signals} args.signals
|
||
* @param {Env} args.env
|
||
* @param {Utilities} args.utils
|
||
* @param {Signal<string | null>} args.qrcode
|
||
*/
|
||
export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||
const LS_SELECTED_KEY = `selected-id`;
|
||
|
||
const urlSelected = utils.url.pathnameToSelectedId();
|
||
const savedSelectedId = utils.storage.read(LS_SELECTED_KEY);
|
||
|
||
/** @type {Signal<Option>} */
|
||
const selected = signals.createSignal(/** @type {any} */ (undefined));
|
||
|
||
const partialOptions = createPartialOptions(colors);
|
||
|
||
/** @type {Option[]} */
|
||
const list = [];
|
||
|
||
/** @type {HTMLDetailsElement[]} */
|
||
const detailsList = [];
|
||
|
||
const treeElement = signals.createSignal(
|
||
/** @type {HTMLDivElement | null} */ (null)
|
||
);
|
||
|
||
/** @type {string[] | undefined} */
|
||
const optionsIds = env.localhost ? [] : undefined;
|
||
|
||
/**
|
||
* @param {AnyFetchedSeriesBlueprint[]} [arr]
|
||
* @param {string} id
|
||
*/
|
||
function arrayToRecord(id, arr = []) {
|
||
return (arr || []).reduce((record, blueprint) => {
|
||
const unit = utils.vecidToUnit(blueprint.key);
|
||
record[unit] ??= [];
|
||
record[unit].push(blueprint);
|
||
return record;
|
||
}, /** @type {Record<Unit, AnyFetchedSeriesBlueprint[]>} */ ({}));
|
||
}
|
||
|
||
/**
|
||
* @param {Object} args
|
||
* @param {Option} args.option
|
||
* @param {string} args.frame
|
||
* @param {Signal<string | null>} args.qrcode
|
||
* @param {string} [args.name]
|
||
* @param {string} [args.id]
|
||
*/
|
||
function createOptionElement({ option, frame, name, id, qrcode }) {
|
||
if (option.kind === "url") {
|
||
const href = option.url();
|
||
|
||
if (option.qrcode) {
|
||
return utils.dom.createButtonElement({
|
||
inside: option.name,
|
||
title: option.title,
|
||
onClick: () => {
|
||
qrcode.set(option.url);
|
||
},
|
||
});
|
||
} else {
|
||
return utils.dom.createAnchorElement({
|
||
href,
|
||
blank: true,
|
||
text: option.name,
|
||
});
|
||
}
|
||
} else {
|
||
const { input, label } = utils.dom.createLabeledInput({
|
||
inputId: `${option.id}-${frame}${id || ""}-selector`,
|
||
inputValue: option.id,
|
||
inputName: `option-${frame}${id || ""}`,
|
||
labelTitle: option.title,
|
||
onClick: () => {
|
||
selected.set(option);
|
||
},
|
||
type: "radio",
|
||
});
|
||
|
||
const anchor = utils.dom.createAnchorElement({
|
||
href: `/${option.id}`,
|
||
text: name || option.name,
|
||
onClick: () => {},
|
||
});
|
||
|
||
label.append(anchor);
|
||
|
||
function createCheckEffect() {
|
||
signals.createEffect(selected, (selected) => {
|
||
if (selected?.id === option.id) {
|
||
input.checked = true;
|
||
utils.storage.write(LS_SELECTED_KEY, option.id);
|
||
} else if (input.checked) {
|
||
input.checked = false;
|
||
}
|
||
});
|
||
}
|
||
|
||
createCheckEffect();
|
||
|
||
return label;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {PartialOptionsTree} partialTree
|
||
* @param {Accessor<HTMLDivElement | HTMLDetailsElement | null>} parent
|
||
* @param {string[] | undefined} path
|
||
* @returns {Accessor<number>}
|
||
*/
|
||
function recursiveProcessPartialTree(partialTree, parent, path = undefined) {
|
||
/** @type {Accessor<number>[]} */
|
||
const listForSum = [];
|
||
|
||
const ul = signals.createMemo(
|
||
// @ts-ignore
|
||
(_previous) => {
|
||
const previous = /** @type {HTMLUListElement | null} */ (_previous);
|
||
previous?.remove();
|
||
|
||
const _parent = parent();
|
||
if (_parent) {
|
||
if ("open" in _parent && !_parent.open) {
|
||
throw "Set accesor to null instead";
|
||
}
|
||
|
||
const ul = window.document.createElement("ul");
|
||
_parent.append(ul);
|
||
return ul;
|
||
} else {
|
||
return null;
|
||
}
|
||
},
|
||
null
|
||
);
|
||
|
||
partialTree.forEach((anyPartial, partialIndex) => {
|
||
const renderLi = signals.createSignal(true);
|
||
|
||
const li = signals.createMemo((_previous) => {
|
||
const previous = _previous;
|
||
previous?.remove();
|
||
|
||
const _ul = ul();
|
||
|
||
if (renderLi() && _ul) {
|
||
const li = window.document.createElement("li");
|
||
utils.dom.insertElementAtIndex(_ul, li, partialIndex);
|
||
return li;
|
||
} else {
|
||
return null;
|
||
}
|
||
}, /** @type {HTMLLIElement | null} */ (null));
|
||
|
||
if ("tree" in anyPartial) {
|
||
const folderId = utils.stringToId(
|
||
`${(path || []).join(" ")} ${anyPartial.name} folder`
|
||
);
|
||
|
||
/** @type {Omit<OptionsGroup, keyof PartialOptionsGroup>} */
|
||
const groupAddons = {
|
||
id: folderId,
|
||
};
|
||
|
||
Object.assign(anyPartial, groupAddons);
|
||
|
||
optionsIds?.push(groupAddons.id);
|
||
|
||
const thisPath = groupAddons.id;
|
||
|
||
const passedDetails = signals.createSignal(
|
||
/** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null)
|
||
);
|
||
|
||
const childOptionsCount = recursiveProcessPartialTree(
|
||
anyPartial.tree,
|
||
passedDetails,
|
||
[...(path || []), thisPath]
|
||
);
|
||
|
||
listForSum.push(childOptionsCount);
|
||
|
||
signals.createEffect(li, (li) => {
|
||
if (!li) {
|
||
passedDetails.set(null);
|
||
return;
|
||
}
|
||
|
||
signals.createEffect(selected, (selected) => {
|
||
if (selected.path.includes(thisPath)) {
|
||
li.dataset.highlight = "";
|
||
} else {
|
||
delete li.dataset.highlight;
|
||
}
|
||
});
|
||
|
||
const details = window.document.createElement("details");
|
||
details.id = folderId;
|
||
detailsList.push(details);
|
||
li.appendChild(details);
|
||
|
||
const summary = window.document.createElement("summary");
|
||
details.append(summary);
|
||
summary.append(anyPartial.name);
|
||
|
||
const supCount = window.document.createElement("sup");
|
||
summary.append(supCount);
|
||
|
||
signals.createEffect(childOptionsCount, (childOptionsCount) => {
|
||
supCount.innerHTML = childOptionsCount.toLocaleString("en-us");
|
||
});
|
||
|
||
details.addEventListener("toggle", () => {
|
||
const open = details.open;
|
||
|
||
if (open) {
|
||
passedDetails.set(details);
|
||
} else {
|
||
passedDetails.set(null);
|
||
}
|
||
});
|
||
});
|
||
|
||
function createRenderLiEffect() {
|
||
signals.createEffect(childOptionsCount, (count) => {
|
||
renderLi.set(!!count);
|
||
});
|
||
}
|
||
createRenderLiEffect();
|
||
} else {
|
||
/** @type {Option} */
|
||
let option;
|
||
|
||
if ("kind" in anyPartial && anyPartial.kind === "table") {
|
||
option = /** @satisfies {TableOption} */ ({
|
||
kind: anyPartial.kind,
|
||
id: anyPartial.kind,
|
||
name: anyPartial.name,
|
||
path: path || [],
|
||
title: anyPartial.title,
|
||
});
|
||
} else if ("kind" in anyPartial && anyPartial.kind === "simulation") {
|
||
option = /** @satisfies {SimulationOption} */ ({
|
||
kind: anyPartial.kind,
|
||
id: anyPartial.kind,
|
||
name: anyPartial.name,
|
||
path: path || [],
|
||
title: anyPartial.title,
|
||
});
|
||
} else if ("url" in anyPartial) {
|
||
option = /** @satisfies {UrlOption} */ ({
|
||
kind: "url",
|
||
id: `${utils.stringToId(anyPartial.name)}-url`,
|
||
name: anyPartial.name,
|
||
path: path || [],
|
||
title: anyPartial.name,
|
||
qrcode: !!anyPartial.qrcode,
|
||
url: anyPartial.url,
|
||
});
|
||
} else {
|
||
const title = anyPartial.title || anyPartial.name;
|
||
const id = `chart-${utils.stringToId(title)}`;
|
||
option = /** @satisfies {ChartOption} */ ({
|
||
kind: "chart",
|
||
id,
|
||
name: anyPartial.name,
|
||
title,
|
||
path: path || [],
|
||
top: arrayToRecord(id, anyPartial.top),
|
||
bottom: arrayToRecord(id, anyPartial.bottom),
|
||
});
|
||
}
|
||
|
||
if (urlSelected === option.id) {
|
||
selected.set(option);
|
||
} else if (!selected() && savedSelectedId === option.id) {
|
||
selected.set(option);
|
||
}
|
||
|
||
list.push(option);
|
||
optionsIds?.push(option.id);
|
||
|
||
signals.createEffect(li, (li) => {
|
||
if (!li) {
|
||
return;
|
||
}
|
||
|
||
signals.createEffect(selected, (selected) => {
|
||
if (selected === option) {
|
||
li.dataset.highlight = "";
|
||
} else {
|
||
delete li.dataset.highlight;
|
||
}
|
||
});
|
||
|
||
const element = createOptionElement({
|
||
option,
|
||
frame: "nav",
|
||
qrcode,
|
||
});
|
||
|
||
li.append(element);
|
||
});
|
||
|
||
listForSum.push(() => 1);
|
||
}
|
||
});
|
||
|
||
return signals.createMemo(() =>
|
||
listForSum.reduce((acc, s) => acc + s(), 0)
|
||
);
|
||
}
|
||
recursiveProcessPartialTree(partialOptions, treeElement);
|
||
|
||
function setDefaultSelectedIfNeeded() {
|
||
if (!selected()) {
|
||
selected.set(list[0]);
|
||
}
|
||
}
|
||
setDefaultSelectedIfNeeded();
|
||
|
||
if (env.localhost) {
|
||
function checkUniqueIds() {
|
||
if (!optionsIds) {
|
||
throw "Should be set";
|
||
} else if (optionsIds.length !== new Set(optionsIds).size) {
|
||
/** @type {Map<string, number>} */
|
||
const m = new Map();
|
||
|
||
optionsIds.forEach((id) => {
|
||
m.set(id, (m.get(id) || 0) + 1);
|
||
});
|
||
|
||
console.log(
|
||
[...m.entries()]
|
||
.filter(([_, value]) => value > 1)
|
||
.map(([key, _]) => key)
|
||
);
|
||
|
||
throw Error("ID duplicate");
|
||
}
|
||
}
|
||
checkUniqueIds();
|
||
}
|
||
|
||
return {
|
||
selected,
|
||
list,
|
||
details: detailsList,
|
||
tree: /** @type {OptionsTree} */ (partialOptions),
|
||
treeElement,
|
||
createOptionElement,
|
||
};
|
||
}
|
||
/** @typedef {ReturnType<typeof initOptions>} Options */
|
||
|
||
// const size = /** @type {const} */ ([
|
||
// {
|
||
// key: "plankton",
|
||
// name: "Plankton",
|
||
// size: "1 sat to 0.1 BTC",
|
||
// },
|
||
// {
|
||
// key: "shrimp",
|
||
// name: "Shrimp",
|
||
// size: "0.1 sat to 1 BTC",
|
||
// },
|
||
// { key: "crab", name: "Crab", size: "1 BTC to 10 BTC" },
|
||
// { key: "fish", name: "Fish", size: "10 BTC to 100 BTC" },
|
||
// { key: "shark", name: "Shark", size: "100 BTC to 1000 BTC" },
|
||
// { key: "whale", name: "Whale", size: "1000 BTC to 10 000 BTC" },
|
||
// {
|
||
// key: "humpback",
|
||
// name: "Humpback",
|
||
// size: "10 000 BTC to 100 000 BTC",
|
||
// },
|
||
// {
|
||
// key: "megalodon",
|
||
// name: "Megalodon",
|
||
// size: "More than 100 000 BTC",
|
||
// },
|
||
// ]);
|
||
|
||
// const type = /** @type {const} */ ([
|
||
// { key: "p2pk", name: "P2PK" },
|
||
// { key: "p2pkh", name: "P2PKH" },
|
||
// { key: "p2sh", name: "P2SH" },
|
||
// { key: "p2wpkh", name: "P2WPKH" },
|
||
// { key: "p2wsh", name: "P2WSH" },
|
||
// { key: "p2tr", name: "P2TR" },
|
||
// ]);
|
||
|
||
// const address = /** @type {const} */ ([...size, ...type]);
|
||
|
||
// const liquidities = /** @type {const} */ ([
|
||
// {
|
||
// key: "illiquid",
|
||
// id: "illiquid",
|
||
// name: "Illiquid",
|
||
// },
|
||
// { key: "liquid", id: "liquid", name: "Liquid" },
|
||
// {
|
||
// key: "highly_liquid",
|
||
// id: "highly-liquid",
|
||
// name: "Highly Liquid",
|
||
// },
|
||
// ]);
|