# Bytelyst Trading Product Flow and Execution Logic

Date: 2026-02-15
Scope: runtime product flow across bot service, execution, monitoring, and dashboard sync

## Purpose

This document explains the live trading flow end-to-end:
- how a profile initiates a trade
- what conditions close a trade
- how monitoring runs periodically
- how Orders, Positions, and History stay linked by `trade_id`
- how dashboard data is synchronized from backend state

## 1) Core Runtime Components

- `bytelyst-trading-bot-service/src/index.ts`
  - boots profile contexts
  - runs strategy loop
  - runs reconciliation and profile hot-reload loops
- `bytelyst-trading-bot-service/src/services/AutoTrader.ts`
  - decides entry/exit actions from strategy result + risk guards
- `bytelyst-trading-bot-service/src/services/TradeExecutor.ts`
  - places exchange orders and manages lifecycle state
  - persists orders/history and updates in-memory active positions
- `bytelyst-trading-bot-service/src/services/tradeMonitor.ts`
  - periodic SL/TP/trailing and exchange-position checks
- `bytelyst-trading-bot-service/src/services/OrderStatusSyncService.ts`
  - periodic stale-order reconciliation with exchange
- `bytelyst-trading-bot-service/src/services/apiServer.ts`
  - websocket state fan-out to dashboard
  - writes `bot_state.json` and snapshots to Supabase

## 2) Profile Configuration Inputs

Per profile, behavior is controlled by `trade_profiles.strategy_config`:

- `rules[]`: which strategy rules run
- `riskLimits`: `maxOpenTrades`, `maxDailyLossUsd`, `dailyProfitTargetUsd`, `maxConsecutiveLosses`
- `execution`:
  - `minRulePassRatio`
  - `orderType`
  - `cooldownMinutes`
  - `profitExitPercent` (optional override)
  - `entryMode`: `both` or `long_only`

Long-only behavior:
- `entryMode = long_only` blocks new SELL entries
- exit SELL orders are still allowed when closing BUY positions
- logic: `bytelyst-trading-bot-service/src/services/AutoTrader.ts`

## 3) End-to-End Entry Flow

1. Market context is built per symbol.
2. Each active profile evaluates rules independently.
3. For each profile result, `AutoTrader.handleSignal(...)` executes.
4. Entry is attempted only if:
   - signal is not `NONE`
   - rules passed
   - symbol is in profile watchlist
   - portfolio guard passes
   - max open trades and cooldown checks pass
   - runtime risk limits pass
   - exchange safety checks pass
   - entry direction allowed by `entryMode`
5. Risk engine computes:
   - side-aware position size
   - side-aware stop-loss and take-profit
6. Trade executor places order, verifies fill, and records:
   - `orders` row with `action=ENTRY`
   - deterministic `trade_id`
   - active in-memory position keyed by symbol/profile context


### Manual entry capital guard flow (`POST /api/trade/manual`)

Manual trade requests now enforce allocated-capital safety without blind rejection:
- compute `remainingCapital = allocatedCapital - committedOpenPositionNotional`
- if no remaining capital, wait up to 60s for release (poll every 3s)
- if capital becomes available, scale requested quantity down to the max tradable qty
- if remaining is below minimum tradable qty, return a clear rejection

Result:
- trade size never exceeds profile/account allocated capital
- user can still get partial execution with available remaining capital
- when capital is fully locked, request waits briefly before returning "waiting for capital release"

Source:
- `bytelyst-trading-bot-service/src/services/ManualTrader.ts`

Primary references:
- `bytelyst-trading-bot-service/src/index.ts`
- `bytelyst-trading-bot-service/src/services/AutoTrader.ts`
- `bytelyst-trading-bot-service/src/services/riskEngine.ts`
- `bytelyst-trading-bot-service/src/services/TradeExecutor.ts`

## 4) Position Close Conditions

Trade close can be initiated from multiple paths:

### A) Strategy signal flip
- If active side is opposite to new signal, close immediately.
- example: BUY position + SELL signal -> close
- source: `AutoTrader.handleSignal(...)`

### B) Profit threshold + neutral signal
- If unrealized profit reaches `profitExitPercent` and signal becomes `NONE`, close.
- source: `AutoTrader.handleSignal(...)`

### C) Safety stop-loss (monitor-driven)
- BUY closes when `price <= stopLoss`
- SELL closes when `price >= stopLoss`
- source: `tradeMonitor.checkOpenPositions(...)`

### D) TP-triggered trailing guard (monitor-driven)
- take-profit hit activates `profitGuardActive`
- after activation, exit only on configured pullback:
  - BUY: pullback from peak by `TRAILING_STOP_PERCENT`
  - SELL: rebound from trough by `TRAILING_STOP_PERCENT`
- source: `tradeMonitor.checkOpenPositions(...)`

### E) Exchange confirms position disappeared
- monitor confirms missing exchange position across required misses and final confirmation
- then finalizes local lifecycle as exchange-closed
- source: `tradeMonitor.checkOpenPositions(...)`

### F) Manual square-off (API)
- manual close endpoint routes to executor close flow
- source: `apiServer` + `ManualTrader` + `TradeExecutor.closePosition(...)`

## 5) Partial Exit Handling

Exit fills are quantity-aware:
- full fill -> finalize trade and remove active position
- partial fill -> reduce active position quantity and continue monitoring remainder
- no valid partial quantity -> quarantine path for manual review

Source:
- `bytelyst-trading-bot-service/src/services/TradeExecutor.ts` (`closePosition`, `applyExitFill`)

## 6) Monitoring and Loop Frequencies

Configured in `bytelyst-trading-bot-service/src/config/index.ts`:

- `POLLING_INTERVAL` (default 60s): strategy/trading loop
- `MONITOR_INTERVAL_MS` (default 60s): profile reconciliation loop and trade monitor tick
- `PROFILE_SYNC_INTERVAL_MS` (default 60s): profile hot-reload loop
- Order status sync interval (currently 5 minutes in `index.ts` constructor call)
- State write debounce: ~200ms in `apiServer.scheduleStateWrite()`
- Supabase snapshot throttle: every 5 minutes in `apiServer.flushStateToDisk()`


Order-status sync behavior (every cycle):
- scans stale `pending_new` rows
- reconciles status from exchange when order still exists
- if exchange returns "order not found" and lifecycle is already closed for that `trade_id`, auto-resolves stale row to `canceled`
- this prevents ghost EXIT orders from staying `pending_new` after lifecycle completion

Sources:
- `bytelyst-trading-bot-service/src/services/OrderStatusSyncService.ts`
- `bytelyst-trading-bot-service/src/services/SupabaseService.ts` (`isTradeLifecycleClosed`)

## 7) Orders, Positions, History Linkage

Lifecycle identity model:
- `trade_id` is created at entry and reused for exit
- `profile_id` scopes ownership/isolation
- `action` distinguishes `ENTRY` vs `EXIT`

Data mapping:
- Orders table: all lifecycle events (`ENTRY` and `EXIT`)
- Active positions: in-memory + websocket `positions_update`
- History table: realized lifecycle slices (full close and partial exit slices)

Primary references:
- `bytelyst-trading-bot-service/src/services/TradeExecutor.ts`
- `bytelyst-trading-bot-service/src/services/apiServer.ts`
- `bytelyst-trading-dashboard-web/src/tabs/PositionsTab.tsx`

## 8) Dashboard Synchronization Flow

Backend emits:
- `positions_update`
- `orders_update`
- `history_update`
- `symbol_update`
- `settings_update`

Frontend hook:
- `bytelyst-trading-dashboard-web/src/hooks/useWebSocket.ts`
  - receives events and updates local `botState`

Positions & Orders tab:
- normalizes `trade_id` (`trade_id` vs `tradeId`)
- infers lifecycle IDs for legacy rows when needed
- builds lifecycle traces per `trade_id`
- shows mismatch diagnostics (missing trade ID, missing entry order, profile mismatch)
- computes deterministic lifecycle states:
  - `OPEN`: entry filled, no exit fill yet
  - `EXIT_PENDING`: exit submitted, waiting for fill sync
  - `PARTIAL_EXIT`: partial exit filled; remaining quantity stays in Open Positions
  - `CLOSED`: lifecycle fully matched/closed
  - `ORPHAN_EXIT`: exit exists but matching entry is not in visible order window

Source:
- `bytelyst-trading-dashboard-web/src/tabs/PositionsTab.tsx`


History tab:
- uses deduplicated lifecycle history ledger for audit totals (`trade_id + profile + timestamp + prices + size + pnl + reason`)
- uses Supabase `trade_history` as primary source; realtime websocket history is used only as fallback when DB rows are unavailable
- Realized P&L formula: `sum(pnl)` over deduplicated history rows in current filter scope
- win rate formula: `winning_rows / total_rows` where `winning_rows = pnl > 0`
- surfaces per-trade `capital used` and highlights losing trades
- normalizes numeric timestamps (seconds vs milliseconds) and unknown side values to avoid mis-sorted or mis-labeled rows
- numeric rendering is NaN-safe for P/L percent and price fields

Source:
- `bytelyst-trading-dashboard-web/src/tabs/HistoryTab.tsx`
- `bytelyst-trading-dashboard-web/src/lib/tradeHistoryLedger.ts`

Overview tab:
- uses the same deduplicated history ledger as History tab for realized metrics and profile-level aggregates
- Realized P&L formula: `sum(pnl)` over deduplicated ledger rows
- Net P&L formula: `Realized P&L + sum(unrealizedPnl of open positions)`
- Capital Scope table now shows profile-level `Realized P&L` from profile-scoped ledger aggregation
- shows per-profile `allocated / used / remaining` capital and runtime state labels
- exposes `P&L Duration` in status bar: elapsed time from first to latest realized ledger event in scope
- does not show aggregate `P&L Capital Used` in status bar (intentionally removed to avoid misleading interpretation at summary level)
- supports win-rate time windows in Overview (`24H`, `7D`, `30D`, `All`) for both global and per-profile win-rate metrics
- windowed win-rate formula: `wins_in_window / trades_in_window`, where `wins_in_window = pnl > 0` within selected window

Source:
- `bytelyst-trading-dashboard-web/src/tabs/OverviewTab.tsx`
- `bytelyst-trading-dashboard-web/src/lib/tradeHistoryLedger.ts`

Strategy Clusters tab:
- profile card P&L and win rate use the same deduplicated history ledger aggregation as Overview/History
- non-admin scope is user-filtered to avoid cross-user P&L contamination

Source:
- `bytelyst-trading-dashboard-web/src/components/TradeProfileManager.tsx`
- `bytelyst-trading-dashboard-web/src/lib/tradeHistoryLedger.ts`

## 9) Operational Notes

- If state changes every minute, local `bot_state.json` can update frequently (debounced).
- Supabase snapshot upload is throttled to 5-minute intervals, so it is a rolling backup, not every state mutation.
- This design reduces backup write overhead while keeping recoverable snapshots.

- `trade_history` insert path is legacy-schema safe: if `source` column is unavailable, bot retries with fallback payload instead of dropping the event.

Source:
- `bytelyst-trading-bot-service/src/services/SupabaseService.ts`

## 10) AI Resilience and Fallback Behavior

### AI trading rule (`AIAnalysisRule`)

- AI provider chain: `openai -> perplexity -> gemini` (configurable fallback list)
- If AI service is unavailable:
  - with `AI_FAIL_OPEN=true` (default), AI rule does not block trading
  - with `AI_FAIL_OPEN=false`, AI rule blocks as a failed rule

Config references:
- `AI_FAIL_OPEN`
- `AI_FALLBACK_LIST`
- `AI_MODEL`
- provider keys (`OPENAI_API_KEY`, `PERPLEXITY_API_KEY`, `GEMINI_API_KEY`)

### Chat profile creation fallback

- Endpoint: `POST /api/chat`
- If AI providers fail or return invalid JSON, backend now returns deterministic profile JSON generated from local heuristics:
  - parses symbols, capital, risk, cooldown, entry mode, sessions
  - supports create/update/explain fallback outputs
  - response includes `fallback: "local_deterministic"`

### AI health endpoint

- Endpoint: `GET /api/ai/health`
- Query:
  - `probe=true` for live provider probe
  - omit `probe` for config-only status
- Response includes:
  - per-provider `configured`, `status`, `message`, `model`
  - effective fallback list and fail-open state
  - summary counts

## 11) Quick Trace Checklist (One Trade)

1. Find profile and symbol signal in websocket symbol state.
2. Check `orders` for `ENTRY` row and `trade_id`.
3. Verify active position carries same `tradeId` + `profileId`.
4. On close, confirm `EXIT` order has same `trade_id`.
5. Confirm history row created with same `trade_id` (or partial-exit realized slice entries).
6. If mismatch, use lifecycle trace/mismatch diagnostics in Positions tab.



## 12) Lifecycle Reconciliation (2026-02-15)

- Legacy compatibility: lifecycle entry detection now treats `action IS NULL` + BUY rows as ENTRY fallback.
- Virtual position reconstruction for legacy no-action rows now infers BUY as ENTRY and SELL as EXIT.
- Reconciliation tool: `npm run reconcile:lifecycle-history -- --apply --start=2026-02-12T00:00:00.000Z <trade_id ...>`
- Reconciliation behavior:
  - computes canonical realized P&L from filled orders per `trade_id` using FIFO closed quantity matching
  - neutralizes conflicting non-canonical history rows by zeroing P&L and prefixing reason with `[RECONCILED_TO_ORDERS]`
  - inserts or updates one `[RECONCILED_CANONICAL]` history row per reconciled lifecycle
- Latest operational result:
  - post-purge lifecycle mismatch count reduced to `0` (order-derived vs history-derived, trade_id scoped)
  - Alpaca vs Supabase order status parity remains `0` mismatches
## 13) Alpaca Reconciliation Carry-In Baseline (2026-02-15)

- Script: `scripts/reconcileAlpacaVsSupabase.ts`
- New capabilities:
  - paged Alpaca order fetch over `carry_in_lookback_days` to reconstruct pre-window state
  - dual realized-PnL outputs: `flat_start` and `with_carry_in`
  - auto primary-mode selection (`alpaca_fill_derived_realized_pnl`) based on Supabase pre-window coverage consistency
  - symbol scope alignment for PnL (`db-order-symbols`) so non-bot symbols do not pollute comparison
- New output diagnostics:
  - `alpaca_carry_in_bootstrap.primary_mode`
  - `alpaca_carry_in_bootstrap.supabase_pre_window_filled_order_count`
  - `alpaca_carry_in_bootstrap.carry_coverage_gap`
  - `alpaca_carry_in_bootstrap.carry_coverage_threshold`
- Usage examples:
  - `node --loader ts-node/esm scripts/reconcileAlpacaVsSupabase.ts 3`
  - `node --loader ts-node/esm scripts/reconcileAlpacaVsSupabase.ts 3 --carry-lookback-days=180`

## 14) Runtime Addendum (2026-02-16)

### Profile-symbol scope synchronization

- Profile bootstrap sync and periodic reconciliation now use profile-resolved symbol scope (`profile.symbols` fallback to `config.SYMBOLS`).
- Trading loop now runs on dynamic union of monitored symbols across all active profiles.
- Profile hot-reload detects symbol-list changes and re-syncs position state using old+new symbol union to avoid stale local state.

Primary source:
- `bytelyst-trading-bot-service/src/index.ts`

### Legacy stale EXIT reconciliation without trade_id

- Order-sync stale reconciliation now handles legacy EXIT-like rows that have no `trade_id`.
- If exchange reports order missing and profile-scoped virtual lifecycle is flat for `profile_id + symbol`, stale order is auto-resolved to `canceled`.
- This closes a real stale-order gap that previously left perpetual `pending_new` rows in Active Orders.

Primary sources:
- `bytelyst-trading-bot-service/src/services/OrderStatusSyncService.ts`
- `bytelyst-trading-bot-service/src/services/SupabaseService.ts`

### Position merge identity hardening

- Position websocket merge now keys stable rows by owner/profile/trade identity so stable lifecycle rows are preserved and not always collapsed by owner+symbol.
- Fallback/no-trade rows still collapse by owner/profile/symbol/side to prevent duplicate noise.

Primary source:
- `bytelyst-trading-bot-service/src/services/apiServer.ts`

### Known structural constraint

- Same-profile same-symbol concurrent lifecycles are still structurally limited by runtime active-position map keyed by symbol (`Map<symbol, PositionState>`).
- Full multi-lifecycle same-symbol support within one profile requires executor state model refactor (position map keyed by `trade_id` or hybrid `symbol+trade_id`).

### 2026-02-16 Commit References

- Lifecycle/profile sync hardening implementation:
  - https://github.com/saravanakumardb/bytelyst-trading-bot-service/commit/78be9e63f4e3e2a0c550e467c4cb4f66c7c2b2df
- Runtime flow documentation addendum:
  - https://github.com/saravanakumardb/bytelyst-trading-bot-service/commit/bb739a0cad9c06ae591ccd95b7f2135f23261170

## 15) Runtime Addendum (2026-02-16, Cycle 2)

### Multi-lifecycle same-symbol handling (per profile)

- Active runtime position state is now trade-id aware (`symbol + trade_id`), so one profile can hold multiple concurrent lifecycles on the same symbol.
- AutoTrader exit logic now evaluates and exits per lifecycle (`trade_id`) for signal flip and neutralized-profit exits.
- TradeMonitor now evaluates SL/TP/trailing exits per lifecycle and finalizes exchange-missing closures per lifecycle.

Primary sources:
- `bytelyst-trading-bot-service/src/services/TradeExecutor.ts`
- `bytelyst-trading-bot-service/src/services/AutoTrader.ts`
- `bytelyst-trading-bot-service/src/services/tradeMonitor.ts`

### Dedicated profile sync behavior

- Profile-scoped sync now reconstructs multiple virtual open slices for a symbol using `profile_id + symbol + trade_id`.
- If per-trade slices are not recoverable, sync safely falls back to aggregate virtual lifecycle state.

Primary sources:
- `bytelyst-trading-bot-service/src/services/SupabaseService.ts`
- `bytelyst-trading-bot-service/src/services/TradeExecutor.ts`

### Continuous parity guard

- Reconciliation loop now includes a scheduled Alpaca-vs-Supabase order parity audit over recent profile orders.
- Safe terminal mismatches are auto-corrected, and runtime health publishes `parityMismatchCount`.

Primary sources:
- `bytelyst-trading-bot-service/src/index.ts`
- `bytelyst-trading-bot-service/src/services/apiServer.ts`

### Manual square-off behavior

- Manual close endpoint now exits all active sub-positions for a symbol (trade-id aware), not only one selected row.

Primary source:
- `bytelyst-trading-bot-service/src/services/apiServer.ts`

### Cycle 2 Commit Link

- Runtime multi-lifecycle + parity guard implementation:
  - https://github.com/saravanakumardb/bytelyst-trading-bot-service/commit/abb6dd5e1fa1b3f12104c9205d2a9efcd7d668cb

## 16) Runtime Addendum (2026-02-16, Cycle 3)

### Lifecycle retention during temporary virtual-sync gaps

- Dedicated-profile sync no longer clears local symbol lifecycles when the exchange still reports an open position but virtual DB reconstruction temporarily returns empty.
- This prevents false drops where filled ENTRY orders briefly disappear from Open Positions due to eventual consistency windows.

Primary source:
- bytelyst-trading-bot-service/src/services/TradeExecutor.ts

### Immediate dashboard position propagation

- Position snapshots are now pushed immediately after confirmed ENTRY fill and after lifecycle finalization.
- Dashboard no longer has to wait for the next trading-loop/reconciliation cycle to reflect these two state transitions.

Primary source:
- bytelyst-trading-bot-service/src/services/TradeExecutor.ts

### Regression coverage added

- Added lifecycle regression assertions for:
  - immediate position snapshot push after filled entry
  - retaining local lifecycle state when exchange is open and virtual lookup is temporarily empty

Primary source:
- bytelyst-trading-bot-service/scripts/testTradeExecutorLifecycle.ts

### Cycle 3 Commit Link

- Runtime lifecycle retention plus immediate position propagation fix:
  - https://github.com/saravanakumardb/bytelyst-trading-bot-service/commit/9c53222939b73fba4ab95f83c7df5d57f9b46683
## 17) Lifecycle Linkage Model (2026-02-16, Cycle 4)

### How lifecycle is tracked end-to-end

- Primary lifecycle key is trade_id, scoped by profile_id.
- Every bot-originated ENTRY and EXIT order carries trade_id and profile_id.
- Order persistence now writes idempotently by order_id, so retries do not create duplicate lifecycle rows.
- Active positions are maintained per lifecycle (symbol plus trade_id) and rendered per profile context.
- Trade history rows are written per lifecycle close/partial-close with the same trade_id.

### How stale and duplicate views are controlled

- Backend stale-order sync now runs on configurable cadence (ORDER_SYNC_INTERVAL_MS) and checks broader pending statuses (pending_new, pending, accepted, new).
- Stale EXIT rows are auto-resolved when lifecycle closure is already proven by trade_id chain.
- Dashboard position rendering dedupes by profile_id plus trade_id to avoid duplicate position cards.
- Dashboard order activity normalizes stale pending EXIT rows to closed state when history confirms lifecycle closure for that trade_id scope.

### Current residual limitation

- Full enterprise-level test coverage is still in-progress. Current implementation hardens lifecycle consistency, but broad component/service test expansion remains required for full target coverage.

### Cycle 4 Commit Links

- Bot lifecycle persistence and stale-sync hardening:
  - https://github.com/saravanakumardb/bytelyst-trading-bot-service/commit/0d3409a48ac60dfb76e84c75be3f4b12c2f98a18
- Dashboard lifecycle table stabilization:
  - https://github.com/saravanakumardb/bytelyst-trading-dashboard-web/commit/e9422a3e330161bde0f32c9275f8f7dea30cdee5
## 18) Validation Addendum (2026-02-16, Cycle 5)

### Dashboard flow validation expansion

- Added deterministic automated coverage for core runtime visibility paths:
  - app shell auth routing (`loading`, `login`, reset callback, authenticated dashboard)
  - lifecycle trace rendering (`OPEN`, `PARTIAL_EXIT`, `CLOSED`, `ORPHAN_EXIT`, `EXIT_PENDING`)
  - stale pending EXIT warning and mismatch diagnostics rendering
  - overview capital/P&L/readiness rendering paths
  - history/config/settings/entries/admin tab shell paths

### Supporting module coverage

- Added automated tests for:
  - `AuthContext` provider and guard behavior
  - `useWebSocket` default-state contract
  - `supabaseClient` env-based initialization
  - strategy-config normalization compatibility (`normalizeStrategyConfig`)

### Result snapshot

- Dashboard coverage (`npm run coverage:full`) now reports:
  - Statements: `42.46%` (`806/1898`)
  - Branches: `34.71%` (`743/2140`)
  - Functions: `33.26%` (`159/478`)
  - Lines: `44.25%` (`762/1722`)

### Commit link

- Dashboard test expansion implementation:
  - https://github.com/saravanakumardb/bytelyst-trading-dashboard-web/commit/3be473d882018d1cd88f7473dbbc24ee5f0be662
## 19) Runtime Merge and Lifecycle Aggregation Hardening (2026-02-16, Cycle 6)

### What changed

- Backend runtime state updates now use canonical merge helpers before publishing positions/orders to bot state.
  - Positions: stable `trade_id` identity first; fallback owner+symbol+side consolidation.
  - Orders: `order_id` identity merge with terminal-status precedence to prevent stale pending downgrade.
- Dashboard lifecycle aggregation now computes realized P&L and win rate per lifecycle chain (`profile_id + trade_id`) instead of row-level counting.
- Dashboard overview and positions views now dedupe runtime positions by lifecycle identity before utilization/P&L rendering.

### Why this closed the duplicate and mismatch issue

- The previous flow could count the same lifecycle multiple times when sync events arrived from different paths or with profile-less rows.
- The new flow keeps one canonical runtime representation per lifecycle and one canonical aggregate record per lifecycle chain.

### Expected user-visible behavior now

- Active positions, order activity, lifecycle table, and history all align by `trade_id` and `profile_id` more consistently.
- Net P&L and win rate no longer inflate from duplicated lifecycle rows.
- Capital used/utilization in overview reflects deduped open runtime positions.

### Commit links

- Bot runtime merge hardening:
  - https://github.com/saravanakumardb/bytelyst-trading-bot-service/commit/af772b265a348f2c47440c5f41c74144156b54f4
- Dashboard lifecycle aggregation and runtime dedupe hardening:
  - https://github.com/saravanakumardb/bytelyst-trading-dashboard-web/commit/ad9afd7537f8ae8bf805d7a10a6cb4ffad5e9abf
