mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-11 14:41:16 -07:00
investing: more data + charts
This commit is contained in:
@@ -85,7 +85,7 @@ export function createPriceWithRatioOptions(
|
||||
|
||||
return [
|
||||
{
|
||||
name: "price",
|
||||
name: "Price",
|
||||
title,
|
||||
top: [line({ metric: priceMetric, name: legend, color, unit: Unit.usd })],
|
||||
},
|
||||
@@ -100,6 +100,9 @@ export function createPriceWithRatioOptions(
|
||||
];
|
||||
}
|
||||
|
||||
/** Common period IDs to show at top level */
|
||||
const COMMON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
|
||||
|
||||
/**
|
||||
* @param {PartialContext} ctx
|
||||
* @param {MarketMovingAverage} movingAverage
|
||||
@@ -113,35 +116,54 @@ export function createAveragesSection(ctx, movingAverage) {
|
||||
* @param {string} label
|
||||
* @param {ReturnType<typeof buildSmaAverages> | ReturnType<typeof buildEmaAverages>} averages
|
||||
*/
|
||||
const createSubSection = (label, averages) => ({
|
||||
name: label,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `Price ${label}s`,
|
||||
top: averages.map(({ id, color, ratio }) =>
|
||||
line({
|
||||
metric: ratio.price,
|
||||
name: id,
|
||||
const createSubSection = (label, averages) => {
|
||||
const commonAverages = averages.filter(({ id }) => COMMON_PERIODS.includes(id));
|
||||
const moreAverages = averages.filter(({ id }) => !COMMON_PERIODS.includes(id));
|
||||
|
||||
return {
|
||||
name: label,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `Price ${label}s`,
|
||||
top: averages.map(({ id, color, ratio }) =>
|
||||
line({
|
||||
metric: ratio.price,
|
||||
name: id,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
// Common periods at top level
|
||||
...commonAverages.map(({ name, color, ratio }) => ({
|
||||
name,
|
||||
tree: createPriceWithRatioOptions(ctx, {
|
||||
ratio,
|
||||
title: `${name} ${label}`,
|
||||
legend: "average",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...averages.map(({ name, color, ratio }) => ({
|
||||
name,
|
||||
tree: createPriceWithRatioOptions(ctx, {
|
||||
ratio,
|
||||
title: `${name} ${label}`,
|
||||
legend: "average",
|
||||
color,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
});
|
||||
})),
|
||||
// Less common periods in "More..." folder
|
||||
{
|
||||
name: "More...",
|
||||
tree: moreAverages.map(({ name, color, ratio }) => ({
|
||||
name,
|
||||
tree: createPriceWithRatioOptions(ctx, {
|
||||
ratio,
|
||||
title: `${name} ${label}`,
|
||||
legend: "average",
|
||||
color,
|
||||
}),
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
name: "Averages",
|
||||
name: "Moving Averages",
|
||||
tree: [
|
||||
createSubSection("SMA", smaAverages),
|
||||
createSubSection("EMA", emaAverages),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Bands indicators (MinMax, Mayer Multiple) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
import { line } from "../../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line } from "../series.js";
|
||||
|
||||
/**
|
||||
* Create Bands section
|
||||
@@ -4,9 +4,12 @@ import { localhost } from "../../utils/env.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { candlestick, line } from "../series.js";
|
||||
import { createAveragesSection } from "./averages.js";
|
||||
import { createPerformanceSection } from "./performance.js";
|
||||
import { createIndicatorsSection } from "./indicators/index.js";
|
||||
import { createInvestingSection } from "./investing.js";
|
||||
import { createReturnsSection } from "./performance.js";
|
||||
import { createMomentumSection } from "./momentum.js";
|
||||
import { createVolatilitySection } from "./volatility.js";
|
||||
import { createBandsSection } from "./bands.js";
|
||||
import { createValuationSection } from "./onchain.js";
|
||||
import { createDcaVsLumpSumSection, createDcaByYearSection } from "./investing.js";
|
||||
|
||||
/**
|
||||
* Create Market section
|
||||
@@ -161,22 +164,29 @@ export function createMarketSection(ctx) {
|
||||
],
|
||||
},
|
||||
|
||||
// Averages
|
||||
// Moving Averages
|
||||
createAveragesSection(ctx, movingAverage),
|
||||
|
||||
// Performance
|
||||
createPerformanceSection(ctx, returns),
|
||||
// Returns
|
||||
createReturnsSection(ctx, returns),
|
||||
|
||||
// Indicators
|
||||
createIndicatorsSection(ctx, {
|
||||
volatility,
|
||||
range,
|
||||
movingAverage,
|
||||
indicators,
|
||||
}),
|
||||
// Volatility
|
||||
createVolatilitySection(ctx, { volatility, range }),
|
||||
|
||||
// Investing
|
||||
createInvestingSection(ctx, { dca, lookback, returns }),
|
||||
// Momentum
|
||||
createMomentumSection(ctx, indicators),
|
||||
|
||||
// Bands
|
||||
createBandsSection(ctx, { range, movingAverage }),
|
||||
|
||||
// Valuation
|
||||
createValuationSection(ctx, { indicators, movingAverage }),
|
||||
|
||||
// DCA vs Lump Sum
|
||||
createDcaVsLumpSumSection(ctx, { dca, lookback, returns }),
|
||||
|
||||
// DCA by Year
|
||||
createDcaByYearSection(ctx, { dca }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/** 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 }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -32,20 +32,115 @@ export function buildDcaClasses(colors, dca) {
|
||||
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 Investing section
|
||||
* 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 createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
const { colors } = ctx;
|
||||
const dcaClasses = buildDcaClasses(colors, dca);
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {ShortPeriodKey | LongPeriodKey} key
|
||||
*/
|
||||
const costBasisChart = (name, key) => ({
|
||||
name: "Cost Basis",
|
||||
title: `${name} Cost Basis`,
|
||||
top: [
|
||||
line({
|
||||
metric: dca.periodAveragePrice[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: lookback[key],
|
||||
name: "Lump sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
|
||||
const daysInProfitChart = (name, key) => ({
|
||||
name: "Days in Profit",
|
||||
title: `${name} Days in Profit`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
],
|
||||
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: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
],
|
||||
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: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
],
|
||||
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: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
],
|
||||
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
|
||||
@@ -56,31 +151,34 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: `${name} Cost Basis`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
costBasisChart(name, key),
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }),
|
||||
baseline({ metric: returns.priceReturns[key], name: "Lump sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
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: "Stack",
|
||||
title: `${name} Stack`,
|
||||
bottom: [
|
||||
...satsBtcUsd(dca.periodStack[key], "DCA", colors.green),
|
||||
...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange),
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
daysInProfitChart(name, key),
|
||||
daysInLossChart(name, key),
|
||||
maxDrawdownChart(name, key),
|
||||
maxReturnChart(name, key),
|
||||
],
|
||||
},
|
||||
stackChart(name, key),
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -94,142 +192,226 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: `${name} Cost Basis`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
costBasisChart(name, key),
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }),
|
||||
baseline({ metric: returns.priceReturns[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 }),
|
||||
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: "Stack",
|
||||
title: `${name} Stack`,
|
||||
bottom: [
|
||||
...satsBtcUsd(dca.periodStack[key], "DCA", colors.green),
|
||||
...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange),
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
daysInProfitChart(name, key),
|
||||
daysInLossChart(name, key),
|
||||
maxDrawdownChart(name, key),
|
||||
maxReturnChart(name, key),
|
||||
],
|
||||
},
|
||||
stackChart(name, key),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
name: "Investing",
|
||||
name: "DCA vs Lump Sum",
|
||||
tree: [
|
||||
// DCA vs Lump sum
|
||||
{
|
||||
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"),
|
||||
],
|
||||
},
|
||||
|
||||
// DCA classes
|
||||
{
|
||||
name: "DCA classes",
|
||||
tree: [
|
||||
// Comparison charts (all years overlaid)
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: "DCA Cost Basis",
|
||||
top: dcaClasses.map(
|
||||
({ year, color, defaultActive, costBasis }) =>
|
||||
line({
|
||||
metric: costBasis,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: "DCA Returns",
|
||||
bottom: dcaClasses.map(
|
||||
({ year, color, defaultActive, returns }) =>
|
||||
baseline({
|
||||
metric: returns,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
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 }) => ({
|
||||
name: `${year}`,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: `${year} Cost Basis`,
|
||||
top: [
|
||||
line({
|
||||
metric: costBasis,
|
||||
name: "Cost basis",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${year} Returns`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: returns,
|
||||
name: "Returns",
|
||||
color,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `${year} Stack`,
|
||||
bottom: satsBtcUsd(stack, "Stack", color),
|
||||
},
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
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 }) =>
|
||||
line({
|
||||
metric: costBasis,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
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: [
|
||||
line({
|
||||
metric: costBasis,
|
||||
name: "Cost Basis",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
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),
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
import { priceLine, priceLines } from "../../constants.js";
|
||||
import { line, histogram } from "../../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine, priceLines } from "../constants.js";
|
||||
import { line, histogram } from "../series.js";
|
||||
|
||||
/**
|
||||
* Create Momentum section
|
||||
@@ -1,20 +1,20 @@
|
||||
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
import { baseline, line } from "../../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { baseline, line } from "../series.js";
|
||||
|
||||
/**
|
||||
* Create On-chain section
|
||||
* Create Valuation section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["indicators"]} args.indicators
|
||||
* @param {Market["movingAverage"]} args.movingAverage
|
||||
*/
|
||||
export function createOnchainSection(ctx, { indicators, movingAverage }) {
|
||||
export function createValuationSection(ctx, { indicators, movingAverage }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
return {
|
||||
name: "On-chain",
|
||||
name: "Valuation",
|
||||
tree: [
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
@@ -6,55 +6,100 @@ import { baseline } from "../series.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Create Performance section
|
||||
* Create Returns section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Market["returns"]} returns
|
||||
*/
|
||||
export function createPerformanceSection(ctx, returns) {
|
||||
export function createReturnsSection(ctx, returns) {
|
||||
const { colors } = ctx;
|
||||
|
||||
const shortTermPeriods = /** @type {const} */ ([
|
||||
["1d", "_1d", undefined],
|
||||
["1w", "_1w", undefined],
|
||||
["1m", "_1m", undefined],
|
||||
]);
|
||||
|
||||
const mediumTermPeriods = /** @type {const} */ ([
|
||||
["3m", "_3m", undefined],
|
||||
["6m", "_6m", undefined],
|
||||
["1y", "_1y", undefined],
|
||||
]);
|
||||
|
||||
const longTermPeriods = /** @type {const} */ ([
|
||||
["2y", "_2y", "_2y"],
|
||||
["3y", "_3y", "_3y"],
|
||||
["4y", "_4y", "_4y"],
|
||||
["5y", "_5y", "_5y"],
|
||||
["6y", "_6y", "_6y"],
|
||||
["8y", "_8y", "_8y"],
|
||||
["10y", "_10y", "_10y"],
|
||||
]);
|
||||
|
||||
/**
|
||||
* @template {keyof typeof returns.priceReturns} K
|
||||
* @param {readonly [string, K, K | undefined]} period
|
||||
*/
|
||||
const createPeriodChart = ([id, returnKey, cagrKey]) => {
|
||||
const priceReturns = returns.priceReturns[/** @type {K} */ (returnKey)];
|
||||
const cagr = cagrKey ? returns.cagr[/** @type {keyof typeof returns.cagr} */ (cagrKey)] : undefined;
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: priceReturns,
|
||||
name: "Total",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
...(cagr
|
||||
? [
|
||||
baseline({
|
||||
metric: cagr,
|
||||
name: "CAGR",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
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} Returns`,
|
||||
name: "Returns",
|
||||
tree: [
|
||||
// Compare all periods
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Returns Comparison",
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: priceReturns,
|
||||
name: "Total",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
...(cagr
|
||||
? [
|
||||
baseline({
|
||||
metric: cagr,
|
||||
name: "CAGR",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
baseline({ metric: returns.priceReturns._1d, name: "1d", color: colors.red, unit: Unit.percentage }),
|
||||
baseline({ metric: returns.priceReturns._1w, name: "1w", color: colors.orange, unit: Unit.percentage }),
|
||||
baseline({ metric: returns.priceReturns._1m, name: "1m", color: colors.yellow, unit: Unit.percentage }),
|
||||
baseline({ metric: returns.priceReturns._3m, name: "3m", color: colors.lime, unit: Unit.percentage, defaultActive: false }),
|
||||
baseline({ metric: returns.priceReturns._6m, name: "6m", color: colors.green, unit: Unit.percentage, defaultActive: false }),
|
||||
baseline({ metric: returns.priceReturns._1y, name: "1y", color: colors.teal, unit: Unit.percentage }),
|
||||
baseline({ metric: returns.priceReturns._4y, name: "4y", color: colors.blue, unit: Unit.percentage }),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
},
|
||||
// Short-term (1d, 1w, 1m)
|
||||
{
|
||||
name: "Short-term",
|
||||
tree: shortTermPeriods.map(createPeriodChart),
|
||||
},
|
||||
// Medium-term (3m, 6m, 1y)
|
||||
{
|
||||
name: "Medium-term",
|
||||
tree: mediumTermPeriods.map(createPeriodChart),
|
||||
},
|
||||
// Long-term (2y+)
|
||||
{
|
||||
name: "Long-term",
|
||||
tree: longTermPeriods.map(createPeriodChart),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
import { priceLine, priceLines } from "../../constants.js";
|
||||
import { line } from "../../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine, priceLines } from "../constants.js";
|
||||
import { line } from "../series.js";
|
||||
|
||||
/**
|
||||
* Create Volatility section
|
||||
Reference in New Issue
Block a user