mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-28 08:39:59 -07:00
global: BIG snapshot
This commit is contained in:
@@ -48,10 +48,16 @@ export function initOptions({ colors, signals, brk, qrcode }) {
|
||||
* @param {AnyFetchedSeriesBlueprint[]} [arr]
|
||||
*/
|
||||
function arrayToRecord(arr = []) {
|
||||
return (arr || []).reduce((record, blueprint) => {
|
||||
return [...(arr || [])].reduce((record, blueprint) => {
|
||||
if (!blueprint.metric) {
|
||||
throw new Error(
|
||||
`Blueprint missing metric: ${JSON.stringify(blueprint)}`,
|
||||
);
|
||||
}
|
||||
markUsed(blueprint.metric);
|
||||
// Use any index's path - unit is the same regardless of index (e.g., supply is "sats" for both height and dateindex)
|
||||
const unit = blueprint.unit ?? serdeUnit.deserialize(blueprint.metric.name);
|
||||
const unit =
|
||||
blueprint.unit ?? serdeUnit.deserialize(blueprint.metric.name);
|
||||
record[unit] ??= [];
|
||||
record[unit].push(blueprint);
|
||||
return record;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,8 @@ export function createSingleSupplySeries(ctx, cohort, title) {
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyTotalSeries(ctx, list) {
|
||||
const { s, constant100 } = ctx;
|
||||
const { s, brk } = ctx;
|
||||
const constant100 = brk.tree.computed.constants.constant100;
|
||||
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
s({ metric: tree.supply.supply.sats, name, color }),
|
||||
@@ -206,11 +207,11 @@ export function createCostBasisPercentilesSeries(ctx, list, useGroupName) {
|
||||
return list.flatMap(({ color, name, tree }) => {
|
||||
const percentiles = tree.costBasis.percentiles;
|
||||
return [
|
||||
s({ metric: percentiles.pct10, name: useGroupName ? `${name} p10` : "p10", color, defaultActive: false }),
|
||||
s({ metric: percentiles.pct25, name: useGroupName ? `${name} p25` : "p25", color, defaultActive: false }),
|
||||
s({ metric: percentiles.pct50, name: useGroupName ? `${name} p50` : "p50", color }),
|
||||
s({ metric: percentiles.pct75, name: useGroupName ? `${name} p75` : "p75", color, defaultActive: false }),
|
||||
s({ metric: percentiles.pct90, name: useGroupName ? `${name} p90` : "p90", color, defaultActive: false }),
|
||||
s({ metric: percentiles.costBasisPct10, name: useGroupName ? `${name} p10` : "p10", color, defaultActive: false }),
|
||||
s({ metric: percentiles.costBasisPct25, name: useGroupName ? `${name} p25` : "p25", color, defaultActive: false }),
|
||||
s({ metric: percentiles.costBasisPct50, name: useGroupName ? `${name} p50` : "p50", color }),
|
||||
s({ metric: percentiles.costBasisPct75, name: useGroupName ? `${name} p75` : "p75", color, defaultActive: false }),
|
||||
s({ metric: percentiles.costBasisPct90, name: useGroupName ? `${name} p90` : "p90", color, defaultActive: false }),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ import {
|
||||
createUtxoCountSeries,
|
||||
createRealizedPriceSeries,
|
||||
createRealizedPriceRatioSeries,
|
||||
createRealizedCapSeries,
|
||||
createCostBasisMinMaxSeries,
|
||||
createCostBasisPercentilesSeries,
|
||||
} from "./shared.js";
|
||||
|
||||
@@ -97,7 +95,12 @@ export function createUtxoCohortFolder(ctx, args) {
|
||||
createRealizedSection(ctx, list, args, useGroupName, isSingle, title),
|
||||
...createUnrealizedSection(ctx, list, useGroupName, title),
|
||||
...(hasPercentiles
|
||||
? createCostBasisSectionWithPercentiles(ctx, /** @type {readonly AgeCohortObject[]} */ (list), useGroupName, title)
|
||||
? createCostBasisSectionWithPercentiles(
|
||||
ctx,
|
||||
/** @type {readonly AgeCohortObject[]} */ (list),
|
||||
useGroupName,
|
||||
title,
|
||||
)
|
||||
: createCostBasisSectionBasic(ctx, list, useGroupName, title)),
|
||||
...createActivitySection(ctx, list, useGroupName, title),
|
||||
],
|
||||
@@ -120,7 +123,11 @@ function createSupplySection(ctx, list, args, useGroupName, isSingle, title) {
|
||||
? {
|
||||
name: "supply",
|
||||
title: `Supply ${title}`,
|
||||
bottom: createSingleSupplySeries(ctx, /** @type {UtxoCohortObject} */ (args), title),
|
||||
bottom: createSingleSupplySeries(
|
||||
ctx,
|
||||
/** @type {UtxoCohortObject} */ (args),
|
||||
title,
|
||||
),
|
||||
}
|
||||
: {
|
||||
name: "supply",
|
||||
@@ -188,13 +195,29 @@ function createRealizedSection(ctx, list, args, useGroupName, isSingle, title) {
|
||||
bottom: createRealizedPriceRatioSeries(ctx, list),
|
||||
},
|
||||
]
|
||||
: createRealizedPriceOptions(ctx, /** @type {UtxoCohortObject} */ (args), title)),
|
||||
: createRealizedPriceOptions(
|
||||
ctx,
|
||||
/** @type {UtxoCohortObject} */ (args),
|
||||
title,
|
||||
)),
|
||||
{
|
||||
name: "capitalization",
|
||||
title: `Realized Capitalization ${title}`,
|
||||
bottom: createRealizedCapWithExtras(ctx, list, args, useGroupName, title),
|
||||
bottom: createRealizedCapWithExtras(
|
||||
ctx,
|
||||
list,
|
||||
args,
|
||||
useGroupName,
|
||||
title,
|
||||
),
|
||||
},
|
||||
...(!useGroupName ? createRealizedPnlSection(ctx, /** @type {UtxoCohortObject} */ (args), title) : []),
|
||||
...(!useGroupName
|
||||
? createRealizedPnlSection(
|
||||
ctx,
|
||||
/** @type {UtxoCohortObject} */ (args),
|
||||
title,
|
||||
)
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -214,7 +237,9 @@ function createRealizedPriceOptions(ctx, args, title) {
|
||||
{
|
||||
name: "price",
|
||||
title: `Realized Price ${title}`,
|
||||
top: [s({ metric: tree.realized.realizedPrice, name: "realized", color })],
|
||||
top: [
|
||||
s({ metric: tree.realized.realizedPrice, name: "realized", color }),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -390,14 +415,22 @@ function createCostBasisSectionWithPercentiles(ctx, list, useGroupName, title) {
|
||||
name: "min",
|
||||
title: `Min Cost Basis ${title}`,
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
s({ metric: tree.costBasis.minCostBasis, name: useGroupName ? name : "Min", color }),
|
||||
s({
|
||||
metric: tree.costBasis.minCostBasis,
|
||||
name: useGroupName ? name : "Min",
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
title: `Max Cost Basis ${title}`,
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
s({ metric: tree.costBasis.maxCostBasis, name: useGroupName ? name : "Max", color }),
|
||||
s({
|
||||
metric: tree.costBasis.maxCostBasis,
|
||||
name: useGroupName ? name : "Max",
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -429,14 +462,22 @@ function createCostBasisSectionBasic(ctx, list, useGroupName, title) {
|
||||
name: "min",
|
||||
title: `Min Cost Basis ${title}`,
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
s({ metric: tree.costBasis.minCostBasis, name: useGroupName ? name : "Min", color }),
|
||||
s({
|
||||
metric: tree.costBasis.minCostBasis,
|
||||
name: useGroupName ? name : "Min",
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
title: `Max Cost Basis ${title}`,
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
s({ metric: tree.costBasis.maxCostBasis, name: useGroupName ? name : "Max", color }),
|
||||
s({
|
||||
metric: tree.costBasis.maxCostBasis,
|
||||
name: useGroupName ? name : "Max",
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @param {string} args.name
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {MetricAccessor<any>} args.price
|
||||
* @param {AnyMetricPattern} args.price
|
||||
* @param {ActivePriceRatioPattern} args.ratio
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
|
||||
@@ -5,45 +5,20 @@
|
||||
* Examples: 0 → constant0, 38.2 → constant382, -1 → constantMinus1
|
||||
* @param {BrkClient["tree"]["computed"]["constants"]} constants
|
||||
* @param {number} num
|
||||
* @returns {Constant0Pattern<any>}
|
||||
* @returns {AnyMetricPattern}
|
||||
*/
|
||||
export function getConstant(constants, num) {
|
||||
const key =
|
||||
num >= 0
|
||||
? `constant${String(num).replace(".", "")}`
|
||||
: `constantMinus${Math.abs(num)}`;
|
||||
const constant = /** @type {Constant0Pattern<any> | undefined} */ (
|
||||
/** @type {Record<string, Constant0Pattern<any>>} */ (constants)[key]
|
||||
const constant = /** @type {AnyMetricPattern | undefined} */ (
|
||||
/** @type {Record<string, AnyMetricPattern>} */ (constants)[key]
|
||||
);
|
||||
if (!constant) throw new Error(`Unknown constant: ${num} (key: ${key})`);
|
||||
return constant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten a Constant0Pattern into a simple MetricAccessor
|
||||
* Constant0Pattern has { dateindex: { by: {...} }, height: { by: {...} }, ... }
|
||||
* This flattens it to { by: { dateindex: MetricNode, height: MetricNode, ... } }
|
||||
* @param {Constant0Pattern<any>} pattern
|
||||
* @returns {MetricAccessor<any>}
|
||||
*/
|
||||
export function flattenConstant(pattern) {
|
||||
return {
|
||||
by: {
|
||||
dateindex: pattern.dateindex.by.dateindex,
|
||||
decadeindex: pattern.decadeindex.by.decadeindex,
|
||||
height: pattern.height.by.height,
|
||||
monthindex: pattern.monthindex.by.monthindex,
|
||||
quarterindex: pattern.quarterindex.by.quarterindex,
|
||||
semesterindex: pattern.semesterindex.by.semesterindex,
|
||||
weekindex: pattern.weekindex.by.weekindex,
|
||||
yearindex: pattern.yearindex.by.yearindex,
|
||||
},
|
||||
indexes() {
|
||||
return /** @type {Index[]} */ (Object.keys(this.by));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a price line series (horizontal reference line)
|
||||
* @param {Object} args
|
||||
@@ -68,7 +43,7 @@ export function createPriceLine({
|
||||
lineStyle,
|
||||
}) {
|
||||
return {
|
||||
metric: flattenConstant(getConstant(constants, number)),
|
||||
metric: getConstant(constants, number),
|
||||
title: name ?? `${number}`,
|
||||
unit,
|
||||
defaultActive,
|
||||
@@ -92,7 +67,7 @@ export function createPriceLine({
|
||||
*/
|
||||
export function createPriceLines({ constants, colors, numbers, unit }) {
|
||||
return numbers.map((number) => ({
|
||||
metric: flattenConstant(getConstant(constants, number)),
|
||||
metric: getConstant(constants, number),
|
||||
title: `${number}`,
|
||||
unit,
|
||||
defaultActive: !number,
|
||||
@@ -109,7 +84,7 @@ export function createPriceLines({ constants, colors, numbers, unit }) {
|
||||
* Create a constant line series
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {Constant0Pattern<any>} args.constant
|
||||
* @param {AnyMetricPattern} args.constant
|
||||
* @param {string} args.name
|
||||
* @param {Unit} args.unit
|
||||
* @param {Color} [args.color]
|
||||
@@ -127,7 +102,7 @@ export function line({
|
||||
defaultActive,
|
||||
}) {
|
||||
return {
|
||||
metric: flattenConstant(constant),
|
||||
metric: constant,
|
||||
title: name,
|
||||
unit,
|
||||
defaultActive,
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { s, fromBlockCount, fromBitcoin, fromBlockSize } from "./series.js";
|
||||
import {
|
||||
getConstant,
|
||||
flattenConstant,
|
||||
createPriceLine,
|
||||
createPriceLines,
|
||||
line,
|
||||
} from "./constants.js";
|
||||
import { createPriceLine, createPriceLines, line } from "./constants.js";
|
||||
|
||||
/**
|
||||
* Create a context object with all dependencies for building partial options
|
||||
@@ -16,13 +10,10 @@ import {
|
||||
*/
|
||||
export function createContext({ colors, brk }) {
|
||||
const constants = brk.tree.computed.constants;
|
||||
const constant100 = flattenConstant(constants.constant100);
|
||||
|
||||
return {
|
||||
colors,
|
||||
brk,
|
||||
constants,
|
||||
constant100,
|
||||
|
||||
// Series helpers
|
||||
s,
|
||||
@@ -33,9 +24,6 @@ export function createContext({ colors, brk }) {
|
||||
fromBlockSize: (pattern, title, color) =>
|
||||
fromBlockSize(colors, pattern, title, color),
|
||||
|
||||
// Constant helpers
|
||||
getConstant: (num) => getConstant(constants, num),
|
||||
flattenConstant,
|
||||
createPriceLine: (args) => createPriceLine({ constants, colors, ...args }),
|
||||
createPriceLines: (args) =>
|
||||
createPriceLines({ constants, colors, ...args }),
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
createUtxoCohortFolder,
|
||||
createAddressCohortFolder,
|
||||
} from "./cohorts/index.js";
|
||||
import { createMarketSection } from "./market.js";
|
||||
import { createMarketSection } from "./market/index.js";
|
||||
import { createChainSection } from "./chain.js";
|
||||
import { createCointimeSection } from "./cointime.js";
|
||||
|
||||
|
||||
@@ -1,480 +0,0 @@
|
||||
/** Market section builder - typed tree-based patterns */
|
||||
|
||||
/**
|
||||
* Convert period ID to readable name
|
||||
* @param {string} id
|
||||
* @param {boolean} [compoundAdjective]
|
||||
*/
|
||||
function periodIdToName(id, compoundAdjective) {
|
||||
const suffix = compoundAdjective || parseInt(id) === 1 ? "" : "s";
|
||||
return id
|
||||
.replace("d", ` day${suffix}`)
|
||||
.replace("w", ` week${suffix}`)
|
||||
.replace("m", ` month${suffix}`)
|
||||
.replace("y", ` year${suffix}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create price with ratio options (for moving averages)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {string} args.name
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {EmaRatioPattern} args.ratio
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createPriceWithRatioOptions(ctx, { name, title, legend, ratio, color }) {
|
||||
const { s, colors, createPriceLine } = ctx;
|
||||
const priceMetric = ratio.price;
|
||||
|
||||
// Percentile USD mappings
|
||||
const percentileUsdMap = [
|
||||
{ name: "pct99", prop: ratio.ratioPct99Usd, color: colors.rose },
|
||||
{ name: "pct98", prop: ratio.ratioPct98Usd, color: colors.pink },
|
||||
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.fuchsia },
|
||||
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.cyan },
|
||||
{ name: "pct2", prop: ratio.ratioPct2Usd, color: colors.sky },
|
||||
{ name: "pct1", prop: ratio.ratioPct1Usd, color: colors.blue },
|
||||
];
|
||||
|
||||
// Percentile ratio mappings
|
||||
const percentileMap = [
|
||||
{ name: "pct99", prop: ratio.ratioPct99, color: colors.rose },
|
||||
{ name: "pct98", prop: ratio.ratioPct98, color: colors.pink },
|
||||
{ name: "pct95", prop: ratio.ratioPct95, color: colors.fuchsia },
|
||||
{ name: "pct5", prop: ratio.ratioPct5, color: colors.cyan },
|
||||
{ name: "pct2", prop: ratio.ratioPct2, color: colors.sky },
|
||||
{ name: "pct1", prop: ratio.ratioPct1, color: colors.blue },
|
||||
];
|
||||
|
||||
// SD patterns by window
|
||||
const sdPatterns = [
|
||||
{ nameAddon: "all", titleAddon: "", sd: ratio.ratioSd },
|
||||
{ nameAddon: "4y", titleAddon: "4y", sd: ratio.ratio4ySd },
|
||||
{ nameAddon: "2y", titleAddon: "2y", sd: ratio.ratio2ySd },
|
||||
{ nameAddon: "1y", titleAddon: "1y", sd: ratio.ratio1ySd },
|
||||
];
|
||||
|
||||
/** @param {Ratio1ySdPattern} sd */
|
||||
const getSdBands = (sd) => [
|
||||
{ name: "0σ", prop: sd._0sdUsd, color: colors.lime },
|
||||
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.yellow },
|
||||
{ name: "+1σ", prop: sd.p1sdUsd, color: colors.amber },
|
||||
{ name: "+1.5σ", prop: sd.p15sdUsd, color: colors.orange },
|
||||
{ name: "+2σ", prop: sd.p2sdUsd, color: colors.red },
|
||||
{ name: "+2.5σ", prop: sd.p25sdUsd, color: colors.rose },
|
||||
{ name: "+3σ", prop: sd.p3sd, color: colors.pink },
|
||||
{ name: "−0.5σ", prop: sd.m05sdUsd, color: colors.teal },
|
||||
{ name: "−1σ", prop: sd.m1sdUsd, color: colors.cyan },
|
||||
{ name: "−1.5σ", prop: sd.m15sdUsd, color: colors.sky },
|
||||
{ name: "−2σ", prop: sd.m2sdUsd, color: colors.blue },
|
||||
{ name: "−2.5σ", prop: sd.m25sdUsd, color: colors.indigo },
|
||||
{ name: "−3σ", prop: sd.m3sd, color: colors.violet },
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
name: "price",
|
||||
title,
|
||||
top: [s({ metric: priceMetric, name: legend, color, unit: "usd" })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
top: [
|
||||
s({ metric: priceMetric, name: legend, color, unit: "usd" }),
|
||||
...percentileUsdMap.map(({ name: pctName, prop, color: pctColor }) =>
|
||||
s({
|
||||
metric: prop,
|
||||
name: pctName,
|
||||
color: pctColor,
|
||||
defaultActive: false,
|
||||
unit: "usd",
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
),
|
||||
],
|
||||
bottom: [
|
||||
s({ metric: ratio.ratio, name: "ratio", color, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio1wSma, name: "1w sma", color: colors.lime, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio1mSma, name: "1m sma", color: colors.teal, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio1ySd.sma, name: "1y sma", color: colors.sky, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio2ySd.sma, name: "2y sma", color: colors.indigo, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio4ySd.sma, name: "4y sma", color: colors.purple, unit: "ratio" }),
|
||||
s({ metric: ratio.ratioSd.sma, name: "all sma", color: colors.rose, unit: "ratio" }),
|
||||
...percentileMap.map(({ name: pctName, prop, color: pctColor }) =>
|
||||
s({
|
||||
metric: prop,
|
||||
name: pctName,
|
||||
color: pctColor,
|
||||
defaultActive: false,
|
||||
unit: "ratio",
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
),
|
||||
createPriceLine({ unit: "ratio", number: 1 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "ZScores",
|
||||
tree: sdPatterns.map(({ nameAddon, titleAddon, sd }) => ({
|
||||
name: nameAddon,
|
||||
title: `${title} ${titleAddon} Z-Score`,
|
||||
top: getSdBands(sd).map(({ name: bandName, prop, color: bandColor }) =>
|
||||
s({ metric: prop, name: bandName, color: bandColor, unit: "usd" }),
|
||||
),
|
||||
bottom: [
|
||||
s({ metric: sd.zscore, name: "zscore", color, unit: "sd" }),
|
||||
createPriceLine({ unit: "sd", number: 3 }),
|
||||
createPriceLine({ unit: "sd", number: 2 }),
|
||||
createPriceLine({ unit: "sd", number: 1 }),
|
||||
createPriceLine({ unit: "sd", number: 0 }),
|
||||
createPriceLine({ unit: "sd", number: -1 }),
|
||||
createPriceLine({ unit: "sd", number: -2 }),
|
||||
createPriceLine({ unit: "sd", number: -3 }),
|
||||
],
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build averages data array from market patterns
|
||||
* @param {Colors} colors
|
||||
* @param {MarketMovingAverage} ma
|
||||
*/
|
||||
function buildAverages(colors, ma) {
|
||||
return /** @type {const} */ ([
|
||||
["1w", 7, "red", ma.price1wSma, ma.price1wEma],
|
||||
["8d", 8, "orange", ma.price8dSma, ma.price8dEma],
|
||||
["13d", 13, "amber", ma.price13dSma, ma.price13dEma],
|
||||
["21d", 21, "yellow", ma.price21dSma, ma.price21dEma],
|
||||
["1m", 30, "lime", ma.price1mSma, ma.price1mEma],
|
||||
["34d", 34, "green", ma.price34dSma, ma.price34dEma],
|
||||
["55d", 55, "emerald", ma.price55dSma, ma.price55dEma],
|
||||
["89d", 89, "teal", ma.price89dSma, ma.price89dEma],
|
||||
["144d", 144, "cyan", ma.price144dSma, ma.price144dEma],
|
||||
["200d", 200, "sky", ma.price200dSma, ma.price200dEma],
|
||||
["1y", 365, "blue", ma.price1ySma, ma.price1yEma],
|
||||
["2y", 730, "indigo", ma.price2ySma, ma.price2yEma],
|
||||
["200w", 1400, "violet", ma.price200wSma, ma.price200wEma],
|
||||
["4y", 1460, "purple", ma.price4ySma, ma.price4yEma],
|
||||
]).map(([id, days, colorKey, sma, ema]) => ({
|
||||
id,
|
||||
name: periodIdToName(id, true),
|
||||
days,
|
||||
color: colors[colorKey],
|
||||
sma,
|
||||
ema,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build DCA classes data array
|
||||
* @param {Colors} colors
|
||||
* @param {MarketDca} dca
|
||||
*/
|
||||
function buildDcaClasses(colors, dca) {
|
||||
return /** @type {const} */ ([
|
||||
[2015, "pink", false, dca.dcaClass2015AvgPrice, dca.dcaClass2015Returns, dca.dcaClass2015Stack],
|
||||
[2016, "red", false, dca.dcaClass2016AvgPrice, dca.dcaClass2016Returns, dca.dcaClass2016Stack],
|
||||
[2017, "orange", true, dca.dcaClass2017AvgPrice, dca.dcaClass2017Returns, dca.dcaClass2017Stack],
|
||||
[2018, "yellow", true, dca.dcaClass2018AvgPrice, dca.dcaClass2018Returns, dca.dcaClass2018Stack],
|
||||
[2019, "green", true, dca.dcaClass2019AvgPrice, dca.dcaClass2019Returns, dca.dcaClass2019Stack],
|
||||
[2020, "teal", true, dca.dcaClass2020AvgPrice, dca.dcaClass2020Returns, dca.dcaClass2020Stack],
|
||||
[2021, "sky", true, dca.dcaClass2021AvgPrice, dca.dcaClass2021Returns, dca.dcaClass2021Stack],
|
||||
[2022, "blue", true, dca.dcaClass2022AvgPrice, dca.dcaClass2022Returns, dca.dcaClass2022Stack],
|
||||
[2023, "purple", true, dca.dcaClass2023AvgPrice, dca.dcaClass2023Returns, dca.dcaClass2023Stack],
|
||||
[2024, "fuchsia", true, dca.dcaClass2024AvgPrice, dca.dcaClass2024Returns, dca.dcaClass2024Stack],
|
||||
[2025, "pink", true, dca.dcaClass2025AvgPrice, dca.dcaClass2025Returns, dca.dcaClass2025Stack],
|
||||
]).map(([year, colorKey, defaultActive, avgPrice, returns, stack]) => ({
|
||||
year,
|
||||
color: colors[colorKey],
|
||||
defaultActive,
|
||||
avgPrice,
|
||||
returns,
|
||||
stack,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Market section
|
||||
* @param {PartialContext} ctx
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createMarketSection(ctx) {
|
||||
const { colors, brk, s, createPriceLine } = ctx;
|
||||
const { market, supply } = brk.tree.computed;
|
||||
const { movingAverage, ath, returns, volatility, range, dca, lookback } = market;
|
||||
|
||||
const averages = buildAverages(colors, movingAverage);
|
||||
const dcaClasses = buildDcaClasses(colors, dca);
|
||||
|
||||
return {
|
||||
name: "Market",
|
||||
tree: [
|
||||
// Price (empty chart, shows candlesticks by default)
|
||||
{
|
||||
name: "Price",
|
||||
title: "Bitcoin Price",
|
||||
},
|
||||
|
||||
// Capitalization
|
||||
{
|
||||
name: "Capitalization",
|
||||
title: "Market Capitalization",
|
||||
bottom: [s({ metric: supply.marketCap.indexes, name: "Capitalization", unit: "usd" })],
|
||||
},
|
||||
|
||||
// All Time High
|
||||
{
|
||||
name: "All Time High",
|
||||
title: "All Time High",
|
||||
top: [s({ metric: ath.priceAth, name: "ath", unit: "usd" })],
|
||||
bottom: [
|
||||
s({ metric: ath.priceDrawdown, name: "Drawdown", color: colors.red, unit: "percentage" }),
|
||||
s({ metric: ath.daysSincePriceAth, name: "since", unit: "days" }),
|
||||
s({ metric: ath.maxDaysBetweenPriceAths, name: "Max", color: colors.red, unit: "days" }),
|
||||
s({ metric: ath.maxYearsBetweenPriceAths, name: "Max", color: colors.red, unit: "years" }),
|
||||
],
|
||||
},
|
||||
|
||||
// Averages
|
||||
{
|
||||
name: "Averages",
|
||||
tree: [
|
||||
{ nameAddon: "Simple", metricAddon: /** @type {const} */ ("sma") },
|
||||
{ nameAddon: "Exponential", metricAddon: /** @type {const} */ ("ema") },
|
||||
].map(({ nameAddon, metricAddon }) => ({
|
||||
name: nameAddon,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `Market Price ${nameAddon} Moving Averages`,
|
||||
top: averages.map(({ id, color, sma, ema }) =>
|
||||
s({
|
||||
metric: (metricAddon === "sma" ? sma : ema).price,
|
||||
name: id,
|
||||
color,
|
||||
unit: "usd",
|
||||
}),
|
||||
),
|
||||
},
|
||||
...averages.map(({ name, color, sma, ema }) => ({
|
||||
name,
|
||||
tree: createPriceWithRatioOptions(ctx, {
|
||||
ratio: metricAddon === "sma" ? sma : ema,
|
||||
name,
|
||||
title: `${name} Market Price ${nameAddon} Moving Average`,
|
||||
legend: "average",
|
||||
color,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
})),
|
||||
},
|
||||
|
||||
// Performance
|
||||
{
|
||||
name: "Performance",
|
||||
tree: /** @type {const} */ ([
|
||||
["1d", returns._1dPriceReturns, undefined],
|
||||
["1w", returns._1wPriceReturns, undefined],
|
||||
["1m", returns._1mPriceReturns, undefined],
|
||||
["3m", returns._3mPriceReturns, undefined],
|
||||
["6m", returns._6mPriceReturns, undefined],
|
||||
["1y", returns._1yPriceReturns, undefined],
|
||||
["2y", returns._2yPriceReturns, returns._2yCagr],
|
||||
["3y", returns._3yPriceReturns, returns._3yCagr],
|
||||
["4y", returns._4yPriceReturns, returns._4yCagr],
|
||||
["5y", returns._5yPriceReturns, returns._5yCagr],
|
||||
["6y", returns._6yPriceReturns, returns._6yCagr],
|
||||
["8y", returns._8yPriceReturns, returns._8yCagr],
|
||||
["10y", returns._10yPriceReturns, returns._10yCagr],
|
||||
]).map(([id, priceReturns, cagr]) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
title: `${name} Performance`,
|
||||
bottom: [
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: priceReturns,
|
||||
title: "total",
|
||||
type: "Baseline",
|
||||
unit: "percentage",
|
||||
}),
|
||||
...(cagr
|
||||
? [
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: cagr,
|
||||
title: "cagr",
|
||||
type: "Baseline",
|
||||
colors: [colors.lime, colors.pink],
|
||||
unit: "percentage",
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
createPriceLine({ unit: "percentage" }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
},
|
||||
|
||||
// Indicators
|
||||
{
|
||||
name: "Indicators",
|
||||
tree: [
|
||||
// Volatility
|
||||
{
|
||||
name: "Volatility",
|
||||
title: "Bitcoin Price Volatility Index",
|
||||
bottom: [
|
||||
s({ metric: volatility.price1wVolatility, name: "1w", color: colors.red, unit: "percentage" }),
|
||||
s({ metric: volatility.price1mVolatility, name: "1m", color: colors.orange, unit: "percentage" }),
|
||||
s({ metric: volatility.price1yVolatility, name: "1y", color: colors.lime, unit: "percentage" }),
|
||||
],
|
||||
},
|
||||
|
||||
// MinMax
|
||||
{
|
||||
name: "MinMax",
|
||||
tree: [
|
||||
{ id: "1w", title: "1 Week", min: range.price1wMin, max: range.price1wMax },
|
||||
{ id: "2w", title: "2 Week", min: range.price2wMin, max: range.price2wMax },
|
||||
{ id: "1m", title: "1 Month", min: range.price1mMin, max: range.price1mMax },
|
||||
{ id: "1y", title: "1 Year", min: range.price1yMin, max: range.price1yMax },
|
||||
].map(({ id, title, min, max }) => ({
|
||||
name: id,
|
||||
title: `Bitcoin Price ${title} MinMax Bands`,
|
||||
top: [
|
||||
s({ metric: min, name: "min", color: colors.red, unit: "usd" }),
|
||||
s({ metric: max, name: "max", color: colors.green, unit: "usd" }),
|
||||
],
|
||||
})),
|
||||
},
|
||||
|
||||
// True range
|
||||
{
|
||||
name: "True range",
|
||||
title: "Bitcoin Price True Range",
|
||||
bottom: [s({ metric: range.priceTrueRange, name: "value", color: colors.yellow, unit: "usd" })],
|
||||
},
|
||||
|
||||
// Choppiness
|
||||
{
|
||||
name: "Choppiness",
|
||||
title: "Bitcoin Price Choppiness Index",
|
||||
bottom: [
|
||||
s({ metric: range.price2wChoppinessIndex, name: "2w", color: colors.red, unit: "index" }),
|
||||
createPriceLine({ unit: "index", number: 61.8 }),
|
||||
createPriceLine({ unit: "index", number: 38.2 }),
|
||||
],
|
||||
},
|
||||
|
||||
// Mayer multiple
|
||||
{
|
||||
name: "Mayer multiple",
|
||||
title: "Mayer multiple",
|
||||
top: [
|
||||
s({ metric: movingAverage.price200dSma.price, name: "200d sma", color: colors.yellow, unit: "usd" }),
|
||||
s({ metric: movingAverage.price200dSmaX24, name: "200d sma x2.4", color: colors.green, unit: "usd" }),
|
||||
s({ metric: movingAverage.price200dSmaX08, name: "200d sma x0.8", color: colors.red, unit: "usd" }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Investing
|
||||
{
|
||||
name: "Investing",
|
||||
tree: [
|
||||
// DCA vs Lump sum
|
||||
{
|
||||
name: "DCA vs Lump sum",
|
||||
tree: [
|
||||
.../** @type {const} */ ([
|
||||
["1w", dca._1wDcaAvgPrice, lookback.price1wAgo, dca._1wDcaReturns, returns._1wPriceReturns],
|
||||
["1m", dca._1mDcaAvgPrice, lookback.price1mAgo, dca._1mDcaReturns, returns._1mPriceReturns],
|
||||
["3m", dca._3mDcaAvgPrice, lookback.price3mAgo, dca._3mDcaReturns, returns._3mPriceReturns],
|
||||
["6m", dca._6mDcaAvgPrice, lookback.price6mAgo, dca._6mDcaReturns, returns._6mPriceReturns],
|
||||
["1y", dca._1yDcaAvgPrice, lookback.price1yAgo, dca._1yDcaReturns, returns._1yPriceReturns],
|
||||
]).map(([id, dcaAvgPrice, priceAgo, dcaReturns, priceReturns]) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "price",
|
||||
title: `${name} DCA vs Lump Sum (Price)`,
|
||||
top: [
|
||||
s({ metric: dcaAvgPrice, name: "DCA avg", color: colors.green, unit: "usd" }),
|
||||
s({ metric: priceAgo, name: "Lump sum", color: colors.orange, unit: "usd" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "returns",
|
||||
title: `${name} DCA vs Lump Sum (Returns)`,
|
||||
bottom: [
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: dcaReturns,
|
||||
title: "DCA",
|
||||
type: "Baseline",
|
||||
unit: "percentage",
|
||||
}),
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: priceReturns,
|
||||
title: "Lump sum",
|
||||
type: "Baseline",
|
||||
colors: [colors.lime, colors.red],
|
||||
unit: "percentage",
|
||||
}),
|
||||
createPriceLine({ unit: "percentage" }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
// DCA classes
|
||||
{
|
||||
name: "DCA classes",
|
||||
tree: [
|
||||
{
|
||||
name: "Average price",
|
||||
title: "DCA Average Price by Year",
|
||||
top: dcaClasses.map(({ year, color, defaultActive, avgPrice }) =>
|
||||
s({ metric: avgPrice, name: `${year}`, color, defaultActive, unit: "usd" }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: "DCA Returns by Year",
|
||||
bottom: dcaClasses.map(({ year, color, defaultActive, returns }) =>
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: returns,
|
||||
title: `${year}`,
|
||||
type: "Baseline",
|
||||
color,
|
||||
defaultActive,
|
||||
unit: "percentage",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: "DCA Stack by Year",
|
||||
bottom: dcaClasses.map(({ year, color, defaultActive, stack }) =>
|
||||
s({ metric: stack, name: `${year}`, color, defaultActive, unit: "sats" }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
179
websites/bitview/scripts/options/partial/market/averages.js
Normal file
179
websites/bitview/scripts/options/partial/market/averages.js
Normal file
@@ -0,0 +1,179 @@
|
||||
/** Moving averages section */
|
||||
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Build averages data array from market patterns
|
||||
* @param {Colors} colors
|
||||
* @param {MarketMovingAverage} ma
|
||||
*/
|
||||
export function buildAverages(colors, ma) {
|
||||
return /** @type {const} */ ([
|
||||
["1w", 7, "red", ma.price1wSma, ma.price1wEma],
|
||||
["8d", 8, "orange", ma.price8dSma, ma.price8dEma],
|
||||
["13d", 13, "amber", ma.price13dSma, ma.price13dEma],
|
||||
["21d", 21, "yellow", ma.price21dSma, ma.price21dEma],
|
||||
["1m", 30, "lime", ma.price1mSma, ma.price1mEma],
|
||||
["34d", 34, "green", ma.price34dSma, ma.price34dEma],
|
||||
["55d", 55, "emerald", ma.price55dSma, ma.price55dEma],
|
||||
["89d", 89, "teal", ma.price89dSma, ma.price89dEma],
|
||||
["144d", 144, "cyan", ma.price144dSma, ma.price144dEma],
|
||||
["200d", 200, "sky", ma.price200dSma, ma.price200dEma],
|
||||
["1y", 365, "blue", ma.price1ySma, ma.price1yEma],
|
||||
["2y", 730, "indigo", ma.price2ySma, ma.price2yEma],
|
||||
["200w", 1400, "violet", ma.price200wSma, ma.price200wEma],
|
||||
["4y", 1460, "purple", ma.price4ySma, ma.price4yEma],
|
||||
]).map(([id, days, colorKey, sma, ema]) => ({
|
||||
id,
|
||||
name: periodIdToName(id, true),
|
||||
days,
|
||||
color: colors[colorKey],
|
||||
sma,
|
||||
ema,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create price with ratio options (for moving averages)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {EmaRatioPattern} args.ratio
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function createPriceWithRatioOptions(ctx, { title, legend, ratio, color }) {
|
||||
const { s, colors, createPriceLine } = ctx;
|
||||
const priceMetric = ratio.price;
|
||||
|
||||
const percentileUsdMap = [
|
||||
{ name: "pct99", prop: ratio.ratioPct99Usd, color: colors.rose },
|
||||
{ name: "pct98", prop: ratio.ratioPct98Usd, color: colors.pink },
|
||||
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.fuchsia },
|
||||
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.cyan },
|
||||
{ name: "pct2", prop: ratio.ratioPct2Usd, color: colors.sky },
|
||||
{ name: "pct1", prop: ratio.ratioPct1Usd, color: colors.blue },
|
||||
];
|
||||
|
||||
const percentileMap = [
|
||||
{ name: "pct99", prop: ratio.ratioPct99, color: colors.rose },
|
||||
{ name: "pct98", prop: ratio.ratioPct98, color: colors.pink },
|
||||
{ name: "pct95", prop: ratio.ratioPct95, color: colors.fuchsia },
|
||||
{ name: "pct5", prop: ratio.ratioPct5, color: colors.cyan },
|
||||
{ name: "pct2", prop: ratio.ratioPct2, color: colors.sky },
|
||||
{ name: "pct1", prop: ratio.ratioPct1, color: colors.blue },
|
||||
];
|
||||
|
||||
const sdPatterns = [
|
||||
{ nameAddon: "all", titleAddon: "", sd: ratio.ratioSd },
|
||||
{ nameAddon: "4y", titleAddon: "4y", sd: ratio.ratio4ySd },
|
||||
{ nameAddon: "2y", titleAddon: "2y", sd: ratio.ratio2ySd },
|
||||
{ nameAddon: "1y", titleAddon: "1y", sd: ratio.ratio1ySd },
|
||||
];
|
||||
|
||||
/** @param {Ratio1ySdPattern} sd */
|
||||
const getSdBands = (sd) => [
|
||||
{ name: "0σ", prop: sd._0sdUsd, color: colors.lime },
|
||||
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.yellow },
|
||||
{ name: "+1σ", prop: sd.p1sdUsd, color: colors.amber },
|
||||
{ name: "+1.5σ", prop: sd.p15sdUsd, color: colors.orange },
|
||||
{ name: "+2σ", prop: sd.p2sdUsd, color: colors.red },
|
||||
{ name: "+2.5σ", prop: sd.p25sdUsd, color: colors.rose },
|
||||
{ name: "+3σ", prop: sd.p3sd, color: colors.pink },
|
||||
{ name: "−0.5σ", prop: sd.m05sdUsd, color: colors.teal },
|
||||
{ name: "−1σ", prop: sd.m1sdUsd, color: colors.cyan },
|
||||
{ name: "−1.5σ", prop: sd.m15sdUsd, color: colors.sky },
|
||||
{ name: "−2σ", prop: sd.m2sdUsd, color: colors.blue },
|
||||
{ name: "−2.5σ", prop: sd.m25sdUsd, color: colors.indigo },
|
||||
{ name: "−3σ", prop: sd.m3sd, color: colors.violet },
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
name: "price",
|
||||
title,
|
||||
top: [s({ metric: priceMetric, name: legend, color, unit: "usd" })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
top: [
|
||||
s({ metric: priceMetric, name: legend, color, unit: "usd" }),
|
||||
...percentileUsdMap.map(({ name: pctName, prop, color: pctColor }) =>
|
||||
s({ metric: prop, name: pctName, color: pctColor, defaultActive: false, unit: "usd", options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
bottom: [
|
||||
s({ metric: ratio.ratio, name: "ratio", color, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio1wSma, name: "1w sma", color: colors.lime, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio1mSma, name: "1m sma", color: colors.teal, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio1ySd.sma, name: "1y sma", color: colors.sky, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio2ySd.sma, name: "2y sma", color: colors.indigo, unit: "ratio" }),
|
||||
s({ metric: ratio.ratio4ySd.sma, name: "4y sma", color: colors.purple, unit: "ratio" }),
|
||||
s({ metric: ratio.ratioSd.sma, name: "all sma", color: colors.rose, unit: "ratio" }),
|
||||
...percentileMap.map(({ name: pctName, prop, color: pctColor }) =>
|
||||
s({ metric: prop, name: pctName, color: pctColor, defaultActive: false, unit: "ratio", options: { lineStyle: 1 } }),
|
||||
),
|
||||
createPriceLine({ unit: "ratio", number: 1 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "ZScores",
|
||||
tree: sdPatterns.map(({ nameAddon, titleAddon, sd }) => ({
|
||||
name: nameAddon,
|
||||
title: `${title} ${titleAddon} Z-Score`,
|
||||
top: getSdBands(sd).map(({ name: bandName, prop, color: bandColor }) =>
|
||||
s({ metric: prop, name: bandName, color: bandColor, unit: "usd" }),
|
||||
),
|
||||
bottom: [
|
||||
s({ metric: sd.zscore, name: "zscore", color, unit: "sd" }),
|
||||
createPriceLine({ unit: "sd", number: 3 }),
|
||||
createPriceLine({ unit: "sd", number: 2 }),
|
||||
createPriceLine({ unit: "sd", number: 1 }),
|
||||
createPriceLine({ unit: "sd", number: 0 }),
|
||||
createPriceLine({ unit: "sd", number: -1 }),
|
||||
createPriceLine({ unit: "sd", number: -2 }),
|
||||
createPriceLine({ unit: "sd", number: -3 }),
|
||||
],
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Averages section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {ReturnType<typeof buildAverages>} averages
|
||||
*/
|
||||
export function createAveragesSection(ctx, averages) {
|
||||
const { s } = ctx;
|
||||
|
||||
return {
|
||||
name: "Averages",
|
||||
tree: [
|
||||
{ nameAddon: "Simple", metricAddon: /** @type {const} */ ("sma") },
|
||||
{ nameAddon: "Exponential", metricAddon: /** @type {const} */ ("ema") },
|
||||
].map(({ nameAddon, metricAddon }) => ({
|
||||
name: nameAddon,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `Market Price ${nameAddon} Moving Averages`,
|
||||
top: averages.map(({ id, color, sma, ema }) =>
|
||||
s({ metric: (metricAddon === "sma" ? sma : ema).price, name: id, color, unit: "usd" }),
|
||||
),
|
||||
},
|
||||
...averages.map(({ name, color, sma, ema }) => ({
|
||||
name,
|
||||
tree: createPriceWithRatioOptions(ctx, {
|
||||
ratio: metricAddon === "sma" ? sma : ema,
|
||||
title: `${name} Market Price ${nameAddon} Moving Average`,
|
||||
legend: "average",
|
||||
color,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
})),
|
||||
};
|
||||
}
|
||||
62
websites/bitview/scripts/options/partial/market/index.js
Normal file
62
websites/bitview/scripts/options/partial/market/index.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/** Market section - Main entry point */
|
||||
|
||||
import { buildAverages, createAveragesSection } from "./averages.js";
|
||||
import { createPerformanceSection } from "./performance.js";
|
||||
import { createIndicatorsSection } from "./indicators/index.js";
|
||||
import { createInvestingSection } from "./investing.js";
|
||||
|
||||
/**
|
||||
* Create Market section
|
||||
* @param {PartialContext} ctx
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createMarketSection(ctx) {
|
||||
const { colors, brk, s } = ctx;
|
||||
const { market, supply } = brk.tree.computed;
|
||||
const { movingAverage, ath, returns, volatility, range, dca, lookback, indicators } = market;
|
||||
|
||||
const averages = buildAverages(colors, movingAverage);
|
||||
|
||||
return {
|
||||
name: "Market",
|
||||
tree: [
|
||||
// Price
|
||||
{
|
||||
name: "Price",
|
||||
title: "Bitcoin Price",
|
||||
},
|
||||
|
||||
// Capitalization
|
||||
{
|
||||
name: "Capitalization",
|
||||
title: "Market Capitalization",
|
||||
bottom: [s({ metric: supply.marketCap.indexes, name: "Capitalization", unit: "usd" })],
|
||||
},
|
||||
|
||||
// All Time High
|
||||
{
|
||||
name: "All Time High",
|
||||
title: "All Time High",
|
||||
top: [s({ metric: ath.priceAth, name: "ath", unit: "usd" })],
|
||||
bottom: [
|
||||
s({ metric: ath.priceDrawdown, name: "Drawdown", color: colors.red, unit: "percentage" }),
|
||||
s({ metric: ath.daysSincePriceAth, name: "since", unit: "days" }),
|
||||
s({ metric: ath.maxDaysBetweenPriceAths, name: "Max", color: colors.red, unit: "days" }),
|
||||
s({ metric: ath.maxYearsBetweenPriceAths, name: "Max", color: colors.red, unit: "years" }),
|
||||
],
|
||||
},
|
||||
|
||||
// Averages
|
||||
createAveragesSection(ctx, averages),
|
||||
|
||||
// Performance
|
||||
createPerformanceSection(ctx, returns),
|
||||
|
||||
// Indicators
|
||||
createIndicatorsSection(ctx, { volatility, range, movingAverage, indicators }),
|
||||
|
||||
// Investing
|
||||
createInvestingSection(ctx, { dca, lookback, returns }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/** Bands indicators (MinMax, Mayer Multiple) */
|
||||
|
||||
/**
|
||||
* Create Bands section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["range"]} args.range
|
||||
* @param {Market["movingAverage"]} args.movingAverage
|
||||
*/
|
||||
export function createBandsSection(ctx, { range, movingAverage }) {
|
||||
const { s, colors } = ctx;
|
||||
|
||||
return {
|
||||
name: "Bands",
|
||||
tree: [
|
||||
{
|
||||
name: "MinMax",
|
||||
tree: [
|
||||
{ id: "1w", title: "1 Week", min: range.price1wMin, max: range.price1wMax },
|
||||
{ id: "2w", title: "2 Week", min: range.price2wMin, max: range.price2wMax },
|
||||
{ id: "1m", title: "1 Month", min: range.price1mMin, max: range.price1mMax },
|
||||
{ id: "1y", title: "1 Year", min: range.price1yMin, max: range.price1yMax },
|
||||
].map(({ id, title, min, max }) => ({
|
||||
name: id,
|
||||
title: `Bitcoin Price ${title} MinMax Bands`,
|
||||
top: [
|
||||
s({ metric: min, name: "min", color: colors.red, unit: "usd" }),
|
||||
s({ metric: max, name: "max", color: colors.green, unit: "usd" }),
|
||||
],
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Mayer Multiple",
|
||||
title: "Mayer Multiple",
|
||||
top: [
|
||||
s({ metric: movingAverage.price200dSma.price, name: "200d sma", color: colors.yellow, unit: "usd" }),
|
||||
s({ metric: movingAverage.price200dSmaX24, name: "200d sma x2.4", color: colors.green, unit: "usd" }),
|
||||
s({ metric: movingAverage.price200dSmaX08, name: "200d sma x0.8", color: colors.red, unit: "usd" }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/** Indicators section - Main entry point */
|
||||
|
||||
import { createMomentumSection } from "./momentum.js";
|
||||
import { createVolatilitySection } from "./volatility.js";
|
||||
import { createBandsSection } from "./bands.js";
|
||||
import { createOnchainSection } from "./onchain.js";
|
||||
|
||||
/**
|
||||
* Create Indicators section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["volatility"]} args.volatility
|
||||
* @param {Market["range"]} args.range
|
||||
* @param {Market["movingAverage"]} args.movingAverage
|
||||
* @param {Market["indicators"]} args.indicators
|
||||
*/
|
||||
export function createIndicatorsSection(ctx, { volatility, range, movingAverage, indicators }) {
|
||||
return {
|
||||
name: "Indicators",
|
||||
tree: [
|
||||
createMomentumSection(ctx, indicators),
|
||||
createVolatilitySection(ctx, { volatility, range }),
|
||||
createBandsSection(ctx, { range, movingAverage }),
|
||||
createOnchainSection(ctx, { indicators, movingAverage }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
|
||||
|
||||
/**
|
||||
* Create Momentum section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Market["indicators"]} indicators
|
||||
*/
|
||||
export function createMomentumSection(ctx, indicators) {
|
||||
const { s, colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Momentum",
|
||||
tree: [
|
||||
{
|
||||
name: "RSI",
|
||||
title: "Relative Strength Index (14d)",
|
||||
bottom: [
|
||||
s({
|
||||
metric: indicators.rsi14d,
|
||||
name: "RSI",
|
||||
color: colors.indigo,
|
||||
unit: "index",
|
||||
}),
|
||||
s({
|
||||
metric: indicators.rsi14dMin,
|
||||
name: "Min",
|
||||
color: colors.red,
|
||||
defaultActive: false,
|
||||
unit: "index",
|
||||
}),
|
||||
s({
|
||||
metric: indicators.rsi14dMax,
|
||||
name: "Max",
|
||||
color: colors.green,
|
||||
defaultActive: false,
|
||||
unit: "index",
|
||||
}),
|
||||
createPriceLine({ unit: "index", number: 70 }),
|
||||
createPriceLine({ unit: "index", number: 50, defaultActive: false }),
|
||||
createPriceLine({ unit: "index", number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "StochRSI",
|
||||
title: "Stochastic RSI",
|
||||
bottom: [
|
||||
// s({
|
||||
// metric: indicators.stochRsi,
|
||||
// name: "Stoch RSI",
|
||||
// color: colors.purple,
|
||||
// unit: "index",
|
||||
// }),
|
||||
s({
|
||||
metric: indicators.stochRsiK,
|
||||
name: "K",
|
||||
color: colors.blue,
|
||||
unit: "index",
|
||||
}),
|
||||
s({
|
||||
metric: indicators.stochRsiD,
|
||||
name: "D",
|
||||
color: colors.orange,
|
||||
unit: "index",
|
||||
}),
|
||||
createPriceLine({ unit: "index", number: 80 }),
|
||||
createPriceLine({ unit: "index", number: 20 }),
|
||||
],
|
||||
},
|
||||
// {
|
||||
// name: "Stochastic",
|
||||
// title: "Stochastic Oscillator",
|
||||
// bottom: [
|
||||
// s({ metric: indicators.stochK, name: "K", color: colors.blue, unit: "index" }),
|
||||
// s({ metric: indicators.stochD, name: "D", color: colors.orange, unit: "index" }),
|
||||
// createPriceLine({ unit: "index", number: 80 }),
|
||||
// createPriceLine({ unit: "index", number: 20 }),
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
name: "MACD",
|
||||
title: "Moving Average Convergence Divergence",
|
||||
bottom: [
|
||||
s({
|
||||
metric: indicators.macdLine,
|
||||
name: "MACD",
|
||||
color: colors.blue,
|
||||
unit: "usd",
|
||||
}),
|
||||
s({
|
||||
metric: indicators.macdSignal,
|
||||
name: "Signal",
|
||||
color: colors.orange,
|
||||
unit: "usd",
|
||||
}),
|
||||
/** @type {FetchedHistogramSeriesBlueprint} */ ({
|
||||
metric: indicators.macdHistogram,
|
||||
title: "Histogram",
|
||||
type: "Histogram",
|
||||
unit: "usd",
|
||||
}),
|
||||
createPriceLine({ unit: "usd" }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
|
||||
|
||||
/**
|
||||
* Create On-chain section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["indicators"]} args.indicators
|
||||
* @param {Market["movingAverage"]} args.movingAverage
|
||||
*/
|
||||
export function createOnchainSection(ctx, { indicators, movingAverage }) {
|
||||
const { s, colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "On-chain",
|
||||
tree: [
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle Top Indicator",
|
||||
top: [
|
||||
s({
|
||||
metric: movingAverage.price111dSma.price,
|
||||
name: "111d SMA",
|
||||
color: colors.green,
|
||||
unit: "usd",
|
||||
}),
|
||||
s({
|
||||
metric: movingAverage.price350dSmaX2,
|
||||
name: "350d SMA x2",
|
||||
color: colors.red,
|
||||
unit: "usd",
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
s({
|
||||
metric: indicators.piCycle,
|
||||
name: "Pi Cycle",
|
||||
color: colors.purple,
|
||||
unit: "ratio",
|
||||
}),
|
||||
createPriceLine({ unit: "ratio", number: 1 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Puell Multiple",
|
||||
title: "Puell Multiple",
|
||||
bottom: [
|
||||
s({
|
||||
metric: indicators.puellMultiple,
|
||||
name: "Puell",
|
||||
color: colors.green,
|
||||
unit: "ratio",
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "NVT",
|
||||
title: "Network Value to Transactions Ratio",
|
||||
bottom: [
|
||||
s({
|
||||
metric: indicators.nvt,
|
||||
name: "NVT",
|
||||
color: colors.orange,
|
||||
unit: "ratio",
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Gini",
|
||||
title: "Gini Coefficient",
|
||||
bottom: [
|
||||
s({
|
||||
metric: indicators.gini,
|
||||
name: "Gini",
|
||||
color: colors.red,
|
||||
unit: "ratio",
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */
|
||||
|
||||
/**
|
||||
* Create Volatility section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["volatility"]} args.volatility
|
||||
* @param {Market["range"]} args.range
|
||||
*/
|
||||
export function createVolatilitySection(ctx, { volatility, range }) {
|
||||
const { s, colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Volatility",
|
||||
tree: [
|
||||
{
|
||||
name: "Index",
|
||||
title: "Bitcoin Price Volatility Index",
|
||||
bottom: [
|
||||
s({ metric: volatility.price1wVolatility, name: "1w", color: colors.red, unit: "percentage" }),
|
||||
s({ metric: volatility.price1mVolatility, name: "1m", color: colors.orange, unit: "percentage" }),
|
||||
s({ metric: volatility.price1yVolatility, name: "1y", color: colors.lime, unit: "percentage" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "True Range",
|
||||
title: "Bitcoin Price True Range",
|
||||
bottom: [s({ metric: range.priceTrueRange, name: "value", color: colors.yellow, unit: "usd" })],
|
||||
},
|
||||
{
|
||||
name: "Choppiness",
|
||||
title: "Bitcoin Price Choppiness Index",
|
||||
bottom: [
|
||||
s({ metric: range.price2wChoppinessIndex, name: "2w", color: colors.red, unit: "index" }),
|
||||
createPriceLine({ unit: "index", number: 61.8 }),
|
||||
createPriceLine({ unit: "index", number: 38.2 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sharpe Ratio",
|
||||
title: "Sharpe Ratio",
|
||||
bottom: [
|
||||
s({ metric: volatility.sharpe1w, name: "1w", color: colors.red, unit: "ratio" }),
|
||||
s({ metric: volatility.sharpe1m, name: "1m", color: colors.orange, unit: "ratio" }),
|
||||
s({ metric: volatility.sharpe1y, name: "1y", color: colors.lime, unit: "ratio" }),
|
||||
createPriceLine({ unit: "ratio" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sortino Ratio",
|
||||
title: "Sortino Ratio",
|
||||
bottom: [
|
||||
s({ metric: volatility.sortino1w, name: "1w", color: colors.red, unit: "ratio" }),
|
||||
s({ metric: volatility.sortino1m, name: "1m", color: colors.orange, unit: "ratio" }),
|
||||
s({ metric: volatility.sortino1y, name: "1y", color: colors.lime, unit: "ratio" }),
|
||||
createPriceLine({ unit: "ratio" }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
197
websites/bitview/scripts/options/partial/market/investing.js
Normal file
197
websites/bitview/scripts/options/partial/market/investing.js
Normal file
@@ -0,0 +1,197 @@
|
||||
/** Investing section (DCA) */
|
||||
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Build DCA classes data array
|
||||
* @param {Colors} colors
|
||||
* @param {MarketDca} dca
|
||||
*/
|
||||
export function buildDcaClasses(colors, dca) {
|
||||
return /** @type {const} */ ([
|
||||
[2025, "pink", true],
|
||||
[2024, "fuchsia", true],
|
||||
[2023, "purple", true],
|
||||
[2022, "blue", true],
|
||||
[2021, "sky", true],
|
||||
[2020, "teal", true],
|
||||
[2019, "green", true],
|
||||
[2018, "yellow", true],
|
||||
[2017, "orange", true],
|
||||
[2016, "red", false],
|
||||
[2015, "pink", false],
|
||||
]).map(([year, colorKey, defaultActive]) => ({
|
||||
year,
|
||||
color: colors[colorKey],
|
||||
defaultActive,
|
||||
costBasis: dca.classAvgPrice[`_${year}`],
|
||||
returns: dca.classReturns[`_${year}`],
|
||||
stack: dca.classStack[`_${year}`],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Investing section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["dca"]} args.dca
|
||||
* @param {Market["lookback"]} args.lookback
|
||||
* @param {Market["returns"]} args.returns
|
||||
*/
|
||||
export function createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
const { s, colors, createPriceLine } = ctx;
|
||||
const dcaClasses = buildDcaClasses(colors, dca);
|
||||
|
||||
return {
|
||||
name: "Investing",
|
||||
tree: [
|
||||
// DCA vs Lump sum
|
||||
{
|
||||
name: "DCA vs Lump sum",
|
||||
tree: /** @type {const} */ ([
|
||||
["1w", "_1w"],
|
||||
["1m", "_1m"],
|
||||
["3m", "_3m"],
|
||||
["6m", "_6m"],
|
||||
["1y", "_1y"],
|
||||
["2y", "_2y"],
|
||||
["3y", "_3y"],
|
||||
["4y", "_4y"],
|
||||
["5y", "_5y"],
|
||||
["6y", "_6y"],
|
||||
["8y", "_8y"],
|
||||
["10y", "_10y"],
|
||||
]).map(([id, key]) => {
|
||||
const name = periodIdToName(id, true);
|
||||
const priceAgo = lookback.priceAgo[key];
|
||||
const priceReturns = returns.priceReturns[key];
|
||||
const dcaCostBasis = dca.periodAvgPrice[key];
|
||||
const dcaReturns = dca.periodReturns[key];
|
||||
const dcaStack = dca.periodStack[key];
|
||||
const lumpSumStack = dca.periodLumpSumStack[key];
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: `${name} DCA vs Lump Sum (Cost Basis)`,
|
||||
top: [
|
||||
s({ metric: dcaCostBasis, name: "DCA", color: colors.green, unit: "usd" }),
|
||||
s({ metric: priceAgo, name: "Lump sum", color: colors.orange, unit: "usd" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} DCA vs Lump Sum (Returns)`,
|
||||
bottom: [
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: dcaReturns,
|
||||
title: "DCA",
|
||||
type: "Baseline",
|
||||
unit: "percentage",
|
||||
}),
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: priceReturns,
|
||||
title: "Lump sum",
|
||||
type: "Baseline",
|
||||
colors: [colors.lime, colors.red],
|
||||
unit: "percentage",
|
||||
}),
|
||||
createPriceLine({ unit: "percentage" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `${name} DCA vs Lump Sum Stack ($100/day)`,
|
||||
bottom: [
|
||||
s({ metric: dcaStack.sats, name: "DCA", color: colors.green, unit: "sats" }),
|
||||
s({ metric: dcaStack.bitcoin, name: "DCA", color: colors.green, unit: "btc" }),
|
||||
s({ metric: dcaStack.dollars, name: "DCA", color: colors.green, unit: "usd" }),
|
||||
s({ metric: lumpSumStack.sats, name: "Lump sum", color: colors.orange, unit: "sats" }),
|
||||
s({ metric: lumpSumStack.bitcoin, name: "Lump sum", color: colors.orange, unit: "btc" }),
|
||||
s({ metric: lumpSumStack.dollars, name: "Lump sum", color: colors.orange, unit: "usd" }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}),
|
||||
},
|
||||
|
||||
// DCA classes
|
||||
{
|
||||
name: "DCA classes",
|
||||
tree: [
|
||||
// Comparison charts (all years overlaid)
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: "DCA Cost Basis by Year",
|
||||
top: dcaClasses.map(({ year, color, defaultActive, costBasis }) =>
|
||||
s({ metric: costBasis, name: `${year}`, color, defaultActive, unit: "usd" }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: "DCA Returns by Year",
|
||||
bottom: dcaClasses.map(({ year, color, defaultActive, returns }) =>
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: returns,
|
||||
title: `${year}`,
|
||||
type: "Baseline",
|
||||
color,
|
||||
defaultActive,
|
||||
unit: "percentage",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: "DCA Stack by Year ($100/day)",
|
||||
bottom: dcaClasses.flatMap(({ year, color, defaultActive, stack }) => [
|
||||
s({ metric: stack.sats, name: `${year}`, color, defaultActive, unit: "sats" }),
|
||||
s({ metric: stack.bitcoin, name: `${year}`, color, defaultActive, unit: "btc" }),
|
||||
s({ metric: stack.dollars, name: `${year}`, color, defaultActive, unit: "usd" }),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Individual year charts
|
||||
...dcaClasses.map(({ year, color, costBasis, returns, stack }) => ({
|
||||
name: `${year}`,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: `DCA Class ${year} Cost Basis`,
|
||||
top: [s({ metric: costBasis, name: "Cost basis", color, unit: "usd" })],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `DCA Class ${year} Returns`,
|
||||
bottom: [
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: returns,
|
||||
title: "Returns",
|
||||
type: "Baseline",
|
||||
color,
|
||||
unit: "percentage",
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `DCA Class ${year} Stack ($100/day)`,
|
||||
bottom: [
|
||||
s({ metric: stack.sats, name: "Stack", color, unit: "sats" }),
|
||||
s({ metric: stack.bitcoin, name: "Stack", color, unit: "btc" }),
|
||||
s({ metric: stack.dollars, name: "Stack", color, unit: "usd" }),
|
||||
],
|
||||
},
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/** Performance section */
|
||||
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Create Performance section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Market["returns"]} returns
|
||||
*/
|
||||
export function createPerformanceSection(ctx, returns) {
|
||||
const { colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Performance",
|
||||
tree: /** @type {const} */ ([
|
||||
["1d", "_1d", undefined],
|
||||
["1w", "_1w", undefined],
|
||||
["1m", "_1m", undefined],
|
||||
["3m", "_3m", undefined],
|
||||
["6m", "_6m", undefined],
|
||||
["1y", "_1y", undefined],
|
||||
["2y", "_2y", "_2y"],
|
||||
["3y", "_3y", "_3y"],
|
||||
["4y", "_4y", "_4y"],
|
||||
["5y", "_5y", "_5y"],
|
||||
["6y", "_6y", "_6y"],
|
||||
["8y", "_8y", "_8y"],
|
||||
["10y", "_10y", "_10y"],
|
||||
]).map(([id, returnKey, cagrKey]) => {
|
||||
const priceReturns = returns.priceReturns[returnKey];
|
||||
const cagr = cagrKey ? returns.cagr[cagrKey] : undefined;
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
title: `${name} Performance`,
|
||||
bottom: [
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: priceReturns,
|
||||
title: "total",
|
||||
type: "Baseline",
|
||||
unit: "percentage",
|
||||
}),
|
||||
...(cagr
|
||||
? [
|
||||
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||
metric: cagr,
|
||||
title: "cagr",
|
||||
type: "Baseline",
|
||||
colors: [colors.lime, colors.pink],
|
||||
unit: "percentage",
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
createPriceLine({ unit: "percentage" }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
23
websites/bitview/scripts/options/partial/market/utils.js
Normal file
23
websites/bitview/scripts/options/partial/market/utils.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/** Market utilities */
|
||||
|
||||
/**
|
||||
* Convert period ID to readable name
|
||||
* @param {string} id
|
||||
* @param {boolean} [compoundAdjective]
|
||||
*/
|
||||
export function periodIdToName(id, compoundAdjective) {
|
||||
const num = parseInt(id);
|
||||
const s = compoundAdjective || num === 1 ? "" : "s";
|
||||
switch (id.slice(-1)) {
|
||||
case "d":
|
||||
return `${num} day${s}`;
|
||||
case "w":
|
||||
return `${num} week${s}`;
|
||||
case "m":
|
||||
return `${num} month${s}`;
|
||||
case "y":
|
||||
return `${num} year${s}`;
|
||||
default:
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Create a single series from a tree accessor
|
||||
* @param {Object} args
|
||||
* @param {MetricAccessor<any>} args.metric - Tree accessor with .by property
|
||||
* @param {AnyMetricPattern} args.metric - Tree accessor with .by property
|
||||
* @param {string} args.name - Display name for the series
|
||||
* @param {Color} [args.color]
|
||||
* @param {Unit} [args.unit]
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
*
|
||||
* @typedef {Object} HistogramSeriesBlueprintSpecific
|
||||
* @property {"Histogram"} type
|
||||
* @property {Color} color
|
||||
* @property {Color | [Color, Color]} [color] - Single color or [positive, negative] colors (defaults to green/red)
|
||||
* @property {HistogramSeriesPartialOptions} [options]
|
||||
* @property {Accessor<HistogramData[]>} [data]
|
||||
* @typedef {BaseSeriesBlueprint & HistogramSeriesBlueprintSpecific} HistogramSeriesBlueprint
|
||||
@@ -36,7 +36,7 @@
|
||||
*
|
||||
* @typedef {AnySeriesBlueprint["type"]} SeriesType
|
||||
*
|
||||
* @typedef {{ metric: MetricAccessor<any>, unit?: Unit }} FetchedAnySeriesOptions
|
||||
* @typedef {{ metric: AnyMetricPattern, unit?: Unit }} FetchedAnySeriesOptions
|
||||
*
|
||||
* @typedef {BaselineSeriesBlueprint & FetchedAnySeriesOptions} FetchedBaselineSeriesBlueprint
|
||||
* @typedef {CandlestickSeriesBlueprint & FetchedAnySeriesOptions} FetchedCandlestickSeriesBlueprint
|
||||
@@ -168,17 +168,13 @@
|
||||
* @typedef {Object} PartialContext
|
||||
* @property {Colors} colors
|
||||
* @property {BrkClient} brk
|
||||
* @property {BrkClient["tree"]["computed"]["constants"]} constants
|
||||
* @property {(args: { metric: MetricAccessor<any>, name: string, color?: Color, defaultActive?: boolean, unit?: Unit, options?: LineSeriesPartialOptions }) => AnyFetchedSeriesBlueprint} s
|
||||
* @property {(args: { metric: AnyMetricPattern, name: string, color?: Color, defaultActive?: boolean, unit?: Unit, options?: LineSeriesPartialOptions }) => AnyFetchedSeriesBlueprint} s
|
||||
* @property {(pattern: BlockCountPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockCount
|
||||
* @property {(pattern: BitcoinPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBitcoin
|
||||
* @property {(pattern: BlockSizePattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockSize
|
||||
* @property {(num: number) => Constant0Pattern<any>} getConstant
|
||||
* @property {(pattern: Constant0Pattern<any>) => MetricAccessor<any>} flattenConstant
|
||||
* @property {(args: { number?: number, name?: string, defaultActive?: boolean, lineStyle?: number, color?: Color, unit: Unit }) => FetchedLineSeriesBlueprint} createPriceLine
|
||||
* @property {(args: { number?: number, name?: string, defaultActive?: boolean, lineStyle?: LineStyle, color?: Color, unit: Unit }) => FetchedLineSeriesBlueprint} createPriceLine
|
||||
* @property {(args: { numbers: number[], unit: Unit }) => FetchedLineSeriesBlueprint[]} createPriceLines
|
||||
* @property {(args: { constant: Constant0Pattern<any>, name: string, unit: Unit, color?: Color, lineStyle?: number, defaultActive?: boolean }) => FetchedLineSeriesBlueprint} line
|
||||
* @property {MetricAccessor<any>} constant100
|
||||
* @property {(args: { constant: AnyMetricPattern, name: string, unit: Unit, color?: Color, lineStyle?: number, defaultActive?: boolean }) => FetchedLineSeriesBlueprint} line
|
||||
*/
|
||||
|
||||
// Re-export for type consumers
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
import { localhost } from "../utils/env.js";
|
||||
|
||||
/** @type {Set<MetricAccessor<any>> | null} */
|
||||
/** @type {Set<AnyMetricPattern> | null} */
|
||||
export const unused = localhost ? new Set() : null;
|
||||
|
||||
/**
|
||||
* Walk and collect MetricAccessors
|
||||
* Walk and collect AnyMetricPatterns
|
||||
* @param {TreeNode | null | undefined} node
|
||||
* @param {Set<MetricAccessor<unknown>>} set
|
||||
* @param {Set<AnyMetricPattern>} set
|
||||
*/
|
||||
function walk(node, set) {
|
||||
if (node && "by" in node) {
|
||||
set.add(/** @type {MetricAccessor<unknown>} */ (node));
|
||||
set.add(/** @type {AnyMetricPattern} */ (node));
|
||||
} else if (node && typeof node === "object") {
|
||||
for (const value of Object.values(node)) {
|
||||
walk(/** @type {TreeNode | null | undefined} */ (value), set);
|
||||
@@ -21,7 +21,7 @@ function walk(node, set) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all MetricAccessors from tree
|
||||
* Collect all AnyMetricPatterns from tree
|
||||
* @param {TreeNode} tree
|
||||
*/
|
||||
export function collect(tree) {
|
||||
@@ -30,7 +30,7 @@ export function collect(tree) {
|
||||
|
||||
/**
|
||||
* Mark a metric as used
|
||||
* @param {MetricAccessor<any>} metric
|
||||
* @param {AnyMetricPattern} metric
|
||||
*/
|
||||
export function markUsed(metric) {
|
||||
unused?.delete(metric);
|
||||
|
||||
Reference in New Issue
Block a user