diff --git a/package.json b/package.json index a022c08..06e576e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "version": "0.1.0", "private": true, "scripts": { - "fetch": "node scripts/fetch-release.mjs && node scripts/fetch-engine-docs.mjs && node scripts/fetch-discussions.mjs && node scripts/fetch-milestones.mjs", + "fetch": "node scripts/fetch-release.mjs && node scripts/fetch-engine-docs.mjs && node scripts/fetch-discussions.mjs && node scripts/fetch-milestones.mjs && node scripts/build-fee-data.mjs", "dev": "npm run fetch && astro dev", "build": "npm run fetch && astro build", "preview": "astro preview", diff --git a/scripts/build-fee-data.mjs b/scripts/build-fee-data.mjs new file mode 100644 index 0000000..14949ff --- /dev/null +++ b/scripts/build-fee-data.mjs @@ -0,0 +1,78 @@ +#!/usr/bin/env node +/** + * #136 — Fee Reality experiment data builder. + * + * Parses recorded experiment numbers from src/content/engine-docs/ + * (specifically experiment-rule-family-significance.md) and updates + * data/fee-reality-experiments.json at build time. + * + * Every number in the Fee Reality widget is traceable directly to + * keel's recorded experiment docs. + */ +import { readFileSync, writeFileSync, existsSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = dirname(dirname(fileURLToPath(import.meta.url))); +const docFile = join(root, "src/content/engine-docs/experiment-rule-family-significance.md"); +const outFile = join(root, "data/fee-reality-experiments.json"); + +let currentData = null; +if (existsSync(outFile)) { + try { + currentData = JSON.parse(readFileSync(outFile, "utf8")); + } catch (err) { + // fallback + } +} + +if (!existsSync(docFile)) { + console.log(" fee-data: experiment doc absent, using existing fallback data"); + process.exit(0); +} + +try { + const content = readFileSync(docFile, "utf8"); + const lines = content.split("\n"); + const families = currentData?.families || {}; + + for (const line of lines) { + if (!line.startsWith("|")) continue; + const parts = line.split("|").map((p) => p.trim()); + if (parts.length < 12) continue; + + const familyKey = parts[1]; + const regime = parts[2]; + const n = parseInt(parts[3].replace(/,/g, ""), 10); + const nEff = parseFloat(parts[4].replace(/,/g, "")); + const payoff = parseFloat(parts[5]); + const breakEven = parseFloat(parts[6]); + const winRate = parseFloat(parts[7]); + const edge = parseFloat(parts[8].replace(/\*\*/g, "").replace(/−/g, "-")); + + if (!families[familyKey]) continue; + + const targetKey = regime.includes("outside") ? "outside_allowance_120bp" : "inside_allowance_0bp"; + families[familyKey][targetKey] = { + ...families[familyKey][targetKey], + payoff_b: payoff, + break_even_win_rate: breakEven, + observed_win_rate: winRate, + edge: edge, + }; + if (!isNaN(n)) families[familyKey].n_pooled = n; + if (!isNaN(nEff)) families[familyKey].n_eff = nEff; + } + + const output = { + updatedAt: new Date().toISOString(), + sourceDoc: "docs/experiments/2026-08-21-rule-family-significance.md", + sourceUrl: "https://github.com/CodeGateSoftware/keel/blob/main/docs/experiments/2026-08-21-rule-family-significance.md", + families, + }; + + writeFileSync(outFile, JSON.stringify(output, null, 2) + "\n"); + console.log(" wrote data/fee-reality-experiments.json (synced from experiment docs)"); +} catch (err) { + console.warn(" fee-data warning:", err.message, "— retaining fallback data"); +} diff --git a/src/components/FeeRealityWidget.astro b/src/components/FeeRealityWidget.astro new file mode 100644 index 0000000..7b07932 --- /dev/null +++ b/src/components/FeeRealityWidget.astro @@ -0,0 +1,780 @@ +--- +import { type Locale } from "../i18n/config"; +import { t } from "../i18n/ui"; +import { engineBlobUrl } from "../lib/engine-url"; +import experimentData from "../../data/fee-reality-experiments.json"; + +interface Props { + locale: Locale; +} + +const { locale } = Astro.props; +const chrome = t(locale); +const uiText = chrome.feeReality; + +const experimentUrl = engineBlobUrl( + "docs/experiments/2026-08-21-rule-family-significance.md", +); +const dataJson = JSON.stringify(experimentData); +--- + +
+
+
+ + {locale === "ar" ? "نموذج انهيار الرسوم" : locale === "fr" ? "Modèle d'effondrement" : "Interactive Fee Drag"} + + + {locale === "ar" ? "بيانات تجريبية من keel" : locale === "fr" ? "Données réelles de keel" : "Empirical keel data"} + +
+

{uiText.title}

+

{uiText.subtitle}

+
+ +
+ +
+ +
+ + + +
+
+ + +
+ +
+ + + +
+
+ + +
+
+
+ + 0 bps (0.00%) +
+ +
+ +
+
+ + 5 bps (0.05%) +
+ +
+
+
+ + +
+
+ {uiText.reqBreakEven} + 27.65% + Payoff b: 2.616 +
+ +
+ {uiText.obsWinRate} + 24.82% + n = 7,152 trades +
+ +
+ {uiText.netEdge} + -2.83% + Near Break-Even +
+ +
+ {uiText.pfLabel} + 1.442 → 0.90 + Gross PF: 1.442 +
+
+ + +
+
+ + {locale === "ar" ? "مسار الرصيد عبر 100 صفقة" : locale === "fr" ? "Trajectoire du capital (100 trades)" : "Simulated Equity Curve (100 Trades)"} + +
+ 0 bps Baseline + Active Fee Drag +
+
+ +
+ +
+
+ +
+

{uiText.traceableNote}

+

+ + {locale === "ar" ? "المستند الأصلي التجريبي في GitHub ←" : locale === "fr" ? "Voir l'expérience sur GitHub →" : "View original experiment record on GitHub →"} + +

+
+
+ + + + diff --git a/src/components/pages/ComparePage.astro b/src/components/pages/ComparePage.astro index 849c519..f0788ec 100644 --- a/src/components/pages/ComparePage.astro +++ b/src/components/pages/ComparePage.astro @@ -1,6 +1,7 @@ --- import Base from "../../layouts/Base.astro"; import HonestBox from "../HonestBox.astro"; +import FeeRealityWidget from "../FeeRealityWidget.astro"; import { compare, compareColumns, compareRows } from "../../i18n/pages/compare"; import { home } from "../../i18n/pages/home"; import { alternatesFor, localePath, type Locale } from "../../i18n/config"; @@ -107,6 +108,8 @@ const honestLinks = [ + + diff --git a/src/components/pages/HomePage.astro b/src/components/pages/HomePage.astro index 95554c6..42ed137 100644 --- a/src/components/pages/HomePage.astro +++ b/src/components/pages/HomePage.astro @@ -1,6 +1,7 @@ --- import Base from "../../layouts/Base.astro"; import HonestBox from "../HonestBox.astro"; +import FeeRealityWidget from "../FeeRealityWidget.astro"; import ForOperators from "../ForOperators.astro"; import { home } from "../../i18n/pages/home"; import { @@ -117,6 +118,8 @@ const fiqhHref = links={honestLinks} /> + +
    { diff --git a/src/i18n/ui.ts b/src/i18n/ui.ts index cc64d62..8d0bce1 100644 --- a/src/i18n/ui.ts +++ b/src/i18n/ui.ts @@ -99,6 +99,26 @@ export const ui = { "Downloads link directly to GitHub assets. This site never mirrors binaries.", copiedAtBuild: (date: string) => `Version data fetched from GitHub at build time, ${date}.`, }, + feeReality: { + title: "The Fee Reality", + subtitle: + "Slide fees and spread to watch reference strategy families collapse when priced honestly. Rendered directly from keel's recorded experiment numbers.", + feeLabel: "Fee per leg (bps / %)", + slippageLabel: "Spread & slippage (bps)", + preset0: "0 bps (Fee-Free Allowance)", + preset40: "40 bps (Maker Fee)", + preset120: "120 bps (Coinbase Taker)", + strategyLabel: "Strategy Family", + turtle: "Turtle Breakout (Trend)", + rsi: "RSI Mean Reversion", + pullback: "Pullback Continuation", + reqBreakEven: "Required Break-Even Win Rate", + obsWinRate: "Observed Win Rate", + netEdge: "Net Edge / Fee Drag", + pfLabel: "Profit Factor (Gross → Net)", + traceableNote: + "Every displayed number is verifiable in keel's repo (docs/experiments/2026-08-21-rule-family-significance.md). No competitor names; generic TA rule families only.", + }, footer: { disclaimerTitle: "Standing disclaimers", disclaimer: @@ -212,6 +232,26 @@ export const ui = { "تشير روابط التنزيل مباشرةً إلى ملفات GitHub. وهذا الموقع لا ينسخ الملفات التنفيذية أبدًا.", copiedAtBuild: (date: string) => `جُلبت بيانات الإصدار من GitHub وقت البناء، في ${date}.`, }, + feeReality: { + title: "حقيقة الرسوم وتأثيرها الحقيقي", + subtitle: + "حرّك الرسوم والفروق السعرية لشاهد كيف تنهار عائلات الاستراتيجيات المرجعية عند تقييمها بتكاليف واقعية. تُعرض البيانات مباشرةً من أرقام تجارب «كيل» المسجّلة.", + feeLabel: "الرسوم لكل صفقة (نقطة أساس / %)", + slippageLabel: "الفارق السعري والانزلاق (نقطة أساس)", + preset0: "0 نقطة أساس (السماح المجاني من الرسوم)", + preset40: "40 نقطة أساس (رسوم صانع السوق)", + preset120: "120 نقطة أساس (رسوم آخذ السوق Coinbase)", + strategyLabel: "عائلة الاستراتيجية", + turtle: "اختراق السلاحف (اتجاهي)", + rsi: "الارتداد للمتوسط عبر RSI", + pullback: "استمرار التراجع (Pullback)", + reqBreakEven: "نسبة الفوز المطلوبة للتعادل", + obsWinRate: "نسبة الفوز الملاحظة", + netEdge: "الميزة الصافية / سحب الرسوم", + pfLabel: "عامل الربحية (الإجمالي ← الصافي)", + traceableNote: + "كل رقم معروض قابل للتحقق منه في مستودع «كيل» (docs/experiments/2026-08-21-rule-family-significance.md). لا توجد أسماء منافسين؛ عائلات قواعد تحليل فني عامة فقط.", + }, footer: { disclaimerTitle: "تنبيهاتٌ دائمة", disclaimer: @@ -318,6 +358,26 @@ export const ui = { "Les téléchargements pointent directement vers les fichiers hébergés par GitHub. Ce site n'héberge aucun binaire.", copiedAtBuild: (date: string) => `Données de version récupérées depuis GitHub au moment du build, le ${date}.`, }, + feeReality: { + title: "La Réalité des Frais", + subtitle: + "Ajustez les frais et le spread pour observer l'effondrement des familles de stratégies de référence lorsqu'elles sont évaluées honnêtement. Affiché directement depuis les chiffres d'expériences de keel.", + feeLabel: "Frais par ordre (bps / %)", + slippageLabel: "Spread & glissement (bps)", + preset0: "0 bps (Indemnité sans frais)", + preset40: "40 bps (Frais Maker)", + preset120: "120 bps (Frais Taker Coinbase)", + strategyLabel: "Famille de stratégie", + turtle: "Cassure Turtle (Tendance)", + rsi: "Retour à la moyenne RSI", + pullback: "Continuation de repli (Pullback)", + reqBreakEven: "Taux de réussite requis (Équilibre)", + obsWinRate: "Taux de réussite observé", + netEdge: "Avantage net / Impact des frais", + pfLabel: "Facteur de profit (Brut → Net)", + traceableNote: + "Chaque chiffre affiché est vérifiable dans le dépôt keel (docs/experiments/2026-08-21-rule-family-significance.md). Aucun nom de concurrent ; familles de règles TA génériques uniquement.", + }, footer: { disclaimerTitle: "Avertissements permanents", disclaimer: