mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 14:49:58 -07:00
global: snapshot
This commit is contained in:
237
website/scripts/options/market/averages.js
Normal file
237
website/scripts/options/market/averages.js
Normal file
@@ -0,0 +1,237 @@
|
||||
/** Moving averages section */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
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 { line, 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: [line({ metric: priceMetric, name: legend, color, unit: Unit.usd })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
top: [
|
||||
line({ metric: priceMetric, name: legend, color, unit: Unit.usd }),
|
||||
...percentileUsdMap.map(({ name: pctName, prop, color: pctColor }) =>
|
||||
line({
|
||||
metric: prop,
|
||||
name: pctName,
|
||||
color: pctColor,
|
||||
defaultActive: false,
|
||||
unit: Unit.usd,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: ratio.ratio, name: "Ratio", color, unit: Unit.ratio }),
|
||||
line({
|
||||
metric: ratio.ratio1wSma,
|
||||
name: "1w SMA",
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1mSma,
|
||||
name: "1m SMA",
|
||||
color: colors.teal,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1ySd.sma,
|
||||
name: "1y SMA",
|
||||
color: colors.sky,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio2ySd.sma,
|
||||
name: "2y SMA",
|
||||
color: colors.indigo,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio4ySd.sma,
|
||||
name: "4y SMA",
|
||||
color: colors.purple,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratioSd.sma,
|
||||
name: "All SMA",
|
||||
color: colors.rose,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
...percentileMap.map(({ name: pctName, prop, color: pctColor }) =>
|
||||
line({
|
||||
metric: prop,
|
||||
name: pctName,
|
||||
color: pctColor,
|
||||
defaultActive: false,
|
||||
unit: Unit.ratio,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
),
|
||||
createPriceLine({ unit: 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 }) =>
|
||||
line({
|
||||
metric: prop,
|
||||
name: bandName,
|
||||
color: bandColor,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
bottom: [
|
||||
line({ metric: sd.zscore, name: "Z-Score", color, unit: Unit.sd }),
|
||||
createPriceLine({ unit: Unit.sd, number: 3 }),
|
||||
createPriceLine({ unit: Unit.sd, number: 2 }),
|
||||
createPriceLine({ unit: Unit.sd, number: 1 }),
|
||||
createPriceLine({ unit: Unit.sd, number: 0 }),
|
||||
createPriceLine({ unit: Unit.sd, number: -1 }),
|
||||
createPriceLine({ unit: Unit.sd, number: -2 }),
|
||||
createPriceLine({ unit: Unit.sd, number: -3 }),
|
||||
],
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Averages section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {ReturnType<typeof buildAverages>} averages
|
||||
*/
|
||||
export function createAveragesSection(ctx, averages) {
|
||||
const { line } = 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 }) =>
|
||||
line({
|
||||
metric: (metricAddon === "sma" ? sma : ema).price,
|
||||
name: id,
|
||||
color,
|
||||
unit: 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,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
})),
|
||||
};
|
||||
}
|
||||
118
website/scripts/options/market/index.js
Normal file
118
website/scripts/options/market/index.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/** Market section - Main entry point */
|
||||
|
||||
import { localhost } from "../../utils/env.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
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, line, candlestick } = ctx;
|
||||
const { market, supply, price } = brk.metrics;
|
||||
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",
|
||||
...(localhost && {
|
||||
top: [
|
||||
candlestick({
|
||||
metric: price.oracle.ohlcDollars,
|
||||
name: "Oracle",
|
||||
unit: Unit.usd,
|
||||
colors: [colors.cyan, colors.purple],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
|
||||
// Capitalization
|
||||
{
|
||||
name: "Capitalization",
|
||||
title: "Market Capitalization",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.marketCap,
|
||||
name: "Capitalization",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
// All Time High
|
||||
{
|
||||
name: "All Time High",
|
||||
title: "All Time High",
|
||||
top: [line({ metric: ath.priceAth, name: "ATH", unit: Unit.usd })],
|
||||
bottom: [
|
||||
line({
|
||||
metric: ath.priceDrawdown,
|
||||
name: "Drawdown",
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: ath.daysSincePriceAth,
|
||||
name: "Since",
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({
|
||||
metric: ath.yearsSincePriceAth,
|
||||
name: "Since",
|
||||
unit: Unit.years,
|
||||
}),
|
||||
line({
|
||||
metric: ath.maxDaysBetweenPriceAths,
|
||||
name: "Max",
|
||||
color: colors.red,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({
|
||||
metric: ath.maxYearsBetweenPriceAths,
|
||||
name: "Max",
|
||||
color: colors.red,
|
||||
unit: Unit.years,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
// Averages
|
||||
createAveragesSection(ctx, averages),
|
||||
|
||||
// Performance
|
||||
createPerformanceSection(ctx, returns),
|
||||
|
||||
// Indicators
|
||||
createIndicatorsSection(ctx, {
|
||||
volatility,
|
||||
range,
|
||||
movingAverage,
|
||||
indicators,
|
||||
}),
|
||||
|
||||
// Investing
|
||||
createInvestingSection(ctx, { dca, lookback, returns }),
|
||||
],
|
||||
};
|
||||
}
|
||||
90
website/scripts/options/market/indicators/bands.js
Normal file
90
website/scripts/options/market/indicators/bands.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/** Bands indicators (MinMax, Mayer Multiple) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
|
||||
/**
|
||||
* 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 { line, 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: [
|
||||
line({
|
||||
metric: min,
|
||||
name: "Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: max,
|
||||
name: "Max",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Mayer Multiple",
|
||||
title: "Mayer Multiple",
|
||||
top: [
|
||||
line({
|
||||
metric: movingAverage.price200dSma.price,
|
||||
name: "200d SMA",
|
||||
color: colors.yellow,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: movingAverage.price200dSmaX24,
|
||||
name: "200d SMA x2.4",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: movingAverage.price200dSmaX08,
|
||||
name: "200d SMA x0.8",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
27
website/scripts/options/market/indicators/index.js
Normal file
27
website/scripts/options/market/indicators/index.js
Normal file
@@ -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 }),
|
||||
],
|
||||
};
|
||||
}
|
||||
111
website/scripts/options/market/indicators/momentum.js
Normal file
111
website/scripts/options/market/indicators/momentum.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
|
||||
/**
|
||||
* Create Momentum section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Market["indicators"]} indicators
|
||||
*/
|
||||
export function createMomentumSection(ctx, indicators) {
|
||||
const { line, histogram, colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Momentum",
|
||||
tree: [
|
||||
{
|
||||
name: "RSI",
|
||||
title: "Relative Strength Index (14d)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.rsi14d,
|
||||
name: "RSI",
|
||||
color: colors.indigo,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: indicators.rsi14dMin,
|
||||
name: "Min",
|
||||
color: colors.red,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: indicators.rsi14dMax,
|
||||
name: "Max",
|
||||
color: colors.green,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.index, number: 70 }),
|
||||
createPriceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "StochRSI",
|
||||
title: "Stochastic RSI",
|
||||
bottom: [
|
||||
// line({
|
||||
// metric: indicators.stochRsi,
|
||||
// name: "Stoch RSI",
|
||||
// color: colors.purple,
|
||||
// unit: Unit.index,
|
||||
// }),
|
||||
line({
|
||||
metric: indicators.stochRsiK,
|
||||
name: "K",
|
||||
color: colors.blue,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: indicators.stochRsiD,
|
||||
name: "D",
|
||||
color: colors.orange,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.index, number: 80 }),
|
||||
createPriceLine({ unit: Unit.index, number: 20 }),
|
||||
],
|
||||
},
|
||||
// {
|
||||
// name: "Stochastic",
|
||||
// title: "Stochastic Oscillator",
|
||||
// bottom: [
|
||||
// line({ metric: indicators.stochK, name: "K", color: colors.blue, unit: Unit.index }),
|
||||
// line({ metric: indicators.stochD, name: "D", color: colors.orange, unit: Unit.index }),
|
||||
// createPriceLine({ unit: Unit.index, number: 80 }),
|
||||
// createPriceLine({ unit: Unit.index, number: 20 }),
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
name: "MACD",
|
||||
title: "Moving Average Convergence Divergence",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.macdLine,
|
||||
name: "MACD",
|
||||
color: colors.blue,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: indicators.macdSignal,
|
||||
name: "Signal",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
histogram({
|
||||
metric: indicators.macdHistogram,
|
||||
name: "Histogram",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
83
website/scripts/options/market/indicators/onchain.js
Normal file
83
website/scripts/options/market/indicators/onchain.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
|
||||
/**
|
||||
* 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 { line, colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "On-chain",
|
||||
tree: [
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle Top Indicator",
|
||||
top: [
|
||||
line({
|
||||
metric: movingAverage.price111dSma.price,
|
||||
name: "111d SMA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: movingAverage.price350dSmaX2,
|
||||
name: "350d SMA x2",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.piCycle,
|
||||
name: "Pi Cycle",
|
||||
color: colors.purple,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.ratio, number: 1 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Puell Multiple",
|
||||
title: "Puell Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.puellMultiple,
|
||||
name: "Puell",
|
||||
color: colors.green,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "NVT",
|
||||
title: "Network Value to Transactions Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.nvt,
|
||||
name: "NVT",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Gini",
|
||||
title: "Gini Coefficient",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.gini,
|
||||
name: "Gini",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
120
website/scripts/options/market/indicators/volatility.js
Normal file
120
website/scripts/options/market/indicators/volatility.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
|
||||
/**
|
||||
* 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 { line, colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Volatility",
|
||||
tree: [
|
||||
{
|
||||
name: "Index",
|
||||
title: "Bitcoin Price Volatility Index",
|
||||
bottom: [
|
||||
line({
|
||||
metric: volatility.price1wVolatility,
|
||||
name: "1w",
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: volatility.price1mVolatility,
|
||||
name: "1m",
|
||||
color: colors.orange,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: volatility.price1yVolatility,
|
||||
name: "1y",
|
||||
color: colors.lime,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "True Range",
|
||||
title: "Bitcoin Price True Range",
|
||||
bottom: [
|
||||
line({
|
||||
metric: range.priceTrueRange,
|
||||
name: "Value",
|
||||
color: colors.yellow,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Choppiness",
|
||||
title: "Bitcoin Price Choppiness Index",
|
||||
bottom: [
|
||||
line({
|
||||
metric: range.price2wChoppinessIndex,
|
||||
name: "2w",
|
||||
color: colors.red,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.index, number: 61.8 }),
|
||||
createPriceLine({ unit: Unit.index, number: 38.2 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sharpe Ratio",
|
||||
title: "Sharpe Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
metric: volatility.sharpe1w,
|
||||
name: "1w",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: volatility.sharpe1m,
|
||||
name: "1m",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: volatility.sharpe1y,
|
||||
name: "1y",
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sortino Ratio",
|
||||
title: "Sortino Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
metric: volatility.sortino1w,
|
||||
name: "1w",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: volatility.sortino1m,
|
||||
name: "1m",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: volatility.sortino1y,
|
||||
name: "1y",
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
284
website/scripts/options/market/investing.js
Normal file
284
website/scripts/options/market/investing.js
Normal file
@@ -0,0 +1,284 @@
|
||||
/** Investing section (DCA) */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
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.classAveragePrice[`_${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 { line, baseline, 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[key];
|
||||
const priceReturns = returns.priceReturns[key];
|
||||
const dcaCostBasis = dca.periodAveragePrice[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: [
|
||||
line({
|
||||
metric: dcaCostBasis,
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: priceAgo,
|
||||
name: "Lump sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} DCA vs Lump Sum (Returns)`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dcaReturns,
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: priceReturns,
|
||||
name: "Lump sum",
|
||||
color: [colors.lime, colors.red],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
createPriceLine({ unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `${name} DCA vs Lump Sum Stack ($100/day)`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: dcaStack.sats,
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: dcaStack.bitcoin,
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: dcaStack.dollars,
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: lumpSumStack.sats,
|
||||
name: "Lump sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: lumpSumStack.bitcoin,
|
||||
name: "Lump sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: lumpSumStack.dollars,
|
||||
name: "Lump sum",
|
||||
color: colors.orange,
|
||||
unit: 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 }) =>
|
||||
line({
|
||||
metric: costBasis,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: "DCA Returns by Year",
|
||||
bottom: dcaClasses.map(
|
||||
({ year, color, defaultActive, returns }) =>
|
||||
baseline({
|
||||
metric: returns,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: "DCA Stack by Year ($100/day)",
|
||||
bottom: dcaClasses.flatMap(
|
||||
({ year, color, defaultActive, stack }) => [
|
||||
line({
|
||||
metric: stack.sats,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: stack.bitcoin,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: stack.dollars,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: 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: [
|
||||
line({
|
||||
metric: costBasis,
|
||||
name: "Cost basis",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `DCA Class ${year} Returns`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: returns,
|
||||
name: "Returns",
|
||||
color,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `DCA Class ${year} Stack ($100/day)`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: stack.sats,
|
||||
name: "Stack",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: stack.bitcoin,
|
||||
name: "Stack",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: stack.dollars,
|
||||
name: "Stack",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
58
website/scripts/options/market/performance.js
Normal file
58
website/scripts/options/market/performance.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/** Performance section */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Create Performance section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Market["returns"]} returns
|
||||
*/
|
||||
export function createPerformanceSection(ctx, returns) {
|
||||
const { colors, baseline, 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: [
|
||||
baseline({
|
||||
metric: priceReturns,
|
||||
name: "Total",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
...(cagr
|
||||
? [
|
||||
baseline({
|
||||
metric: cagr,
|
||||
name: "CAGR",
|
||||
color: [colors.lime, colors.pink],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
createPriceLine({ unit: Unit.percentage }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
23
website/scripts/options/market/utils.js
Normal file
23
website/scripts/options/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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user