Forex Positions
Retail forex positioning by pair (share of traders long versus short), collected from several broker sentiment feeds.
forex_positions — the dataset name to pass to the Obscura API.
What one row means
One retail-forex positioning snapshot for one pair from one broker/vendor source at one point in time: percent long/short by account count, account counts and USD-notional volumes on each side, and (where exposed) a reference price. Unified across sources (Dukascopy SWFX, AMarkets, OANDA position book, Myfxbook, IG); history accretes forward only (no source offers a deep archive).
One row per (source, pair, event_time).
Point-in-time availability
Every Obscura dataset carries available_date: the calendar day the publisher made the row
available, day-of, with no session rounding. It is the one column a backtest filters on, and it means the
same thing on every dataset in the catalog.
For forex_positions: snapshot: available_date = captured_date, a STORED GENERATED column (migration 000205). WHY THAT IS THE DAY IT BECAME AVAILABLE: not one of the five positioning sources offers an archive endpoint, so a past book cannot be fetched and this history exists only because a 30-minute poll captured it — the day it was captured is the day it was obtainable, and there is no vendor dissemination event for it to lag. captured_date replaced a generated `(event_time AT TIME ZONE 'UTC')::date`: substantively identical (event_time is the clamped observation instant) but a poll instant cannot NAME itself the capture day. Existing rows were backfilled from exactly that expression, so no stored value moved.
Refresh cadence
Obscura refreshes forex_positions every 30 minutes — the most frequent scheduled job that re-collects or re-exports it. This is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when a row became public is recorded per row in available_date.
Schema — 13 columns
The full public column list for forex_positions, with the meaning of every field. The same
schema is served unauthenticated at https://api.obscura.trade/v1/catalog/forex_positions.
| Column | Type | Description |
|---|---|---|
| source | text · not null | Which collector/vendor produced this snapshot: dukascopy | amarkets | oanda | myfxbook | ig. Part of the primary key. |
| pair | text · not null | Normalized instrument symbol (EURUSD, XAUUSD, BTCUSD, ...); separators stripped, upper-cased. Not a resolved equity ticker. Part of the primary key. |
| event_time | timestamp with time zone · not null | Snapshot instant: source-reported timestamp where available (dukascopy epoch-ms, oanda RFC3339, amarkets widget Timestamp), else Utc::now() at scrape (myfxbook, ig). CLAMPED TO THE OBSERVATION INSTANT: the amarkets widget stamps its broker-local wall clock and labels it UTC, running ~2h ahead, so an unclamped evening snapshot took tomorrow's available_date; a source timestamp later than the moment we read the response is impossible and is pulled back to it. Part of the primary key; available_date derives from it. |
| captured_date | date · not null | The UTC calendar day the collector POLLED this source's positioning book — the date-part of the clamped observation instant on a live run, the bronze object's capture day on a replay. available_date is generated directly from it. |
| available_date | date | PUBLIC-availability date = captured_date. DB-generated (STORED), read-only; the point-in-time column to filter/join on. No source offers a positioning archive, so the day we polled is the only day this book was obtainable. |
| long_pct | double precision | Percent (0-100) of the source's retail book positioned long, by account count; complement of short_pct. |
| short_pct | double precision | Percent (0-100) positioned short, by account count; for amarkets computed as 100 - long_pct. |
| long_volume | double precision | Notional volume on the long side in the source's own units (amarkets: USD). Not comparable across sources. |
| short_volume | double precision | Notional volume on the short side in the source's own units (amarkets: USD). |
| long_positions | bigint | Count of open long positions/accounts at this source and snapshot (amarkets: Buyers). |
| short_positions | bigint | Count of open short positions/accounts at this source and snapshot (amarkets: Sellers). |
| price | double precision | Reference/mid price at snapshot where the source exposes one (e.g. oanda order-book bucket price); NULL for sources like amarkets that report positioning only. |
| raw | jsonb | Source's raw per-pair payload verbatim, for fields not mapped onto typed columns (e.g. amarkets SentimentByVolumes). |
Access forex_positions
Two delivery paths, one identifier. Both require an Obscura account and an active subscription; the catalog entry and the schema above are public.
import obscura
client = obscura.Client("obs_live_…")
df = client.query(
dataset="forex_positions",
symbols=["NVDA", "AAPL"],
start="2024-01-01",
)
Create a free account Browse all 95 datasets
Frequently asked questions
What is in the forex_positions dataset?
Retail forex positioning by pair (share of traders long versus short), collected from several broker sentiment feeds. One retail-forex positioning snapshot for one pair from one broker/vendor source at one point in time: percent long/short by account count, account counts and USD-notional volumes on each side, and (where exposed) a reference price. Unified across sources (Dukascopy SWFX, AMarkets, OANDA position book, Myfxbook, IG); history accretes forward only (no source offers a deep archive).
How do I avoid look-ahead bias with forex_positions?
Filter on forex_positions.available_date, the day the publisher made the row public. For this dataset that date is derived as follows — snapshot: available_date = captured_date, a STORED GENERATED column (migration 000205). WHY THAT IS THE DAY IT BECAME AVAILABLE: not one of the five positioning sources offers an archive endpoint, so a past book cannot be fetched and this history exists only because a 30-minute poll captured it — the day it was captured is the day it was obtainable, and there is no vendor dissemination event for it to lag. captured_date replaced a generated `(event_time AT TIME ZONE 'UTC')::date`: substantively identical (event_time is the clamped observation instant) but a poll instant cannot NAME itself the capture day. Existing rows were backfilled from exactly that expression, so no stored value moved. A query of the form WHERE available_date <= '<as-of date>' never sees a row before it existed.
In what formats can I get forex_positions?
As a Parquet bulk export (POST https://api.obscura.trade/v1/download) or as JSON from the typed query API (POST https://api.obscura.trade/v1/query), both with dataset="forex_positions". The column schema is public at https://api.obscura.trade/v1/catalog/forex_positions.
Can I filter forex_positions by company or symbol?
Yes. forex_positions carries pair, the column the API's symbols filter resolves against.
How often is forex_positions updated?
Obscura refreshes forex_positions on a every 30 minutes schedule — that is the most frequent scheduled job that re-collects or re-exports the table. It is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when the publisher makes a row available is described by the availability rule above, and is recorded per row in available_date.