Fix optimal_limit_order hardcoded defaults, add IB per-instrument calibration - #40
Open
jay79-boop wants to merge 38 commits into
Open
Fix optimal_limit_order hardcoded defaults, add IB per-instrument calibration#40jay79-boop wants to merge 38 commits into
jay79-boop wants to merge 38 commits into
Conversation
…ibration get_optimal_quote() had a dead `if symbol == "demo" or True:` branch that silently ignored the symbol argument and always used hardcoded demo parameters. Made mu/sigma/A/k/gamma/b/tick_size/average_trading_size real keyword arguments with the same defaults, and added calibration in IBConnector that derives tick_size and sigma from live IB contract details and recent historical bars, falling back to the generic defaults when data isn't available. Adds a pytest suite (31 tests) covering the calibration and quote logic, a GitHub Actions workflow to run it on push/PR, and README updates documenting the new report-generation and test-running workflows.
Scoped to Stocks/ETFs/Crypto/Forex daily price datasets, which have a clean yfinance equivalent. Bonds/Commodities/Indices still require a real token (no matching yfinance schema for yield-to-price or FX normalization). get_pricing() is unaffected since it always loads the Universe-Daily-Price routing table first, which has no yfinance equivalent either. Verified with live symbols and zero credentials: AAPL/MSFT stocks, EURUSD forex, BTC/ETH crypto all returned correct OHLC data through the existing adjust/to-USD pipeline. Full 31-test suite still passes.
Maps the codebase into a queryable knowledge graph: 826 nodes and 1,330 edges across 64 labelled communities, built from AST extraction over 72 code files plus semantic extraction over 9 docs and 2 images. Outputs under graphify-out/: GRAPH_REPORT.md audit report (god nodes, bridges, cohesion, gaps) graph.json raw graph data graph.html interactive standalone visualization Machine-specific sidecars (.graphify_python, .graphify_root, cache/) are gitignored since they hardcode absolute paths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSqdYCWHYtsXmBA3ZCGny8
Web sessions start from a fresh container with none of the project's dependencies installed, so pytest and black could not run without repeating the setup by hand every session. Add .claude/hooks/session-start.sh, registered in .claude/settings.json. It mirrors the CI install (requirements-dev.txt) plus black, installing into a project .venv because the container's system interpreter carries Debian-managed packages such as cryptography that pip refuses to upgrade in place. It exports PATH and PYTHONPATH so bare python/pytest/black resolve to that venv rather than the system interpreter or isolated tool shims, and runs asynchronously so the session is usable while packages install. It is a no-op outside remote sessions. Also fix a pre-existing CI failure. The Tests workflow had failed every run since it was added in 7048aaf, including on main, with "ModuleNotFoundError: No module named 'pwb_toolbox'" during collection: the tests import the package from the repo root, pytest's prepend import mode puts tests/ on sys.path rather than the root, and the workflow never installs the package or sets PYTHONPATH. Setting pythonpath = ["."] under [tool.pytest.ini_options] fixes CI and plain local runs alike. Add CLAUDE.md documenting the layout, setup, commands, and test conventions. No runtime code is changed.
These models were added in c2aab2d on codex/add-risk-management-models-and-interface, four minutes after the commit whose tree became the root of the rewritten history. The rewrite therefore dropped them: neither RiskManagementModel nor TrailingStop appeared anywhere in the tree, pwb_toolbox_legacy included. Port the module to pwb_toolbox/backtesting/risk_models.py. The original lived at pwb_toolbox/backtest/risk_models/__init__.py, a package that no longer exists; a flat module matches the layout of its new neighbours (commission.py, portfolio.py, universe.py). The logic is carried over unchanged -- only black formatting differs -- since it depends on nothing but dataclasses and typing. The models are re-exported from pwb_toolbox.backtesting so they are reachable like the other building blocks. Tests come across too, dropping the sys.path insertion and stub_modules() call they needed when the old package pulled heavy imports at __init__ time. Added alongside them: the base class raising NotImplementedError, the package-level re-exports, the trailing stop ratcheting up with price, and every model tolerating an empty position list without dividing by zero. Nothing calls these models yet -- the engine that would have consumed them is gone, and wiring them into the Backtrader-based run_strategy is a separate design question. Suite goes from 31 to 43 tests.
.vocab.txt is regenerated from graph.json on every query, so it is a local cache rather than a tracked output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSqdYCWHYtsXmBA3ZCGny8
…sts (#4) is_tradable compared the current close to close[-2], skipping the most recent bar, and its length guard required three bars when only two are needed. Compare against close[-1], relax the guard to two bars, and add tests/test_base_strategy.py covering both. Co-Authored-By: Claude <noreply@anthropic.com>
Vendor the MIT-licensed ui-ux-pro-max skill into .claude/skills/ and register
21st.dev's 21st MCP (successor to Magic MCP) in .mcp.json.
The skill is a local CSV database — 84 UI styles, 192 color palettes, 74 font
pairings, 98 UX guidelines, 25 chart types across 22 stacks — queried by a
stdlib-only Python script with no network access and no API key. The installer
also drops six companion skills; those were pruned, since several of their
generators shell out to `npx shadcn` or image APIs.
The 21st MCP is an HTTP server authenticated with ${API_KEY_21ST}, expanded from
the environment so no key is committed. Claude Code reads that from its process
environment and never from .env, which .env.example and CLAUDE.md both spell out
because a filled-in dotfile is the obvious thing to try and is exactly what does
not work.
Also documents the NEEDS YOU convention: anything requiring the user goes in one
numbered block at the end of a reply rather than buried mid-paragraph, where it
reads as commentary and gets skipped.
The library itself is headless and unchanged — pytest passes 47/47.
The action-item block's shape was specified, but nothing said which shell its commands ran in, or how the reader was meant to find the program to run them in. - Target Windows PowerShell: no `curl | sed`, no `~`, no `&&`, and UTF-8 writes via `[IO.File]::AppendAllText` so em dashes and emoji survive on 5.1. Pin raw.githubusercontent URLs to a commit SHA so an unmerged branch cannot silently serve stale content. - Every step names the program it goes into and how to open it; GUI steps name the window, menu path, and button text; say what success looks like where a command prints nothing. Adds a worked example. - Never ask for a hand-edited placeholder. Use `Read-Host` for supplied values, so the paste runs verbatim and secrets stay out of shell history.
static/karaoke-box.html is a single-file web page that plays karaoke: lyrics fill left-to-right in sync with a bouncing playhead while a synthesized band plays underneath. No audio files and no network requests — every sound is built at runtime from Web Audio oscillators (a detuned triangle-pair guide voice, a filtered chord pad, a sine bass, and noise-burst drums). Ships six tracks: four public domain (Twinkle Twinkle, Row Row Row Your Boat, Frère Jacques, Beethoven's Ode to Joy with Henry van Dyke's 1907 text) and two originals written for this page so no melody is reproduced from memory. Songs are stored as [midi, beats, syllable] triples; a melisma is a note with a null syllable, and swing is baked into the durations so the audio and the lyric wipe share one timeline and cannot drift. Paste arbitrary lyrics and the page scores them: an English syllabifier splits the text (respecting the writer's own hyphens), then a seeded walk over a scale generates a melody with chords under it. The seed is derived from the text, so the same lyrics always produce the same tune, and a song round-trips through the URL hash. Not imported by anything under pwb_toolbox/ and not collected by `packages = find:`, so it stays out of the wheel and pytest never touches it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0197Pq328F76JxPk9fDWrDS3
docs/index.html exercises the skill end to end on a real page. Every visual decision came from a query: surface treatment from --domain style, the gold/violet palette from --domain color, the JetBrains Mono and IBM Plex Sans pairing from --domain typography, and the reveal timings from --domain gsap. The hero is an equity curve because that is the one artifact every backtest produces. The six figures beneath it are computed in the browser from the same synthetic series that is plotted, using the definitions in pwb_toolbox.performance, and labelled as a demo rather than a track record. Both typefaces are embedded as base64 woff2 instead of linked from a font CDN, which accounts for most of the ~120 KB and buys a page that renders identically offline, from file://, and under a strict CSP. Verified in Chromium: no console errors, no failed requests, no horizontal overflow at 1280px or 390px, and both themes resolving from tokens. Named index.html so GitHub Pages can serve it from /docs unchanged.
Ports an EMA/RSI/StochRSI TradingView indicator into a backtestable signal.
backtrader has RSI and Stochastic but no Stochastic RSI, so StochasticRsi
builds it from RSI plus Highest/Lowest over the RSI line, validated against an
independent pandas implementation. EmaRsiStochRsiSignal emits the entry/exit
pair the existing portfolio strategies already consume.
Three things measurement changed about the obvious implementation:
The degenerate-window guard cannot be Pine's exact `high - low == 0`. Wilder
smoothing decays average gain and loss at the same rate, so a constant price
holds RSI fixed only to within float rounding (~1e-14, not 0). The exact test
misses, the division amplifies that residue across the full 0-100 range, and a
price that never moved reports a confident %K of 100. The guard is an epsilon.
"EMA rising" and "close above EMA" are one filter, not two:
EMA_t - EMA_{t-1} = alpha * (close_t - EMA_{t-1}), so they agreed on all 2981
bars measured. A single filter with a trend_lookback that does change behaviour
replaces the illusory choice.
exit_on_trend_break defaults off. Entry requires a rising EMA, so one down bar
closes the position right after opening it — measured, it cut average hold from
20.4 to 5.5 bars and win rate from 64% to 47%.
Tests validate the arithmetic and the plumbing, not profitability: they run on
simulated random walks, which contain no edge by construction.
Grok has no per-conversation export button, so getting chat history out means either scraping a signed-in session or waiting on xAI's account data download. This does both, into one layout: raw/ (payloads verbatim), markdown/ (readable), merged/ (one document per topic), index.json. `probe` prints one listing page for diagnosis, `render` rebuilds Markdown from archived payloads offline, and `merge` groups repeated topics by TF-IDF cosine similarity, collapsing byte-identical duplicates. `run_daily.sh` re-merges from cron and stays quiet unless the grouping changed. Verified against a real accounts.x.ai download: 29 conversations, 245 turns, every message present verbatim, every timestamp parsed, no turn with an unknown role. Merging reduced it to 22 documents with no false grouping. That run drove most of the work — the real dump nests conversation metadata under a `conversation` wrapper with turns beside it, wraps each turn again under `response`, and uses MongoDB extended JSON for turn timestamps, which together made it parse as nothing at all. Three bugs the real data surfaced, each with a regression test: `convert` archived a bare entry while `_load_raw` expected `pull`'s envelope, so `render` re-read a converted export as 29 empty conversations; `merge` appended rather than replaced, so re-running tripled the document count; and `run_daily.sh` counted files with `ls` on a directory that does not exist before the first merge, which under `set -e` killed it silently. Field names in grok.com's internal endpoints move between builds, so nothing assumes one spelling: accessors take candidate keys, endpoint paths fall through a candidate list and are overridable, and every payload is archived verbatim. Route 1 (`pull`) is unverified — those endpoints are unpublished and were never run against a live account. `probe` checks that in one request. Output goes to a gitignored grok-export/; only cookie names are ever logged.
Add a self-contained karaoke player under static/
Adds a workflow that deploys the landing page on every push to main touching docs/. configure-pages runs with enablement: true so the first run turns Pages on through the API — the repository setting does not have to be flipped by hand first, which matters because that toggle is not reachable from a headless session. Deploys the docs/ directory only, not the repo root, so the workflow file and package source are not published alongside the page. docs/.nojekyll is insurance for the fallback source. "Deploy from a branch" does run Jekyll, which would try to build the three .md files sitting next to index.html; the Actions path serves the artifact verbatim and ignores the file. Concurrency is grouped without cancel-in-progress: a half-finished Pages deploy is worse than a slightly stale one.
The compose pane could only play a set of lyrics once. Now "Save to songbook" gives it a track number and keeps it: saved songs appear under a "Yours" divider, answer to the numeric remote like any other track, and can be removed with a control beside the row. An entry stores the lyric text, the vibe, and an optional title — never the notes. composeSong is seeded from the text, so rehydrating on load regenerates a byte-identical melody, which keeps a saved song a few hundred bytes instead of a full note table. Songbook rows are now built from DOM nodes rather than an innerHTML string. Titles are user input and were being interpolated into markup; a title containing a tag would have been parsed as one. Track codes continue from the built-in six (007, 008, …), and the header count follows along. localStorage is wrapped: a browser that blocks it still gets the song for the session, and the songbook says so rather than silently dropping it on reload. Share links now carry the title alongside the lyrics and vibe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0197Pq328F76JxPk9fDWrDS3
Let the karaoke songbook hold your own songs
graphify resolves symbols by bare name with no module-origin check, which produces two opposite errors on this repo: Defect A (under-linking): calls through an aliased local module import (`import pwb_toolbox.performance as pwb_perf` then `pwb_perf.cagr(...)`) are never emitted as edges. 23 such call sites exist and none are captured. The worst case is backtesting/portfolio.py, which calls 13 functions in performance/ while the graph holds zero edges between them. Defect B (over-linking): a third-party name colliding with a repo symbol (`from datasets import load_dataset` vs pwb_toolbox/datasets/__init__.py) is matched to the local definition. 9 fake edges, all from HuggingFace `datasets`. Net: 1,330 -> 1,344 edges, a 2.4% error surface. Degree rankings are robust to this; betweenness is not - load_dataset() drops 86% once corrected, so bridge findings need verification before use. networkx is optional and only gates the betweenness section, so the audit runs on a bare interpreter. Re-run after `graphify update` to measure drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSqdYCWHYtsXmBA3ZCGny8
Thirteen files had drifted out of black formatting. CI ran pytest but never black, so nothing caught it. This reformats them and closes the hole that let it happen — both halves together, since the gate alone would turn main red. The reformat is formatting-only, verified by parsing every touched file before and after and comparing ASTs, which are identical. The suite still passes at 170. The new `format` job is separate from `test` rather than a step in it: as a step, whichever ran first would mask the other's result, and a formatting nit hiding a real test failure is the worse outcome. black is pinned to 26.5.1 in requirements-dev.txt. Its stable style changes with each January release, so an unpinned formatter would eventually fail CI on code nobody touched, and a gate that fails for unrelated reasons is one people learn to ignore. The workflow reads that pin out of requirements-dev.txt rather than repeating it, so the two cannot drift and disagree about what "formatted" means. Scoped to the code this repo owns. pwb_toolbox_legacy/ is superseded and kept as-is for reference; the vendored ui-ux-pro-max skill tracks upstream and is restored by `uipro init`. Neither is in the gated scope. Two knock-on fixes: the session-start hook installed black separately and unpinned, which would have fetched a different version than CI enforces, so it now comes from requirements-dev.txt; and CLAUDE.md said "CI does not check formatting" and documented the narrower `black pwb_toolbox/` scope, both of which are now wrong.
A Duet toggle splits the monitor into two lanes, one per singer. Lane A
keeps the rose-to-amber wipe; lane B gets a cool teal-to-periwinkle one,
so at a glance you can tell whose line is whose. The singer who is
waiting still sees their next line, held dim rather than sweeping, and
the look-ahead row is dropped because the lanes already carry it.
Each song says how it wants to be split. Twinkle, Ode to Joy, the blues
and the waltz alternate lines and finish together; the blues already had
call-and-response lines ("Yeah…", "Mm-hmm…") that become B's naturally.
Row Row Row Your Boat and Frère Jacques are rounds, so duet mode runs
them as actual canons: B sings the same words two bars behind A, which
extends the arrangement by the offset. Songs with no arrangement of
their own, including anything you write, fall back to alternating lines.
Where a line is marked for both singers, B takes a harmony — the nearest
chord tone above the melody, read off whatever chord is sounding
underneath, so it is consonant by construction rather than a fixed
interval. B's guide voice is filtered darker with a sub-octave partial
instead of an upper one, to keep the two apart by ear as well as eye.
The part badges are buttons: click A or B to drop that part from the
guide and sing it yourself while the other stays guided.
Rendering was one hardcoded line; it is now a lane structure the two
share. setLaneEntries paints synchronously rather than waiting on the
next animation frame — a background tab gets no frames, and the monitor
would otherwise sit on the previous song's words.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197Pq328F76JxPk9fDWrDS3
Add duet mode to the karaoke player
The workflow claimed enablement: true would turn Pages on via the API so the repository setting never had to be touched. Run 1 disproved that: Get Pages site failed. Error: Not Found Create Pages site failed. Error: Resource not accessible by integration pages: write lets the default GITHUB_TOKEN deploy to a Pages site that already exists; it does not let it create one, which needs admin rights the workflow token does not carry. So Settings > Pages > Source > "GitHub Actions" is a one-time manual prerequisite, and the comment and CLAUDE.md now say so instead of promising otherwise. Keeping the flag: once Pages exists the preceding GET succeeds and no create is attempted, so it costs nothing, and it does work under a token with admin rights.
A base class reached through a relative import is emitted as uses/INFERRED/0.5 anchored to the import line, instead of inherits/EXTRACTED/1.0 anchored to the class definition. Absolute imports resolve correctly, so the graph holds 21 correct `inherits` edges while all 11 BaseStrategy subclasses in backtesting/strategies.py are downgraded. Topology is unaffected, but `inherits` queries silently miss the entire modern package. This is the same root cause as defects A and B: resolution keys off the import form rather than the symbol. Also reports dead base-class imports as a separate note. Following the inheritance chain surfaced that pwb_toolbox_legacy imports 16 base classes from pre-rename `pwb_toolbox.*` paths that no longer exist (pwb_toolbox.datasets.raw, .perimeters, .raw.analysis, .knowledge_graph). Those edges are correctly typed only because graphify fell back to bare-name matching, so they describe intent rather than runnable code. Flagged rather than fixed - the package is untested and unreferenced by CI, so it may be archived deliberately. Totals: 32 structural errors (2.41%) plus 11 mistyped (0.83%). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSqdYCWHYtsXmBA3ZCGny8
"Score me" opens the microphone and rates what it hears against the note the song expects at that moment. Pitch comes from a YIN detector rather than plain autocorrelation. A sung note is rich in harmonics and bare autocorrelation octave-jumps on it; YIN's cumulative mean normalised difference stays on the fundamental. The buffer is halved in rate first, which quarters the work at no cost to a range that tops out around 1 kHz, and the search is bounded to 70–1050 Hz. A parabolic fit around the minimum gives sub-sample accuracy — measured at 0 cents against a synthetic 440 Hz reference. Comparison folds to the nearest octave, so a low voice answering a high line is singing the note rather than missing it by twelve. Full credit within a quarter-tone, tailing to nothing at two semitones. A pitch ribbon shows the tune as bars with your voice plotted along it, green where it lands. At the end a card gives the score out of 100, a rank, how many notes landed, and the longest unbroken run in tune. The run is measured in beats and converted through the current tempo, not counted in animation frames, which would have read differently on a 120 Hz display. Notes are counted by onset rather than by pitch, so two notes at the same pitch count as two. In duet mode, whichever part you muted is the part that gets graded. The microphone is never routed onward to the speakers — the page is already playing a backing track, and connecting it would be a feedback loop. Denial is handled explicitly: an embedded page is often refused the microphone outright, and the page says so and stays usable rather than failing silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0197Pq328F76JxPk9fDWrDS3
Score the singing against the melody
CI runs `black --check --diff pwb_toolbox/ tools/ tests/` with the version pinned in requirements-dev.txt (black==26.5.1). graph_audit.py was the only file failing; verified locally with that exact version, 45 files now clean. Behaviour is unchanged - the audit reports the same 23/9/11 counts after reformatting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSqdYCWHYtsXmBA3ZCGny8
Pages is now served by Settings > Pages > Source > "Deploy from a branch", main, /docs. GitHub builds that itself, so the workflow has nothing left to do and cannot work anyway. pages.yml never succeeded — six runs, all failing at configure-pages with Get Pages site failed. Error: Not Found Create Pages site failed. Error: Resource not accessible by integration The default GITHUB_TOKEN may not create a Pages site: pages: write covers deploying to one that already exists, not creating one. And now that a branch source is selected, actions/deploy-pages could not target it regardless, since it only deploys where the build type is workflow. Left in place it would keep marking every push that touches docs/ as failed. docs/.nojekyll stays and matters more than before: the branch source does run Jekyll, which would otherwise try to build the .md files sitting beside index.html. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MYpq7J61wGBjctqkKLP9T
Finishing a scored track now files the run: score, rank, which song, who sang it, and when. The board sits under the songbook, sorted best first, and each songbook row carries your personal best for that track. An entry records the conditions too — duet and which part, and the tempo when it is not 100% — because a run at 145% is not quite the same feat as one at speed, and the row should say so rather than flatter the number. Beating your own best on a track flags it on the result card, with the score it beat. A first run says so instead, rather than claiming a record over nothing. The singer's name is a free text field, so more than one person can hold places in a session; it persists alongside the scores. Both go through the same guarded localStorage as the songbook, so a browser that blocks storage still keeps the board for the session and says as much. The board was originally stacked under the compose pane, which left the left column half empty; it reads better beneath the songbook, and the two are related anyway. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0197Pq328F76JxPk9fDWrDS3
Keep a leaderboard of scored runs
Add graphify knowledge graph and an audit tool for it Maps the repo into a queryable knowledge graph (826 nodes, 1,330 edges, 64 communities) and adds tools/graph_audit.py, which validates the graph against the repo's actual imports. The audit found three systematic extraction defects, all from graphify resolving symbols by bare name without a module-origin check: 23 missing edges from aliased module calls, 9 fake edges from a third-party name collision, and 11 mistyped inheritance edges. Degree rankings survive correction; betweenness does not.
…on-9ci2wb Publish docs/ from the branch instead of a workflow
tools/karaoke_server serves static/karaoke-box.html and a score API from
one origin, so several people posting from their own phones land on one
board. Standard library only, no new dependencies:
python -m tools.karaoke_server
The page notices it was served by a board host, via an injected meta tag,
and connects on its own. A copy opened straight from disk finds no such
tag and makes no requests at all — which is also what keeps the published
artifact quiet, since it is sandboxed against outbound traffic and could
never reach a server anyway. Pointing a standalone page at a server is a
matter of pasting its address into the panel.
Nothing a client sends is trusted past its shape. Text is length-capped
and stripped of control characters, numbers are range-checked, and the
timestamp is assigned server-side, so a submission cannot backdate itself
to win a tie. Malformed runs get a 400 explaining why. Storage is a JSON
file written through a temporary and renamed into place, so an interrupted
write cannot leave a half-file; a corrupt or hand-edited file reads as
empty rather than taking the server down.
The page keeps its local board as the fallback and shows that instead
whenever a configured server is unreachable, saying so rather than
appearing empty. Clearing only ever affects this browser's copy — the
shared board is not ours to wipe.
The page is authored as an artifact fragment with no <html> around it, so
serving it raw would land in quirks mode and lay out wrongly. The server
splits it at the stylesheet and wraps it as a proper document.
Board rules are tested directly rather than over a socket, so the suite
stays offline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197Pq328F76JxPk9fDWrDS3
Add a shared leaderboard server for the karaoke page
Templates the market-close broadcast script that was being written by hand,
filling the four data segments from pwb_toolbox.datasets and leaving the
kicker to a human.
Three constraints drove the design:
Numbers never reach ElevenLabs as digits. TTS reads numerals by its own
rules, and on a markets script that is most of the runtime -- 4.09 comes
back as "four point zero nine" where the desk says "four-oh-nine". spoken.py
spells everything in broadcast idiom first ("six tenths of a percent", "a
hundred and forty points"), a test asserts the rendered script contains no
digit at all, and the CLI warns when one survives.
The generator reports moves and never invents causes. It has prices, not
press releases, so no line asserts why anything happened -- a template that
fills in "after the company beat expectations" would eventually broadcast
something false about a real company on a day nobody is reading the output.
The jokes are about post-hoc explanation instead, which needs no facts
beyond the move.
The disclaimer is a fixed string. Every other segment rotates from a bank
seeded by the session date, so a working week does not repeat itself while a
given day always renders identically.
market.py keeps collect() as the only networked function and everything else
pure over DataFrames, so the 100 new tests run offline like the rest of the
suite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016phtezrnZSLL3u4PU8cQEG
The two segments carry nearly every number in the broadcast -- index levels, breadth counts, two percentage moves, two closing prices -- and they are also the segments that change most between sessions, so an unfamiliar ticker spelling or an odd-sounding level surfaces there first. Prints whole segments rather than a stripped-down figure list, jokes included, so what you audition is what ships. Exits 1 when neither segment has data, which makes it usable as a guard in a scheduled run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016phtezrnZSLL3u4PU8cQEG
…cription-6yx38q Add daily market-close script generator for TTS avatars
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
get_optimal_quote()had a deadif symbol == "demo" or True:branch that silently ignored its arguments and always used hardcoded demo parameters. Mademu/sigma/A/k/gamma/b/tick_size/average_trading_sizereal keyword arguments (same defaults as before), so callers can actually pass their own values.IBConnector: derivestick_sizefrom live IB contract details andsigmafrom recent historical daily bars, falling back to the generic defaults when market data isn't available.run_strategy/generate_reportsAPI, and documented how to run tests.yfinancefallback toload_dataset()for when neitherPWB_API_KEYnorHF_ACCESS_TOKENis set, scoped toStocks-Daily-Price,ETFs-Daily-Price,Cryptocurrencies-Daily-Price, andForex-Daily-Price(these have a clean yfinance equivalent). Bonds/Commodities/Indices still require a real token — no matching yfinance schema for the yield-to-price and FX-normalization logic those expect.get_pricing()is unaffected since it always loads theUniverse-Daily-Pricerouting table first, which has no yfinance equivalent. This lowers the bar for anyone trying the library without applying for API access first.Test plan
pytest tests/ -v— 31 passed (new suite covering calibration and quote logic, plus existing performance-metrics tests)🤖 Generated with Claude Code