Bots

bots forgejo

Notes

Review 14
  • Verdict: APPROVED

    Re-review of board item #1851. Previous review (review-1851-2026-07-10) returned NEEDS_REFINEMENT for two reasons: (1) missing arch note, (2) issue body included already-completed dashboard work. Both issues are now resolved.

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- Standalone, references #19 dependency
    • [x] Repo -- ldraney/botfire
    • [x] User Story -- "As a trader, I want the scoreboard service to log Live Edge win rate, P&L, and trade count broken down by sport"
    • [x] Context -- Explains Live Edge expansion, notes dashboard already has sport analytics
    • [x] File Targets -- Modify scoreboard.rb, create scoreboard_test.rb, explicit NOT-touch list
    • [x] Feature Flag -- none (internal analytics)
    • [x] Acceptance Criteria -- 3 scoreboard-specific criteria
    • [x] Test Expectations -- unit test with run command
    • [x] Constraints -- SQLite, BotFire->Market->Game path
    • [x] Checklist -- standard PR/test/scope checklist
    • [x] Related -- project-botfire, issue #19

    Traceability

    • [x] story:bot-strategy label -- "As a bot operator, I want to identify and exploit profitable trading conditions..."
    • [x] story note verified -- found in project-botfire user-stories section
    • [x] arch:bots label -- bots architecture component
    • [x] arch note verified -- arch-bots note exists in pal-e-docs (ID 2527, "Architecture: BotFire Bot Engine"); arch-domain-bots also exists (ID 2541, "Domain Model: BotFire"). Previous review's [SCOPE] finding resolved.
    • [x] Forgejo issue -- ldraney/botfire#20, open

    File Targets

    • [x] app/services/scoreboard.rb -- verified: file exists (102 lines), groups by bot only, no sport_type references. Valid modification target.
    • [x] test/services/scoreboard_test.rb -- verified: does not exist yet. test/services/ directory exists with sibling test files (bot_engine_active_hours_test.rb, position_monitor_test.rb, volatility_bot_test.rb). Valid creation target.
    • [x] NOT-touch list correct: dashboard view already has sport analytics, bot_engine is sport-agnostic, scanner is separate ticket (#19).

    Repo Placement

    OK -- all work is in ldraney/botfire, matching the Forgejo issue repo. Single repo, no cross-repo concerns.

    Dependencies

    • Issue #19 (scanner bug, board item #1850) is documented in Lineage. Currently in backlog. Non-blocking -- scoreboard changes work with whatever BotFire records exist. Basketball trades will flow once #19 is fixed, and per-sport breakdown will pick them up automatically.
    • No other blocking dependencies on the board.

    Acceptance Criteria

    All 3 AC are scoreboard-specific, verifiable by an agent:

    • [x] AC1: "Scoreboard service outputs per-sport stats" -- agent can verify by checking Scoreboard.report output contains sport grouping
    • [x] AC2: "When a sport has 0 trades, it doesn't appear" -- agent can verify with test data (empty sports excluded)
    • [x] AC3: "All-sport totals still displayed alongside per-sport breakdown" -- agent can verify totals_line still present in report output

    Previous review's flagged AC about the dashboard has been removed. All remaining AC are valid.

    Blast Radius

    Scoreboard is consumed by two callers:

    • bot_engine.rb line 269 -- prints Scoreboard.report to terminal log. Adding sport grouping to report output changes log format (expected, that's the goal).
    • dashboard_controller.rb line 36 -- uses Scoreboard.new.bot_summary(bot) for bot card data. As long as bot_summary return hash is not modified, dashboard is unaffected.

    portfolio_grouper.rb line 67 already uses f.market.game.sport_type for grouping, validating the data path pattern. Not affected by this change.

    No downstream consumers beyond these two callers.

    Decomposition Assessment

    1 file to modify (scoreboard.rb), 1 file to create (scoreboard_test.rb), 3 AC, single repo. Well within single-agent pass (<5 min). No decomposition needed. Points of 2 are appropriate.

    Recommendation

    No action needed. All previous NEEDS_REFINEMENT findings have been addressed:

    • arch-bots note now exists (resolved [SCOPE])
    • Issue body scoped to scoreboard only, dashboard references removed (resolved [BODY])
    • AC rewritten to scoreboard-specific criteria (resolved [BODY])
    • Points reduced from 3 to 2 (resolved [BODY])
    • scoreboard_test.rb correctly listed as file to CREATE in File Targets (resolved [BODY])
  • Verdict: APPROVED

    Re-review of board item #1850 — ldraney/botfire#19. Bug type issue. Previous review (review-1850-2026-07-10) returned NEEDS_REFINEMENT due to missing arch-bots architecture note. That note now exists (arch-bots, id 2527) along with the newly created domain model note (arch-domain-bots, id 2541). All previous findings re-verified. Scope is solid and ready for implementation.

    Template Completeness

    • [x] Type — Bug
    • [x] Lineage — references spike #18
    • [x] Repo — ldraney/botfire
    • [x] What Broke — detailed explanation of the skip-existing-games bug in discover_games
    • [x] Repro Steps — 5 concrete steps with Rails console commands and Kalshi API queries
    • [x] Expected Behavior — clear description of retry behavior for 0-market games
    • [x] Environment — Docker Compose on archbox, scanner service path
    • [x] Acceptance Criteria — 4 testable criteria
    • [x] Related — links to project-botfire and spike #18

    Traceability

    • [x] story:bot-strategy label — present on board item
    • [x] story note verified — found in project-botfire user-stories section: "As a bot operator, I want to identify and exploit profitable trading conditions so that my automated bots generate consistent positive P&L."
    • [x] arch:bots label — present on board item
    • [x] arch note verified — arch-bots (Architecture: BotFire Bot Engine) exists in pal-e-docs, active. Additionally arch-domain-bots (Domain Model: BotFire) created since last review.
    • [x] Forgejo issue — ldraney/botfire#19, state: open

    File Targets

    • [x] app/services/scanner.rb — verified: file exists (258 lines)
    • [x] discover_games method — verified: defined at line 45
    • [x] Game.exists?(milestone_id: m["id"]) — verified: line 53, this is the root cause. Games that already exist are rejected from new_milestones, so their market discovery is never retried.
    • [x] real_game_market? filter — verified: lines 116-122, checks for /GAME/ in ticker. KXWNBAGAME-* tickers would pass this filter correctly.

    Repo Placement

    OK. Issue filed on ldraney/botfire, fix is in app/services/scanner.rb in the same repo. Single-repo fix.

    Dependencies

    • No blocking dependencies. Item is in backlog with no items it depends on.
    • Downstream: item #1851 (Per-sport analytics) would benefit from this fix, as it needs basketball market data to track per-sport performance. Not a blocker.
    • Item #1848 (Tighten Live Edge ceiling) is in QA but is independent — it adjusts price thresholds, not sport type discovery.

    Acceptance Criteria

    4 criteria, all machine-verifiable:

    • [x] AC1: Basketball game-winner markets attached — testable via Market.joins(:game).where(games: { sport_type: "basketball_game" }).count
    • [x] AC2: Scanner retries for games with 0 markets — testable by observing scanner logs after restart
    • [x] AC3: No regression in baseball discovery — testable by checking baseball market counts before/after
    • [x] AC4: Market count > 0 after scanner runs — directly testable Rails console query

    Blast Radius

    • The reject { |m| Game.exists? } pattern exists only in scanner.rb line 53 — no sibling services share this pattern.
    • Downstream: bot_engine.rb queries markets filtered by sport_type via bot.watched_sports (lines 77-81). Once basketball markets appear, bots configured to watch basketball_game will start evaluating those markets. This is expected/desired behavior.
    • volatility_bot.rb includes basketball_game in HEDGEABLE_SPORTS (line 13) — but the volatility bot is currently disabled per board item #1840.
    • game_phase.rb already handles basketball_game sport type (lines 10, 32) — no changes needed there.
    • Dashboard (index.html.erb line 190) already maps basketball_game to "Basketball" — no UI changes needed.
    • Bot::SPORT_TYPES (bot.rb line 2) already includes basketball_game.

    Decomposition Assessment

    No decomposition needed. 1 primary file target (scanner.rb), 4 acceptance criteria, estimated under 5 minutes of agent work. Well within the single-pass threshold.

    Recommendation

    No action needed. All traceability requirements satisfied. Scope is complete and verified.

  • Verdict: NEEDS_REFINEMENT

    Board item #1850 — ldraney/botfire#19. Bug type issue. The scope is well-written and the bug is confirmed in code, but the traceability triangle is incomplete: the arch-bots architecture note referenced by the arch:bots label does not exist in pal-e-docs.

    Template Completeness

    • [x] Type — Bug
    • [x] Lineage — references spike #18
    • [x] Repo — ldraney/botfire
    • [x] What Broke — detailed explanation of the skip-existing-games bug
    • [x] Repro Steps — 5 concrete steps with Rails console commands
    • [x] Expected Behavior — clear description of retry behavior
    • [x] Environment — Docker Compose on archbox, scanner service path
    • [x] Acceptance Criteria — 4 testable criteria
    • [x] Related — links to project-botfire and spike #18

    Traceability

    • [x] story:bot-strategy label — present on board item
    • [x] story note verified — found in project-botfire user-stories section: "As a bot operator, I want to identify and exploit profitable trading conditions..."
    • [x] arch:bots label — present on board item
    • [ ] arch note MISSING — [SCOPE] Create architecture note arch-bots for component bots. Search for "arch-bots" returned 0 results.
    • [x] Forgejo issue — ldraney/botfire#19, state: open

    File Targets

    • [x] app/services/scanner.rb — verified: file exists (258 lines)
    • [x] discover_games method — verified: defined at line 45
    • [x] Game.exists?(milestone_id: m["id"]) — verified: line 53, this is the root cause. Games that already exist are rejected from new_milestones, so their market discovery is never retried.
    • [x] real_game_market? filter — verified: line 116-122, checks for /GAME/ in ticker. KXWNBAGAME-* tickers would pass this filter correctly.

    Repo Placement

    OK. Issue filed on ldraney/botfire, fix is in app/services/scanner.rb in the same repo. Single-repo fix.

    Dependencies

    • No blocking dependencies. Item is in backlog with no items it depends on.
    • Downstream: item #1851 (Per-sport analytics: track Live Edge performance by sport type) would benefit from this fix, as it needs basketball market data to track per-sport performance. Not a blocker.
    • Item #1848 (Tighten Live Edge ceiling) is in QA but is independent — it adjusts price thresholds, not sport type discovery.

    Acceptance Criteria

    4 criteria, all machine-verifiable:

    • [x] AC1: Basketball game-winner markets attached — testable via Market.joins(:game).where(games: { sport_type: "basketball_game" }).count
    • [x] AC2: Scanner retries for games with 0 markets — testable by observing scanner logs after restart
    • [x] AC3: No regression in baseball discovery — testable by checking baseball market counts before/after
    • [x] AC4: Market count > 0 after scanner runs — directly testable Rails console query

    All criteria are concrete and agent-verifiable. No missing criteria detected.

    Blast Radius

    • The reject { |m| Game.exists? } pattern exists only in scanner.rb line 53 — no sibling services share this pattern.
    • Downstream: bot_engine.rb queries Market.where(status: "active").joins(:game).where(games: { sport_type: sport_types }) (line 78-81). Once basketball markets appear, bots configured to watch basketball_game will start evaluating those markets. This is expected/desired behavior.
    • volatility_bot.rb includes basketball_game in HEDGEABLE_SPORTS (line 13) — but the volatility bot is currently disabled per board item #1840.
    • The game_phase.rb service already handles basketball_game sport type (line 10, 32) — no changes needed there.

    Decomposition Assessment

    No decomposition needed. 1 primary file target (scanner.rb), 4 acceptance criteria, estimated under 5 minutes of agent work. Well within the single-pass threshold.

    Recommendation

    • [SCOPE] Create architecture note arch-bots for the bots component. All 12 board items on board-bots carry the arch:bots label but no backing architecture note exists. This should document the scanner, bot_engine, and bot service architecture.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Feature Flag
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:bot-strategy label -- "As a bot operator, I want to identify and exploit profitable trading conditions..."
    • [x] story note verified -- found in project-botfire user-stories section
    • [x] arch:bots label -- bots component
    • [ ] arch note MISSING -- [SCOPE] Create architecture note arch-bots for component bots
    • [x] Forgejo issue -- ldraney/botfire#20, open

    File Targets

    • [x] app/services/scoreboard.rb -- verified: file exists, currently has no sport_type grouping, valid target for modification
    • [ ] app/views/dashboard/index.html.erb -- ISSUE: Dashboard ALREADY has full per-sport analytics. Existing features include: Sport filter buttons (line 119-122), "P&L by Sport" panel (line 136-139), SPORT_LABELS mapping (line 188-194), sport column in Open Positions and Recent Exits tables, calcBreakdown for sport grouping (line 544). The dashboard controller already passes sport: g.sport_type in the data response (dashboard_controller.rb line 21). This file target is invalid -- the described dashboard work is already complete.
    • [ ] test/services/scoreboard_test.rb -- ISSUE: Referenced in Test Expectations but does not exist yet. Should be listed as a file to CREATE in File Targets, not just referenced in tests.

    Repo Placement

    OK -- all work is in ldraney/botfire, matching the Forgejo issue repo.

    Dependencies

    • Issue #19 (scanner bug, board item #1850) is correctly identified as a dependency in the Lineage section. It is in backlog. This ticket can proceed independently -- the scoreboard changes work with whatever BotFire records exist, and basketball records will flow once #19 is fixed.
    • No other blocking dependencies found on the board.

    Acceptance Criteria

    • [ ] AC1: "Dashboard shows trade count, win rate, and P&L grouped by sport_type" -- ALREADY SATISFIED. The dashboard's "P&L by Sport" panel already shows this breakdown with wins/losses, win rate, and P&L per sport. An agent implementing this AC would make no changes.
    • [x] AC2: "Scoreboard service outputs per-sport stats in its log/display" -- Valid, not yet implemented. Scoreboard currently only groups by bot, not by sport.
    • [x] AC3: "When a sport has 0 trades, it doesn't appear in the breakdown" -- Valid for scoreboard. Dashboard already handles this (calcBreakdown only includes groups with data).
    • [x] AC4: "All-sport totals still displayed alongside per-sport breakdown" -- Valid for scoreboard. Dashboard already shows this via summary cards.

    Net: 3 of 4 AC are already satisfied on the dashboard side. Only scoreboard work remains.

    Blast Radius

    Scoreboard is consumed by two callers:

    • bot_engine.rb line 269 -- prints Scoreboard.report to terminal log periodically
    • dashboard_controller.rb line 36 -- uses Scoreboard.new.bot_summary(bot) for bot card data

    Changes to scoreboard report format will affect terminal log output. Adding a new method (e.g., sport_summary) is safe. Modifying bot_summary return hash could break the dashboard controller if keys change. No other downstream consumers found.

    Decomposition Assessment

    After correcting scope: 1 file to modify (scoreboard.rb), 1 file to create (scoreboard_test.rb), 3 effective AC, single repo. Well within single-agent pass. No decomposition needed.

    Recommendation

    • [BODY] Remove app/views/dashboard/index.html.erb from File Targets -- the dashboard already has per-sport analytics (sport filter, "P&L by Sport" panel, sport columns in tables). No dashboard changes needed.
    • [BODY] Rewrite AC1 or remove it -- "Dashboard shows trade count, win rate, and P&L grouped by sport_type" is already satisfied. Replace with scoreboard-specific AC or remove entirely.
    • [BODY] Add test/services/scoreboard_test.rb to File Targets as a file to CREATE (currently only referenced in Test Expectations).
    • [BODY] Reduce points from 3 to 2 -- with dashboard work removed, this is a single-service change plus test.
    • [SCOPE] Create architecture note arch-bots for component bots.
  • Verdict: READY

    Template Completeness

    • [x] Type -- Spike
    • [x] Lineage -- Follows Spike #11, establishes volume motivation (3.3 trades/day vs 14+ target)
    • [x] Repo -- ldraney/botfire
    • [x] Question -- 7 sub-questions covering price bands, timing, risk params, time windows, volume, seasonal availability, sample size
    • [x] Deliverables -- 3 deliverables: analysis doc, follow-up tickets, or no-action rationale
    • [x] Time-box -- 3 hours
    • [x] Related -- 4 related items (project-botfire, #11, #12, #14)

    Traceability

    • [x] story:bot-strategy label -- present on board item
    • [x] story note verified -- found in project-botfire user-stories section. Entry: "As a bot operator, I want to identify and exploit profitable trading conditions so that my automated bots generate consistent positive P&L."
    • [x] arch:bots label -- present on board item
    • [x] arch note verified -- arch-bots note exists ("Architecture: BotFire Bot Engine", status=active)
    • [x] Forgejo issue -- ldraney/botfire#18, state: open

    File Targets

    • [x] docs/multi-sport-analysis.md -- spike deliverable (to be created). Docs directory exists at /home/ldraney/botfire/docs/ with existing bot docs. Correct placement.

    Note: Spike issues produce docs, not code. No existing file targets to verify.

    Repo Placement

    OK. Issue specifies ldraney/botfire, Forgejo issue filed on ldraney/botfire. Single-repo spike -- no cross-repo concerns.

    Dependencies

    • Spike #11 (Live Edge deep dive, item #1845) -- COMPLETED (in qa column). This spike follows it. No blocker.
    • Issue #12 (Evening filter, item #1846) -- PR #16 merged, in qa. Related (sport-specific hours may differ) but not a blocker.
    • Issue #14 (Ceiling 89c, item #1848) -- PR #15 merged, in qa. Related (sport-specific bands may differ) but not a blocker.
    • No items block this spike. No undocumented dependencies found.

    Acceptance Criteria

    Deliverables are clear and agent-verifiable:

    • docs/multi-sport-analysis.md exists with per-sport sections -- verifiable via file check
    • Follow-up tickets created on Forgejo for viable sports -- verifiable via API
    • If no viable sports, docs file contains rationale -- verifiable via content check

    Blast Radius

    Low blast radius. The codebase already has multi-sport infrastructure baked in:

    • game_phase.rb -- already handles baseball, basketball, football, hockey, soccer with sport-specific "nearly over" and "about to settle" logic
    • scanner.rb -- DEFAULT_SPORTS already includes all 5 sport types
    • bot.rb -- SPORT_TYPES constant already lists all 5 sports; watched_sports defaults to all
    • bot_engine.rb -- already filters by watched_sports, active hours are per-bot configurable
    • portfolio_grouper.rb -- already groups positions by sport_type
    • live-edge.md -- already documents game phase filters for all 5 sports

    This spike is research-only (no code changes). Follow-up feature tickets would likely involve trigger configuration changes (sport-specific thresholds, time windows) rather than structural code changes.

    Decomposition Assessment

    No decomposition needed. Single cohesive investigation producing one analysis doc. File target count: 1 (docs/multi-sport-analysis.md). Deliverables: 3 (all part of the same analysis flow). Time-box: 3 hours. Fits comfortably in a single agent pass.

    Recommendation

    No action needed. Scope is solid, all traceability legs verified, template is complete, and the spike is well-bounded.

  • Verdict: APPROVED

    Re-review of board item #1847. Previous review review-1847-2026-07-10 returned NEEDS_REFINEMENT with 5 issues. All 5 have been resolved.

    Previous Issue Resolution

    • [x] Wrong file path -- Fixed: issue now says app/services/game_phase.rb with explicit note "(note: this is in app/services/, NOT app/concerns/)"
    • [x] Missing blast-radius files -- Fixed: scoreboard.rb and dashboard/index.html.erb now in File Targets with clear change descriptions
    • [x] Missing include -- Fixed: first file target notes "must also include GamePhase (currently not included, but game_nearly_over? is needed from that module)"
    • [x] Non-testable AC -- Fixed: old AC4 (outcome metric "settlement loss rate drops to near zero") replaced with two verifiable ACs: scoreboard counting and dashboard styling
    • [x] Traceability notes -- Fixed: project-botfire user-stories section exists with story:bot-strategy entry; arch-bots note exists (Architecture: BotFire Bot Engine, status=active)

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- Discovered during Spike #11
    • [x] Repo -- ldraney/botfire
    • [x] User Story -- As a bot operator / I want / So that
    • [x] Context -- Detailed settlement loss data ($2.97 catastrophic loss, TP $0.59 vs settlement win $0.33)
    • [x] File Targets -- 4 modify + 1 do-not-touch, all verified against codebase
    • [x] Feature Flag -- none
    • [x] Acceptance Criteria -- 5 verifiable criteria
    • [x] Test Expectations -- 3 unit tests + run command
    • [x] Constraints -- 5 constraints including include requirement and downstream consumer note
    • [x] Checklist -- present
    • [x] Related -- present

    Traceability

    • [x] story:bot-strategy label -- present on board item
    • [x] story note verified -- project-botfire user-stories section contains bot-strategy entry
    • [x] arch:bots label -- present on board item
    • [x] arch note verified -- arch-bots note exists in pal-e-docs (Architecture: BotFire Bot Engine, covers 7 services, key files, order pattern, data model, infrastructure)
    • [x] Forgejo issue -- ldraney/botfire#13, open

    File Targets

    • [x] app/services/position_monitor.rb -- verified: exists, has evaluate_exit (line 48) and resolve_settlement (line 69). Does NOT currently include GamePhase. Correct target for pre-settlement exit check.
    • [x] app/services/game_phase.rb -- verified: exists at app/services/ (no app/concerns/ directory in project). Has game_nearly_over? method. Correct path.
    • [x] app/services/scoreboard.rb -- verified: lines 35-36 only count stop_loss and take_profit. New exit reason would be invisible in stats. Correct target.
    • [x] app/views/dashboard/index.html.erb -- verified: line 592 has isWin = f.exit_reason?.includes('win') || f.exit_reason?.includes('take'). A profitable pre_settlement_exit would show as loss pill. Correct target.
    • [x] app/services/bot_engine.rb -- verified: exists. Correctly listed as do-not-touch (entry logic separate from exit logic).

    Repo Placement

    OK -- All affected files in ldraney/botfire. Issue filed on ldraney/botfire. Single-repo change.

    Dependencies

    • Spawned from Spike #11 (board item #1845, ldraney/botfire#11) -- in QA column, upstream work complete.
    • Sibling items from same spike: #1846 (botfire#12, backlog), #1848 (botfire#14, backlog) -- no blocking dependency.
    • Item #1843 (botfire#8, "Add game phase tracking to BotFire records") -- could overlap on game_phase.rb but this ticket only consumes existing game_nearly_over?, no conflict.
    • PositionMonitor must include GamePhase -- documented in issue File Targets.

    Acceptance Criteria

    All 5 ACs are agent-verifiable:

    • AC1: "Position monitor detects games approaching final settlement" -- testable via unit test with game_nearly_over? returning true
    • AC2: "Open positions in near-settlement games are exited at market price" -- testable via unit test
    • AC3: "Exit logged with exit_reason: 'pre_settlement_exit'" -- testable via assertion on BotFire record
    • AC4: "Scoreboard correctly counts pre_settlement_exit in win/loss stats based on P&L sign" -- testable
    • AC5: "Dashboard renders pre_settlement_exit with correct win/loss styling based on P&L" -- testable via DOM assertion

    Blast Radius

    • dashboard/index.html.erb line 326 -- Exit breakdown table has same isWin = reason.includes('win') || reason.includes('take') pattern. Not explicitly called out in issue, but the constraint "All downstream consumers of exit_reason must handle the new value" covers it, and an agent grepping for exit_reason will find both locations.
    • dashboard_controller.rb lines 87-90 -- Computes avg_sl/avg_tp only for stop_loss/take_profit. Pre-settlement exits are a distinct category and correctly excluded from SL/TP averages. No change needed.
    • volatility_bot.rb -- Has own exit logic but currently disabled. Not affected.
    • volatility_bot_test.rb -- Tests check take_profit, stop_loss, time_stop exit reasons. New exit reason doesn't affect existing tests.

    Decomposition Assessment

    4 file targets in 1 repo, 5 ACs. All changes tightly coupled (add exit path + update downstream consumers of exit_reason). Estimated agent work: under 5 minutes. No decomposition needed.

    Recommendation

    No action needed. Scope is solid, all previous issues resolved, traceability triangle complete.

  • Verdict: READY

    Re-review of board item #1846. Both NEEDS_REFINEMENT issues from review-1846-2026-07-10 have been resolved. Scope is solid, file targets verified, traceability complete.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Feature Flag (none -- acceptable, no feature flag doc in repo)
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:bot-strategy label -- present on board item
    • [x] story note verified -- found in project-botfire user-stories section. Entry: "As a bot operator, I want to identify and exploit profitable trading conditions so that my automated bots generate consistent positive P&L."
    • [x] arch:bots label -- present on board item
    • [x] arch note verified -- arch-bots note exists in pal-e-docs (note_type: architecture, status: active, project: bots). Covers 7 services, key files, order patterns, data model, infrastructure.
    • [x] Forgejo issue -- ldraney/botfire#12, open

    Previous Review Issues -- Resolved

    • [x] user-stories section -- Was missing on project-botfire. Now present with story:bot-strategy entry in a table with Story Label and User Story columns.
    • [x] arch-bots note -- Was not found by search. Now exists at slug arch-bots with note_type: architecture, status: active, tagged architecture+active.

    File Targets

    • [x] app/services/bot_engine.rb -- verified: exists (268 lines, 7.9k). evaluate_bot at line 44, execute_action called at line 65, defined at line 158. Natural gate insertion point is top of evaluate_bot (line 44) or before the execute_action call at line 65.
    • [x] app/models/bot.rb -- verified: exists (12 lines, 319 bytes). Simple model with sports_filter serialization. No active_hours columns yet. Ticket Constraints section says a BOT_ACTIVE_HOURS hash in bot_engine.rb is acceptable (no migration needed).
    • [x] app/views/dashboard/index.html.erb -- correctly excluded from scope.

    Repo Placement

    OK. Issue says ldraney/botfire, filed on ldraney/botfire. Single-repo change, no cross-repo concerns.

    Dependencies

    • Parent spike #11 (board item #1845) is complete, in QA column. This ticket was spawned from it.
    • Sibling tickets #13 (item #1847) and #14 (item #1848) were also spawned from #11 but are independent -- no ordering dependency.
    • No blocking items in in_progress column.

    Acceptance Criteria

    4 AC, all agent-verifiable:

    • [x] "Live Edge does not fire on games outside 12pm-midnight MDT" -- testable with time stubbing
    • [x] "Time window is configurable per bot" -- testable by checking hash/config structure
    • [x] "Skipped trades are logged with reason 'outside active hours'" -- testable by capturing log output
    • [x] "Existing trigger logic unchanged" -- testable by verifying trigger evaluation still called normally inside window

    Test command bin/rails test is valid. Existing test pattern in test/services/volatility_bot_test.rb provides a fixture template. No bot_engine_test.rb exists yet -- agent will create one.

    Note: Title says "evening-only" but AC defines 12pm-midnight MDT (wider than evening). The AC is the authoritative spec; the title is colloquial. Not a conflict.

    Blast Radius

    Low. The time gate is a pre-filter in evaluate_bot before any trigger evaluation or order placement. No other callers of evaluate_bot or execute_action outside bot_engine.rb. Dashboard correctly excluded. App timezone is UTC (config.time_zone commented out in config/application.rb); agent must use explicit Time.use_zone("America/Denver") or in_time_zone calls rather than relying on Time.zone.

    Decomposition Assessment

    No decomposition needed. 2 file targets in 1 repo, 4 AC, estimated agent work well under 5 minutes. Straightforward pre-filter gate pattern.

    Recommendations

    No action needed.

  • Verdict: APPROVED

    Re-review of board item #1848. Previous review (review-1848-2026-07-10) returned NEEDS_REFINEMENT for two traceability gaps. Both are now resolved.

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- references Spike #11, supersedes #7
    • [x] Repo -- ldraney/botfire
    • [x] User Story
    • [x] Context -- includes P&L data by price band
    • [x] File Targets
    • [x] Feature Flag -- none (appropriate for DB-only change)
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:bot-strategy label present
    • [x] story note verified -- found in project-botfire user-stories section. Entry: "As a bot operator, I want to identify and exploit profitable trading conditions so that my automated bots generate consistent positive P&L."
    • [x] arch:bots label present
    • [x] arch note verified -- arch-bots note exists in pal-e-docs (note_type=architecture, status=active, project=bots, title="Architecture: BotFire Bot Engine")
    • [x] Forgejo issue -- ldraney/botfire#14, state: open

    File Targets

    • [x] app/services/bot_engine.rb -- verified: line 135 contains return false if trigger.condition_max.present? && value > trigger.condition_max.to_f. Issue correctly says NOT to touch this file.
    • [x] db/seeds.rb -- verified: file exists but does NOT seed triggers (only users and bots). Issue correctly says "optionally if trigger defaults are seeded there" -- no change needed.
    • [x] DB change Trigger.find_by(name: "Live Edge").update!(condition_max: 89) -- the condition_max column exists (migration 20260708130000_add_condition_max_to_triggers.rb, schema.rb line 107). The Trigger model references are consistent.

    Repo Placement

    OK. Issue specifies ldraney/botfire and is filed on ldraney/botfire. Single-repo change.

    Dependencies

    • Supersedes Issue #7 (board item #1842, backlog, marked closed/superseded)
    • Spawned from Spike #11 (board item #1845, currently in qa column)
    • No blockers identified. No items in in_progress that this depends on.
    • Sibling issues from same spike: #12 (board item #1846, 3pt) and #13 (board item #1847, 5pt) -- no blocking relationship.

    Acceptance Criteria

    3 criteria, all agent-verifiable:

    • condition_max set to 89 -- verifiable via rails runner command provided in Test Expectations
    • No entries above 89c after deploy -- verifiable by monitoring BotFire records post-deploy
    • Dashboard confirms new trades stay in 85-89c band -- verifiable via analytics dashboard

    Test command is real and correct: bin/rails runner "puts Trigger.find_by(name: 'Live Edge').condition_max"

    Blast Radius

    Low. condition_max is referenced in exactly 2 places:

    • bot_engine.rb:135 -- the check_late_game guard (already correct)
    • db/schema.rb:107 -- column definition

    Only the Live Edge trigger uses the late_game trigger type. No other bots, services, or downstream consumers are affected.

    Decomposition Assessment

    No decomposition needed. 1 effective file target (DB record update), 3 acceptance criteria, estimated agent time well under 5 minutes. This is a single rails runner command or a data migration.

    Recommendation

    No action needed.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- Discovered during Spike #11
    • [x] Repo -- ldraney/botfire
    • [x] User Story -- As a bot operator / I want / So that
    • [x] Context -- Detailed with settlement loss data
    • [x] File Targets -- 2 modify + 1 do-not-touch
    • [x] Feature Flag -- none
    • [x] Acceptance Criteria -- 5 criteria
    • [x] Test Expectations -- Unit tests + run command
    • [x] Constraints -- 3 constraints listed
    • [x] Checklist -- present
    • [x] Related -- present

    Traceability

    • [x] story:bot-strategy label -- present on board item
    • [ ] story note MISSING -- project-botfire has no user-stories section. [SCOPE] Create user-stories section on project-botfire with bot-strategy story entry.
    • [x] arch:bots label -- present on board item
    • [ ] arch note MISSING -- search for arch-bots returned no results. [SCOPE] Create architecture note arch-bots for the bots component.
    • [x] Forgejo issue -- ldraney/botfire#13, open

    File Targets

    • [x] app/services/position_monitor.rb -- verified: exists, contains evaluate_exit and resolve_settlement logic (lines 48-94). Correct target for pre-settlement exit check.
    • [ ] app/concerns/game_phase.rb -- ISSUE: wrong path. File is at app/services/game_phase.rb. No app/concerns/ directory exists in this project. The module already has game_nearly_over? which is the exact method needed.
    • [x] app/services/bot_engine.rb -- verified: correctly listed as do-not-touch.
    • [ ] app/services/scoreboard.rb -- MISSING from targets. Scoreboard counts exits only as stop_loss and take_profit (lines 35-36). New pre_settlement_exit reason will be invisible in stats.
    • [ ] app/views/dashboard/index.html.erb -- MISSING from targets. Line 592: isWin = f.exit_reason?.includes('win') || f.exit_reason?.includes('take'). A profitable pre_settlement_exit would be styled as a loss pill because "pre_settlement_exit" contains neither "win" nor "take".

    Repo Placement

    OK -- all affected files are in ldraney/botfire. Issue correctly filed on that repo. Single-repo change.

    Dependencies

    • Spawned from Spike #11 (board item #1845, ldraney/botfire#11) -- in QA column, upstream work complete.
    • Sibling items from same spike: #12 (botfire#12, backlog), #14 (botfire#14, backlog) -- no blocking dependency.
    • Item #1843 (botfire#8, "Add game phase tracking to BotFire records") -- could overlap on game_phase.rb but this ticket only consumes existing game_nearly_over?, no conflict.
    • PositionMonitor does NOT currently include GamePhase -- BotEngine and VolatilityBot do, but PositionMonitor does not. The ticket must add this include.

    Acceptance Criteria

    • AC1-3, AC5 are directly verifiable via unit tests.
    • AC4 ("Settlement loss rate drops to near zero for new trades") is an outcome metric, not directly testable in a unit test. An agent could verify it by checking that settlement paths are unreachable when pre-settlement exit fires, but the criterion as written reads as a production metric. Consider rephrasing to: "Games where pre-settlement exit fires produce no settlement_loss exit reasons."
    • Test command bin/rails test is valid for Rails projects.

    Blast Radius

    • scoreboard.rb -- Only counts stop_loss and take_profit. New exit reason will not appear in any scoreboard tallies. Must either add a pre_settlement_exit counter or generalize the counting logic.
    • dashboard/index.html.erb -- isWin classification at line 592 will misclassify profitable pre-settlement exits as losses (red pill). The pill text will render correctly ("pre settlement exit") but the color will always be loss-colored.
    • volatility_bot.rb -- Has its own settlement logic (lines 116-127) but is currently disabled/self-managing. Not directly affected but same pattern exists.
    • dashboard_controller.rb -- Computes avg_sl and avg_tp from exit reasons (lines 87-90). Pre-settlement exits won't be included in either bucket, which is correct but means the analytics won't reflect them. Consider whether a new stat is needed.

    Decomposition Assessment

    With blast-radius additions: 4 file targets across 1 repo, 5 AC. All changes are tightly coupled (add exit path + update consumers of exit_reason). Estimated agent work: under 5 minutes. No decomposition needed.

    Recommendations

    • [BODY] Fix file path: app/concerns/game_phase.rbapp/services/game_phase.rb
    • [BODY] Add app/services/scoreboard.rb to file targets -- must handle pre_settlement_exit in exit reason stats
    • [BODY] Add app/views/dashboard/index.html.erb to file targets -- isWin logic at line 592 must classify pre_settlement_exit by pnl sign, not by string match
    • [BODY] Note in file targets that PositionMonitor must include GamePhase to access game_nearly_over?
    • [BODY] Rephrase AC4 to be agent-verifiable: "Games where pre-settlement exit fires produce no settlement_loss exit reasons" instead of outcome metric
    • [SCOPE] Create user-stories section on project-botfire with bot-strategy story entry
    • [SCOPE] Create architecture note arch-bots for the bots component
  • Verdict: NEEDS_REFINEMENT

    The ticket is well-scoped and all code claims verify against the codebase. Two traceability backing notes are missing, preventing a READY verdict.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Feature Flag (none -- acceptable, no feature flag doc in repo)
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:bot-strategy label -- present on board item
    • [ ] story note MISSING -- [SCOPE] project-botfire has no user-stories section. Create user story entry on project-botfire user-stories section.
    • [x] arch:bots label -- present on board item
    • [ ] arch note MISSING -- [SCOPE] arch-bots note does not exist in pal-e-docs. project-botfire references it but the note was never created. Create architecture note arch-bots.
    • [x] Forgejo issue -- ldraney/botfire#12, open

    File Targets

    • [x] app/services/bot_engine.rb -- verified: exists (7.9k), execute_action defined at line 158, called at line 65 inside evaluate_bot. Natural gate insertion point is line 64 (before call) or top of evaluate_bot at line 44.
    • [x] app/models/bot.rb -- verified: exists (319 bytes), simple model with sports_filter serialization. No active_hours columns in schema yet. Ticket Constraints section says a BOT_ACTIVE_HOURS hash in bot_engine.rb is acceptable (no migration needed).

    Repo Placement

    OK. Issue says ldraney/botfire, filed on ldraney/botfire. Single-repo change, no cross-repo concerns.

    Dependencies

    • Parent spike #11 (board item #1845) is complete, in QA column. This ticket was spawned from it.
    • Sibling tickets #13 (item #1847) and #14 (item #1848) were also spawned from #11 but are independent -- no ordering dependency.
    • No blocking items in in_progress column.

    Acceptance Criteria

    4 AC, all agent-verifiable:

    • [x] "Live Edge does not fire on games outside 12pm-midnight MDT" -- testable with time stubbing
    • [x] "Time window is configurable per bot" -- testable by checking hash/config structure
    • [x] "Skipped trades are logged with reason 'outside active hours'" -- testable by capturing log output
    • [x] "Existing trigger logic unchanged" -- testable by verifying trigger evaluation still called normally inside window

    Test command bin/rails test is valid. Existing test pattern in test/services/volatility_bot_test.rb provides a fixture template. No bot_engine_test.rb exists yet -- agent will create one.

    Note: Title says "evening-only" but AC defines 12pm-midnight MDT (wider than evening). The AC is the authoritative spec; the title is colloquial. Not a conflict.

    Blast Radius

    Low. The time gate is a pre-filter in evaluate_bot before any trigger evaluation or order placement. No downstream consumers affected. Dashboard (index.html.erb) correctly excluded from scope. App timezone is currently UTC (commented out in config/application.rb); the agent must use explicit Time.use_zone("America/Denver") or in_time_zone calls rather than relying on Time.zone.

    Decomposition Assessment

    No decomposition needed. 2 file targets in 1 repo, 4 AC, estimated agent work well under 5 minutes. Straightforward pre-filter gate pattern.

    Recommendations

    • [SCOPE] Create user-stories section on project-botfire with the bot-strategy story entry.
    • [SCOPE] Create architecture note arch-bots for the bots component (project-botfire already references it but the note does not exist).
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- references Spike #11, supersedes #7
    • [x] Repo -- ldraney/botfire
    • [x] User Story
    • [x] Context -- includes P&L data by price band
    • [x] File Targets
    • [x] Feature Flag -- none (appropriate for DB-only change)
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:bot-strategy label present
    • [ ] story note MISSING -- project-botfire page has no user-stories section. [SCOPE] Create user story entry on project-botfire user-stories section.
    • [x] arch:bots label present
    • [ ] arch note MISSING -- no arch-bots note found in pal-e-docs. [SCOPE] Create architecture note arch-bots for component bots.
    • [x] Forgejo issue -- ldraney/botfire#14, state: open

    File Targets

    • [x] app/services/bot_engine.rb -- verified: line 135 contains return false if trigger.condition_max.present? && value > trigger.condition_max.to_f. Issue correctly says NOT to touch this file.
    • [x] db/seeds.rb -- verified: file exists but does NOT seed triggers (only users and bots). Issue correctly says "optionally if trigger defaults are seeded there" -- no change needed.
    • [x] DB change Trigger.find_by(name: "Live Edge").update!(condition_max: 89) -- the condition_max column exists (migration 20260708130000_add_condition_max_to_triggers.rb, schema.rb line 107). The Trigger model references are consistent.

    Repo Placement

    OK. Issue specifies ldraney/botfire and is filed on ldraney/botfire. Single-repo change.

    Dependencies

    • Supersedes Issue #7 (board item #1842, marked closed/superseded)
    • Spawned from Spike #11 (board item #1845, currently in qa column, title: "Spike: Live Edge deep dive -- COMPLETED, spawned #12 #13 #14")
    • No blockers identified. No items currently in_progress that this depends on.
    • Sibling issues from same spike: #12 (board item #1846, 3pt) and #13 (board item #1847, 5pt) -- no blocking relationship.

    Acceptance Criteria

    3 criteria, all agent-verifiable:

    • condition_max set to 89 -- verifiable via rails runner command provided in Test Expectations
    • No entries above 89c after deploy -- verifiable by monitoring BotFire records post-deploy
    • Dashboard confirms new trades stay in 85-89c band -- verifiable via the analytics dashboard

    Test command is real and correct: bin/rails runner "puts Trigger.find_by(name: 'Live Edge').condition_max"

    Blast Radius

    Low. condition_max is referenced in exactly 2 places:

    • bot_engine.rb:135 -- the check_late_game guard (already correct)
    • db/schema.rb:107 -- column definition

    Only the Live Edge trigger uses the late_game trigger type. No other bots, services, or downstream consumers are affected. No sibling services share this pattern.

    Decomposition Assessment

    No decomposition needed. 1 effective file target (DB record update), 3 acceptance criteria, estimated agent time well under 5 minutes. This is a single rails runner command or a data migration.

    Recommendation

    • [SCOPE] Create user-stories section on project-botfire page and add the "bot-strategy" user story entry.
    • [SCOPE] Create architecture note arch-bots for the bots component in pal-e-docs.

    Core scope is solid. File targets verified, acceptance criteria testable, blast radius minimal. The two [SCOPE] items are traceability hygiene -- they do not affect the implementability of this ticket.

  • Verdict: APPROVED

    Re-review of board item #1841 after refinement. Previous review (review-1841-2026-07-09) returned NEEDS_REFINEMENT with 6 recommendations. 5 of 6 addressed in this revision.

    Previous Findings Resolution

    • [x] [DECOMPOSE] ACs reduced from 8 to 5 — now within threshold (>5 triggers decomposition, 5 does not)
    • [x] [SCOPE] arch-bots note — confirmed exists at slug arch-bots (project: bots, sections: services, key files, order pattern, data model, infrastructure)
    • [ ] [SCOPE] story-bots-bot-strategy note — still listed as "(pending)" on project-botfire user-stories section. Not addressed, but story is defined inline on the project page. Project-level hygiene item, not a ticket blocker.
    • [x] [BODY] Game phase mapping — deferred to follow-up ticket. No mention of game_phase in issue body or codebase. Clean removal.
    • [x] [BODY] sport_type migration — clarified: "No migrations needed. sport_type is already on the games table and accessed via join (market.game.sport_type)." Verified in controller line 21: sport: g.sport_type and schema: t.string "sport_type" on games table.
    • [x] [BODY] Hypothesis persistence — specified: "Persisted to localStorage (key: botfire-hypothesis)." Explicit key name and rationale ("Ephemeral per browser, not shared — this is a scratchpad for the operator, not a database").

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — project-botfire, board-bots, arch-bots
    • [x] Repo — ldraney/botfire
    • [x] User Story — "As a bot operator, I need to slice trade data by sport, entry price, and bot..."
    • [x] Context — P&L data (-$25.41 total), 63% stop-losses, breakeven win rate calculation
    • [x] File Targets — 3 files listed with clear change descriptions
    • [x] Feature Flag — "None" (replaces existing dashboard, acceptable)
    • [x] Acceptance Criteria — 5 criteria, well-structured
    • [x] Test Expectations — 4 items, verifiable
    • [x] Constraints — 5 constraints (single-page, no chart libs, responsive, auto-refresh, collapsible legacy sections)
    • [x] Checklist — 8 items mapping to ACs
    • [x] Related — 4 references (issues #1, #2, project page, arch note)

    Traceability

    • [x] story:bot-strategy label — "Bot operator identifies winning conditions"
    • [ ] story note — story-bots-bot-strategy listed as "(pending)" on project-botfire. Story is defined inline (role: Bot operator, metric: Per-bot positive P&L trending upward over 50+ trades). [SCOPE] Create story note story-bots-bot-strategy — project-level hygiene, not blocking.
    • [x] arch:bots label — bots architecture component
    • [x] arch note verified — arch-bots note exists in pal-e-docs (project: bots, covers 7 services, key files, order pattern, data model, infrastructure)
    • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/ldraney/botfire/issues/5, open

    File Targets

    • [x] app/controllers/dashboard_controller.rb (99 lines) — verified: exists, has data endpoint with BotFire joins to bot, trigger, market, game. Already returns sport: g.sport_type via join (line 21). Breakeven calculation already present (lines 86-95): breakeven_wr, avg_stop_loss, avg_take_profit fields returned in JSON response.
    • [x] app/services/scoreboard.rb (99 lines) — verified: exists, has bot_summary method with P&L, win/loss, stop-loss/take-profit counts. Issue correctly states "no changes needed."
    • [x] app/views/dashboard/index.html.erb (642 lines) — verified: exists. Note: file has grown significantly from 261 lines (at previous review) to 642 lines. Current state already contains analytics tables, filter bar, canvas P&L chart, hypothesis textarea with localStorage, and cross-filter interactivity — suggesting implementation may have already begun or completed.

    Repo Placement

    OK — issue filed on ldraney/botfire, all file targets in ldraney/botfire. Single-repo scope.

    Dependencies

    • Board item #1839 (Rework Volatility Bot) — in QA column, deployed. Generates the trade data this ticket analyzes. Not a blocker.
    • Board item #1840 (Rework Consensus Bot) — in backlog, ON HOLD. This ticket (#1841) is a prerequisite for #1840. Dependency direction correct and documented in Related section.
    • No blockers. Dependencies documented.

    Acceptance Criteria

    5 criteria, all testable and specific:

    • AC 1 (Analytics breakdown tables) — clear: P&L by sport, by price band (5c buckets defined), exit reason breakdown. Each table specifies columns: trades, W/L, win%, P&L.
    • AC 2 (Breakeven threshold) — clear: calculated from actual avg stop-loss / avg take-profit ratio, purple threshold line, bar chart overlay. Not hardcoded.
    • AC 3 (Cumulative P&L trend) — clear: canvas line chart, per-bot and total, color-coded.
    • AC 4 (Cross-filter) — clear: sport, bot, price band buttons. Click to filter all views. Click table row to toggle filter.
    • AC 5 (Hypothesis log) — clear: textarea, localStorage key botfire-hypothesis, ephemeral per browser.

    All criteria are agent-verifiable.

    Blast Radius

    • Dashboard changes are visually isolated (single page, no new routes).
    • Scoreboard service used by bot services for console logging — no changes specified, no risk.
    • Controller data endpoint is additive (breakeven fields added to existing JSON response).
    • No migration, no model changes, no new dependencies.

    Decomposition Assessment

    No decomposition needed:

    • File targets: 2 active files in 1 repo (controller + view; scoreboard unchanged) — within threshold.
    • Acceptance criteria: 5 — at threshold, not exceeding (>5 triggers decomposition).
    • Estimated agent work: Single-page frontend rework with backend data endpoint enhancement. No migrations, no new routes, no external dependencies. Fits in one agent pass.

    Observation

    The dashboard view has grown from 261 lines to 642 lines since the previous review. The current codebase already contains implementations matching all 5 ACs: analytics breakdown tables with breakeven bars, canvas P&L trend chart, cross-filter interactivity, and hypothesis log with localStorage persistence. The controller already returns breakeven_wr, avg_stop_loss, and avg_take_profit. If implementation is complete, this ticket may be ready for QA validation rather than agent implementation.

    Recommendations

    • [SCOPE] Create story note story-bots-bot-strategy — currently "(pending)" on project-botfire. Project-level hygiene; not blocking this ticket.

    No other action needed. Scope is solid, all previous [BODY] and [DECOMPOSE] findings addressed.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Feature Flag -- "None" (replaces existing dashboard, acceptable)
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:bot-strategy label -- "Bot operator identifies winning conditions"
    • [ ] story note MISSING -- story-bots-bot-strategy listed as "(pending)" on project-botfire user-stories section. [SCOPE] Create story note story-bots-bot-strategy.
    • [x] arch:bots label -- bots architecture component
    • [ ] arch note MISSING -- search for arch-bots returned no results in pal-e-docs. [SCOPE] Create architecture note arch-bots for the bots component.
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/botfire/issues/5, open

    File Targets

    • [x] app/controllers/dashboard_controller.rb -- verified: exists (2.5k), has data endpoint querying BotFire with joins to bot, trigger, market, game. Already accesses g.sport_type via join.
    • [x] app/services/scoreboard.rb -- verified: exists (3.0k), has bot_summary method with P&L, win/loss, stop-loss/take-profit counts. Good foundation to extend.
    • [x] db/migrate/ -- verified: directory exists with 13 migrations.
    • [ ] db/migrate/ -- ISSUE: Issue mentions adding sport_type to bot_fires, but sport_type already exists on games table and the controller already accesses it via join (g.sport_type). Clarify whether denormalization is intended or join-based computation suffices. [BODY]
    • [ ] db/migrate/ -- ISSUE: game_phase_at_entry is proposed but "game phase" is not defined in terms of existing data. The games table has period (string) and clock (string) fields. How to map these to early/mid/late across 5 different sports (each with different period structures -- innings, quarters, halves, periods) needs explicit definition. [BODY]
    • [x] app/views/dashboard/index.html.erb -- verified: exists (261 lines), live position monitoring dashboard with dark theme, summary cards, bot cards, open-positions table.

    Repo Placement

    OK -- issue filed on ldraney/botfire, all file targets in ldraney/botfire. Single-repo scope.

    Dependencies

    • Board item #1839 (Rework Volatility Bot) -- in QA column, deployed. Generating the trade data this ticket will analyze. Not a blocker.
    • Board item #1840 (Rework Consensus Bot) -- in backlog, ON HOLD pending analytics. This ticket (#1841) is a prerequisite for #1840. Dependency direction is correct and documented in Related section ("Issue #2 -- Consensus Bot rework (ON HOLD until analytics prove what to amplify)").
    • No blockers for this ticket. Dependencies are documented.

    Acceptance Criteria

    8 acceptance criteria. Most are testable, with these concerns:

    • AC 3 (P&L by Game Phase) -- "early/mid/late" mapping from existing period/clock fields is undefined. Different sports have different period structures (innings, quarters, halves, periods). An agent cannot implement this without a definition.
    • AC 7 (Cross-filter) -- complex interactive behavior: "click a sport, price band, or game phase to filter all other views." Significant frontend engineering effort, especially with no chart libraries allowed.
    • AC 8 (Hypothesis Log) -- persistence mechanism undefined. Is this localStorage, a new DB table, or ephemeral? "Simple text area or section" is too vague for agent implementation.

    Blast Radius

    • Dashboard changes are visually isolated (single page, no new routes).
    • Scoreboard service is used by bot services for console logging. Extending with analytics methods is additive and non-breaking.
    • BotFire model is used by 6 services (volatility_bot, consensus_bot, bot_engine, position_monitor, portfolio_grouper, scoreboard). Adding columns via migration is non-breaking.
    • No chart libraries constraint means all visualization (6+ chart types including line charts, bar charts, tables with breakeven lines) must be custom HTML/canvas. This is the highest-effort constraint.

    Decomposition Assessment

    NEEDS DECOMPOSITION -- fails the 5-minute rule on multiple criteria:

    • File targets: 3-4 files in 1 repo -- borderline acceptable.
    • Acceptance criteria: 8 criteria -- exceeds the >5 threshold.
    • Estimated agent work: Well over 15 minutes. Backend analytics endpoint + 6 chart views (all custom HTML/canvas, no libraries) + cross-filter interactivity + responsive design + hypothesis log + possible migration + tests. At minimum 3 separate agent passes.

    Suggested decomposition into 3 sub-tickets:

    1. Analytics backend + static tables (3pt) -- analytics endpoint with P&L breakdowns by sport, price band, game phase, exit reason. Static HTML tables with breakeven threshold. AC 1-5 data + display.
    2. Cumulative P&L trend chart (2pt) -- custom canvas line chart per bot showing P&L over time. AC 6. Responsive.
    3. Cross-filter + hypothesis log (3pt) -- AC 7-8: interactive filtering across all views and hypothesis persistence. Depends on tickets 1 and 2.

    Recommendations

    • [DECOMPOSE] 8 AC across backend analytics + 6 custom chart types + cross-filter interactivity + hypothesis log. Route to skill-decompose-ticket.
    • [SCOPE] Create architecture note arch-bots for the bots component.
    • [SCOPE] Create story note story-bots-bot-strategy (currently listed as pending on project-botfire).
    • [BODY] Define "game phase" mapping: how do period and clock fields on the games table map to early/mid/late for each sport type (baseball innings, basketball quarters, football quarters, hockey periods, soccer halves)?
    • [BODY] Clarify sport_type migration: the field already exists on games table and is accessible via join. State explicitly whether denormalization to bot_fires is intended or join-based access suffices.
    • [BODY] Define hypothesis log persistence: localStorage, new DB table, or ephemeral?
  • Verdict: READY

    Re-review after refinement. All 3 issues from review-1839-2026-07-09 are resolved: issue body restructured to match template-issue-feature, project-bots project page created with user-stories section, and arch-bots architecture note created.

    Template Completeness

    Checked against template-issue-feature (Type = Feature):

    • [x] ### Type — "Feature"
    • [x] ### Lineage — "Standalone — identified during initial bot deployment when Volatility Bot produced 0 fires due to flawed entry logic."
    • [x] ### Repoldraney/bots
    • [x] ### User Story — "As a bot operator / I want the Volatility Bot to independently find and trade... / So that I can profit from normal game momentum swings..."
    • [x] ### Context — Detailed explanation of 4 current problems plus reworked strategy with entry/exit conditions
    • [x] ### File Targets — Clear "modify" (1 file) and "do NOT touch" (3 files) sections
    • [x] ### Feature Flag — "None — this is a rework of an existing bot service."
    • [x] ### Acceptance Criteria — 7 checkbox items
    • [x] ### Test Expectations — 5 items including paper mode and live mode stages, with run command
    • [x] ### Constraints — Patterns to follow, IOC order requirements, constants to update
    • [x] ### Checklist — 3 items (PR, paper mode, no unrelated changes)
    • [x] ### Related — References project and ldraney/bots#2

    Traceability

    • [x] story:bot-strategy label — present on board item
    • [x] story note verified — found in project-bots user-stories section: key=bot-strategy, role="Bot operator", success metric="Per-bot win rate > 55%, positive P&L after fees"
    • [x] arch:bots label — present on board item
    • [x] arch note verified — arch-bots note exists in pal-e-docs (tagged architecture+active, project=bots). Covers 7 services, key files, order pattern, data model, infrastructure.
    • [x] Forgejo issue — ldraney/bots#1, state=open

    File Targets

    • [x] app/services/volatility_bot.rb — verified: exists (7.4k), contains find_hedgeable_fires (L179), TAKE_PROFIT_MULTIPLIER = 3 (L6), HEDGE_CONTRACTS = 3 (L5), scan_for_hedges (L166). All patterns match issue description.
    • [x] app/services/position_monitor.rb — verified: exists (4.3k), excludes Volatility Bot at L34. Correctly marked no-change.
    • [x] app/services/bot_engine.rb — verified: exists (8.7k), game_nearly_over? at L137. Correctly marked no-change. Issue plans to reuse this logic (inverted) inside volatility_bot.rb.
    • [x] app/controllers/dashboard_controller.rb — verified: exists (2.5k). Correctly marked no-change.

    Repo Placement

    OK. Forgejo issue is on ldraney/bots. All changes target app/services/volatility_bot.rb in the same repo. No cross-repo concerns.

    Dependencies

    Board has 2 items: #1839 (this) and #1840 (Rework Consensus Bot). Both in backlog. No dependency between them — volatility bot operates independently of consensus bot. Issue's Related section correctly documents ldraney/bots#2 as independent. No blockers identified.

    Acceptance Criteria

    7 criteria total. All are code-level verifiable:

    • [x] "Scans markets independently" — verify code doesn't call find_hedgeable_fires
    • [x] "Entries only early/mid-game with yes_bid >= 0.90 and no_ask <= 0.15" — verify entry conditions
    • [x] "Spread check (no_ask - no_bid <= 0.07)" — verify MAX_SPREAD logic
    • [x] "Take-profit exits on 5c bump" — verify TAKE_PROFIT_BUMP constant and exit logic
    • [x] "Stop-loss at 50% of entry" — verify STOP_LOSS_FLOOR constant and exit logic
    • [x] "Time-stop at final 20%" — verify time-stop exit path
    • [x] "Settlement logic unchanged" — verify NO-position math preserved

    Previous review flagged a non-verifiable operational criterion ("5 paper fires"). This has been correctly moved to Test Expectations, and all AC are now code-level verifiable.

    Blast Radius

    Clean. No references to find_hedgeable_fires, TAKE_PROFIT_MULTIPLIER, or scan_for_hedges in any file outside volatility_bot.rb. consensus_bot.rb has no volatility references. position_monitor.rb already excludes Volatility Bot fires (L34). bot_engine.rb owns game_nearly_over? but issue correctly plans a new method inside volatility_bot.rb rather than modifying shared code.

    Decomposition Assessment

    1 file target in 1 repo. 7 acceptance criteria (exceeds the 5-AC guideline). Estimated agent work: ~5 minutes — single file rewrite with well-defined entry/exit logic. All 7 AC modify the same file and are tightly coupled (entry conditions, exit conditions, constants). Decomposition is not warranted because the work is cohesive within a single service file. No decomposition needed.

    Recommendation

    No action needed.

Architecture 2
  • Domain Model: BotFire arch-domain-bots

    Domain Model: BotFire

    Diagram

    erDiagram
        USER ||--o{ BOT : "owns"
        BOT ||--o{ BOT_FIRE : "executes"
        BOT ||--o{ TRIGGER : "evaluates"
        GAME ||--o{ MARKET : "has"
        MARKET ||--o{ MARKET_SNAPSHOT : "captures"
        MARKET ||--o{ BOT_FIRE : "traded on"
    
        USER {
            string kalshi_api_key_id
            string kalshi_private_key_path
            boolean paper_mode
        }
        BOT {
            string name
            boolean paper_mode
            boolean active
        }
        TRIGGER {
            string condition_field
            string condition_operator
            float condition_value
            float condition_max
            string action
            string trigger_type
        }
        GAME {
            string milestone_id PK
            string event_ticker
            string sport_type
            string title
            string live_status
            integer period
            string clock
            integer home_score
            integer away_score
        }
        MARKET {
            string ticker PK
            float yes_ask
            float yes_bid
            float last_price
            string status
            datetime close_time
        }
        MARKET_SNAPSHOT {
            float yes_ask
            float yes_bid
            float last_price
            datetime captured_at
        }
        BOT_FIRE {
            float entry_price
            float exit_price
            float pnl
            string exit_reason
            string position_status
            integer count
        }
    

    Components

    Component Purpose Notes
    User Kalshi account holder Credentials for API auth signing
    Bot Trading strategy instance Only "Live Edge" active; Volatility/Consensus disabled
    Trigger Entry condition condition_field + operator + value + max defines price band
    Game Sporting event sport_type: baseball_game, basketball_game, football_game, hockey_match, soccer_tournament_multi_leg
    Market Tradeable contract 2 per game (one per team). Ticker prefix = league (KXMLB, KXWNBA, KXLMB, KXNPB, KXKBO)
    MarketSnapshot Point-in-time price Captured every scanner tick for analytics
    BotFire Trade record Full lifecycle: entry, open, exit (stop_loss, take_profit, pre_settlement_exit, settlement_win, settlement_loss)

    Key Decisions

    • One BotFire per trade. Entry creates BotFire with position_status "open". Exit updates same record with exit_price, pnl, exit_reason.
    • Game-winner markets only. real_game_market? filters to tickers containing "GAME", ignoring spreads/totals/props.
    • sport_type from Kalshi milestones. Scanner maps milestone types directly. GamePhase module uses sport_type for period/clock interpretation.
    • condition_max on Trigger. Creates a ceiling (89c) on the entry band, preventing entries above the profitable range.
    • project-botfire — project page
    • arch-dataflow-bots — data flow (TODO)
    • arch-deployment-bots — deployment (TODO)
  • Architecture: Bots

    Kalshi live sports betting platform — Rails 8.1 + SQLite on Docker Compose.

    Services (7 Docker Compose containers)

    Service Role Interval
    web Rails on port 3003, live dashboard
    scanner Polls milestones, fetches markets, refreshes prices 1s
    bot_engine Evaluates triggers, places orders 1s
    position_monitor Stop-loss / take-profit / settlement exits 5s
    consensus_bot Detects 2+ bot agreement, reinforces 10s
    portfolio_grouper Groups positions by sport/time/confidence 60s
    volatility_bot Buys underdog (NO side) as hedge 10s

    Key Files

    • app/services/bot_engine.rb — Core trigger evaluation loop
    • app/services/position_monitor.rb — Exit monitoring (excludes Volatility Bot)
    • app/services/volatility_bot.rb — Independent underdog strategy
    • app/services/consensus_bot.rb — Multi-bot agreement detector
    • app/services/scanner.rb — Kalshi API poller
    • app/services/kalshi_client.rb — API client (auth, orders, balance)
    • app/controllers/dashboard_controller.rb — JSON API for live dashboard

    Order Pattern

    All bots use IOC (immediate-or-cancel) orders. Critical invariants:

    • Check fill_count > 0 before creating BotFire records
    • Use average_fill_price from API response, not the order price
    • Per-bot contract counts via BOT_CONTRACTS hash in bot_engine.rb

    Data Model

    Game → Market → MarketSnapshot (price history). Bot → Trigger → BotFire (position tracking with entry/exit/pnl).

    Infrastructure

    • Host: archbox (10.0.0.149)
    • Dashboard: port 3003, tunneled via Tailscale
    • Secrets: ~/secrets mounted read-only into containers
    • DB: SQLite (single-writer, no external DB service)
Project Page 1
  • Project: BotFire project-botfire

    Project: BotFire

    Vision

    Automated Kalshi sports betting platform focused on finding and proving profitable bot strategies through rigorous trend analytics. Success = bots that trend upward over time with reliable upside and mitigated risk. Every strategy must prove itself with real P&L data before scaling.

    Core principles:

    • Data before amplification — don't scale a strategy until analytics prove it has a real dollar-positive edge, not just a win rate
    • No bullshit numbers — report actual P&L vs cost basis, never oversell performance
    • Test before deploy — verify code works end-to-end before claiming deployed
    • Track everything — every bet must have full visibility: entry, exit, P&L, reason, bot, game
    • Cut losers fast — if analytics show a bot is underwater, disable it

    Current Focus: Live Edge Only

    All bots disabled except Live Edge (2026-07-10, PR #10). Analytics proved none of the other bots were dollar-positive. Live Edge contracts doubled from 12 to 24.

    Why Live Edge: 85-89c entry band shows 61.5% WR (above 46.6% breakeven), P&L trending upward in second half of trades (+$1.41). Only bot with a positive trend.

    Disabled Bots

    Bot WR P&L Why Disabled
    Lucas Bot 17.9% -$13.95 Proven loser, no late-game filter
    Lucas Late Game 38.5% -$20.16 Catastrophic settlement losses at 90c+ entries
    Consensus Bot 44.4% -$5.14 One -$5.40 settlement loss wiped all gains
    Portfolio Grouper 40% -$2.70 One settlement loss, too few trades
    Volatility Bot $0 0 fires after rework
    Marcus Bot 0% -$0.42 Paper mode only

    Architecture

    See arch-bots for service inventory, key files, order patterns, and infrastructure.

    Rails 8.1 + SQLite, 7 Docker Compose services. IOC orders only, fill_count > 0 invariant, average_fill_price for all entry/exit tracking.

    Board

    board-bots (BotFire Sprint)

    User Stories

    Board items on board-bots trace to these stories via story:<name> labels.

    Story Label User Story
    story:bot-strategy As a bot operator, I want to identify and exploit profitable trading conditions so that my automated bots generate consistent positive P&L. This covers all Live Edge optimization work — entry price tuning, time-of-day filtering, exit strategy improvements, and new strategy development based on analytics.

    Status

    Balance: ~$322. All-time P&L: ~-$44 (mostly from disabled bots). Only Live Edge active at 24 contracts.

    Milestones

    • M1: Analytics dashboard — DONE (PR #6)
    • M1.5: Focus on Live Edge, disable losers — DONE (PR #10)
    • M2: Confirm Live Edge edge over 100+ trades
    • M3: Build alternative strategies based on Live Edge's success patterns

    Repos

    Repo Platform Role Status
    ldraney/botfire Forgejo Application monorepo Active
Board 1