-
Notifications
You must be signed in to change notification settings - Fork 4
Lab Experiment Specification
Related deployment handover notes: Deployment CICD
LOBX supports two authentication modes for experiments:
| Mode | Use case | Link format |
|---|---|---|
| Lab | In-person experiments (e.g., Udine) | london-trader.web.app?LAB=T1_P1 |
| Prolific | Online recruitment | london-trader.web.app?PROLIFIC_PID=xxx&STUDY_ID=yyy&SESSION_ID=zzz&TREATMENT=0 |
To run a lab participant session, use:
https://london-trader.web.app/?LAB=T1_P1
Change the treatment and participant digits as needed, for example T1_P2.
To log out:
https://london-trader.web.app/logout
To reset an experiment and set the market count in a session back to zero, use Reset Experiment in the admin dashboard.
sequenceDiagram
participant E as Experimenter
participant P1 as Participant 1
participant P2 as Participant 2
participant Platform as LOBX Platform
participant Log as Log Files
Note over E: Before the session
E->>Platform: Configure treatments.yaml + admin settings
Note over E: In the lab room
E->>P1: "Open london-trader.web.app?LAB=T1_P1"
E->>P2: "Open london-trader.web.app?LAB=T1_P2"
P1->>Platform: GET ?LAB=T1_P1
Platform->>Platform: Parse T1 = treatment 0, P1 = participant 1
Platform->>P1: Login OK, join waiting room T0_M0
P2->>Platform: GET ?LAB=T1_P2
Platform->>Platform: Parse T1 = treatment 0, P2 = participant 2
Platform->>P2: Login OK, join waiting room T0_M0
Note over Platform: All participants ready
Platform->>Platform: Start market
Platform->>Log: T0_M0_1775749397.log
Platform->>Log: parameter_history.json (participants, treatment, params)
Note over E: Next session or next day
E->>Platform: Reset Experiment
E->>P1: Same link: ?LAB=T1_P1
Platform->>Log: T0_M0_1775753000.log (different timestamp)
The experimenter controls assignment. The platform only parses the link and records data. No token generation, no link management, no state to get out of sync.
?LAB=T{treatment}_P{participant}
-
T1= first treatment,T2= second treatment (1-indexed) -
P1= first participant in that treatment group,P2= second, etc. - Links are reusable entry points, not unique identifiers. The same link can be used across experiment sessions (e.g., morning and afternoon runs).
-
Uniqueness is guaranteed server-side via timestamps in log files and
parameter_history.jsonrecords.
Example: 4 participants across 2 treatments
T1_P1, T1_P2 (treatment 1)
T2_P1, T2_P2 (treatment 2)
Links are constructed directly by the experimenter. No platform-side generation is needed. In a lab setting, the experimenter distributes links to participants in person (e.g., printed on paper, written on a whiteboard, or shared verbally). The format T{n}_P{n} is parsed server-side on login.
?PROLIFIC_PID=xxx&STUDY_ID=yyy&SESSION_ID=zzz&TREATMENT=0
-
PROLIFIC_PIDis assigned by Prolific and serves as the unique participant identifier. -
TREATMENT(optional) assigns a treatment group (0-indexed).
Treatments are defined in back/config/treatments.yaml:
treatments:
- name: "1"
informed_trade_intensity: 0.36
- name: "2"
informed_trade_intensity: 0.69Mapping: T1 in links to index 0 to treatment name "1", T2 to index 1 to treatment name "2".
The T{n} in ?LAB=T{n}_P{m} is the 1-based position of the treatment in treatments.yaml (validated as digits in back/api/lab_auth.py). The name field is only a display label in the admin dashboard and in parameter_history.json. Changing name: "1" to name: "Low" does not change the link; it is still ?LAB=T1_P1.
- Admin dashboard Config tab sets the base parameters applied to every market, such as trading duration, number of markets, and lot sizes.
-
treatments.yamldefines per-treatment overrides. When a participant joins via?LAB=TN_PM, the treatment at positionNis merged on top of the base settings at market start.
The admin dashboard Config tab includes a Treatment Sequence section (front/src/components/market/admin/ConfigTab.vue) that reads and writes back/config/treatments.yaml directly:
- Load Treatments fetches the current file.
- Save Treatments writes edits back.
The YAML file and the UI editor share the same source of truth; either can be used.
T{treatment_group}_M{market_index}_{unix_timestamp}.log
| Component | Description | Example |
|---|---|---|
T{n} |
Treatment group (0-indexed) |
T0 = first treatment |
M{n} |
Market index within sequence |
M0 = first market |
{unix_timestamp} |
Epoch seconds at market start | 1775749397 |
Example: T0_M0_1775749397.log. first treatment, first market, started at 2026-04-09 16:43:17.
SESSION_{timestamp}_{uuid}_PROLIFIC_{pid}_MARKET_{index}.log
Example: SESSION_1775749181_7a4952bc_PROLIFIC_test123_MARKET_0.log
The timestamp in the log file name is the primary uniqueness mechanism. Two experiment runs on different days will produce different timestamps even if the same lab links (T1_P1) are reused. This is intentional. links are stable entry points; logs are unique records.
Located at back/logs/parameters/parameter_history.json. Every market start is recorded:
{
"2026-04-09T16:43:17.084684": {
"source": "market_start",
"market_id": "T0_M0_1775749397",
"session_id": "T0_M0",
"participants": ["LAB_T1_P1"],
"treatment_name": "1",
"treatment_index": 0,
"parameters": { ... }
}
}| Field | Description |
|---|---|
market_id |
Matches log file name (without .log) |
session_id |
Groups markets in the same sequence |
participants |
List of usernames in this market |
treatment_name |
Human-readable treatment name from treatments.yaml
|
treatment_index |
0-based treatment index actually applied to the market |
parameters |
Full parameter snapshot used for this market |
Link (?LAB=T1_P1)
to username: LAB_T1_P1
to log file: T0_M0_{timestamp}.log
to parameter_history.json: market_id, participants, treatment_index, treatment_name
To reconstruct which participant was in which market with which treatment:
# Find all markets for a participant
python3 -c "
import json
with open('back/logs/parameters/parameter_history.json') as f:
data = json.load(f)
for ts, e in data.items():
if e.get('source') == 'market_start' and 'LAB_T1_P1' in e.get('participants', []):
print(f'{e[\"market_id\"]} treatment={e.get(\"treatment_name\")}')
"Admin dashboard: https://london-trader.web.app/admin (log in with the admin password). If you are currently signed in as a participant, go to /logout first.
- Configure treatments in
back/config/treatments.yamlor via the Treatment Sequence section in the admin Config tab - Set base parameters via the admin panel Config tab (trading duration, number of markets, etc.)
- Experimenter assigns links to participants in person. the platform does not manage link distribution
- Participants open
london-trader.web.app?LAB=T{treatment}_P{participant}to consent to onboarding to trading - After experiment: download logs +
parameter_history.jsonvia admin panel
- Same setup as above (steps 1-2)
- Configure study on Prolific with the base URL + query parameters
- Prolific handles participant assignment and distribution
Between experiment runs (e.g., morning vs afternoon session), use the admin panel "Reset Experiment" button. This clears:
- Active markets and waiting rooms
- User session state and market counts
It does not clear:
- Log files (they accumulate with unique timestamps)
-
parameter_history.json(append-only)
The PnL calculation function is in back/utils/logfiles_analysis.py.
For raw log analysis, it computes realized PnL from closed trades as:
sum(prices_sell) - sum(prices_buy)
Then it marks any unmatched position to the latest market with a liquidity adjustment:
-
num_buy == num_sell: fully flat, no extra adjustment -
num_buy > num_sell: net long leftover units, assumes closing them by selling at the last best bid minus 2 -
num_sell > num_buy: net short leftover units, assumes closing them by buying at the last best ask plus 2
The hard-coded 2 can be changed to another value.
For participants with a non-zero goal, the final goal-adjusted metrics also use calculate_vwap_reward in the same file. That calculation uses the participant's completed trades, VWAP, latest mid price, target prices 110/90, and incomplete-goal penalty multipliers 1.5 for buys and 0.5 for sells. In other words, raw PnL and goal-adjusted reward/PnL are related but not identical.
Parameter default values as they appear in the admin dashboard are defined in back/core/data_models.py.
The parameters used in a specific experiment are saved under the experiment logs parameters directory. Historical notes mention data/experiment_logs_20260401/parameters/base_settings.json; confirm the exact run directory for each experiment before analysis.
To change the main instructions on the welcome page, edit front/public/instructions/instructions.md.
Changes to the structure of the instruction pages require frontend route/session updates in files such as:
UserLanding.vuenavigation.jsindex.jsguards.jssession.js
Given the current structure, content for instruction pages other than the main welcome instructions is edited in the relevant *.vue files under front/src/components/pages/.