Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
// Generated, do not edit. sha256:9ca0c45b0945e94868e99e2fd93c72e9ebb7d1e8b99a59b57aa8e5980d47892d
export const GENESIS_ISO = '2009-01-03';
export const POWERLAW_PROJECT_DAYS = 365;
export const POWERLAW = Object.freeze({
FIT: Object.freeze({ A: 1e-17, N: 5.8 }),
LOWER: Object.freeze({ DEFAULT: 0.47 }),
UPPER: Object.freeze({ DEFAULT_BY_CCY: Object.freeze({ EUR: 4.5, USD: 5.0 }) }),
});
export const WMA = Object.freeze({
WEEKS: 200,
FAST_WEEKS: 50,
SIGNAL_MULT: 1.2,
RED_MULT: 1.5,
});
export const MAYER = Object.freeze({
DAYS: 200,
GREEN_BELOW: 1.0,
RED_ABOVE: 2.4,
});
export const ZOOM_RECENT_DAYS = 180;
export const MS_PER_DAY = 24 * 60 * 60 * 1000;
export const MS_PER_WEEK = 7 * MS_PER_DAY;
+127
View File
@@ -0,0 +1,127 @@
// Generated, do not edit. sha256:19640cdaa8812f06608f0f5d7ab246cafacd608773aca0ad6879af4404a7d7ed
import { GENESIS_ISO, MS_PER_WEEK, MS_PER_DAY, WMA, MAYER, POWERLAW } from './constants.js';
import { t } from '../i18n/index.js';
const GENESIS_MS = new Date(GENESIS_ISO).getTime();
export function daysSinceGenesis(tsMs) {
return (tsMs - GENESIS_MS) / (24 * 60 * 60 * 1000);
}
export function genesisMs() { return GENESIS_MS; }
export function toDailyGrid(rows) {
const byDay = new Map();
for (const r of rows) {
const v = r.fiatPrice;
if (v != null && v > 0) byDay.set(r.observedAt.slice(0, 10), v);
}
if (byDay.size === 0) throw new Error(t('signals.noHistory'));
const days = [...byDay.keys()].sort();
const startMs = Date.parse(`${days[0]}T00:00:00Z`);
const endMs = Date.parse(`${days[days.length - 1]}T00:00:00Z`);
const out = [];
let last = null;
for (let ms = startMs; ms <= endMs; ms += MS_PER_DAY) {
const iso = new Date(ms).toISOString().slice(0, 10);
if (byDay.has(iso)) last = byDay.get(iso);
out.push([ms, last]);
}
return out;
}
export function dailyGridSources(rows) {
const byDay = new Map();
for (const r of rows) {
const v = r.fiatPrice;
if (v != null && v > 0) {
byDay.set(Date.parse(`${r.observedAt.slice(0, 10)}T00:00:00Z`), { providerId: r.providerId, sourceName: r.sourceName ?? null });
}
}
return byDay;
}
export function powerLawModel() {
return { A: POWERLAW.FIT.A, n: POWERLAW.FIT.N };
}
export function powerLawValue(fit, tsMs) {
return fit.A * Math.pow(daysSinceGenesis(tsMs), fit.n) * (fit.scaleAt ? fit.scaleAt(tsMs) : 1);
}
export function powerLawFor(currency, usdRates = null) {
const own = POWERLAW.UPPER.DEFAULT_BY_CCY[String(currency).toUpperCase()];
if (own != null) return { fit: powerLawModel(), upper: own };
if (!usdRates || !usdRates.length) return null;
return { fit: { ...powerLawModel(), scaleAt: ts => rateOn(usdRates, ts) }, upper: POWERLAW.UPPER.DEFAULT_BY_CCY.USD };
}
function rateOn(grid, ts) {
let lo = 0, hi = grid.length - 1, hit = 0;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
if (grid[mid][0] <= ts) { hit = mid; lo = mid + 1; } else hi = mid - 1;
}
return grid[hit][1];
}
export function weeklyAggregate(daily) {
const out = [];
if (daily.length === 0) return out;
let week = Math.floor(daily[0][0] / MS_PER_WEEK);
let sum = 0, count = 0;
for (const [ts, price] of daily) {
const w = Math.floor(ts / MS_PER_WEEK);
if (w !== week) {
if (count > 0) out.push([week * MS_PER_WEEK, sum / count]);
week = w; sum = price; count = 1;
} else { sum += price; count++; }
}
if (count > 0) out.push([week * MS_PER_WEEK, sum / count]);
return out;
}
export function movingAverage(series, window) {
const out = [];
let sum = 0;
for (let i = 0; i < series.length; i++) {
sum += series[i][1];
if (i >= window) sum -= series[i - window][1];
out.push({ x: series[i][0], y: i >= window - 1 ? sum / window : null });
}
return out;
}
export function mayerSeries(daily, days) {
const ma = movingAverage(daily, days);
return daily.map(([ts, price], i) => ({
x: ts,
y: ma[i].y == null ? null : price / ma[i].y,
}));
}
export function powerLawSignal(daily, fit, lowerFactor, upperFactor) {
const [ts, price] = daily[daily.length - 1];
const f = powerLawValue(fit, ts);
if (price <= f * lowerFactor) return { color: 'green', key: 'plStrong' };
if (price <= f) return { color: 'green', key: 'plBuy' };
if (price <= f * upperFactor) return { color: 'yellow', key: 'plAbove' };
return { color: 'red', key: 'plFar' };
}
export function wmaSignal(weekly) {
const ma = movingAverage(weekly, WMA.WEEKS);
const lastWma = [...ma].reverse().find(d => d.y != null);
if (!lastWma || weekly.length === 0) return null;
const ratio = weekly[weekly.length - 1][1] / lastWma.y;
if (ratio < WMA.SIGNAL_MULT) return { color: 'green', key: 'wmaBuy', mult: ratio };
if (ratio < WMA.RED_MULT) return { color: 'yellow', key: 'wmaAbove', mult: ratio };
return { color: 'red', key: 'wmaFar', mult: ratio };
}
export function mayerSignal(mayer) {
const last = [...mayer].reverse().find(d => d.y != null);
if (!last) return null;
const v = last.y;
if (v < MAYER.GREEN_BELOW) return { color: 'green', key: 'mayerLow', val: v };
if (v < MAYER.RED_ABOVE) return { color: 'yellow', key: 'mayerMid', val: v };
return { color: 'red', key: 'mayerHigh', val: v };
}