mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-26 23:59:58 -07:00
212 lines
6.3 KiB
JavaScript
212 lines
6.3 KiB
JavaScript
/** Moving averages section */
|
|
|
|
import { price } from "../series.js";
|
|
import { createRatioChart, createZScoresFolder, formatCohortTitle } from "../shared.js";
|
|
import { periodIdToName } from "./utils.js";
|
|
|
|
/**
|
|
* @param {Colors} colors
|
|
* @param {MarketMovingAverage} ma
|
|
*/
|
|
function buildSmaAverages(colors, ma) {
|
|
return /** @type {const} */ ([
|
|
["1w", 7, "red", ma.price1wSma],
|
|
["8d", 8, "orange", ma.price8dSma],
|
|
["13d", 13, "amber", ma.price13dSma],
|
|
["21d", 21, "yellow", ma.price21dSma],
|
|
["1m", 30, "lime", ma.price1mSma],
|
|
["34d", 34, "green", ma.price34dSma],
|
|
["55d", 55, "emerald", ma.price55dSma],
|
|
["89d", 89, "teal", ma.price89dSma],
|
|
["111d", 111, "cyan", ma.price111dSma],
|
|
["144d", 144, "sky", ma.price144dSma],
|
|
["200d", 200, "blue", ma.price200dSma],
|
|
["350d", 350, "indigo", ma.price350dSma],
|
|
["1y", 365, "violet", ma.price1ySma],
|
|
["2y", 730, "purple", ma.price2ySma],
|
|
["200w", 1400, "fuchsia", ma.price200wSma],
|
|
["4y", 1460, "pink", ma.price4ySma],
|
|
]).map(([id, days, colorKey, ratio]) => ({
|
|
id,
|
|
name: periodIdToName(id, true),
|
|
days,
|
|
color: colors[colorKey],
|
|
ratio,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* @param {Colors} colors
|
|
* @param {MarketMovingAverage} ma
|
|
*/
|
|
function buildEmaAverages(colors, ma) {
|
|
return /** @type {const} */ ([
|
|
["1w", 7, "red", ma.price1wEma],
|
|
["8d", 8, "orange", ma.price8dEma],
|
|
["12d", 12, "amber", ma.price12dEma],
|
|
["13d", 13, "yellow", ma.price13dEma],
|
|
["21d", 21, "lime", ma.price21dEma],
|
|
["26d", 26, "green", ma.price26dEma],
|
|
["1m", 30, "emerald", ma.price1mEma],
|
|
["34d", 34, "teal", ma.price34dEma],
|
|
["55d", 55, "cyan", ma.price55dEma],
|
|
["89d", 89, "sky", ma.price89dEma],
|
|
["144d", 144, "blue", ma.price144dEma],
|
|
["200d", 200, "indigo", ma.price200dEma],
|
|
["1y", 365, "violet", ma.price1yEma],
|
|
["2y", 730, "purple", ma.price2yEma],
|
|
["200w", 1400, "fuchsia", ma.price200wEma],
|
|
["4y", 1460, "pink", ma.price4yEma],
|
|
]).map(([id, days, colorKey, ratio]) => ({
|
|
id,
|
|
name: periodIdToName(id, true),
|
|
days,
|
|
color: colors[colorKey],
|
|
ratio,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* 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 pricePattern = ratio.price;
|
|
|
|
return [
|
|
{
|
|
name: "Price",
|
|
title,
|
|
top: [price({ metric: pricePattern, name: legend, color })],
|
|
},
|
|
createRatioChart(ctx, { title: formatCohortTitle(title), pricePattern, ratio, color }),
|
|
createZScoresFolder(ctx, {
|
|
title,
|
|
legend,
|
|
pricePattern,
|
|
ratio,
|
|
color,
|
|
}),
|
|
];
|
|
}
|
|
|
|
/** Common period IDs to show at top level */
|
|
const COMMON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
|
|
|
|
/** Periods to compare SMA vs EMA */
|
|
const COMPARISON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
|
|
|
|
/**
|
|
* Create SMA vs EMA comparison section
|
|
* @param {ReturnType<typeof buildSmaAverages>} smaAverages
|
|
* @param {ReturnType<typeof buildEmaAverages>} emaAverages
|
|
*/
|
|
function createCompareSection(smaAverages, emaAverages) {
|
|
// Find matching SMA/EMA pairs
|
|
const pairs = COMPARISON_PERIODS.map(id => {
|
|
const sma = smaAverages.find(a => a.id === id);
|
|
const ema = emaAverages.find(a => a.id === id);
|
|
if (!sma || !ema) return null;
|
|
return { id, sma, ema };
|
|
}).filter(/** @type {(p: any) => p is { id: string, sma: ReturnType<typeof buildSmaAverages>[number], ema: ReturnType<typeof buildEmaAverages>[number] }} */ (p) => p !== null);
|
|
|
|
return {
|
|
name: "Compare",
|
|
tree: [
|
|
{
|
|
name: "All Periods",
|
|
title: "SMA vs EMA Comparison",
|
|
top: pairs.flatMap(({ sma, ema }) => [
|
|
price({ metric: sma.ratio.price, name: `${sma.id} SMA`, color: sma.color }),
|
|
price({ metric: ema.ratio.price, name: `${ema.id} EMA`, color: ema.color, options: { lineStyle: 1 } }),
|
|
]),
|
|
},
|
|
...pairs.map(({ id, sma, ema }) => ({
|
|
name: periodIdToName(id, true),
|
|
title: `${periodIdToName(id, true)} SMA vs EMA`,
|
|
top: [
|
|
price({ metric: sma.ratio.price, name: "SMA", color: sma.color }),
|
|
price({ metric: ema.ratio.price, name: "EMA", color: ema.color, options: { lineStyle: 1 } }),
|
|
],
|
|
})),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {PartialContext} ctx
|
|
* @param {MarketMovingAverage} movingAverage
|
|
*/
|
|
export function createAveragesSection(ctx, movingAverage) {
|
|
const { colors } = ctx;
|
|
const smaAverages = buildSmaAverages(colors, movingAverage);
|
|
const emaAverages = buildEmaAverages(colors, movingAverage);
|
|
|
|
/**
|
|
* @param {string} label
|
|
* @param {ReturnType<typeof buildSmaAverages> | ReturnType<typeof buildEmaAverages>} averages
|
|
*/
|
|
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 }) =>
|
|
price({
|
|
metric: ratio.price,
|
|
name: id,
|
|
color,
|
|
}),
|
|
),
|
|
},
|
|
// Common periods at top level
|
|
...commonAverages.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: "Moving Averages",
|
|
tree: [
|
|
createCompareSection(smaAverages, emaAverages),
|
|
createSubSection("SMA", smaAverages),
|
|
createSubSection("EMA", emaAverages),
|
|
],
|
|
};
|
|
}
|