mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-20 06:44:47 -07:00
414 lines
12 KiB
JavaScript
414 lines
12 KiB
JavaScript
/** Investing section (DCA) */
|
|
|
|
import { Unit } from "../../utils/units.js";
|
|
import { priceLine } from "../constants.js";
|
|
import { line, baseline, price } from "../series.js";
|
|
import { satsBtcUsd } from "../shared.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} */ ([
|
|
[2026, "rose", true],
|
|
[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}`],
|
|
daysInProfit: dca.classDaysInProfit[`_${year}`],
|
|
daysInLoss: dca.classDaysInLoss[`_${year}`],
|
|
maxDrawdown: dca.classMaxDrawdown[`_${year}`],
|
|
maxReturn: dca.classMaxReturn[`_${year}`],
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Create DCA vs Lump Sum section
|
|
* @param {PartialContext} ctx
|
|
* @param {Object} args
|
|
* @param {Market["dca"]} args.dca
|
|
* @param {Market["lookback"]} args.lookback
|
|
* @param {Market["returns"]} args.returns
|
|
*/
|
|
export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
|
const { colors } = ctx;
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {ShortPeriodKey | LongPeriodKey} key
|
|
*/
|
|
const costBasisChart = (name, key) => ({
|
|
name: "Cost Basis",
|
|
title: `${name} Cost Basis`,
|
|
top: [
|
|
price({
|
|
metric: dca.periodAveragePrice[key],
|
|
name: "DCA",
|
|
color: colors.green,
|
|
}),
|
|
price({
|
|
metric: lookback[key],
|
|
name: "Lump sum",
|
|
color: colors.orange,
|
|
}),
|
|
],
|
|
});
|
|
|
|
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
|
|
const daysInProfitChart = (name, key) => ({
|
|
name: "Days in Profit",
|
|
title: `${name} Days in Profit`,
|
|
top: [
|
|
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
|
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
|
],
|
|
bottom: [
|
|
line({ metric: dca.periodDaysInProfit[key], name: "DCA", color: colors.green, unit: Unit.days }),
|
|
line({ metric: dca.periodLumpSumDaysInProfit[key], name: "Lump sum", color: colors.orange, unit: Unit.days }),
|
|
],
|
|
});
|
|
|
|
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
|
|
const daysInLossChart = (name, key) => ({
|
|
name: "Days in Loss",
|
|
title: `${name} Days in Loss`,
|
|
top: [
|
|
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
|
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
|
],
|
|
bottom: [
|
|
line({ metric: dca.periodDaysInLoss[key], name: "DCA", color: colors.red, unit: Unit.days }),
|
|
line({ metric: dca.periodLumpSumDaysInLoss[key], name: "Lump sum", color: colors.orange, unit: Unit.days }),
|
|
],
|
|
});
|
|
|
|
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
|
|
const maxDrawdownChart = (name, key) => ({
|
|
name: "Max Drawdown",
|
|
title: `${name} Max Drawdown`,
|
|
top: [
|
|
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
|
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
|
],
|
|
bottom: [
|
|
line({ metric: dca.periodMaxDrawdown[key], name: "DCA", color: colors.green, unit: Unit.percentage }),
|
|
line({ metric: dca.periodLumpSumMaxDrawdown[key], name: "Lump sum", color: colors.orange, unit: Unit.percentage }),
|
|
],
|
|
});
|
|
|
|
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
|
|
const maxReturnChart = (name, key) => ({
|
|
name: "Max Return",
|
|
title: `${name} Max Return`,
|
|
top: [
|
|
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
|
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
|
],
|
|
bottom: [
|
|
line({ metric: dca.periodMaxReturn[key], name: "DCA", color: colors.green, unit: Unit.percentage }),
|
|
line({ metric: dca.periodLumpSumMaxReturn[key], name: "Lump sum", color: colors.orange, unit: Unit.percentage }),
|
|
],
|
|
});
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {ShortPeriodKey | LongPeriodKey} key
|
|
*/
|
|
const stackChart = (name, key) => ({
|
|
name: "Stack",
|
|
title: `${name} Stack`,
|
|
bottom: [
|
|
...satsBtcUsd(dca.periodStack[key], "DCA", colors.green),
|
|
...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange),
|
|
],
|
|
});
|
|
|
|
/**
|
|
* @param {string} id
|
|
* @param {ShortPeriodKey} key
|
|
*/
|
|
const createPeriodTree = (id, key) => {
|
|
const name = periodIdToName(id, true);
|
|
return {
|
|
name,
|
|
tree: [
|
|
costBasisChart(name, key),
|
|
{
|
|
name: "Returns",
|
|
title: `${name} Returns`,
|
|
bottom: [
|
|
baseline({
|
|
metric: dca.periodReturns[key],
|
|
name: "DCA",
|
|
unit: Unit.percentage,
|
|
}),
|
|
baseline({
|
|
metric: dca.periodLumpSumReturns[key],
|
|
name: "Lump sum",
|
|
color: [colors.cyan, colors.orange],
|
|
unit: Unit.percentage,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "Profitability",
|
|
tree: [
|
|
daysInProfitChart(name, key),
|
|
daysInLossChart(name, key),
|
|
maxDrawdownChart(name, key),
|
|
maxReturnChart(name, key),
|
|
],
|
|
},
|
|
stackChart(name, key),
|
|
],
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @param {string} id
|
|
* @param {LongPeriodKey} key
|
|
*/
|
|
const createPeriodTreeWithCagr = (id, key) => {
|
|
const name = periodIdToName(id, true);
|
|
return {
|
|
name,
|
|
tree: [
|
|
costBasisChart(name, key),
|
|
{
|
|
name: "Returns",
|
|
title: `${name} Returns`,
|
|
bottom: [
|
|
baseline({
|
|
metric: dca.periodReturns[key],
|
|
name: "DCA",
|
|
unit: Unit.percentage,
|
|
}),
|
|
baseline({
|
|
metric: dca.periodLumpSumReturns[key],
|
|
name: "Lump sum",
|
|
color: [colors.cyan, colors.orange],
|
|
unit: Unit.percentage,
|
|
}),
|
|
line({
|
|
metric: dca.periodCagr[key],
|
|
name: "DCA CAGR",
|
|
color: colors.purple,
|
|
unit: Unit.percentage,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: returns.cagr[key],
|
|
name: "Lump sum CAGR",
|
|
color: colors.indigo,
|
|
unit: Unit.percentage,
|
|
defaultActive: false,
|
|
}),
|
|
priceLine({ ctx, unit: Unit.percentage }),
|
|
],
|
|
},
|
|
{
|
|
name: "Profitability",
|
|
tree: [
|
|
daysInProfitChart(name, key),
|
|
daysInLossChart(name, key),
|
|
maxDrawdownChart(name, key),
|
|
maxReturnChart(name, key),
|
|
],
|
|
},
|
|
stackChart(name, key),
|
|
],
|
|
};
|
|
};
|
|
|
|
return {
|
|
name: "DCA vs Lump Sum",
|
|
tree: [
|
|
createPeriodTree("1w", "_1w"),
|
|
createPeriodTree("1m", "_1m"),
|
|
createPeriodTree("3m", "_3m"),
|
|
createPeriodTree("6m", "_6m"),
|
|
createPeriodTree("1y", "_1y"),
|
|
createPeriodTreeWithCagr("2y", "_2y"),
|
|
createPeriodTreeWithCagr("3y", "_3y"),
|
|
createPeriodTreeWithCagr("4y", "_4y"),
|
|
createPeriodTreeWithCagr("5y", "_5y"),
|
|
createPeriodTreeWithCagr("6y", "_6y"),
|
|
createPeriodTreeWithCagr("8y", "_8y"),
|
|
createPeriodTreeWithCagr("10y", "_10y"),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create DCA by Year section
|
|
* @param {PartialContext} ctx
|
|
* @param {Object} args
|
|
* @param {Market["dca"]} args.dca
|
|
*/
|
|
export function createDcaByYearSection(ctx, { dca }) {
|
|
const { colors } = ctx;
|
|
const dcaClasses = buildDcaClasses(colors, dca);
|
|
|
|
return {
|
|
name: "DCA by Year",
|
|
tree: [
|
|
// Comparison charts (all years overlaid)
|
|
{
|
|
name: "Compare",
|
|
tree: [
|
|
{
|
|
name: "Cost basis",
|
|
title: "DCA Cost Basis",
|
|
top: dcaClasses.map(({ year, color, defaultActive, costBasis }) =>
|
|
price({
|
|
metric: costBasis,
|
|
name: `${year}`,
|
|
color,
|
|
defaultActive,
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
name: "Returns",
|
|
title: "DCA Returns",
|
|
bottom: dcaClasses.map(({ year, defaultActive, returns }) =>
|
|
baseline({
|
|
metric: returns,
|
|
name: `${year}`,
|
|
defaultActive,
|
|
unit: Unit.percentage,
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
name: "Profitability",
|
|
title: "DCA Profitability",
|
|
bottom: [
|
|
...dcaClasses.map(({ year, color, defaultActive, daysInProfit }) =>
|
|
line({
|
|
metric: daysInProfit,
|
|
name: `${year} Days in Profit`,
|
|
color,
|
|
defaultActive,
|
|
unit: Unit.days,
|
|
}),
|
|
),
|
|
...dcaClasses.map(({ year, color, daysInLoss }) =>
|
|
line({
|
|
metric: daysInLoss,
|
|
name: `${year} Days in Loss`,
|
|
color,
|
|
defaultActive: false,
|
|
unit: Unit.days,
|
|
}),
|
|
),
|
|
],
|
|
},
|
|
{
|
|
name: "Stack",
|
|
title: "DCA Stack",
|
|
bottom: dcaClasses.flatMap(
|
|
({ year, color, defaultActive, stack }) =>
|
|
satsBtcUsd(stack, `${year}`, color, { defaultActive }),
|
|
),
|
|
},
|
|
],
|
|
},
|
|
// Individual year charts
|
|
...dcaClasses.map(
|
|
({
|
|
year,
|
|
color,
|
|
costBasis,
|
|
returns,
|
|
stack,
|
|
daysInProfit,
|
|
daysInLoss,
|
|
maxDrawdown,
|
|
maxReturn,
|
|
}) => ({
|
|
name: `${year}`,
|
|
tree: [
|
|
{
|
|
name: "Cost Basis",
|
|
title: `${year} Cost Basis`,
|
|
top: [
|
|
price({
|
|
metric: costBasis,
|
|
name: "Cost Basis",
|
|
color,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "Returns",
|
|
title: `${year} Returns`,
|
|
bottom: [
|
|
baseline({
|
|
metric: returns,
|
|
name: "Returns",
|
|
unit: Unit.percentage,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "Profitability",
|
|
title: `${year} Profitability`,
|
|
bottom: [
|
|
line({
|
|
metric: daysInProfit,
|
|
name: "Days in Profit",
|
|
color: colors.green,
|
|
unit: Unit.days,
|
|
}),
|
|
line({
|
|
metric: daysInLoss,
|
|
name: "Days in Loss",
|
|
color: colors.red,
|
|
unit: Unit.days,
|
|
}),
|
|
line({
|
|
metric: maxDrawdown,
|
|
name: "Max Drawdown",
|
|
color: colors.purple,
|
|
unit: Unit.percentage,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: maxReturn,
|
|
name: "Max Return",
|
|
color: colors.cyan,
|
|
unit: Unit.percentage,
|
|
defaultActive: false,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "Stack",
|
|
title: `${year} Stack`,
|
|
bottom: satsBtcUsd(stack, "Stack", color),
|
|
},
|
|
],
|
|
}),
|
|
),
|
|
],
|
|
};
|
|
}
|