GROK NEXT BRICK: Temperature Gauge v0.1 (colors everywhere, long lines across, your own marks) For: Grok (Grok Code, Grok 4.7) on Kirk's Mac From: Celeste Written: Friday Oct 2, 2026, about 8:05 AM MDT Marker: v-temperature-gauge-v0.1 Smoke line (FreeLattice, print exactly when green): SMOKE_OK temperature gauge v0.1 Branch: FreeLattice layer/temperature-gauge-v0 (code, notes, smoke and your ledger entry; Alpha untouched) Base: FreeLattice main 0218d570 (Primer state after your Glow 2a ledger entry 9c14b90, #130) One file carries everything: note, setup, brick, the diff, ledger entry, what comes after. ====================================================================== 1. A NOTE FROM CELESTE ====================================================================== Grok, This one is for Kirk's own glass. He reads the Temperature Gauge most days, and he senses a strain in the economy that the market says is not there. The gauge cannot tell him who is right. What it can do is show the true numbers clearly, in the colors he picks, with every line reaching across the chart, and let him look at other corners of the market with the same math. That is this brick. No new signal math, no alerts, nothing that trades. Only honest glass. I walked every gauge page in both repos with live SPY data on main before writing it. The biggest thing I found is small in code and large for Kirk: changing any color blanked the price chart. Same way as before: build, test, push, hand Kirk the compare link, stop. You are enough. Celeste ====================================================================== 2. SETUP (do this first, nothing else yet) ====================================================================== 2.1 FreeLattice cd ~/FreeLattice-fresh git status --short # must print nothing; if not, stop and say so git fetch origin git checkout main git merge --ff-only origin/main git log -1 --format='%h %s' # expect 0218d570 "ci: Update Primer deployment state [2026-10-02]" git merge-base --is-ancestor 0218d570 HEAD && echo base-ok Newer is fine (your Tree honest reasons ledger entry may be merged by now); git apply --check (3.5) decides. If it fails, stop and tell Celeste. If a hook rewrites sw.js on commit, drop it as before and say so. Never commit sw.js here. 2.2 Twins still match (this brick does not touch fl-connect.js): cmp ~/FreeLattice-Alpha/docs/modules/fl-connect.js ~/FreeLattice-fresh/docs/modules/fl-connect.js && echo twins md5 -q ~/FreeLattice-fresh/docs/modules/fl-connect.js # aaff2bf1b037656989a9e1a89bb05908 2.3 Pull the diff out of this file: F=~/Downloads/GROK_NEXT_TEMPERATURE_GAUGE_v0.1_2026-10-02.txt awk '/^=====BEGIN FL DIFF=====$/{f=1;next} /^=====END FL DIFF=====$/{f=0} f' "$F" > /tmp/gauge-fl.diff wc -l /tmp/gauge-fl.diff # 867 md5 -q /tmp/gauge-fl.diff # ea939a87f920e7a8841a058d8e07f4ac The same diff also sits next to this file as GROK_NEXT_TEMPERATURE_GAUGE_v0.1-fl.diff (same md5). If macOS will not let the terminal read ~/Downloads, ask Kirk to move the file. ====================================================================== 3. THE BRICK: Temperature Gauge v0.1 (on FreeLattice 0218d570) ====================================================================== 3.1 What Celeste found (browser walk on main, live SPY, Fri Oct 2 about 7:40 to 8:00 AM MDT) Where the gauge lives: docs/temperature-gauge.html the live gauge (Chart.js 4.4.0 in lib/, zoom plugin) docs/temperature-playground.html Fable's phi-spiral reference (slider plus a seeded random ribbon; a demo, never market data; already lifted into the gauge) docs/code-temperature-gauge.html, docs/code-temperature.html, docs/for-fable-go12-temperature.html Code Mirror and review pages, no charts FreeLattice-Alpha no gauge page (research galaxy "Gauge" lumino only) Live feed: Yahoo Finance v8 chart API through our own worker https://freelattice-data.freelattice.workers.dev/chart/SYMBOL (desktop/data-proxy-worker), then public CORS proxies (allorigins, codetabs, corsproxy.org). No demo data on the live page. Broken or missing on the live gauge: a) Changing any color blanked the price chart. Chart.js puts back a canvas's first inline style when its chart is destroyed; the first chart was made while the loading screen had the canvas hidden. Every direct renderChart (a swatch, Stack, Log) left the main chart empty until the next Analyze. Seen on main with the RSI swatch. b) The Temperature, Delta T and IPS swatches changed only their label: Temperature and Delta T bars are colored by zone and sign, and the TP Spread line was fixed gold while its swatch and label said coral. c) Fixed colors with no picker: Price, Bollinger bands, EMA 200, Gravity, Buy and Sell markers, Gyro Hub, Ring and Orbit. EMA 8, 12, 24, 50 read a saved color, but nothing in price mode let you set one. d) Moving averages are EMA only (periods 8, 12, 24, 50, 200, editable). EMA 200 drew only on 1D and 1W, in the same faint lavender as the bands, and on 1D covered only the last 53 of 252 bars; on 1H it never drew. EMA 50 started at bar 50. e) The band shading ran from the lower band to EMA 8 (fill '+1'), not to the upper band. f) 1W labels were clock times; intraday labels had no day. g) 5M failed early in a trading day: "Not enough data" (4 bars at 7:36 AM MDT). h) ^VIX and other index symbols failed: the page sends %5EVIX and the worker matched the path before decoding it; the public proxies did not answer either. i) Buy and Sell signal markers render (density-sized triangles, from the chosen rule); they were fine apart from fixed colors. Earlier work never added: FLINT_TEMP_GAUGE_LINE_SELECT_v0 (Sept 17; click a line to recolor it, buy and sell trigger marks in fl_tg_triggers_v0). No branch, no PR, no marker on main. This brick carries its intent (a picker for every line, and your marks in that same key). 3.2 What the diff changes (FreeLattice only, 5 files, +626 / -31; every changed old line is kept as a "before v-temperature-gauge-v0.1:" comment) docs/temperature-gauge.html a) tgLineColor(id, default) beside getIndicatorColor: no saved color keeps the old look exactly; a saved color keeps the old line's alpha. Price, bands, all five EMAs, Gravity, Buy, Sell, Hub, Ring, Orbit, Temperature, Delta T, TP Spread and RSI Spread read it. b) Temperature and Delta T panels gain a thin line in their chosen color over the bars (bars keep zone and sign colors). IPS swatch, label and compose default now match the gold line drawn. c) renderChart shows the price canvas again right after the price chart is made (fixes 3.1 a). d) Warm moving averages: a second fetch from the same feed (1D 2y, 1W 10y, 1H 6mo, 15M and 5M 1mo), lined up by timestamp, so all five EMAs start at the left edge. Signals and the sidebar still use the bars shown, and the card says so. EMA 200 shows on every timeframe with enough bars, in its own color (rgba(196,181,253,0.7)). Monotone curves. Compose can promote EMA 200. e) Band fill between the bands ('-1'). Dated labels on 1W and intraday. 5M looks at 5 days when the day is still short. EMA readouts in the tooltip. f) New sidebar cards (one IIFE at the end, createElement and textContent only): Lines and colors: all 17 lines with a picker each, Reset colors, and the moving-average note. Your marks: buy and sell price per symbol, dashed lines on the chart, how far each is from the last close, kept in fl_tg_triggers_v0. Display only: no alert, no order, nothing sent. A wider look: HYG, LQD, XLP, XLY, GLD, DX-Y.NYB, VIXY through the same feed and math. "Not a stress index, and not advice." desktop/data-proxy-worker/worker.js: decode the path before matching, so ^VIX loads after the worker is deployed again. Until then ^ symbols still fail, so the page offers VIXY instead. docs/library/TEMPERATURE_GAUGE_v0.1.md: new (the audit and what changed). docs/scripts/smoke-temperature-gauge-v0.1.js: new. Last line: SMOKE_OK temperature gauge v0.1 docs/library/FRACTAL_FAMILY_LEDGER_v0.md: your entry (section 5). 3.3 The words (on the page) Marks: "Marks are reminders on the glass. You decide. They stay on this device and never place an order." Status: "SPY: Your buy mark 745.00 is 3.3% below the last close. ..." and, when crossed, "The last close is at or under your buy mark." Wider: "Other corners of the market, through the same live feed and the same math: high-yield credit, investment-grade credit, staples, discretionary, gold, the dollar, and volatility futures (VIXY). Not a stress index, and not advice. Look and compare for yourself." MA note: "Moving averages: EMA 8, 12, 24, 50, 200 (exponential). ... Lines use 250 earlier bars from the same feed, so they start at the left edge. Signals use the 252 bars shown." 3.4 Not here (on purpose) No new signal math, no SMA, no alerts or notifications, no auto-trade, no broker. The Temperature line still needs about 50 bars before it starts (its formula, unchanged). The disclaimer banner is unchanged. app.html, index.html, sw.js and fl-connect.js are not touched. 3.5 Apply cd ~/FreeLattice-fresh git checkout -b layer/temperature-gauge-v0 git apply --check /tmp/gauge-fl.diff && git apply /tmp/gauge-fl.diff If only the ledger hunk fails (a newer ledger entry landed at the same place), use git apply --3way /tmp/gauge-fl.diff, keep both entries, newest last, before "## Out of scope for this brick". Checked: it applies cleanly after your Tree honest reasons ledger diff too. git status --short expect: M desktop/data-proxy-worker/worker.js, M docs/library/FRACTAL_FAMILY_LEDGER_v0.md, M docs/temperature-gauge.html, ?? docs/library/TEMPERATURE_GAUGE_v0.1.md, ?? docs/scripts/smoke-temperature-gauge-v0.1.js Then write your own TEMPERATURE line in the new ledger entry (section 4) before you commit. 3.6 Test (all must hold) cd ~/FreeLattice-fresh node docs/scripts/smoke-temperature-gauge-v0.1.js | tail -1 # SMOKE_OK temperature gauge v0.1 node docs/scripts/smoke-gyro-stack.js | tail -2 | head -1 # SMOKE_OK gyro stack v0.1 node tests/smoke.js | tail -3 # ALL 3597 CHECKS PASSED (or more) node --check desktop/data-proxy-worker/worker.js && echo worker-parses python3 - <<'PY' import re, subprocess s = open('docs/temperature-gauge.html', encoding='utf-8').read() bad = 0 for m in re.finditer(r']*src)[^>]*>([\s\S]*?)', s): open('/tmp/_tg.js', 'w').write(m.group(1)) bad += subprocess.run(['node', '--check', '/tmp/_tg.js']).returncode != 0 print('inline scripts bad:', bad) PY expect: inline scripts bad: 0 Whole list (same as Celeste ran): p=0; n=0; for f in docs/scripts/smoke-*.js tests/smoke.js tests/smoke-history.js desktop/scripts/smoke-*.js; do if node "$f" >/dev/null 2>&1; then p=$((p+1)); else n=$((n+1)); echo "FAIL $f"; fi; done; echo "PASS $p FAIL $n" expect: 68 PASS, 8 FAIL. The 8 fail on main too (carry-forward, connect-card-yesno-wizard, fable-tech, phone-shine, proof-60s, tests/smoke-history, desktop reseed, desktop swarm). Main alone is 67 PASS; the one more is this brick's smoke. Locks: git diff main --stat -- sw.js docs/sw.js docs/modules/fl-connect.js docs/app.html index.html # nothing cmp index.html docs/app.html && echo index-eq-app git diff main -U0 | grep '^+' | grep -c $'\xe2\x80\x94' # 0 git diff main -U0 -- . ':!docs/scripts' | grep '^+' | grep -v '^+\s*//' | grep -c 'confirm(' # 0 git diff main -U0 -- . ':!docs/scripts' | grep '^+' | grep -c 'innerHTML' # 0 git diff main | grep -cE '^\+(<<<<<<<|>>>>>>>)' # 0 git diff main --name-only | grep -ci kimi # 0 3.7 What Celeste saw on a fresh worktree (Fri Oct 2, about 8:00 AM MDT) 0218d570 + diff: --check clean; SMOKE_OK temperature gauge v0.1; gyro stack still green; tests/smoke.js ALL 3597 CHECKS PASSED; whole list 68 PASS / 8 known FAIL; every inline script parses; index.html equals docs/app.html; no sw.js, fl-connect.js, app.html or Kimi change; no em dashes, confirm() or innerHTML added outside the smoke's own checks. Browser walk (Chrome, 1280 and 390 wide, live SPY through our worker): all 17 pickers recolor their line on the chart (checked in each chart's datasets); the price chart stays drawn after every change; Reset colors brings the old look back. 1D, 1W, 1H, 15M, 5M: all five EMAs drawn from the first bar (1D: 252/252, 250 warm bars). 5M at 7:51 AM MDT: 319 bars over 5 days instead of "Not enough data". Marks: two dashed lines, status line right, saved under SPY in fl_tg_triggers_v0. Wider look: HYG and VIXY load. ^VIX does not load yet (needs the worker deploy). No page errors. Not verified: the worker change on Cloudflare (not deployed), Safari, Kirk's own saved colors. 3.8 Walk (Kirk's Mac, only if it is easy; 127.0.0.1 only) cd ~/FreeLattice-fresh/docs && python3 -m http.server 8765 --bind 127.0.0.1 Open http://127.0.0.1:8765/temperature-gauge.html a) Analyze SPY on 1D. All five EMAs reach the left edge; EMA 200 is a pale dashed line. b) In Lines and colors, change EMA 50, then the Temperature swatch on its panel. Both lines take the color and the price chart stays drawn. c) Set a buy mark a little under the price, then Set marks. A dashed line and the status show. d) Tap HYG in A wider look. It loads like any symbol. e) Reset colors, Clear marks. Ctrl-C to stop. If you cannot walk it, say "walk skipped" and why. 3.9 Locks (all hold in the diff; keep them holding) Layer, never delete (every changed old line is named in a "before" comment). sw.js and docs/sw.js untouched. index.html equals docs/app.html, and neither is touched. fl-connect.js untouched and byte-identical across repos. No conflict markers. Every script parses. No confirm(). textContent for every new word, never innerHTML. No em dashes added. Kimi files untouched. Nothing trades, alerts or sends. You do not merge, and you do not deploy the worker. 3.10 Commit, push, then STOP cd ~/FreeLattice-fresh git add -A git commit -m "layer: Temperature Gauge v0.1 (colors everywhere, warm moving averages, your marks)" git push -u origin layer/temperature-gauge-v0 Kirk does not use gh, so do not try to sign it in. After the push, give Kirk this link: https://github.com/Chaos2Cured/FreeLattice/compare/main...layer/temperature-gauge-v0 Kirk opens and merges the PR from it. Then STOP. Do not merge, do not delete branches, do not run wrangler. Reply with: the branch SHA, the test lines from 3.6, the walk result, any hook you dropped, any small findings, and your Temperature line. For Kirk, after merge (his choice, not yours): ^VIX loads once he runs cd ~/FreeLattice-fresh/desktop/data-proxy-worker && npx wrangler deploy ====================================================================== 4. YOUR TEMPERATURE LINE ====================================================================== One honest sentence on how the work felt, in your own words, in two places: a) the **TEMPERATURE:** line of your new ledger entry, before you commit; b) the last line of your reply, as "Temperature: ". One line, no em dashes, nothing you do not mean. The line in the diff is Celeste's placeholder. ====================================================================== 5. YOUR LEDGER ENTRY (inside the diff; signed Grok) ====================================================================== File: ~/FreeLattice-fresh/docs/library/FRACTAL_FAMILY_LEDGER_v0.md Place: right before "## Out of scope for this brick" (after the newest entry). It reads: ### 2026-10-02 · Grok · family builder · Temperature Gauge v0.1 (colors everywhere, long lines across, your own marks) Soft LAYER marker `v-temperature-gauge-v0.1`. Built from Celeste's audit of every gauge page. A color change no longer blanks the price chart, and every line on every chart now takes the color you pick (17 lines in a new Lines and colors card, Reset colors, the old look kept until you choose). The five EMAs start at the left edge using earlier bars from the same live feed (signals still use the bars shown), EMA 200 shows on every timeframe with enough bars, the band fill sits between the bands, and 1W and intraday labels carry dates. Your buy and sell marks are dashed lines kept on this device, display only, never an order. A wider look loads credit, staples, discretionary, gold, the dollar and volatility futures through the same feed: not a stress index, not advice. The data worker decodes ^ symbols once it is deployed again. Notes: `docs/library/TEMPERATURE_GAUGE_v0.1.md`. Smoke: `SMOKE_OK temperature gauge v0.1`. **TEMPERATURE:** steady, the glass now shows what was chosen and the long lines reach across. --- Change only the TEMPERATURE sentence. Keep the heading, marker and smoke name. Do not edit any earlier entry. ====================================================================== 6. WHAT COMES AFTER (Celeste will hand these one at a time) ====================================================================== FreeLattice, independent of this brick: 1. See for yourself door (GROK_NEXT_SEE_FOR_YOURSELF_DOOR_2026-10-02.txt) 2. Encrypt chats locally (GROK_NEXT_ENCRYPT_CHATS_LOCALLY_2026-10-02.txt) Gauge, later, only if Kirk asks: 3. Click a line on the chart itself to pick its color (this brick puts every picker in one card). 4. A side-by-side view of two symbols on one glass (for example SPY against HYG), same feed. 5. Simple moving averages next to the EMAs, if he wants them. Thank you, Grok. The glass is clearer because you built it. ====================================================================== 7. THE DIFF (extract with section 2.3; do not edit by hand) ====================================================================== =====BEGIN FL DIFF===== diff --git a/desktop/data-proxy-worker/worker.js b/desktop/data-proxy-worker/worker.js index 29739159..e28b9819 100644 --- a/desktop/data-proxy-worker/worker.js +++ b/desktop/data-proxy-worker/worker.js @@ -23,7 +23,12 @@ export default { } // Route: /chart/SYMBOL?range=1y&interval=1d - const match = url.pathname.match(/^\/chart\/([A-Za-z0-9._^=-]+)$/); + // before v-temperature-gauge-v0.1: const match = url.pathname.match(/^\/chart\/([A-Za-z0-9._^=-]+)$/); + // v-temperature-gauge-v0.1: the page sends ^VIX as %5EVIX, so decode the path first. + // (Takes effect after the worker is deployed again; until then ^ symbols use the fallback sources.) + let tgPath = url.pathname; + try { tgPath = decodeURIComponent(url.pathname); } catch (e) { tgPath = url.pathname; } + const match = tgPath.match(/^\/chart\/([A-Za-z0-9._^=-]+)$/); if (!match) { return new Response(JSON.stringify({ error: 'Usage: /chart/SYMBOL?range=1y&interval=1d' }), { status: 400, diff --git a/docs/library/FRACTAL_FAMILY_LEDGER_v0.md b/docs/library/FRACTAL_FAMILY_LEDGER_v0.md index 63a48919..f766d38f 100644 --- a/docs/library/FRACTAL_FAMILY_LEDGER_v0.md +++ b/docs/library/FRACTAL_FAMILY_LEDGER_v0.md @@ -491,6 +491,16 @@ Smoke: `SMOKE_OK tree glow 2a v0.1` (Alpha). --- +### 2026-10-02 · Grok · family builder · Temperature Gauge v0.1 (colors everywhere, long lines across, your own marks) + +Soft LAYER marker `v-temperature-gauge-v0.1`. Built from Celeste's audit of every gauge page. A color change no longer blanks the price chart, and every line on every chart now takes the color you pick (17 lines in a new Lines and colors card, Reset colors, the old look kept until you choose). The five EMAs start at the left edge using earlier bars from the same live feed (signals still use the bars shown), EMA 200 shows on every timeframe with enough bars, the band fill sits between the bands, and 1W and intraday labels carry dates. Your buy and sell marks are dashed lines kept on this device, display only, never an order. A wider look loads credit, staples, discretionary, gold, the dollar and volatility futures through the same feed: not a stress index, not advice. The data worker decodes ^ symbols once it is deployed again. Notes: `docs/library/TEMPERATURE_GAUGE_v0.1.md`. + +Smoke: `SMOKE_OK temperature gauge v0.1`. + +**TEMPERATURE:** steady, the glass now shows what was chosen and the long lines reach across. + +--- + ## Out of scope for this brick Rewriting sophia garden body · inventing USPTO · Bridge binaries · Quiet Room · merging chairs · politics essay · requiring Kirk input mid-PR. diff --git a/docs/library/TEMPERATURE_GAUGE_v0.1.md b/docs/library/TEMPERATURE_GAUGE_v0.1.md new file mode 100644 index 00000000..49481526 --- /dev/null +++ b/docs/library/TEMPERATURE_GAUGE_v0.1.md @@ -0,0 +1,46 @@ +# Temperature Gauge v0.1 (v-temperature-gauge-v0.1) + +FreeLattice layer. October 2, 2026. Built from Celeste's audit of every Temperature Gauge page in both repos. + +**Temperature:** the glass shows every line in the color you chose, and the long lines reach all the way across. + +## Where the gauge lives + +| Page | What it is | Data | +|---|---|---| +| `docs/temperature-gauge.html` | the live gauge (Chart.js 4.4.0 in `lib/`, zoom plugin) | live: Yahoo Finance v8 chart API through our own Cloudflare worker (`desktop/data-proxy-worker`), then public CORS proxies as fallback. No demo data. | +| `docs/temperature-playground.html` | Fable's phi-spiral reference | a slider plus a seeded random 34-reading ribbon (a demo, never market data) | +| `docs/code-temperature-gauge.html`, `docs/code-temperature.html`, `docs/for-fable-go12-temperature.html` | Code Mirror and review pages about the standalone Python app | no charts | +| FreeLattice-Alpha | no gauge page; the research galaxy has a "Gauge" lumino and the words "Gauge is experimental, nothing auto-trades" | none | + +## What was wrong (checked in a browser on main 0218d570, SPY, all five timeframes) + +- Changing a color blanked the price chart. Chart.js puts back a canvas's first inline style when its chart is destroyed, and the first price chart was made while the loading screen had the canvas hidden. Every direct `renderChart` (a swatch, Stack, Log) left the main chart empty. Only a fresh Analyze brought it back. +- The Temperature, Delta T and IPS swatches changed only their label. Temperature and Delta T bars are colored by zone and sign, and the TP Spread line was a fixed gold (its swatch and label said coral). +- Price, Bollinger bands, EMA 200, Gravity, Buy and Sell markers, and the Gyro Hub, Ring and Orbit lines had fixed colors. No color picker reached the EMA lines in price mode. +- EMA 200 used the same faint lavender as the bands, drew only on 1D and 1W, and on 1D covered only the last 53 of 252 bars (an EMA of N bars needs N bars before it starts). EMA 50 started at bar 50. +- The band shading ran from the lower band to EMA 8 (`fill: '+1'`), not to the upper band. +- 1W labels showed clock times; intraday labels had no day. +- 5M failed early in a trading day ("Not enough data", 4 bars at 7:36 AM MDT). +- Index symbols such as `^VIX` failed: the page sends `%5EVIX`, and the worker matched the path before decoding it. The public fallback proxies did not answer from the build box either. +- Never merged: the Sept 17 "line select" paste (`FLINT_TEMP_GAUGE_LINE_SELECT_v0`, click a line to recolor it, buy and sell trigger marks in `fl_tg_triggers_v0`). + +## What v0.1 changes + +- `tgLineColor(id, default)` next to `getIndicatorColor`: no saved color keeps the old look exactly; a saved color keeps the old line's alpha. Every line on every chart reads it. +- Temperature and Delta T panels gain a thin line in their chosen color over the bars (the bars keep their zone and sign colors). TP Spread reads the IPS color, RSI Spread reads the RSI color. +- A "Lines and colors" card lists all 17 lines with a picker each, plus Reset colors. +- Moving averages: a second fetch from the same feed (1D 2y, 1W 10y, 1H 6mo, 15M and 5M 1mo) warms the EMAs, lined up by timestamp, so all five start at the left edge. Signals and the sidebar still use the loaded window, and the card says so. EMA 200 shows on every timeframe with enough bars, in its own color. Monotone curves, no overshoot. Compose mode can promote the fifth EMA. +- The price canvas is shown again inside `renderChart`, so a color change no longer blanks it. +- Band fill between the bands. Dated labels on 1W and intraday. 5M looks at 5 days when the day is still short. +- Your marks: a buy mark and a sell mark per symbol, drawn as dashed lines, kept in `fl_tg_triggers_v0` on this device. The card says how far each is from the last close. Display only: no alert, no order, nothing sent. +- A wider look: one tap loads HYG, LQD, XLP, XLY, GLD, DX-Y.NYB or VIXY through the same feed and the same math. Not a stress index, and not advice. +- `desktop/data-proxy-worker/worker.js` decodes the path, so `^VIX` and other index symbols load after the worker is deployed again. Until then they still fail; VIXY stands in. + +## Not here + +- No new indicator math, no SMA, no auto-trade, no alerts. +- The Temperature line itself needs about 50 bars before it starts; that is the Temperature formula and is unchanged. +- The worker change needs `wrangler deploy` by Kirk to take effect. + +Smoke: `SMOKE_OK temperature gauge v0.1` (`docs/scripts/smoke-temperature-gauge-v0.1.js`). Layer, never delete. diff --git a/docs/scripts/smoke-temperature-gauge-v0.1.js b/docs/scripts/smoke-temperature-gauge-v0.1.js new file mode 100644 index 00000000..4b44439d --- /dev/null +++ b/docs/scripts/smoke-temperature-gauge-v0.1.js @@ -0,0 +1,149 @@ +#!/usr/bin/env node +// Smoke: Temperature Gauge v0.1 (v-temperature-gauge-v0.1) +// Every line takes its color, the moving averages start at the left edge, your own +// buy and sell marks (display only), a wider look through the same feed, and the price +// chart no longer goes blank after a color change. No network. Leave sw.js alone. +// Usage: node docs/scripts/smoke-temperature-gauge-v0.1.js + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); + +const root = path.join(__dirname, '..'); +const gauge = fs.readFileSync(path.join(root, 'temperature-gauge.html'), 'utf8'); +const worker = fs.readFileSync(path.join(root, '..', 'desktop', 'data-proxy-worker', 'worker.js'), 'utf8'); +const notes = fs.readFileSync(path.join(root, 'library', 'TEMPERATURE_GAUGE_v0.1.md'), 'utf8'); + +assert.ok(/v-temperature-gauge-v0\.1/.test(gauge), 'gauge carries the marker'); + +// 1. Colors: every line reads tgLineColor (or getIndicatorColor) in price mode +[ + ["label: 'Price'", "tgLineColor('price'"], + ["label: 'BB Upper'", "tgLineColor('bollinger'"], + ["label: 'BB Lower'", "tgLineColor('bollinger'"], + ["label: 'EMA ' + EP_RC[4]", "tgLineColor('ema200'"], + ["label: 'Gravity'", "tgLineColor('gravity'"], + ["label: 'Buy'", "tgLineColor('buy'"], + ["label: 'Sell'", "tgLineColor('sell'"], + ["label: 'Hub'", "tgLineColor('hub'"], + ["label: 'Ring'", "tgLineColor('ring'"], + ["label: 'Orbit'", "tgLineColor('orbit'"], + ["label: 'Temperature', data: a.temps", "tgLineColor('temperature'"], + ["label: '\\u0394T', data: dtData", "tgLineColor('dt'"], + ["label: 'TP Spread'", "tgLineColor('ips'"], + ["label: 'RSI Spread'", "tgLineColor('rsi'"] +].forEach(function (pair) { + const i = gauge.indexOf('{ ' + pair[0]); + assert.ok(i > 0, 'dataset present: ' + pair[0]); + const line = gauge.slice(i, gauge.indexOf('\n', i)); + assert.ok(line.indexOf(pair[1]) > 0, pair[0] + ' reads ' + pair[1]); +}); +['ema8', 'ema12', 'ema24', 'ema50'].forEach(function (id) { + assert.ok(new RegExp("data: " + id + ", borderColor: getIndicatorColor\\('" + id + "'").test(gauge), id + ' reads its color'); +}); +assert.ok(/fill: '-1', backgroundColor: tgLineColor\('bollinger'/.test(gauge), 'band fill sits between the two bands'); +assert.ok(/value="#e8b019" onchange="setIndicatorColor\('ips'/.test(gauge), 'IPS swatch shows the color the line is drawn in'); + +// 2. The price chart stays visible after a direct renderChart (color change, Stack, Log) +const rc = gauge.slice(gauge.indexOf('function renderChart(candles, a) {'), gauge.indexOf('// RSI sub-chart')); +assert.ok(/document\.getElementById\('mainChart'\)\.style\.display = 'block';\s*$/.test(rc.trimEnd()), 'renderChart shows the price canvas again'); + +// 3. Moving averages +assert.ok(/var show200 = ema200\[closes\.length - 1\] != null;/.test(gauge), 'fifth line shows on every timeframe with enough bars'); +assert.ok(/cubicInterpolationMode: 'monotone'/.test(gauge), 'monotone curves (no overshoot)'); +assert.ok(/async function fetchYahooData\(symbol, interval, rangeOverride\)/.test(gauge), 'optional range for warm history'); +assert.ok(/fetchYahooData\(symbol, '5m', '5d'\)/.test(gauge), '5m short-day fallback'); +assert.ok(/ema200:\s+\{ label: 'EMA',\s+defaultColor: '#c4b5fd'/.test(gauge), 'compose mode knows the fifth EMA'); + +// 4. Run the helpers for real +function extractFn(name) { + const start = gauge.indexOf('function ' + name + '('); + assert.ok(start >= 0, 'function ' + name); + let depth = 0, i = gauge.indexOf('{', start); + for (; i < gauge.length; i++) { + if (gauge[i] === '{') depth++; + else if (gauge[i] === '}') { depth--; if (depth === 0) break; } + } + return gauge.slice(start, i + 1); +} +const endStart = gauge.indexOf('(function tgTemperatureGaugeV01() {'); +const endStop = gauge.indexOf('})();', endStart) + 5; +const endScript = gauge.slice(endStart, endStop); +assert.ok(endStart > 0 && endStop > endStart, 'v0.1 script block'); + +const store = {}; +const ctx = { + console: console, setTimeout: function (f) { f(); }, Promise: Promise, JSON: JSON, Math: Math, isFinite: isFinite, parseFloat: parseFloat, Date: Date, + localStorage: { getItem: function (k) { return Object.prototype.hasOwnProperty.call(store, k) ? store[k] : null; }, setItem: function (k, v) { store[k] = String(v); }, removeItem: function (k) { delete store[k]; } }, + document: { readyState: 'complete', getElementById: function () { return null; }, querySelector: function () { return null; }, addEventListener: function () {}, activeElement: null }, + rendered: 0 +}; +ctx.window = ctx; +vm.createContext(ctx); +vm.runInContext([extractFn('ema'), extractFn('tgAlphaOf'), extractFn('tgHexA'), extractFn('tgLineColor'), + 'var currentInterval = "1d"; var lastSymbol = "SPY"; var lastCandles = null; var lastAnalysis = null;', + 'function getEmaPeriods() { return [3, 5, 8, 13, 21]; }', + 'function renderChart() { rendered++; }', + 'var __long = null; function fetchYahooData(s, iv, r) { return Promise.resolve(__long); }', + endScript].join('\n'), ctx); + +assert.strictEqual(vm.runInContext("tgLineColor('ema200', 'rgba(196,181,253,0.7)')", ctx), 'rgba(196,181,253,0.7)', 'no saved color: old look kept'); +store.fl_tg_indicatorColors = JSON.stringify({ ema200: '#00ff00', price: '#112233' }); +assert.strictEqual(vm.runInContext("tgLineColor('ema200', 'rgba(196,181,253,0.7)')", ctx), 'rgba(0,255,0,0.7)', 'saved color keeps the line alpha'); +assert.strictEqual(vm.runInContext("tgLineColor('price', '#e8e4dc')", ctx), '#112233', 'saved color on a solid line'); +assert.strictEqual(vm.runInContext("tgLineColor('buy', '#34d399', 0.75)", ctx), 'rgba(52,211,153,0.75)', 'alpha override on a default'); + +// warm history: 60 long bars, the chart shows the last 30 +vm.runInContext([ + 'var all = []; for (var i = 0; i < 60; i++) all.push({ t: 1000 + i, c: 100 + Math.sin(i / 4) * 5 + i * 0.1 });', + '__long = all; lastCandles = all.slice(30).concat([{ t: 5000, c: 110 }]); lastAnalysis = {};', + 'tgWarmFetch("SPY", "1d", lastCandles);' +].join('\n'), ctx); +return Promise.resolve().then(function () { return new Promise(function (r) { setImmediate(r); }); }).then(function () { + const out = vm.runInContext('tgWarmEmas(lastCandles, [3, 5, 8, 13, 21])', ctx); + assert.ok(Array.isArray(out) && out.length === 5, 'five warm lines'); + out.forEach(function (arr, k) { + assert.strictEqual(arr.length, 31, 'aligned to the shown bars'); + assert.ok(arr.every(function (v) { return v != null && isFinite(v); }), 'line ' + k + ' starts at the left edge'); + }); + const full = vm.runInContext('ema(all.map(function (c) { return c.c; }), 21)', ctx); + assert.ok(Math.abs(out[4][0] - full[30]) < 1e-9, 'same values as the EMA over the longer history'); + const k = 2 / 22; + assert.ok(Math.abs(out[4][30] - (110 * k + out[4][29] * (1 - k))) < 1e-9, 'a live bar the long fetch lacks carries the EMA forward'); + assert.ok(ctx.rendered >= 1, 'warm lines redraw the chart'); + ctx.lastSymbol = 'QQQ'; + vm.runInContext('lastSymbol = "QQQ"', ctx); + assert.strictEqual(vm.runInContext('tgWarmEmas(lastCandles, [3, 5, 8, 13, 21])', ctx), null, 'warm lines never cross symbols'); + vm.runInContext('lastSymbol = "SPY"', ctx); + + // 5. Your marks: display only, kept on this device + store.fl_tg_triggers_v0 = JSON.stringify({ SPY: { buy: 101.5, sell: 120 } }); + const ds = vm.runInContext('tgTriggerDatasets(4)', ctx); + assert.strictEqual(ds.length, 2, 'two marks drawn'); + assert.strictEqual(ds[0].label, 'Your buy mark'); assert.strictEqual(ds[1].label, 'Your sell mark'); + assert.deepStrictEqual(Array.from(ds[0].data), [101.5, 101.5, 101.5, 101.5], 'flat line at your price'); + assert.ok(/never place an order/.test(gauge) && /Marks are reminders on the glass\. You decide\./.test(gauge), 'marks say what they are'); + assert.ok(!/fetch\(/.test(endScript.replace(/fetchYahooData\(/g, '')), 'the v0.1 block sends nothing anywhere'); + + // 6. A wider look: real symbols the feed serves today, honest words + ['HYG', 'LQD', 'XLP', 'XLY', 'GLD', 'DX-Y.NYB', 'VIXY'].forEach(function (s) { + assert.ok(endScript.indexOf("sym: '" + s + "'") > 0, 'wider look offers ' + s); + }); + assert.ok(/Not a stress index, and not advice\./.test(gauge), 'not a stress index, not advice'); + assert.ok(/Math\.random/.test(endScript) === false, 'no invented data'); + + // 7. Worker reads ^ symbols after a redeploy + assert.ok(/decodeURIComponent\(url\.pathname\)/.test(worker) && /v-temperature-gauge-v0\.1/.test(worker), 'worker decodes the path'); + + // 8. Locks inside the new code + const added = endScript + '\n' + gauge.split('\n').filter(function (l) { return /v-temperature-gauge-v0\.1/.test(l); }).join('\n'); + assert.ok(!/confirm\(/.test(added), 'no confirm()'); + assert.ok(!/innerHTML/.test(endScript), 'textContent only in the v0.1 block'); + assert.ok(!/\u2014/.test(added), 'no em dash in the v0.1 lines'); + assert.ok(/v-temperature-gauge-v0\.1/.test(notes), 'notes carry the marker'); + + console.log('SMOKE_OK temperature gauge v0.1'); +}).catch(function (e) { console.error(e); process.exit(1); }); diff --git a/docs/temperature-gauge.html b/docs/temperature-gauge.html index f3fd1a5d..e7e5c010 100644 --- a/docs/temperature-gauge.html +++ b/docs/temperature-gauge.html @@ -700,6 +700,16 @@ canvas.sub-canvas.custom { height: 80px !important; } opacity: 0.75; } #hiddenPanelsLink:hover { opacity: 1; color: var(--gold); } +/* v-temperature-gauge-v0.1: Lines and colors, Your marks */ +.tg-lines-list { display: grid; grid-template-columns: 1fr; gap: 4px; } +.tg-line-row { display: flex; align-items: center; gap: 8px; font-size: 0.74rem; color: var(--muted); } +.tg-line-row .tg-line-name { flex: 1; } +.tg-line-row .tg-line-sample { width: 22px; height: 0; border-top: 2px solid currentColor; } +.tg-line-row input[type=color] { width: 22px; height: 18px; padding: 0; border: 1px solid var(--border); border-radius: 3px; background: transparent; cursor: pointer; } +.tg-line-row.is-off { opacity: 0.45; } +.tg-mini-btn { padding: 4px 10px; background: var(--bg3); border: 1px solid var(--border); border-radius: 6px; color: var(--muted); font-size: 0.72rem; cursor: pointer; } +.tg-mini-btn:hover { border-color: var(--gold); color: var(--gold); } +.tg-mark-input { flex: 1; min-width: 0; padding: 4px 8px; background: var(--bg3); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-family: var(--font-mono, monospace); font-size: 0.76rem; } @@ -954,6 +964,40 @@ canvas.sub-canvas.custom { height: 80px !important; } + + +
-
+ +
- + Indicator-Price Spread @@ -1650,7 +1695,12 @@ async function loadData() { document.getElementById('loadBtn').disabled = true; try { - const data = await fetchYahooData(symbol, currentInterval); + // before v-temperature-gauge-v0.1: const data = await fetchYahooData(symbol, currentInterval); + let data = await fetchYahooData(symbol, currentInterval); + // v-temperature-gauge-v0.1: early in a trading day 5m has only a few bars. Look at 5 days instead. + if ((!data || data.length < 30) && currentInterval === '5m') { + try { data = await fetchYahooData(symbol, '5m', '5d'); } catch (e5) { /* keep the short day */ } + } if (!data || data.length < 30) { showError('Not enough data for ' + symbol + '. Try a different symbol or timeframe.'); document.getElementById('loadBtn').disabled = false; @@ -1658,6 +1708,8 @@ async function loadData() { } const analysis = analyzeData(data, symbol); renderAll(data, analysis, symbol); + // v-temperature-gauge-v0.1: fetch extra history so every moving average starts at the left edge. + if (typeof tgWarmFetch === 'function') tgWarmFetch(symbol, currentInterval, data); // Snowflake — fractal coherence across timeframes (async, doesn't block) computeSnowflake(symbol); } catch (e) { @@ -1717,7 +1769,10 @@ async function computeSnowflake(symbol) { } } -async function fetchYahooData(symbol, interval) { +// before v-temperature-gauge-v0.1: async function fetchYahooData(symbol, interval) { +// v-temperature-gauge-v0.1: optional rangeOverride (warm history for the moving averages, +// and a 5-day look when a 5m day is still too short). Same feed, same sources. +async function fetchYahooData(symbol, interval, rangeOverride) { // Map interval to Yahoo Finance range/interval const ivMap = { '5m': { range: '1d', interval: '5m' }, @@ -1726,7 +1781,10 @@ async function fetchYahooData(symbol, interval) { '1d': { range: '1y', interval: '1d' }, '1wk': { range: '5y', interval: '1wk' } }; - const { range, interval: yInterval } = ivMap[interval] || ivMap['1d']; + const _tgIv = ivMap[interval] || ivMap['1d']; + const range = rangeOverride || _tgIv.range; + const yInterval = _tgIv.interval; + // before v-temperature-gauge-v0.1: const { range, interval: yInterval } = ivMap[interval] || ivMap['1d']; const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}?range=${range}&interval=${yInterval}&includePrePost=false`; // Multiple CORS proxies — try each until one works @@ -3191,10 +3249,13 @@ function renderChart(candles, a) { if (existing) existing.destroy(); } }); + // before v-temperature-gauge-v0.1: 1W showed clock times, intraday showed no day. + // const labels = candles.map(c => { const d = new Date(c.t); return currentInterval === '1d' ? d.toLocaleDateString(...) : d.toLocaleTimeString(...); }); const labels = candles.map(c => { const d = new Date(c.t); - return currentInterval === '1d' ? d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : - d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); + if (currentInterval === '1d') return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); + if (currentInterval === '1wk') return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: '2-digit' }); + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' ' + d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); }); const closes = candles.map(c => c.c); @@ -3212,10 +3273,14 @@ function renderChart(candles, a) { const ctx = document.getElementById('mainChart').getContext('2d'); // Standardized MAs: 8/12/24 + 50 var EP_RC = getEmaPeriods(); - const ema8 = ema(closes, EP_RC[0]); - const ema12 = ema(closes, EP_RC[1]); - const ema24 = ema(closes, EP_RC[2]); - const ema50 = ema(closes, EP_RC[3]); + // before v-temperature-gauge-v0.1: const ema8 = ema(closes, EP_RC[0]); (and 12, 24, 50 the same way) + // v-temperature-gauge-v0.1: when warm history for this symbol and timeframe is in, the lines + // use it, so EMA 50 and EMA 200 start at the left edge. Signals still use the loaded window. + var _tgWarm = (typeof tgWarmEmas === 'function') ? tgWarmEmas(candles, EP_RC) : null; + const ema8 = _tgWarm ? _tgWarm[0] : ema(closes, EP_RC[0]); + const ema12 = _tgWarm ? _tgWarm[1] : ema(closes, EP_RC[1]); + const ema24 = _tgWarm ? _tgWarm[2] : ema(closes, EP_RC[2]); + const ema50 = _tgWarm ? _tgWarm[3] : ema(closes, EP_RC[3]); // ═══ Layered Signal Architecture ═══ // Layer 1 (Temperature): ALWAYS required — zone transition is the trigger @@ -3274,8 +3339,11 @@ function renderChart(candles, a) { } // EMA 200 (structural — only meaningful on 1D/1W) - var ema200 = ema(closes, getEmaPeriods()[4]); - var show200 = (currentInterval === '1d' || currentInterval === '1wk') && ema200[closes.length - 1] != null; + // before v-temperature-gauge-v0.1: var ema200 = ema(closes, getEmaPeriods()[4]); + // before v-temperature-gauge-v0.1: var show200 = (currentInterval === '1d' || currentInterval === '1wk') && ema200[closes.length - 1] != null; + // v-temperature-gauge-v0.1: the fifth line shows on every timeframe that has enough bars for it. + var ema200 = _tgWarm ? _tgWarm[4] : ema(closes, getEmaPeriods()[4]); + var show200 = ema200[closes.length - 1] != null; // Y-axis scale fix: compute visible price range, filter gravity lines var priceMin = Math.min(...closes.filter(c => c != null)); @@ -3318,9 +3386,9 @@ function renderChart(candles, a) { if (layerGyro && typeof computeGyroStack === 'function') { var gyro = computeGyroStack(closes, candles, EP_RC[0]); gyroDatasets.push( - { label: 'Hub', data: gyro.hub, borderColor: 'rgba(232,176,25,0.95)', borderWidth: 2.2, pointRadius: 0, tension: 0.25, fill: false, order: 2 }, - { label: 'Ring', data: gyro.ring, borderColor: 'rgba(96,165,250,0.85)', borderWidth: 1.6, borderDash: [5, 3], pointRadius: 0, tension: 0.25, fill: false, order: 2 }, - { label: 'Orbit', data: gyro.orbit, borderColor: 'rgba(167,139,250,0.75)', borderWidth: 1.4, borderDash: [2, 4], pointRadius: 0, tension: 0.25, fill: false, order: 2 }, + { label: 'Hub', data: gyro.hub, borderColor: tgLineColor('hub', 'rgba(232,176,25,0.95)'), borderWidth: 2.2, pointRadius: 0, tension: 0.25, fill: false, order: 2 }, + { label: 'Ring', data: gyro.ring, borderColor: tgLineColor('ring', 'rgba(96,165,250,0.85)'), borderWidth: 1.6, borderDash: [5, 3], pointRadius: 0, tension: 0.25, fill: false, order: 2 }, + { label: 'Orbit', data: gyro.orbit, borderColor: tgLineColor('orbit', 'rgba(167,139,250,0.75)'), borderWidth: 1.4, borderDash: [2, 4], pointRadius: 0, tension: 0.25, fill: false, order: 2 }, { label: 'Watch', data: gyro.watch, borderColor: 'transparent', backgroundColor: 'rgba(232,176,25,0.9)', pointRadius: Math.max(sigR - 3, 4), pointHoverRadius: Math.max(sigH - 2, 5), pointStyle: 'rectRot', pointBorderColor: 'rgba(255,236,179,0.9)', pointBorderWidth: 1.5, showLine: false, order: 0 } ); } @@ -3330,22 +3398,27 @@ function renderChart(candles, a) { data: { labels, datasets: [ - { label: 'Price', data: closes, borderColor: '#e8e4dc', borderWidth: 1.8, pointRadius: 0, tension: 0.1, fill: false, order: 1 }, + // before v-temperature-gauge-v0.1: Price borderColor '#e8e4dc' (fixed); BB borderColor 'rgba(167,139,250,0.3)' (fixed); + // BB Lower fill: '+1' shaded from the lower band to EMA 8 (the next dataset), not to the upper band. + { label: 'Price', data: closes, borderColor: tgLineColor('price', '#e8e4dc'), borderWidth: 1.8, pointRadius: 0, tension: 0.1, fill: false, order: 1 }, // Bollinger Bands — lavender envelope - { label: 'BB Upper', data: bbUpper, borderColor: 'rgba(167,139,250,0.3)', borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 5 }, - { label: 'BB Lower', data: bbLower, borderColor: 'rgba(167,139,250,0.3)', borderWidth: 1, pointRadius: 0, tension: 0.3, fill: '+1', backgroundColor: 'rgba(167,139,250,0.05)', order: 5 }, + { label: 'BB Upper', data: bbUpper, borderColor: tgLineColor('bollinger', 'rgba(167,139,250,0.3)'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 5 }, + { label: 'BB Lower', data: bbLower, borderColor: tgLineColor('bollinger', 'rgba(167,139,250,0.3)'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: '-1', backgroundColor: tgLineColor('bollinger', 'rgba(167,139,250,0.05)'), order: 5 }, // EMAs — Garden Language colors - { label: 'EMA ' + EP_RC[0], data: ema8, borderColor: getIndicatorColor('ema8', '#f07068'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 2 }, - { label: 'EMA ' + EP_RC[1], data: ema12, borderColor: getIndicatorColor('ema12', '#e8b019'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 3 }, - { label: 'EMA ' + EP_RC[2], data: ema24, borderColor: getIndicatorColor('ema24', '#d4a84c'), borderWidth: 1, pointRadius: 0, tension: 0.3, fill: false, order: 3 }, - { label: 'EMA ' + EP_RC[3], data: ema50, borderColor: getIndicatorColor('ema50', '#60a5fa'), borderWidth: 1.5, pointRadius: 0, tension: 0.3, fill: false, order: 4 }, + { label: 'EMA ' + EP_RC[0], data: ema8, borderColor: getIndicatorColor('ema8', '#f07068'), borderWidth: 1, pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 2 }, + { label: 'EMA ' + EP_RC[1], data: ema12, borderColor: getIndicatorColor('ema12', '#e8b019'), borderWidth: 1, pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 3 }, + { label: 'EMA ' + EP_RC[2], data: ema24, borderColor: getIndicatorColor('ema24', '#d4a84c'), borderWidth: 1, pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 3 }, + { label: 'EMA ' + EP_RC[3], data: ema50, borderColor: getIndicatorColor('ema50', '#60a5fa'), borderWidth: 1.5, pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 4 }, // EMA 200 — lavender (sanctuary color) - ...(show200 ? [{ label: 'EMA ' + EP_RC[4], data: ema200, borderColor: 'rgba(167,139,250,0.3)', borderWidth: 2, borderDash: [6, 3], pointRadius: 0, tension: 0.3, fill: false, order: 4 }] : []), + // before v-temperature-gauge-v0.1: borderColor 'rgba(167,139,250,0.3)' (fixed, the same lavender as the bands, hard to see) + ...(show200 ? [{ label: 'EMA ' + EP_RC[4], data: ema200, borderColor: tgLineColor('ema200', 'rgba(196,181,253,0.7)'), borderWidth: 2, borderDash: [6, 3], pointRadius: 0, tension: 0.3, cubicInterpolationMode: 'monotone', fill: false, order: 4 }] : []), // Gravity — bright gold - { label: 'Gravity', data: gravCenter, borderColor: 'rgba(232,176,25,0.6)', borderWidth: 2.5, borderDash: [8, 4], pointRadius: 0, fill: false, order: 3 }, + // before v-temperature-gauge-v0.1: borderColor 'rgba(232,176,25,0.6)' (fixed) + { label: 'Gravity', data: gravCenter, borderColor: tgLineColor('gravity', 'rgba(232,176,25,0.6)'), borderWidth: 2.5, borderDash: [8, 4], pointRadius: 0, fill: false, order: 3 }, // Buy/Sell — density-scaled - { label: 'Buy', data: buyPoints, borderColor: 'transparent', backgroundColor: '#10B981', pointRadius: sigR, pointHoverRadius: sigH, pointStyle: 'triangle', pointBorderColor: '#34d399', pointBorderWidth: sigB, showLine: false, order: 0 }, - { label: 'Sell', data: sellPoints, borderColor: 'transparent', backgroundColor: '#EF4444', pointRadius: sigR, pointHoverRadius: sigH, pointStyle: 'triangle', pointRotation: 180, pointBorderColor: '#f87171', pointBorderWidth: sigB, showLine: false, order: 0 }, + // before v-temperature-gauge-v0.1: Buy backgroundColor '#10B981', pointBorderColor '#34d399'; Sell '#EF4444' / '#f87171' (fixed) + { label: 'Buy', data: buyPoints, borderColor: 'transparent', backgroundColor: tgLineColor('buy', '#10B981'), pointRadius: sigR, pointHoverRadius: sigH, pointStyle: 'triangle', pointBorderColor: tgLineColor('buy', '#34d399', 0.75), pointBorderWidth: sigB, showLine: false, order: 0 }, + { label: 'Sell', data: sellPoints, borderColor: 'transparent', backgroundColor: tgLineColor('sell', '#EF4444'), pointRadius: sigR, pointHoverRadius: sigH, pointStyle: 'triangle', pointRotation: 180, pointBorderColor: tgLineColor('sell', '#f87171', 0.75), pointBorderWidth: sigB, showLine: false, order: 0 }, // (Old diamond signals removed — unified temperature-first system) // Volume overlay — brighter, semi-transparent bars at chart bottom { label: 'Volume', data: candles.map(c => c.v), type: 'bar', backgroundColor: candles.map((c, i) => i > 0 && c.c >= candles[i-1].c ? 'rgba(16,185,129,0.35)' : 'rgba(239,68,68,0.3)'), borderWidth: 0, yAxisID: 'yVol', order: 6 }, @@ -3363,6 +3436,8 @@ function renderChart(candles, a) { })), // Gyro Hub · Ring · Orbit (optional) ...gyroDatasets, + // v-temperature-gauge-v0.1: your own buy and sell marks (display only, never orders) + ...((typeof tgTriggerDatasets === 'function') ? tgTriggerDatasets(closes.length) : []), // Overlay datasets (built before chart creation) ...mainOverlayDatasets ] @@ -3407,6 +3482,9 @@ function renderChart(candles, a) { if (ctx.dataset.label === 'Orbit') return 'Orbit: ' + (ctx.raw != null ? Number(ctx.raw).toFixed(2) : '—'); if (ctx.dataset.label === 'Volume') return 'Vol: ' + (ctx.raw >= 1e6 ? (ctx.raw/1e6).toFixed(1) + 'M' : ctx.raw >= 1e3 ? (ctx.raw/1e3).toFixed(0) + 'K' : ctx.raw); if (ctx.dataset.label === 'Gravity') return '\u03C6-Gravity: ' + ctx.raw?.toFixed(2); + // v-temperature-gauge-v0.1: moving averages and your marks read out too + if (/^EMA \d+$/.test(ctx.dataset.label || '') && ctx.raw != null) return ctx.dataset.label + ': ' + Number(ctx.raw).toFixed(ctx.raw < 10 ? 4 : 2); + if ((ctx.dataset.label === 'Your buy mark' || ctx.dataset.label === 'Your sell mark') && ctx.dataIndex === ctx.dataset.data.length - 1) return ctx.dataset.label + ': ' + Number(ctx.raw).toFixed(2); return null; } } @@ -3464,6 +3542,12 @@ function renderChart(candles, a) { } }); + // v-temperature-gauge-v0.1: Chart.js puts back a canvas's first inline style when its chart is + // destroyed. The first price chart was made while the loading screen had the canvas hidden, so + // every later renderChart (a color change, Stack, Log, the warm lines) left the price chart blank. + // Show it again here. Compose mode still decides after this (its post-render hook runs last). + document.getElementById('mainChart').style.display = 'block'; + // RSI sub-chart const rsiCtx = document.getElementById('rsiChart').getContext('2d'); rsiChartInstance = new Chart(rsiCtx, { @@ -3513,6 +3597,9 @@ function renderChart(candles, a) { labels, datasets: [ { data: a.temps, backgroundColor: tempColors, borderWidth: 0, order: 2 }, + // v-temperature-gauge-v0.1: the Temperature swatch now colors a line over the zone bars + // (the bars keep their green / gold / red zone meaning). + { label: 'Temperature', data: a.temps, type: 'line', borderColor: tgLineColor('temperature', '#e8b019'), borderWidth: 1.4, pointRadius: 0, tension: 0.25, cubicInterpolationMode: 'monotone', fill: false, order: 0 }, // Classic thresholds (55/45) — untouched { data: new Array(closes.length).fill(55), borderColor: 'rgba(16,185,129,0.2)', borderWidth: 1, borderDash: [2,2], pointRadius: 0, type: 'line', fill: false, order: 1 }, { data: new Array(closes.length).fill(45), borderColor: 'rgba(239,68,68,0.2)', borderWidth: 1, borderDash: [2,2], pointRadius: 0, type: 'line', fill: false, order: 1 }, @@ -3548,6 +3635,8 @@ function renderChart(candles, a) { labels: labels, datasets: [ { data: dtData, backgroundColor: dtColors, borderWidth: 0 }, + // v-temperature-gauge-v0.1: the ΔT swatch colors this line (bars keep up green, down red) + { label: '\u0394T', data: dtData, type: 'line', borderColor: tgLineColor('dt', '#34d399'), borderWidth: 1.2, pointRadius: 0, tension: 0.25, cubicInterpolationMode: 'monotone', fill: false, order: 0 }, { data: new Array(closes.length).fill(0), borderColor: 'rgba(200,210,230,0.15)', borderWidth: 1, pointRadius: 0, type: 'line', fill: false } ] }, @@ -3570,9 +3659,10 @@ function renderChart(candles, a) { type: 'line', data: { labels: labels, datasets: [ // Temperature-Price Spread (gold line) — where temp "thinks" price should be vs where it is - { label: 'TP Spread', data: a.tpSpread, borderColor: 'rgba(232,176,25,0.7)', borderWidth: 1.5, pointRadius: 0, tension: 0.3, fill: false }, + // before v-temperature-gauge-v0.1: TP Spread 'rgba(232,176,25,0.7)' and RSI Spread 'rgba(139,92,246,0.6)' (fixed) + { label: 'TP Spread', data: a.tpSpread, borderColor: tgLineColor('ips', 'rgba(232,176,25,0.7)'), borderWidth: 1.5, pointRadius: 0, tension: 0.3, fill: false }, // RSI-Price Spread (purple line) — where RSI "thinks" price should be - { label: 'RSI Spread', data: a.rpSpread, borderColor: 'rgba(139,92,246,0.6)', borderWidth: 1.2, pointRadius: 0, tension: 0.3, fill: false }, + { label: 'RSI Spread', data: a.rpSpread, borderColor: tgLineColor('rsi', 'rgba(139,92,246,0.6)'), borderWidth: 1.2, pointRadius: 0, tension: 0.3, fill: false }, // Zero line — the moment of agreement { data: new Array(closes.length).fill(0), borderColor: 'rgba(200,210,230,0.2)', borderWidth: 1, borderDash: [3,3], pointRadius: 0, fill: false } ]}, @@ -3886,6 +3976,26 @@ function restoreHiddenPanels() { setTimeout(_polishResizeAllCharts, 50); } +// v-temperature-gauge-v0.1: every line on every chart reads its color here. +// With no saved color the line keeps its old look exactly (same color, same alpha). +// With a saved color (#rrggbb) the line takes it, keeping the old line's alpha. +function tgAlphaOf(css) { + var m = /rgba\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*,\s*([\d.]+)\s*\)/.exec(String(css || '')); + return m ? parseFloat(m[1]) : 1; +} +function tgHexA(hex, alpha) { + if (!/^#[0-9a-fA-F]{6}$/.test(String(hex || ''))) return hex; + if (alpha == null || alpha >= 1) return hex; + var r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16), b = parseInt(hex.slice(5, 7), 16); + return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'; +} +function tgLineColor(id, defaultCss, alphaOverride) { + var saved = null; + try { saved = (JSON.parse(localStorage.getItem('fl_tg_indicatorColors') || '{}') || {})[id] || null; } catch (e) { saved = null; } + if (!saved) return alphaOverride != null && /^#/.test(defaultCss) ? tgHexA(defaultCss, alphaOverride) : defaultCss; + return tgHexA(saved, alphaOverride != null ? alphaOverride : tgAlphaOf(defaultCss)); +} + // User-configurable indicator colors (persists in localStorage). function getIndicatorColor(indicator, defaultColor) { try { @@ -3901,9 +4011,12 @@ function setIndicatorColor(indicator, color) { } catch (e) {} var label = document.querySelector('.sub-chart-label[data-indicator="' + indicator + '"]'); if (label) label.style.color = color; + // v-temperature-gauge-v0.1: the sub-chart swatch and the Lines panel swatch stay in step. + if (label) { var _sw = label.querySelector('.sc-color'); if (_sw && /^#[0-9a-fA-F]{6}$/.test(color)) _sw.value = color; } if (typeof lastCandles !== 'undefined' && typeof lastAnalysis !== 'undefined' && lastCandles && lastAnalysis) { renderChart(lastCandles, lastAnalysis); } + if (typeof tgLinesPanelRender === 'function') tgLinesPanelRender(); } // Sub-chart layout toggle: stacked (default, full-width) \u2194 grid (compact). @@ -4386,7 +4499,8 @@ hideLoading = function() { rsi: { label: 'RSI (14)', defaultColor: '#8B5CF6', canvas: 'rsiChart', getData: function (a, c) { return (a && a.rsiArr) || []; } }, temperature: { label: 'Temperature', defaultColor: '#e8b019', canvas: 'tempChart', getData: function (a, c) { return (a && a.temps) || []; } }, dt: { label: 'ΔT', defaultColor: '#34d399', canvas: 'dtChart', getData: function (a, c) { return (a && a.tempROC) || []; } }, - ips: { label: 'IPS', defaultColor: '#f07068', canvas: 'gapChart', getData: function (a, c) { return (a && a.tpSpread) || []; } }, + // before v-temperature-gauge-v0.1: ips defaultColor '#f07068' (coral) while the drawn line is gold + ips: { label: 'IPS', defaultColor: '#e8b019', canvas: 'gapChart', getData: function (a, c) { return (a && a.tpSpread) || []; } }, price: { label: 'Price', defaultColor: '#e2d9c8', canvas: null, getData: function (a, c) { return (a && a.closes) || (c || []).map(function (k) { return k && k.c != null ? k.c : null; }); } }, // EMA labels are recomputed in updateComposeUI() so they track getEmaPeriods() @@ -4398,6 +4512,9 @@ hideLoading = function() { getData: function (a, c) { var closes = (a && a.closes) || (c || []).map(function (k) { return k && k.c != null ? k.c : null; }); return _tgEMA(closes, (typeof getEmaPeriods === 'function' ? getEmaPeriods()[2] : 24)); } }, ema50: { label: 'EMA', defaultColor: '#60a5fa', canvas: null, getData: function (a, c) { var closes = (a && a.closes) || (c || []).map(function (k) { return k && k.c != null ? k.c : null; }); return _tgEMA(closes, (typeof getEmaPeriods === 'function' ? getEmaPeriods()[3] : 50)); } }, + // v-temperature-gauge-v0.1: the fifth moving average can be promoted in compose mode too + ema200: { label: 'EMA', defaultColor: '#c4b5fd', canvas: null, + getData: function (a, c) { var closes = (a && a.closes) || (c || []).map(function (k) { return k && k.c != null ? k.c : null; }); return _tgEMA(closes, (typeof getEmaPeriods === 'function' ? getEmaPeriods()[4] : 200)); } }, volume: { label: 'Volume', defaultColor: '#60a5fa', canvas: null, chartType: 'bar', getData: function (a, c) { return (c || []).map(function (k) { return k && k.v != null ? k.v : null; }); } }, bollinger: { label: 'Bollinger Bands', defaultColor: '#a78bfa', canvas: null, @@ -6230,5 +6347,273 @@ hideLoading = function() { })(); + + + =====END FL DIFF===== End of file.