Skip to content

feat(reporting): Discipline Workshop plan renderer - #12

Open
davdunc wants to merge 2 commits into
mainfrom
feat/discipline-workshop-renderer
Open

feat(reporting): Discipline Workshop plan renderer#12
davdunc wants to merge 2 commits into
mainfrom
feat/discipline-workshop-renderer

Conversation

@davdunc

@davdunc davdunc commented Aug 29, 2026

Copy link
Copy Markdown
Owner

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 rendererrender_dw_plan(plan, config=None) in reporting/render.py:

Top runners:
AEO-High volume Float: 3.26B

AEO- 17.50 / 18.00 / 18.50, stop out 17.00 Float: 3.26B Notes: Earnings beat...

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 a fmt= 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) and RiskLevel enums, 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: explicit entry_lines → a complete support/inflexion/resistance triplet (sorted descending for SHORT) → the single entry → empty. A lone support does 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_VERSION stays at 1.0 and previously archived gameplan/GLOBAL/*.json records 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/tradekit unchanged at 167 pre-existing errors — none in the touched files.

Pre-existing failures, not from this PR

uv run pytest -q3 failed, 112 passed. The same three fail on a clean main:

  • TestIngest::test_accounts_from_falcon_verbatim_and_kinds
  • TestIngest::test_build_daily_card_merges_deterministic_and_narrative
  • TestIngest::test_ingested_card_renders_canonical_tables

Cause: DEFAULT_ACCOUNT_KINDS in reporting/ingest.py:34 was 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_plan has no caller anywhere in src/, and there is no cards command in cli.py — though README.md:29 documents tradekit cards --help. So render_dw_plan is reachable from the library but not from the command line, and the morning workflow can't invoke it yet. Happy to add a tradekit cards gameplan --format dw command (or whatever surface you'd prefer) in a follow-up — didn't want to design a new CLI namespace inside a renderer PR.

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.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-facing render_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_trades correctly 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).
@davdunc

davdunc commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

Added the CLI entry point flagged in the description, so this branch now covers the full path from stored record to postable plan.

tradekit cards gameplan [DATE] --format {dw,table,json}

DATE defaults to today in ET. The group is named cards to match what README.md already documented but never shipped.

--out PATH rather than shell redirection. The top-level group prints a session banner to stdout with ANSI styling, so tradekit cards gameplan --format dw > plan.md yields a file that literally begins \x1b[2mSat Aug \x1b[0m... before the heading. --out writes the rendered text only and creates parent directories, so a scheduled job can target a dated path. A test asserts the written file contains no escape sequences and matches stdout.

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 click.echo, not console.print. Rich treats bracketed text as markup, and plan notes legitimately contain things like [30% gap], which would be silently swallowed.

Risk options build a config only when supplied. --r-dollars, --daily-max-r, --per-trade-max-r, --max-trades. If none are passed the risk block is omitted rather than rendered from defaults — publishing a stock $280 1R as though it were the trader's own number seemed worse than showing nothing. Partial input fills the remaining R fields from defaults.

One typing change beyond the CLI: ReportDocument.from_item is now annotated Self instead of ReportDocument, so GamePlanRecord.from_item(...) is statically a GamePlanRecord and the call site needs no cast. Side effect: repo mypy errors drop 167 → 161, i.e. six pre-existing errors elsewhere resolve too.

Sample output:

## Discipline Workshop Plan — 2026-08-31

**Market Assessment:** HOT MARKET
**Bias:** LONG (first bounce)
**Thesis Trade:** AEO

Top runners:
AEO-High volume Float: 3.26B
BBLG-High volume Float: 5.12M

AEO- 17.50 / 18.00 / 18.50, stop out 17.00 Float: 3.26B Notes: Earnings beat [30% gap] holding VWAP.

BBLG- 2.60 / 2.80 / 3.00, stop out 2.40 Float: 5.12M Notes: Low float scalp.

The money flow will be vital to adding to winners and avoiding watchlist lines if attention is changed to watchlist stock. Technical reasons for adjusting plans are valid.

**Risk:** 1R = $280 | per-trade max 1R ($280) | daily stop 3R ($840) | max 5 trades

### Rules for Today
1. Max 5 trades

> Withheld (no entry ladder or no stop): NODATA

README updated with both the feature bullet and usage examples, including the --out caveat.

Checks: pytest -q → 129 passed, 3 failed — the same three pre-existing TestIngest failures described above, unchanged. ruff check . clean. mypy src/tradekit 161, down from the 167 baseline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant