feat(reporting): Discipline Workshop plan renderer - #12
Conversation
Adds a `render_dw_plan()` surface that emits the morning game plan in the format the MyInvestingClub Discipline Workshop Tab Group Guidelines require, plus the schema fields that format depends on. The existing `render_game_plan()` table (Z-score, covariance, R) is left untouched and remains the analyst-facing view. The two are separate renderers rather than one flagged function because they serve different readers and share almost no layout. Schema: - `MarketCycle` / `RiskLevel` enums (workshop's own vocabulary) - `TradePlan.entry_lines`, `.float_shares`, `.sector`, `.price`, `.volume`, `.risk_level` - `TradePlan.mic_entry_lines()` resolves the three-line entry ladder: explicit `entry_lines`, else a full support/inflexion/resistance triplet (descending for SHORT), else the single `entry`, else nothing. It never invents levels to pad a ladder. - `GamePlanRecord.market_cycle`, `.bias`, `.top_runners` Risk: - `RiskConfig.max_trades`. A round-trip cap is not expressible in R -- a session can stay inside its R budget while churning dozens of trades -- so the overtrading limit needs its own field. All additions are optional with defaults, so SCHEMA_VERSION stays at 1.0 and existing archived records deserialize unchanged. Plans missing an entry ladder or a stop are withheld from the posted body and listed explicitly, so an incomplete setup is visible rather than silently dropped. Tests: 26 new cases covering line format, ladder resolution order, float abbreviation, withholding, and a persistence round-trip.
There was a problem hiding this comment.
Summary
This PR adds a new render_dw_plan() function that formats game plans in the MyInvestingClub Discipline Workshop format. The implementation is well-designed and production-ready.
Key Strengths:
- Clean separation between the analyst-facing
render_game_plan()and the workshop-facingrender_dw_plan()renderers - Backward-compatible schema changes with comprehensive field documentation
- Excellent test coverage (26 new tests) covering edge cases like missing stops, incomplete ladders, and persistence round-trips
- Thoughtful handling of incomplete plans (withheld rather than posted with invented data)
- Proper validation logic in
TradePlan.mic_entry_lines()with clear precedence order
Architecture Highlights:
- New enums (
MarketCycle,RiskLevel) use workshop vocabulary verbatim, eliminating translation layer RiskConfig.max_tradescorrectly captures overtrading risk that R-units alone cannot express- Schema version remains at 1.0 as all fields are optional with defaults
The PR is well-documented, maintains consistency with existing code patterns, and includes no security concerns or logic defects.
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
Gives the Discipline Workshop renderer a command-line entry point. Before
this, `render_dw_plan` (and `render_game_plan` before it) had no caller
anywhere in src/ and could not be reached from a terminal, so the morning
workflow had no way to invoke it -- despite README.md advertising
`tradekit cards --help`.
Adds a `cards` group, matching the name the README already documented, with
a `gameplan` subcommand:
tradekit cards gameplan [DATE] --format {dw,table,json}
DATE defaults to today in ET. `dw` is the channel post format, `table` the
existing analyst view, `json` the raw stored item.
`--out PATH` is provided because shell redirection is not usable here: the
top-level group prints a session banner to stdout with ANSI styling, so
`> plan.md` produces a file with escape codes before the heading. `--out`
writes the rendered text only, and creates parent directories so a
scheduled job can target a dated path.
Plan text goes through click.echo rather than console.print -- Rich would
interpret bracketed text in a plan's notes (e.g. "[30% gap]") as markup and
swallow it.
Risk options (--r-dollars, --daily-max-r, --per-trade-max-r, --max-trades)
build a RiskConfig only when at least one is supplied. Defaulting one in
unconditionally would publish a stock 1R figure as if it were the trader's
own; absent options mean the risk block is simply omitted.
Also types `ReportDocument.from_item` as `Self` instead of
`ReportDocument`, so `GamePlanRecord.from_item(...)` is statically known to
be a GamePlanRecord. This removes the need for a cast at the call site and
drops the repo's mypy error count from 167 to 161.
Tests: 17 new CLI cases (format dispatch, missing-record exit code, stdout
left clean on failure, --out byte-for-byte match with stdout and free of
ANSI, risk-option defaulting).
|
Added the CLI entry point flagged in the description, so this branch now covers the full path from stored record to postable plan.
I left the banner itself alone — routing it to stderr would be the better fix, but it changes the output contract of all 18 existing commands and doesn't belong in this PR. Plan text goes through Risk options build a config only when supplied. One typing change beyond the CLI: Sample output: README updated with both the feature bullet and usage examples, including the Checks: |
Closes the four conformance gaps between
tradekit's game-plan output and the format the MyInvestingClub Discipline Workshop requires for a plan posted to the channel by 9:00 AM market time.What changed
New renderer —
render_dw_plan(plan, config=None)inreporting/render.py:render_game_plan()is unchanged. It emits an analyst table (Z-score, covariance, R) that is useful internally but is not what the workshop reads. I kept them as two renderers rather than adding afmt=flag to one function: they share no layout, and a flag would have made the table path pay for the plan path's field lookups.Schema (
reporting/schema.py)MarketCycle(HOT MARKET/IDEAL FOR SHORT/SLOW MARKET) andRiskLevelenums, using the workshop's vocabulary verbatim so the posted text needs no translation layer.TradePlan:entry_lines,float_shares,sector,price,volume,risk_level.TradePlan.mic_entry_lines()resolves the three-line ladder in preference order: explicitentry_lines→ a complete support/inflexion/resistance triplet (sorted descending for SHORT) → the singleentry→ empty. A lonesupportdoes not become a ladder, and a single entry is not padded out to three invented levels.GamePlanRecord:market_cycle,bias,top_runners.Risk (
reporting/runits.py) —RiskConfig.max_trades. This can't be folded into the existing R fields: a session can stay comfortably inside its 3R daily budget while still taking 80 round-trips, which is the specific overtrading failure the workshop grades on. The cap needs to be a count.Compatibility
Every new field is optional with a default, so
SCHEMA_VERSIONstays at1.0and previously archivedgameplan/GLOBAL/*.jsonrecords deserialize unchanged. A round-trip test asserts this.Behaviour worth reviewing
A plan lacking either an entry ladder or a stop is withheld from the posted body and named in a trailing line, rather than being rendered with a
—where the stop goes. Posting a setup with no stop defeats the point of the review, but silently dropping the ticker hides that you were watching it. Open to a different call here.Tests
26 new cases in
tests/test_reporting.py: line-format exactness, ladder resolution order (including SHORT descending), float abbreviation (8.44B/40.15M/1M), top-runners derivation and override, the required closing line, withholding, risk block, and the persistence round-trip.ruff check .clean.mypy src/tradekitunchanged at 167 pre-existing errors — none in the touched files.Pre-existing failures, not from this PR
uv run pytest -q→ 3 failed, 112 passed. The same three fail on a cleanmain:TestIngest::test_accounts_from_falcon_verbatim_and_kindsTestIngest::test_build_daily_card_merges_deterministic_and_narrativeTestIngest::test_ingested_card_renders_canonical_tablesCause:
DEFAULT_ACCOUNT_KINDSinreporting/ingest.py:34was deliberately emptied (# Load account mappings from environment or config file instead of hardcoding), but the tests still assert the removed hardcoded mapping, e.g.TR4425 → SIM. The fix belongs in the tests or in a config fixture, not in the renderer, so I left it out of scope.Open question: no CLI entry point
render_game_planhas no caller anywhere insrc/, and there is nocardscommand incli.py— thoughREADME.md:29documentstradekit cards --help. Sorender_dw_planis reachable from the library but not from the command line, and the morning workflow can't invoke it yet. Happy to add atradekit cards gameplan --format dwcommand (or whatever surface you'd prefer) in a follow-up — didn't want to design a new CLI namespace inside a renderer PR.