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:
- Classifies the current window from UTC time.
- Gates or shapes trade ideas by session (hard blocks, confidence bumps, wider stops).
- Segments learning and telemetry by session so baselines and adaptation are not one-size-fits-all.
- 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,transition→asialondon,overlap_london_ny→londonnew_york,late_new_york→new_yorkoff_hours→off_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):
overlap_london_ny— if12 <= h < 16transition— if7 <= h < 9(Tokyo + London), or6 <= h < 7, or21 <= h < 24london— London active, NY not activetokyo— Tokyo active, London not activesydney— Sydney active, Tokyo not activenew_york—16 <= h < 21, or NY flag set otherwiseoff_hours— fallback when nothing else fits
Then a special case:
late_new_york— if19 <= h < 21and primary would benew_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 wellavoid— 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
sessionis in the pair’savoidlist → 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):
- Base delta from
SESSION_CONFIDENCE_DELTA(e.g. +0.04 London, −0.12 off_hours) - +0.05 if session is in pair
preferred - −0.15 if session is in pair
avoid(normally blocked before this runs) - Extra −0.08 for
late_new_yorkonAUDUSD,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):
-
Telemetry baselines —
_candidate_reject_stats(symbol, session_label)and_candidate_market_baselines(symbol, session_label)query SQLite keyed by coarsesession_label(with symbol+session and session-only fallbacks). -
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
-
Technical call —
_technical.analyze(..., session_label=..., fx_session=...) -
Combined score floor —
_adaptive_combined_score_floor(..., session_label=...):off_hours: floor +0.03asia: floor +0.01
-
Precheck payload — includes
"session": session_labelfor 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_deltafrompair_session_confidence_delta(symbol, fx_session)added to the confidence sum (with pattern win rate, MTF, RSI, etc.)._session_sl_multfromsession_sl_multiplier(fx_session)passed intocalculate_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
-
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.
-
Granular vs coarse — Pair rules and SL/confidence deltas use
fx_session; adaptation and SQLite baselines mostly usesession_label. When debugging, check both fields on proposals andtrade_history. -
Overlap is explicit —
12–16 UTCis alwaysoverlap_london_ny, not plainlondon, so pair rules and multipliers target the highest-liquidity window. -
Transitions — Short windows (e.g. Sydney→Tokyo, Tokyo→London handoff) get
transitionwith a negative confidence delta (−0.04) and elevated SL mult (1.45); prefilter may only soft-warn on spread (spread_elevated_soft). -
Failsafe — If
fx_sessionsimport fails insidegenerate_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).