global: 4y zscore + 200d sma + mayer's multiple

This commit is contained in:
nym21
2025-06-17 10:01:49 +02:00
parent 7e1fb6472d
commit bbe9f1bad2
8 changed files with 945 additions and 295 deletions
@@ -24,8 +24,10 @@ pub struct ComputedRatioVecsFromDateIndex {
pub ratio_1w_sma: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_1m_sma: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_1y_sma: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_4y_sma: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_1y_sma_momentum_oscillator: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_standard_deviation: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_sd: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_4y_sd: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_p99_9: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_p99_5: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_p99: ComputedVecsFromDateIndex<StoredF32>,
@@ -51,6 +53,7 @@ pub struct ComputedRatioVecsFromDateIndex {
pub ratio_m2sd_as_price: ComputedVecsFromDateIndex<Dollars>,
pub ratio_m3sd_as_price: ComputedVecsFromDateIndex<Dollars>,
pub ratio_zscore: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_4y_zscore: ComputedVecsFromDateIndex<StoredF32>,
}
const VERSION: Version = Version::ZERO;
@@ -116,6 +119,14 @@ impl ComputedRatioVecsFromDateIndex {
format,
options,
)?,
ratio_4y_sma: ComputedVecsFromDateIndex::forced_import(
path,
&format!("{name}_ratio_4y_sma"),
true,
version + VERSION + Version::ZERO,
format,
options,
)?,
ratio_1y_sma_momentum_oscillator: ComputedVecsFromDateIndex::forced_import(
path,
&format!("{name}_ratio_1y_sma_momentum_oscillator"),
@@ -124,9 +135,17 @@ impl ComputedRatioVecsFromDateIndex {
format,
options,
)?,
ratio_standard_deviation: ComputedVecsFromDateIndex::forced_import(
ratio_sd: ComputedVecsFromDateIndex::forced_import(
path,
&format!("{name}_ratio_standard_deviation"),
&format!("{name}_ratio_sd"),
true,
version + VERSION + Version::ZERO,
format,
options,
)?,
ratio_4y_sd: ComputedVecsFromDateIndex::forced_import(
path,
&format!("{name}_ratio_4y_sd"),
true,
version + VERSION + Version::ZERO,
format,
@@ -332,6 +351,14 @@ impl ComputedRatioVecsFromDateIndex {
format,
options,
)?,
ratio_4y_zscore: ComputedVecsFromDateIndex::forced_import(
path,
&format!("{name}_ratio_4y_zscore"),
true,
version + VERSION + Version::ZERO,
format,
options,
)?,
})
}
@@ -476,6 +503,22 @@ impl ComputedRatioVecsFromDateIndex {
},
)?;
self.ratio_4y_sma.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_sma_(
starting_indexes.dateindex,
self.ratio.dateindex.as_ref().unwrap(),
4 * 365,
exit,
Some(min_ratio_date),
)
},
)?;
self.ratio_1y_sma_momentum_oscillator.compute_all(
indexer,
indexes,
@@ -528,6 +571,8 @@ impl ComputedRatioVecsFromDateIndex {
let mut sma_iter = self.ratio_sma.dateindex.as_ref().unwrap().into_iter();
let mut _4y_sma_iter = self.ratio_4y_sma.dateindex.as_ref().unwrap().into_iter();
let nan = StoredF32::from(f32::NAN);
self.ratio
.dateindex
@@ -566,7 +611,12 @@ impl ComputedRatioVecsFromDateIndex {
.as_mut()
.unwrap()
.forced_push_at(index, nan, exit)?;
self.ratio_standard_deviation
self.ratio_sd
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, nan, exit)?;
self.ratio_4y_sd
.dateindex
.as_mut()
.unwrap()
@@ -645,12 +695,26 @@ impl ComputedRatioVecsFromDateIndex {
.sqrt(),
);
self.ratio_standard_deviation
self.ratio_sd
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, sd, exit)?;
let _4y_avg = _4y_sma_iter.unwrap_get_inner(index);
let _4y_sd = StoredF32::from(
(sorted.iter().map(|v| (**v - *_4y_avg).powi(2)).sum::<f32>()
/ (index.unwrap_to_usize() + 1) as f32)
.sqrt(),
);
self.ratio_4y_sd
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, _4y_sd, exit)?;
self.ratio_p1sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg + sd,
@@ -726,7 +790,13 @@ impl ComputedRatioVecsFromDateIndex {
exit,
None as Option<&EagerVec<_, _>>,
)?;
self.ratio_standard_deviation.compute_rest(
self.ratio_sd.compute_rest(
indexes,
starting_indexes,
exit,
None as Option<&EagerVec<_, _>>,
)?;
self.ratio_4y_sd.compute_rest(
indexes,
starting_indexes,
exit,
@@ -1007,21 +1077,27 @@ impl ComputedRatioVecsFromDateIndex {
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
let mut sma_iter = self.ratio_sma.dateindex.as_ref().unwrap().into_iter();
let mut sd_iter = self
.ratio_standard_deviation
.dateindex
.as_ref()
.unwrap()
.into_iter();
vec.compute_transform(
vec.compute_zscore(
starting_indexes.dateindex,
self.ratio.dateindex.as_ref().unwrap(),
|(i, ratio, ..)| {
let sma = sma_iter.unwrap_get_inner(i);
let sd = sd_iter.unwrap_get_inner(i);
(i, (ratio - sma) / sd)
},
self.ratio_sma.dateindex.as_ref().unwrap(),
self.ratio_sd.dateindex.as_ref().unwrap(),
exit,
)
},
)?;
self.ratio_4y_zscore.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_zscore(
starting_indexes.dateindex,
self.ratio.dateindex.as_ref().unwrap(),
self.ratio_4y_sma.dateindex.as_ref().unwrap(),
self.ratio_4y_sd.dateindex.as_ref().unwrap(),
exit,
)
},
@@ -1032,7 +1108,8 @@ impl ComputedRatioVecsFromDateIndex {
fn mut_ratio_vecs(&mut self) -> Vec<&mut EagerVec<DateIndex, StoredF32>> {
vec![
self.ratio_standard_deviation.dateindex.as_mut().unwrap(),
self.ratio_sd.dateindex.as_mut().unwrap(),
self.ratio_4y_sd.dateindex.as_mut().unwrap(),
self.ratio_p99_9.dateindex.as_mut().unwrap(),
self.ratio_p99_5.dateindex.as_mut().unwrap(),
self.ratio_p99.dateindex.as_mut().unwrap(),
@@ -1056,8 +1133,10 @@ impl ComputedRatioVecsFromDateIndex {
self.ratio_1w_sma.vecs(),
self.ratio_1m_sma.vecs(),
self.ratio_1y_sma.vecs(),
self.ratio_4y_sma.vecs(),
self.ratio_1y_sma_momentum_oscillator.vecs(),
self.ratio_standard_deviation.vecs(),
self.ratio_sd.vecs(),
self.ratio_4y_sd.vecs(),
self.ratio_p99_9.vecs(),
self.ratio_p99_5.vecs(),
self.ratio_p99.vecs(),
@@ -1083,6 +1162,7 @@ impl ComputedRatioVecsFromDateIndex {
self.ratio_m2sd_as_price.vecs(),
self.ratio_m3sd_as_price.vecs(),
self.ratio_zscore.vecs(),
self.ratio_4y_zscore.vecs(),
]
.into_iter()
.flatten()
+78 -1
View File
@@ -36,11 +36,15 @@ pub struct Vecs {
pub indexes_to_55d_sma: ComputedRatioVecsFromDateIndex,
pub indexes_to_89d_sma: ComputedRatioVecsFromDateIndex,
pub indexes_to_144d_sma: ComputedRatioVecsFromDateIndex,
pub indexes_to_200d_sma: ComputedRatioVecsFromDateIndex,
pub indexes_to_1y_sma: ComputedRatioVecsFromDateIndex,
pub indexes_to_2y_sma: ComputedRatioVecsFromDateIndex,
pub indexes_to_200w_sma: ComputedRatioVecsFromDateIndex,
pub indexes_to_4y_sma: ComputedRatioVecsFromDateIndex,
pub indexes_to_200d_sma_x2_4: ComputedVecsFromDateIndex<Dollars>,
pub indexes_to_200d_sma_x0_8: ComputedVecsFromDateIndex<Dollars>,
pub price_1d_ago: ComputedVecsFromDateIndex<Dollars>,
pub price_1w_ago: ComputedVecsFromDateIndex<Dollars>,
pub price_1m_ago: ComputedVecsFromDateIndex<Dollars>,
@@ -306,6 +310,14 @@ impl Vecs {
format,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_200d_sma: ComputedRatioVecsFromDateIndex::forced_import(
path,
"200d_sma",
true,
version + VERSION + Version::ZERO,
format,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_1y_sma: ComputedRatioVecsFromDateIndex::forced_import(
path,
"1y_sma",
@@ -1215,6 +1227,23 @@ impl Vecs {
format,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_200d_sma_x2_4: ComputedVecsFromDateIndex::forced_import(
path,
"200d_sma_x2_4",
true,
version + VERSION + Version::ZERO,
format,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_200d_sma_x0_8: ComputedVecsFromDateIndex::forced_import(
path,
"200d_sma_x0_8",
true,
version + VERSION + Version::ZERO,
format,
StorableVecGeneatorOptions::default().add_last(),
)?,
})
}
@@ -1771,6 +1800,7 @@ impl Vecs {
(&mut self.indexes_to_55d_sma, 55),
(&mut self.indexes_to_89d_sma, 89),
(&mut self.indexes_to_144d_sma, 144),
(&mut self.indexes_to_200d_sma, 200),
(&mut self.indexes_to_1y_sma, 365),
(&mut self.indexes_to_2y_sma, 2 * 365),
(&mut self.indexes_to_200w_sma, 200 * 7),
@@ -1797,7 +1827,51 @@ impl Vecs {
});
});
Ok(())
})
})?;
self.indexes_to_200d_sma_x0_8.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.dateindex,
self.indexes_to_200d_sma
.price
.as_ref()
.unwrap()
.dateindex
.as_ref()
.unwrap(),
|(i, v, ..)| (i, v * 0.8),
exit,
)
},
)?;
self.indexes_to_200d_sma_x2_4.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.dateindex,
self.indexes_to_200d_sma
.price
.as_ref()
.unwrap()
.dateindex
.as_ref()
.unwrap(),
|(i, v, ..)| (i, v * 2.4),
exit,
)
},
)?;
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
@@ -1817,10 +1891,13 @@ impl Vecs {
self.indexes_to_55d_sma.vecs(),
self.indexes_to_89d_sma.vecs(),
self.indexes_to_144d_sma.vecs(),
self.indexes_to_200d_sma.vecs(),
self.indexes_to_1y_sma.vecs(),
self.indexes_to_2y_sma.vecs(),
self.indexes_to_200w_sma.vecs(),
self.indexes_to_4y_sma.vecs(),
self.indexes_to_200d_sma_x0_8.vecs(),
self.indexes_to_200d_sma_x2_4.vecs(),
self.price_1d_ago.vecs(),
self.price_1w_ago.vecs(),
self.price_1m_ago.vecs(),
+19 -5
View File
@@ -178,6 +178,17 @@ impl Mul<usize> for Close<Dollars> {
}
}
impl Mul<f64> for Dollars {
type Output = Dollars;
fn mul(self, rhs: f64) -> Self::Output {
if rhs.fract() != 0.0 {
Self::from(self.0 * rhs)
} else {
self * rhs as i64
}
}
}
impl Mul<Bitcoin> for Dollars {
type Output = Self;
fn mul(self, rhs: Bitcoin) -> Self::Output {
@@ -208,11 +219,14 @@ impl Mul<Sats> for Dollars {
impl Mul<StoredF32> for Dollars {
type Output = Self;
fn mul(self, rhs: StoredF32) -> Self::Output {
if rhs.fract() != 0.0 {
Self::from(self.0 * *rhs as f64)
} else {
self * *rhs as i64
}
self * *rhs as f64
}
}
impl Mul<StoredF64> for Dollars {
type Output = Self;
fn mul(self, rhs: StoredF64) -> Self::Output {
self * *rhs
}
}
+26
View File
@@ -1015,6 +1015,32 @@ where
self.safe_flush(exit)
}
pub fn compute_zscore(
&mut self,
max_from: I,
ratio: &impl AnyIterableVec<I, StoredF32>,
sma: &impl AnyIterableVec<I, StoredF32>,
sd: &impl AnyIterableVec<I, StoredF32>,
exit: &Exit,
) -> Result<()>
where
T: From<StoredF32>,
{
let mut sma_iter = sma.iter();
let mut sd_iter = sd.iter();
self.compute_transform(
max_from,
ratio,
|(i, ratio, ..)| {
let sma = sma_iter.unwrap_get_inner(i);
let sd = sd_iter.unwrap_get_inner(i);
(i, T::from((ratio - sma) / sd))
},
exit,
)
}
}
impl EagerVec<DateIndex, Sats> {
@@ -141,7 +141,7 @@ function createChartElement({
}
: {}),
// ..._options,
})
}),
);
ichart.priceScale("right").applyOptions({
@@ -173,7 +173,7 @@ function createChartElement({
},
},
});
}
},
);
signals.createEffect(index, (index) => {
@@ -181,12 +181,12 @@ function createChartElement({
index === /** @satisfies {MonthIndex} */ (7)
? 1
: index === /** @satisfies {QuarterIndex} */ (19)
? 2
: index === /** @satisfies {YearIndex} */ (23)
? 6
: index === /** @satisfies {DecadeIndex} */ (1)
? 60
: 0.5;
? 2
: index === /** @satisfies {YearIndex} */ (23)
? 6
: index === /** @satisfies {DecadeIndex} */ (1)
? 60
: 0.5;
ichart.applyOptions({
timeScale: {
@@ -209,7 +209,7 @@ function createChartElement({
activeResources.forEach((v) => {
v.fetch();
});
})
}),
);
if (fitContent) {
@@ -240,7 +240,8 @@ function createChartElement({
const children = Array.from(parent.childNodes).filter(
(element) =>
/** @type {HTMLElement} */ (element).dataset.position === position
/** @type {HTMLElement} */ (element).dataset.position ===
position,
);
if (children.length === 1) {
@@ -262,7 +263,7 @@ function createChartElement({
fieldset.append(createChild(pane));
}),
paneIndex ? 50 : 0
paneIndex ? 50 : 0,
);
}
@@ -346,7 +347,7 @@ function createChartElement({
// Or remove ?
iseries.applyOptions({
visible: active,
})
}),
);
iseries.setSeriesOrder(order);
@@ -377,7 +378,7 @@ function createChartElement({
index,
index === /** @satisfies {Height} */ (5)
? "timestamp-fixed"
: "timestamp"
: "timestamp",
);
timeResource.fetch();
@@ -400,13 +401,17 @@ function createChartElement({
({ _indexes, values }) => {
if (!_indexes?.length || !values?.length) return;
const consoleTimeLabel = `${vecId}-time`;
console.time(consoleTimeLabel);
const indexes = /** @type {number[]} */ (_indexes);
let length = Math.min(indexes.length, values.length);
// TODO: Don't create new Array if data already present, update instead
/** @type {LineData[] | CandlestickData[]} */
const data = new Array(length);
const data = /** @type {LineData[] | CandlestickData[]} */ (
Array.from({ length })
);
let prevTime = null;
let timeOffset = 0;
@@ -489,54 +494,81 @@ function createChartElement({
while (i < data.length) {
const dataI = data[i];
const iTime = dataI.time;
const seriesDataJ = /** @type {typeof dataI} */ (
seriesData[j]
);
const jTime = seriesDataJ.time;
if (iTime === jTime) {
const historicalUpdate = iTime < last.time;
if ("value" in dataI) {
if (
// @ts-ignore
dataI.value !== seriesDataJ.value &&
// @ts-ignore
(!isNaN(dataI.value) || !isNaN(seriesDataJ.value))
) {
// console.log(vecId);
iseries.update(dataI, historicalUpdate);
}
} else if (
// @ts-ignore
dataI.open !== seriesDataJ.open ||
// @ts-ignore
dataI.high !== seriesDataJ.high ||
// @ts-ignore
dataI.low !== seriesDataJ.low ||
// @ts-ignore
dataI.close !== seriesDataJ.close
) {
// console.log({
// vecId,
// dataI,
// i,
// data,
// j,
// seriesDataJ,
// seriesData,
// });
iseries.update(dataI, historicalUpdate);
}
i++;
j++;
} else if (iTime < jTime) {
iseries.update(dataI, true);
i++;
} else if (iTime > last.time) {
if (iTime > last.time) {
console.log(0, {
vecId,
dataI,
i,
data,
});
iseries.update(dataI);
i++;
} else if (iTime > jTime) {
j++;
} else {
const seriesDataJ = /** @type {typeof dataI} */ (
seriesData[j]
);
const jTime = seriesDataJ.time;
if (iTime === jTime) {
const historicalUpdate = iTime < last.time;
if ("value" in dataI) {
if (
// @ts-ignore
dataI.value !== seriesDataJ.value &&
// @ts-ignore
(!isNaN(dataI.value) || !isNaN(seriesDataJ.value))
) {
console.log(1, {
vecId,
dataI,
i,
data,
j,
seriesDataJ,
seriesData,
});
iseries.update(dataI, historicalUpdate);
}
} else if (
// @ts-ignore
dataI.open !== seriesDataJ.open ||
// @ts-ignore
dataI.high !== seriesDataJ.high ||
// @ts-ignore
dataI.low !== seriesDataJ.low ||
// @ts-ignore
dataI.close !== seriesDataJ.close
) {
console.log(2, {
vecId,
dataI,
i,
data,
j,
seriesDataJ,
seriesData,
});
iseries.update(dataI, historicalUpdate);
}
i++;
j++;
} else if (iTime < jTime) {
console.log(3, {
vecId,
dataI,
i,
data,
j,
seriesDataJ,
seriesData,
});
iseries.update(dataI, true);
i++;
} else if (iTime > jTime) {
j++;
} else {
throw Error("Unreachable");
}
}
}
}
@@ -546,7 +578,9 @@ function createChartElement({
index,
unit,
});
}
console.timeEnd(consoleTimeLabel);
},
);
} else {
activeResources.delete(valuesResource);
@@ -639,7 +673,7 @@ function createChartElement({
borderVisible: false,
visible: defaultActive !== false,
},
paneIndex
paneIndex,
)
);
@@ -695,7 +729,7 @@ function createChartElement({
color: color(),
...options,
},
paneIndex
paneIndex,
)
);
@@ -763,7 +797,7 @@ function createChartElement({
topFillColor2: "transparent",
lineVisible: true,
},
paneIndex
paneIndex,
)
);
@@ -926,7 +960,7 @@ function createLegend({ signals, utils }) {
} else {
spanColor.style.backgroundColor = tameColor(color);
}
}
},
);
});
@@ -1086,17 +1120,17 @@ function numberToShortUSFormat(value, digits) {
if (modulused === 0) {
return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex),
3
3,
)}${letter}`;
} else if (modulused === 1) {
return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex),
2
2,
)}${letter}`;
} else {
return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex),
1
1,
)}${letter}`;
}
}
@@ -1146,7 +1180,7 @@ function createOklchToRGBA() {
return rgb.map((c) =>
Math.abs(c) > 0.0031308
? (c < 0 ? -1 : 1) * (1.055 * Math.abs(c) ** (1 / 2.4) - 0.055)
: 12.92 * c
: 12.92 * c,
);
}
/**
@@ -1158,7 +1192,7 @@ function createOklchToRGBA() {
1, 0.3963377773761749, 0.2158037573099136, 1, -0.1055613458156586,
-0.0638541728258133, 1, -0.0894841775298119, -1.2914855480194092,
]),
lab
lab,
);
const LMS = /** @type {[number, number, number]} */ (
LMSg.map((val) => val ** 3)
@@ -1169,7 +1203,7 @@ function createOklchToRGBA() {
-0.0405757452148008, 1.112286803280317, -0.0717110580655164,
-0.0763729366746601, -0.4214933324022432, 1.5869240198367816,
]),
LMS
LMS,
);
}
/**
@@ -1182,7 +1216,7 @@ function createOklchToRGBA() {
-0.9692436362808796, 1.8759675015077202, 0.04155505740717559,
0.05563007969699366, -0.20397695888897652, 1.0569715142428786,
],
xyz
xyz,
);
}
@@ -1205,8 +1239,8 @@ function createOklchToRGBA() {
});
const rgb = srgbLinear2rgb(
xyz2rgbLinear(
oklab2xyz(oklch2oklab(/** @type {[number, number, number]} */ (lch)))
)
oklab2xyz(oklch2oklab(/** @type {[number, number, number]} */ (lch))),
),
).map((v) => {
return Math.max(Math.min(Math.round(v * 255), 255), 0);
});
+46 -45
View File
@@ -63,14 +63,14 @@ function initPackages() {
const imports = {
async signals() {
return import("../packages/solid-signals/wrapper.js").then(
(d) => d.default
(d) => d.default,
);
},
async lightweightCharts() {
return window.document.fonts.ready.then(() =>
import("../packages/lightweight-charts/wrapper.js").then(
(d) => d.default
)
(d) => d.default,
),
);
},
async leanQr() {
@@ -78,7 +78,7 @@ function initPackages() {
},
async ufuzzy() {
return import("../packages/ufuzzy/v1.0.18/script.js").then(
({ default: d }) => d
({ default: d }) => d,
);
},
};
@@ -586,7 +586,7 @@ function createUtils() {
window.history.pushState(
null,
"",
`${pathname}?${urlParams.toString()}`
`${pathname}?${urlParams.toString()}`,
);
} catch (_) {}
},
@@ -603,7 +603,7 @@ function createUtils() {
window.history.replaceState(
null,
"",
`${pathname}?${urlParams.toString()}`
`${pathname}?${urlParams.toString()}`,
);
} catch (_) {}
},
@@ -752,7 +752,8 @@ function createUtils() {
(id.includes("realized") &&
!id.includes("ratio") &&
!id.includes("relative-to")) ||
(id.endsWith("sma") && !id.includes("ratio")) ||
((id.endsWith("sma") || id.includes("sma-x")) &&
!id.includes("ratio")) ||
id === "ath")
) {
if (unit) throw Error(`Unit "${unit}" already assigned "${id}"`);
@@ -1242,8 +1243,8 @@ function createUtils() {
today.getUTCDate(),
0,
0,
0
)
0,
),
);
},
/**
@@ -1336,7 +1337,7 @@ function createUtils() {
*/
function getNumberOfDaysBetweenTwoDates(oldest, youngest) {
return Math.round(
Math.abs((youngest.getTime() - oldest.getTime()) / date.ONE_DAY_IN_MS)
Math.abs((youngest.getTime() - oldest.getTime()) / date.ONE_DAY_IN_MS),
);
}
@@ -1554,7 +1555,7 @@ function createVecsResources(signals, utils) {
const fetchedRecord = signals.createSignal(
/** @type {Map<string, {loading: boolean, at: Date | null, vec: Signal<T[] | null>}>} */ (
new Map()
)
),
);
return {
@@ -1602,7 +1603,7 @@ function createVecsResources(signals, utils) {
index,
id,
from,
to
to,
)
);
fetched.at = new Date();
@@ -1863,7 +1864,7 @@ function initWebSockets(signals, utils) {
window.document.addEventListener(
"visibilitychange",
reinitWebSocketIfDocumentNotHidden
reinitWebSocketIfDocumentNotHidden,
);
window.document.addEventListener("online", reinitWebSocket);
@@ -1872,7 +1873,7 @@ function initWebSockets(signals, utils) {
ws?.close();
window.document.removeEventListener(
"visibilitychange",
reinitWebSocketIfDocumentNotHidden
reinitWebSocketIfDocumentNotHidden,
);
window.document.removeEventListener("online", reinitWebSocket);
live.set(false);
@@ -1898,7 +1899,7 @@ function initWebSockets(signals, utils) {
symbol: ["BTC/USD"],
interval: 1440,
},
})
}),
);
});
@@ -1927,7 +1928,7 @@ function initWebSockets(signals, utils) {
/** @type {ReturnType<typeof createWebsocket<CandlestickData>>} */
const kraken1dCandle = createWebsocket((callback) =>
krakenCandleWebSocketCreator(callback)
krakenCandleWebSocketCreator(callback),
);
kraken1dCandle.open();
@@ -1986,7 +1987,7 @@ function main() {
}
const frame = window.document.getElementById(
/** @type {string} */ (input.value)
/** @type {string} */ (input.value),
);
if (!frame) {
@@ -2084,23 +2085,23 @@ function main() {
function initDark() {
const preferredColorSchemeMatchMedia = window.matchMedia(
"(prefers-color-scheme: dark)"
"(prefers-color-scheme: dark)",
);
const dark = signals.createSignal(
preferredColorSchemeMatchMedia.matches
preferredColorSchemeMatchMedia.matches,
);
preferredColorSchemeMatchMedia.addEventListener(
"change",
({ matches }) => {
dark.set(matches);
}
},
);
return dark;
}
const dark = initDark();
const qrcode = signals.createSignal(
/** @type {string | null} */ (null)
/** @type {string | null} */ (null),
);
function createLastHeightResource() {
@@ -2111,7 +2112,7 @@ function main() {
lastHeight.set(h);
},
/** @satisfies {Height} */ (5),
"height"
"height",
);
}
fetchLastHeight();
@@ -2155,10 +2156,10 @@ function main() {
const owner = signals.getOwner();
const chartOption = signals.createSignal(
/** @type {ChartOption | null} */ (null)
/** @type {ChartOption | null} */ (null),
);
const simOption = signals.createSignal(
/** @type {SimulationOption | null} */ (null)
/** @type {SimulationOption | null} */ (null),
);
let previousElement = /** @type {HTMLElement | undefined} */ (
@@ -2204,9 +2205,9 @@ function main() {
webSockets,
vecsResources,
vecIdToIndexes,
})
)
)
}),
),
),
);
}
firstTimeLoadingChart = false;
@@ -2227,8 +2228,8 @@ function main() {
vecsResources,
option,
vecIdToIndexes,
})
)
}),
),
);
}
firstTimeLoadingTable = false;
@@ -2252,9 +2253,9 @@ function main() {
signals,
utils,
vecsResources,
})
)
)
}),
),
),
);
}
firstTimeLoadingSimulation = false;
@@ -2283,7 +2284,7 @@ function main() {
createMobileSwitchEffect();
utils.dom.onFirstIntersection(elements.aside, () =>
signals.runWithOwner(owner, initSelectedFrame)
signals.runWithOwner(owner, initSelectedFrame),
);
}
initSelected();
@@ -2361,7 +2362,7 @@ function main() {
if (indexes?.length) {
const maxIndex = Math.min(
(order || indexes).length - 1,
minIndex + RESULTS_PER_PAGE - 1
minIndex + RESULTS_PER_PAGE - 1,
);
list = Array(maxIndex - minIndex + 1);
@@ -2437,7 +2438,7 @@ function main() {
haystack,
needle,
undefined,
infoThresh
infoThresh,
);
if (!result?.[0]?.length || !result?.[1]) {
@@ -2445,7 +2446,7 @@ function main() {
haystack,
needle,
outOfOrder,
infoThresh
infoThresh,
);
}
@@ -2454,7 +2455,7 @@ function main() {
haystack,
needle,
outOfOrder,
infoThresh
infoThresh,
);
}
@@ -2463,7 +2464,7 @@ function main() {
haystack,
needle,
outOfOrder,
infoThresh
infoThresh,
);
}
@@ -2472,7 +2473,7 @@ function main() {
haystack,
needle,
undefined,
infoThresh
infoThresh,
);
}
@@ -2481,7 +2482,7 @@ function main() {
haystack,
needle,
outOfOrder,
infoThresh
infoThresh,
);
}
@@ -2564,7 +2565,7 @@ function main() {
shareDiv.hidden = false;
});
})
}),
);
}
initShare();
@@ -2588,7 +2589,7 @@ function main() {
utils.storage.write(barWidthLocalStorageKey, String(width));
} else {
elements.main.style.width = elements.style.getPropertyValue(
"--default-main-width"
"--default-main-width",
);
utils.storage.remove(barWidthLocalStorageKey);
}
@@ -2625,9 +2626,9 @@ function main() {
window.addEventListener("mouseleave", setResizeFalse);
}
initDesktopResizeBar();
})
)
)
}),
),
),
);
}
main();
+83 -36
View File
@@ -162,15 +162,16 @@ function createPartialOptions(colors) {
*/
const averages = /** @type {const} */ ([
{ name: "1 Week", key: "1w", days: 7, color: colors.orange },
{ name: "8 Day", key: "8d", days: 8, color: colors.amber },
{ name: "13 Day", key: "13d", days: 13, color: colors.yellow },
{ name: "21 Day", key: "21d", days: 21, color: colors.lime },
{ name: "1 Month", key: "1m", days: 30, color: colors.green },
{ name: "34 Day", key: "34d", days: 34, color: colors.emerald },
{ name: "55 Day", key: "55d", days: 55, color: colors.teal },
{ name: "89 Day", key: "89d", days: 89, color: colors.cyan },
{ name: "144 Day", key: "144d", days: 144, color: colors.sky },
{ name: "1 Week", key: "1w", days: 7, color: colors.red },
{ name: "8 Day", key: "8d", days: 8, color: colors.orange },
{ name: "13 Day", key: "13d", days: 13, color: colors.amber },
{ name: "21 Day", key: "21d", days: 21, color: colors.yellow },
{ name: "1 Month", key: "1m", days: 30, color: colors.lime },
{ name: "34 Day", key: "34d", days: 34, color: colors.green },
{ name: "55 Day", key: "55d", days: 55, color: colors.emerald },
{ name: "89 Day", key: "89d", days: 89, color: colors.teal },
{ name: "144 Day", key: "144d", days: 144, color: colors.cyan },
{ name: "200 Day", key: "200d", days: 200, color: colors.sky },
{ name: "1 Year", key: "1y", days: 365, color: colors.blue },
{ name: "2 Year", key: "2y", days: 2 * 365, color: colors.indigo },
{ name: "200 Week", key: "200w", days: 200 * 7, color: colors.violet },
@@ -1057,12 +1058,6 @@ function createPartialOptions(colors) {
},
},
}),
createBaseSeries({
key: `${key}-ratio-sma`,
name: "sma",
color: colors.yellow,
defaultActive: false,
}),
createBaseSeries({
key: `${key}-ratio-p1sd`,
name: "+1σ",
@@ -1153,6 +1148,18 @@ function createPartialOptions(colors) {
color: colors.rose,
defaultActive: false,
}),
createBaseSeries({
key: `${key}-ratio-4y-sma`,
name: "4y-sma",
color: colors.violet,
defaultActive: false,
}),
createBaseSeries({
key: `${key}-ratio-sma`,
name: "sma",
color: colors.yellow,
defaultActive: false,
}),
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
key: `${key}-ratio-1y-sma-momentum-oscillator`,
title: "1Y Momentum",
@@ -1165,7 +1172,7 @@ function createPartialOptions(colors) {
}),
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
key: `${key}-ratio-zscore`,
title: "Score",
title: "All time",
type: "Baseline",
options: {
createPriceLine: {
@@ -1173,6 +1180,17 @@ function createPartialOptions(colors) {
},
},
}),
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
key: `${key}-ratio-4y-zscore`,
title: "4y",
type: "Baseline",
colors: [colors.yellow, colors.pink],
options: {
createPriceLine: {
value: 0,
},
},
}),
],
};
}
@@ -1413,7 +1431,7 @@ function createPartialOptions(colors) {
key: `${fixKey(key)}realized-price`,
name,
color,
})
}),
),
}
: createPriceWithRatio({
@@ -1496,7 +1514,7 @@ function createPartialOptions(colors) {
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline",
key: `${fixKey(
key
key,
)}net-realized-profit-and-loss-relative-to-realized-cap`,
title: useGroupName ? name : "Net",
color: useGroupName ? color : undefined,
@@ -1530,6 +1548,9 @@ function createPartialOptions(colors) {
key: `${fixKey(key)}adjusted-spent-output-profit-ratio`,
title: useGroupName ? name : "asopr",
color: useGroupName ? color : undefined,
colors: useGroupName
? undefined
: [colors.yellow, colors.pink],
options: {
createPriceLine: {
value: 1,
@@ -1567,7 +1588,7 @@ function createPartialOptions(colors) {
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline",
key: `${fixKey(
key
key,
)}adjusted-spent-output-profit-ratio`,
title: useGroupName ? name : "asopr",
color: useGroupName ? color : undefined,
@@ -1590,7 +1611,7 @@ function createPartialOptions(colors) {
key: `${fixKey(key)}sell-side-risk-ratio`,
name: useGroupName ? name : "Risk",
color: color,
})
}),
),
},
],
@@ -1679,7 +1700,7 @@ function createPartialOptions(colors) {
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline",
key: `${fixKey(
key
key,
)}net-unrealized-profit-and-loss-relative-to-market-cap`,
title: useGroupName ? name : "Net",
color: useGroupName ? color : undefined,
@@ -1857,7 +1878,7 @@ function createPartialOptions(colors) {
key: `${key}-sma`,
name: key,
color,
})
}),
),
},
...averages.map(({ key, name, color }) =>
@@ -1867,7 +1888,7 @@ function createPartialOptions(colors) {
title: `${name} Market Price Moving Average`,
legend: "average",
color,
})
}),
),
],
},
@@ -1904,6 +1925,32 @@ function createPartialOptions(colors) {
],
})),
},
{
name: "Indicators",
tree: [
{
name: "Mayer's multiple",
title: "Mayer's multiple",
top: [
createBaseSeries({
key: `200d-sma`,
name: "200d sma",
color: colors.yellow,
}),
createBaseSeries({
key: `200d-sma-x2-4`,
name: "200d sma x2.4",
color: colors.green,
}),
createBaseSeries({
key: `200d-sma-x0-8`,
name: "200d sma x0.8",
color: colors.red,
}),
],
},
],
},
],
},
{
@@ -1958,7 +2005,7 @@ function createPartialOptions(colors) {
},
}),
],
})
}),
),
.../** @type {const} */ ([
{ name: "2 Year", key: "2y" },
@@ -2029,7 +2076,7 @@ function createPartialOptions(colors) {
},
}),
],
})
}),
),
],
},
@@ -2045,7 +2092,7 @@ function createPartialOptions(colors) {
name: `${year}`,
color,
defaultActive,
})
}),
),
},
...dcaClasses.map(
@@ -2072,7 +2119,7 @@ function createPartialOptions(colors) {
},
}),
],
})
}),
),
],
},
@@ -2133,10 +2180,10 @@ function createPartialOptions(colors) {
bottom: [
...createAverageSumCumulativeMinMaxPercentilesSeries("fee"),
...createAverageSumCumulativeMinMaxPercentilesSeries(
"fee-in-btc"
"fee-in-btc",
),
...createAverageSumCumulativeMinMaxPercentilesSeries(
"fee-in-usd"
"fee-in-usd",
),
],
},
@@ -2892,7 +2939,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
const detailsList = [];
const treeElement = signals.createSignal(
/** @type {HTMLDivElement | null} */ (null)
/** @type {HTMLDivElement | null} */ (null),
);
/** @type {string[] | undefined} */
@@ -3004,7 +3051,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
return null;
}
},
null
null,
);
partialTree.forEach((anyPartial, partialIndex) => {
@@ -3027,7 +3074,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
if ("tree" in anyPartial) {
const folderId = utils.stringToId(
`${(path || []).join(" ")} ${anyPartial.name} folder`
`${(path || []).join(" ")} ${anyPartial.name} folder`,
);
/** @type {Omit<OptionsGroup, keyof PartialOptionsGroup>} */
@@ -3042,13 +3089,13 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
const thisPath = groupAddons.id;
const passedDetails = signals.createSignal(
/** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null)
/** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null),
);
const childOptionsCount = recursiveProcessPartialTree(
anyPartial.tree,
passedDetails,
[...(path || []), thisPath]
[...(path || []), thisPath],
);
listForSum.push(childOptionsCount);
@@ -3180,7 +3227,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
});
return signals.createMemo(() =>
listForSum.reduce((acc, s) => acc + s(), 0)
listForSum.reduce((acc, s) => acc + s(), 0),
);
}
recursiveProcessPartialTree(partialOptions, treeElement);
@@ -3207,7 +3254,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
console.log(
[...m.entries()]
.filter(([_, value]) => value > 1)
.map(([key, _]) => key)
.map(([key, _]) => key),
);
throw Error("ID duplicate");
File diff suppressed because it is too large Load Diff