Skip to content

Backend: Add daily trading log with preparation, execution, and reflection tracking #1

Description

@Benaiah-Varner

Goal

Implement the backend for a new daily trading log system in EdgeFinder. This feature should track performance separate from P/L by day, with three sections:

  1. Preparation
  2. Execution
  3. Reflection

This issue is backend-only. Do not build frontend UI in this issue.

Current context:

  • The current Prisma schema has User, Trade, and Strategy.
  • Trade should remain a clean raw trade record.
  • Do not re-add execution booleans like properEntry, R, alignedWithTrend, properConditions, followedTpPlan, or properSize to the Trade table.
  • Execution tracking should be stored at the daily log level, with optional links to trades when a mistake maps to one or more trades.

Backend requirements

Create a daily trading log backend system that supports:

  • One daily log per user per trading date.
  • Preparation score and preparation checklist fields.
  • Execution score, execution mistakes, leakage calculations, and optional links from mistakes to trades.
  • Reflection notes and rule-break analysis.
  • Monthly aggregation endpoints for process performance.

Required schema design

Update access-control/prisma/schema.prisma.

Update User model

Add relation:

dailyTradingLogs DailyTradingLog[]

Main table: DailyTradingLog

This is the parent record for one trading day.

model DailyTradingLog {
  id        String   @id @default(cuid())
  userId    String
  date      DateTime

  // Overall daily scores
  totalScore       Int @default(0)
  preparationScore Int @default(0)
  executionScore   Int @default(100)
  reflectionScore  Int @default(0)

  // P/L summary pulled from trades for this date
  actualPnl Decimal @default(0) @db.Decimal(10, 2)
  tradeCount Int @default(0)

  // Execution-adjusted P/L metrics
  ruleBasedPnl Decimal @default(0) @db.Decimal(10, 2)
  executionDelta Decimal @default(0) @db.Decimal(10, 2)
  leakageCost Decimal @default(0) @db.Decimal(10, 2)
  ruleViolationGain Decimal @default(0) @db.Decimal(10, 2)

  // Daily summary
  mainIssue String?
  nextSessionFocus String?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  preparation DailyPreparationLog?
  reflection DailyReflectionLog?
  executionMistakes DailyExecutionMistake[]

  @@unique([userId, date])
  @@map("daily_trading_logs")
}

Preparation section

The preparation section should be logged once per daily log.

Preparation scoring

Suggested scoring total: 20 points.

Field Points
Reviewed previous trades 5
Completed market analysis 5
Created watchlist 4
Reviewed rules 3
Wrote emotional/risk note 3

Preparation table

model DailyPreparationLog {
  id String @id @default(cuid())
  dailyTradingLogId String @unique

  reviewedPreviousTrades Boolean @default(false)
  completedMarketAnalysis Boolean @default(false)
  createdWatchlist Boolean @default(false)
  reviewedRules Boolean @default(false)
  wroteEmotionalRiskNote Boolean @default(false)

  marketAnalysisNotes String?
  watchlistNotes String?
  emotionalState String?
  riskNote String?

  score Int @default(0)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  dailyTradingLog DailyTradingLog @relation(fields: [dailyTradingLogId], references: [id], onDelete: Cascade)

  @@map("daily_preparation_logs")
}

Execution section

Execution should be daily-log based, not per-trade based.

Trades remain raw records. Execution mistakes are logged as daily events. A mistake may optionally link to one or more trades.

Execution concepts

Daily actual P/L should be pulled from trades for that user/date.

Each mistake stores:

actualPnlImpact
ruleBasedPnlImpact
executionDelta = actualPnlImpact - ruleBasedPnlImpact
leakageCost = max(ruleBasedPnlImpact - actualPnlImpact, 0)
ruleViolationGain = max(actualPnlImpact - ruleBasedPnlImpact, 0)

Daily rule-based P/L:

dailyRuleBasedPnl = dailyActualPnl - sum(executionDelta)

This handles all cases:

Situation Actual impact Rule-based impact Meaning
Ineligible trade loses -150 0 Trade should not exist; full loss is leakage
Ineligible trade wins +220 0 Bad-process profit; counted as rule violation gain
Valid trade exits early +120 +300 $180 of leakage
Stop loss violation -150 -60 $90 of leakage

Execution mistake table

model DailyExecutionMistake {
  id String @id @default(cuid())
  dailyTradingLogId String

  ruleCode ExecutionRuleCode
  category ExecutionMistakeCategory
  label String
  severity ExecutionMistakeSeverity

  // True if this mistake means the affected trade should not have been taken.
  invalidatesTrade Boolean @default(false)

  // Money fields
  actualPnlImpact Decimal @db.Decimal(10, 2)
  ruleBasedPnlImpact Decimal @db.Decimal(10, 2)
  executionDelta Decimal @db.Decimal(10, 2)
  leakageCost Decimal @db.Decimal(10, 2)
  ruleViolationGain Decimal @db.Decimal(10, 2)

  pointsLost Int

  technicalReason String?
  emotionalReason String?
  correction String?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  dailyTradingLog DailyTradingLog @relation(fields: [dailyTradingLogId], references: [id], onDelete: Cascade)
  tradeLinks DailyExecutionMistakeTrade[]

  @@map("daily_execution_mistakes")
}

Optional link table from mistakes to trades

model DailyExecutionMistakeTrade {
  id String @id @default(cuid())
  dailyExecutionMistakeId String
  tradeId String

  dailyExecutionMistake DailyExecutionMistake @relation(fields: [dailyExecutionMistakeId], references: [id], onDelete: Cascade)
  trade Trade @relation(fields: [tradeId], references: [id], onDelete: Cascade)

  @@unique([dailyExecutionMistakeId, tradeId])
  @@map("daily_execution_mistake_trades")
}

Add this relation to Trade:

executionMistakeLinks DailyExecutionMistakeTrade[]

Execution rule enums

enum ExecutionMistakeCategory {
  TRADE_ELIGIBILITY
  SETUP_SELECTION
  MARKET_CONDITIONS
  ENTRY
  POSITION_SIZE
  STOP_LOSS
  TAKE_PROFIT
  TRADE_MANAGEMENT
}
enum ExecutionMistakeSeverity {
  MINOR
  MODERATE
  MAJOR
  CRITICAL
}
enum ExecutionRuleCode {
  NOT_ALIGNED_WITH_TREND
  NOT_RELATIVE_STRENGTH_WEAKNESS
  NO_HTF_KEY_LEVEL
  NO_VOLUME_CONFIRMATION
  WRONG_SIDE_EMA_VWAP
  POOR_MARKET_CONDITIONS
  INVALID_SETUP_SELECTION

  IMPROPER_SIZE
  IMPROPER_ENTRY
  STOP_LOSS_VIOLATION
  MOVED_STOP_INCORRECTLY
  EARLY_FIRST_TRIM
  EARLY_SECOND_TRIM
  FULL_EXIT_TOO_EARLY
  HELD_PAST_SELL_SIGNAL
}

Rule defaults

Implement a backend helper that maps rule code to defaults:

Rule Invalidates trade Default points lost
NOT_ALIGNED_WITH_TREND true 20
NOT_RELATIVE_STRENGTH_WEAKNESS true 20
NO_HTF_KEY_LEVEL true 20
NO_VOLUME_CONFIRMATION true 15
WRONG_SIDE_EMA_VWAP true 20
POOR_MARKET_CONDITIONS true 25
INVALID_SETUP_SELECTION true 30
IMPROPER_SIZE false 20
IMPROPER_ENTRY false 15
STOP_LOSS_VIOLATION false 35
MOVED_STOP_INCORRECTLY false 30
EARLY_FIRST_TRIM false 10
EARLY_SECOND_TRIM false 15
FULL_EXIT_TOO_EARLY false 20
HELD_PAST_SELL_SIGNAL false 20

Reflection section

Reflection should be logged once per daily log.

Reflection scoring

Suggested scoring total: 15 points.

Field Points
Reviewed all trades 4
Identified rule breaks 4
Explained technical reason 3
Explained emotional reason 3
Defined one fix for next session 1

Reflection table

model DailyReflectionLog {
  id String @id @default(cuid())
  dailyTradingLogId String @unique

  reviewedAllTrades Boolean @default(false)
  identifiedRuleBreaks Boolean @default(false)
  explainedTechnicalReason Boolean @default(false)
  explainedEmotionalReason Boolean @default(false)
  definedNextSessionFix Boolean @default(false)

  whatWentWell String?
  whatWentPoorly String?
  rulesFollowed String?
  rulesBroken String?
  technicalReason String?
  emotionalReason String?
  nextSessionFix String?

  score Int @default(0)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  dailyTradingLog DailyTradingLog @relation(fields: [dailyTradingLogId], references: [id], onDelete: Cascade)

  @@map("daily_reflection_logs")
}

Backend logic requirements

Create helpers/services for recalculating daily log totals.

Preparation score helper

preparationScore =
  reviewedPreviousTrades ? 5 : 0
  + completedMarketAnalysis ? 5 : 0
  + createdWatchlist ? 4 : 0
  + reviewedRules ? 3 : 0
  + wroteEmotionalRiskNote ? 3 : 0

Reflection score helper

reflectionScore =
  reviewedAllTrades ? 4 : 0
  + identifiedRuleBreaks ? 4 : 0
  + explainedTechnicalReason ? 3 : 0
  + explainedEmotionalReason ? 3 : 0
  + definedNextSessionFix ? 1 : 0

Execution score helper

executionScore = Math.max(0, 100 - sum(pointsLost))

Mistake P/L helper

executionDelta = actualPnlImpact - ruleBasedPnlImpact
leakageCost = Math.max(ruleBasedPnlImpact - actualPnlImpact, 0)
ruleViolationGain = Math.max(actualPnlImpact - ruleBasedPnlImpact, 0)

Daily totals helper

actualPnl = sum trade.pnl for trades on the log date
tradeCount = count trades on the log date
executionDelta = sum mistake.executionDelta
leakageCost = sum mistake.leakageCost
ruleViolationGain = sum mistake.ruleViolationGain
ruleBasedPnl = actualPnl - executionDelta
totalScore = preparationScore + executionScore + reflectionScore

Note: total score can exceed 100 if using 20 + 100 + 15. That is okay if intentionally represented as /135. Alternatively normalize later in frontend. Backend should store raw section scores.


API endpoints

Create a new controller, likely:

access-control/controllers/dailyTradingLogController.ts

Add routes under something like:

/daily-logs

Required endpoints:

GET /daily-logs
GET /daily-logs/:id
GET /daily-logs/by-date/:date
POST /daily-logs
PUT /daily-logs/:id
DELETE /daily-logs/:id

Preparation:

PUT /daily-logs/:id/preparation

Reflection:

PUT /daily-logs/:id/reflection

Execution mistakes:

POST /daily-logs/:id/execution-mistakes
PUT /daily-logs/:id/execution-mistakes/:mistakeId
DELETE /daily-logs/:id/execution-mistakes/:mistakeId

Aggregations:

GET /daily-logs/monthly-summary?year=2026&month=5

Monthly summary should return:

{
  year: number;
  month: number;
  tradingDays: number;
  actualPnl: number;
  ruleBasedPnl: number;
  executionDelta: number;
  leakageCost: number;
  ruleViolationGain: number;
  averagePreparationScore: number;
  averageExecutionScore: number;
  averageReflectionScore: number;
  averageTotalScore: number;
  mostCommonRuleBreak: string | null;
  mostExpensiveRuleBreak: string | null;
}

Important behavior

When creating a daily log:

  1. Enforce one log per user/date.
  2. Pull trades for that user/date.
  3. Sum actual P/L from those trades.
  4. Default rule-based P/L to actual P/L unless execution mistakes exist.

When adding/editing/deleting an execution mistake:

  1. Calculate mistake money fields.
  2. Save optional trade links.
  3. Recalculate the parent DailyTradingLog totals.

When an execution mistake has invalidatesTrade = true:

  • If linked to a trade, default actualPnlImpact to that trade’s P/L.
  • Default ruleBasedPnlImpact to 0.
  • This captures both ineligible losers and ineligible winners.

When an execution mistake has invalidatesTrade = false:

  • User must provide actualPnlImpact and ruleBasedPnlImpact.
  • This handles stop violations, early trims, early full exits, holding too long, improper size, and poor entry.

Validation

Use express-validator or equivalent existing project pattern.

Validate:

  • Date format.
  • Money fields are numeric.
  • Scores and points are integers.
  • ruleCode, category, and severity are valid enum values.
  • User can only access their own logs, trades, and mistakes.
  • Linked trades must belong to the authenticated user.

Acceptance criteria

  • Prisma schema is updated with the new daily log models/enums.
  • Migration is created.
  • Daily log CRUD endpoints work.
  • Preparation update endpoint calculates and stores preparation score.
  • Reflection update endpoint calculates and stores reflection score.
  • Execution mistake create/update/delete endpoints calculate money fields and refresh daily totals.
  • Monthly summary endpoint returns actual P/L, rule-based P/L, leakage, rule violation gain, and process scores.
  • Existing trade journaling still works unchanged.
  • Trade remains clean and does not regain old execution boolean fields.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions