Back to articles
DocumentationSource: backend/agents/notes/SESSION_AWARENESS.md

AUTHOR: Tony Mudau

Session awareness — how it works

This document explains how the agentic-edge-trader stack detects FX market sessions and uses that context across orchestration, technical analysis, adaptation, telemetry, and the dashboard.


Overview

Session awareness means the system knows which global FX liquidity window is active (Sydney → Tokyo → London → New York, plus overlaps and transitions), then:

  1. Classifies the current window from UTC time.
  2. Gates or shapes trade ideas by session (hard blocks, confidence bumps, wider stops).
  3. Segments learning and telemetry by session so baselines and adaptation are not one-size-fits-all.
  4. Surfaces the live session on the dashboard.

The canonical implementation lives in backend/agents/fx_sessions.py. Orchestration wraps it in _fx_session_context(); the technical agent consumes both a granular session (fx_session) and a legacy coarse label (session_label).


Two session identifiers

The code deliberately keeps two parallel names:

Field Granularity Examples Primary uses
fx_session Fine-grained overlap_london_ny, tokyo, late_new_york, transition, off_hours Pair-specific allow/block lists, SL multipliers, session confidence deltas
session_label Coarse (legacy) asia, london, new_york, off_hours SQLite telemetry indexes, reject-rate baselines, performance adaptation, NY-specific strength gate

Mapping from granular → legacy (_legacy_session_label in fx_sessions.py):

  • sydney, tokyo, transitionasia
  • london, overlap_london_nylondon
  • new_york, late_new_yorknew_york
  • off_hoursoff_hours

Anything else defaults to asia.


Step 1 — Detecting the current session (get_fx_session)

File: backend/agents/fx_sessions.py
Entry: get_fx_session(utc_dt=None) — defaults to datetime.now(timezone.utc).

UTC hour windows (membership flags)

These booleans can overlap; several sessions can be “active” at once:

Session UTC hours (inclusive logic in code)
Sydney h >= 21 or h < 6
Tokyo 0 <= h < 9
London 7 <= h < 16
New York 12 <= h < 21
London–NY overlap 12 <= h < 16

Primary session resolution (priority order)

The primary session string is chosen by this precedence (not simply “first match” on flags):

  1. overlap_london_ny — if 12 <= h < 16
  2. transition — if 7 <= h < 9 (Tokyo + London), or 6 <= h < 7, or 21 <= h < 24
  3. london — London active, NY not active
  4. tokyo — Tokyo active, London not active
  5. sydney — Sydney active, Tokyo not active
  6. new_york16 <= h < 21, or NY flag set otherwise
  7. off_hours — fallback when nothing else fits

Then a special case:

  • late_new_york — if 19 <= h < 21 and primary would be new_york

Return payload

{
    "utc_hour": h,
    "session": "<granular session>",
    "session_label": "<asia|london|new_york|off_hours>",
    "active_sessions": ["sydney", "tokyo", ...],  # all flags true
    "is_overlap_london_ny": bool,
    "is_transition": bool,
    "sl_multiplier": float,  # from SESSION_SL_MULT
}

Orchestration adds display_name via session_display_name() for the UI.

Tests: backend/agents/agents_test/test_fx_sessions.py (overlap at 14:00 UTC, pair avoid rules, confidence deltas).


Step 2 — Pair-specific session profiles

Constants: PAIR_SESSION_RULES in fx_sessions.py

Each symbol (6-letter logical name, e.g. EURUSD) has:

  • preferred — sessions where the pair tends to behave well
  • avoid — sessions where new entries are blocked (when blocking is enabled)

Example: AUDUSD prefers Tokyo/London/overlap and avoids Sydney, off_hours, and late New York.

DEFAULT applies to symbols not listed explicitly.

pair_session_allows_trade(symbol, session, block_avoid=True)

Used by the technical agent as a hard gate:

  • If session is in the pair’s avoid list → reject (reject_pair_session_blocked)
  • If session == "off_hours" → reject (off_hours_blocked) regardless of pair list
  • Otherwise → allow

pair_session_confidence_delta(symbol, session)

Adjusts hypothesis confidence (soft, not a block):

  1. Base delta from SESSION_CONFIDENCE_DELTA (e.g. +0.04 London, −0.12 off_hours)
  2. +0.05 if session is in pair preferred
  3. −0.15 if session is in pair avoid (normally blocked before this runs)
  4. Extra −0.08 for late_new_york on AUDUSD, NZDUSD, AUDJPY

session_sl_multiplier(session)

Wider stops in volatile/liquid windows (SESSION_SL_MULT):

Session Multiplier
overlap_london_ny 1.5
transition 1.45
london 1.35
new_york 1.3
sydney / tokyo / off_hours 1.2–1.25

Applied inside calculate_hybrid_stop_loss() as session_sl_mult on structure buffer and ATR floor/max distances (clamped 0.85–2.5).


Step 3 — Orchestration: one session context per cycle

File: backend/agents/orchestration_agent/main.py

Wrappers

_fx_session_context(now_utc)  → get_fx_session() + display_name
_session_label(now_utc)       → session_label only

Where session is computed

At the start of each run cycle (run_cycle path ~4180):

_sess_ctx = _fx_session_context(now_utc)
_session     = _sess_ctx["session_label"]   # coarse
_fx_session  = _sess_ctx["session"]         # granular

The same _session and _fx_session are passed into every parallel _evaluate_symbol_worker() call so all symbols in a cycle share one clock snapshot.

Also refreshed for:

  • Scheduled trade revalidation (_revalidate_scheduled_trade) — thesis must still pass in the current session
  • Exit management paths that re-evaluate symbols
  • Dashboard feed and GET /session-context

What workers do with session

_evaluate_symbol_worker(..., session_label, fx_session):

  1. Telemetry baselines_candidate_reject_stats(symbol, session_label) and _candidate_market_baselines(symbol, session_label) query SQLite keyed by coarse session_label (with symbol+session and session-only fallbacks).

  2. Adaptive prefilter_adaptive_prefilter_limits(..., session_label=...):

    • off_hours / asia: spread cap × 1.15 (more tolerant spread in quieter windows)
    • Reject-rate telemetry can loosen/tighten spread and “dead market” thresholds
  3. Technical call_technical.analyze(..., session_label=..., fx_session=...)

  4. Combined score floor_adaptive_combined_score_floor(..., session_label=...):

    • off_hours: floor +0.03
    • asia: floor +0.01
  5. Precheck payload — includes "session": session_label for tracing

On trade open, stamp_trade_session() writes both fx_session and session_label onto trade_history for MAE analytics.


Step 4 — Technical agent: gates and confidence

File: backend/agents/technical_agent/main.py
Flow: TechnicalAnalysisAgent.analyze()generate_trade_hypothesis(..., session_label, fx_session)

Session checks run after direction is resolved and before stop/TP and confidence math.

Gate A — Strategy × coarse session (_session_allowed)

Uses session_label only:

Strategy mode Blocked when
trend, htf_pullback, mean_reversion session_label == "off_hours"

All other coarse sessions are allowed for these modes (Asia/London/NY can carry intraday trends).

Failure → reject_session_blocked.

Gate B — Pair × granular session (pair_session_allows_trade)

Uses fx_session (falls back to session_label if missing):

Failure → reject_pair_session_blocked.

Gate C — New York strength (coarse)

Uses session_label == "new_york" (not granular):

Reject if NY session, volatility regime is not expansion, and trend_strength_atr < 0.8:

reject_new_york_not_expansion_weak_trend

This filters low-energy NY setups without blocking the whole session.

Confidence and stops

  • _session_conf_delta from pair_session_confidence_delta(symbol, fx_session) added to the confidence sum (with pattern win rate, MTF, RSI, etc.).
  • _session_sl_mult from session_sl_multiplier(fx_session) passed into calculate_hybrid_stop_loss().

Successful hypotheses expose session_label, fx_session, and log session_conf=±X.XX in reasoning.

Volatility regime logging also stores session_label per snapshot in technical telemetry tables.


Step 5 — Entry execution: performance adaptation by session

File: backend/agents/performance_adaptation.py

Closed trades call on_closed_trade_for_adaptation(), which increments session_trade_stats keyed by entry_learning.session_label (coarse).

Before placing a trade, orchestration may block on:

effective_min_proposal_confidence(session_label, base=..., bump=ADAPT_SESSION_CONFIDENCE_BUMP)

If that session has enough closed trades (ADAPT_SESSION_MIN_TRADES, default 8), win rate below ADAPT_SESSION_WINRATE_THRESHOLD (0.4), and losses ≥ wins → minimum confidence is raised by the bump (default 0.06, tunable via execution tuning UI).

Trace reason: entry_blocked_session_confidence_floor.

This is learned session weakness (historical), separate from the static pair_session_confidence_delta in fx_sessions.py.


Step 6 — Telemetry and closed-loop learning

Candidate telemetry (candidate_telemetry table)

Every prefilter reject / candidate path can record:

  • symbol, session_label, outcome, rejection_reason, scores, volatility, spread

Used to compute rolling symbol_session_reject_rate and session_reject_rate, which feed back into prefilter limits and combined-score floors.

Trade history / MAE

excursion_tracking.stamp_trade_session() stores:

  • fx_session (granular)
  • session_label (coarse)

build_mae_analytics() aggregates MAE/MFE by_session for the dashboard.

Volatility snapshots

Technical agent volatility analysis accepts session_label so regime stats can be segmented in storage.


Step 7 — Frontend display

Piece Role
GET /api/agent-force-1/session-context Standalone current session JSON
Dashboard feed session_context Same payload bundled with transparency/history
SessionBadge Shows display_name, highlights overlap, optional SL×multiplier
use-agent-dashboard.ts Holds sessionContext from feed
FxSessionContext type frontend/lib/agent-force.ts
MAE analytics table Columns fx_session / session_label per trade

The UI does not recompute sessions; it displays backend get_fx_session() output.


End-to-end flow (one cycle)

flowchart TD
    A[UTC now] --> B[get_fx_session]
    B --> C[session + session_label + sl_multiplier]
    C --> D[Orchestration run_cycle]
    D --> E[Parallel symbol workers]
    E --> F[Prefilter limits by session_label]
    E --> G[Reject stats / baselines by session_label]
    E --> H[technical_agent.analyze]
    H --> I[_session_allowed session_label]
    H --> J[pair_session_allows_trade fx_session]
    H --> K[NY strength gate session_label]
    H --> L[confidence += pair_session_confidence_delta]
    H --> M[SL *= session_sl_multiplier]
    E --> N[Combined score floor by session_label]
    N --> O{Candidate selected?}
    O --> P[Adaptation: session confidence floor]
    P --> Q[Execute + stamp_trade_session]
    Q --> R[on_closed_trade → session_trade_stats]

Configuration knobs

Setting / table Purpose
PAIR_SESSION_RULES Per-pair preferred/avoid sessions
SESSION_SL_MULT Stop distance scaling
SESSION_CONFIDENCE_DELTA Base confidence shift by granular session
ADAPT_SESSION_WINRATE_THRESHOLD When to tighten entry floor
ADAPT_SESSION_CONFIDENCE_BUMP How much to raise floor
ADAPT_SESSION_MIN_TRADES Samples before adaptation applies
execution_tuning.adaptation_session_confidence_bump UI/API override for bump
session_trade_stats Rolling wins/losses per coarse session
candidate_telemetry Reject rates per symbol+session

Reject reason codes (session-related)

Code Layer
reject_session_blocked Coarse off_hours vs strategy
reject_pair_session_blocked Pair avoid list / off_hours
reject_new_york_not_expansion_weak_trend NY coarse + weak trend
entry_blocked_session_confidence_floor Orchestration adaptation
off_hours_blocked Inside pair_session_allows_trade

Design notes and pitfalls

  1. Same cycle, one timestamp — All symbols in a run share the session snapshot taken at cycle start; long cycles do not re-check mid-flight unless revalidation/exit paths run again.

  2. Granular vs coarse — Pair rules and SL/confidence deltas use fx_session; adaptation and SQLite baselines mostly use session_label. When debugging, check both fields on proposals and trade_history.

  3. Overlap is explicit12–16 UTC is always overlap_london_ny, not plain london, so pair rules and multipliers target the highest-liquidity window.

  4. Transitions — Short windows (e.g. Sydney→Tokyo, Tokyo→London handoff) get transition with a negative confidence delta (−0.04) and elevated SL mult (1.45); prefilter may only soft-warn on spread (spread_elevated_soft).

  5. Failsafe — If fx_sessions import fails inside generate_trade_hypothesis, pair gates are skipped and session confidence/SL multipliers default to neutral (0.0 / 1.0).


Key source files

File Responsibility
backend/agents/fx_sessions.py Detection, pair rules, SL/confidence helpers
backend/agents/orchestration_agent/main.py Cycle wiring, workers, APIs, adaptation gate
backend/agents/technical_agent/main.py Gates, confidence, hybrid SL
backend/agents/performance_adaptation.py Session win-rate stats and dynamic confidence floor
backend/agents/excursion_tracking.py Persist session on trades; MAE by session
backend/agents/agents_test/test_fx_sessions.py Unit tests
frontend/components/dashboard/session-badge.tsx UI badge

Related notes: remediation_closed_loop.md, score_and_trade_diagnostics_20260428.md, trade_discipline_rules_20260425.md (Rule 6 — NY filter).