SET50 Alternative Data Platform

Tourism-first vertical slice for a deterministic SET50 alternative-data research system. The app can run against a clearly-labelled fixture or fetch a point-in-time Tourism Indicators vintage from the Bank of Thailand report backed by the Ministry of Tourism and Sports.

Current scope:

fixture or BOT source observation
  → Tourism Pulse surprise
  → versioned exposure score
  → ranked target weights
  → English dashboard
  → internal paper ledger

No external webhook receiver and no live MT5 execution are enabled.

Run the backend

python -m venv .venv
.venv/bin/pip install -r backend/requirements.txt
PAPER_WRITE_TOKEN=local-paper-token TOURISM_SOURCE=fixture PYTHONPATH=backend .venv/bin/python backend/run.py

Health check:

curl http://127.0.0.1:5000/api/v1/health

Run the dashboard

In a second terminal:

cd frontend
npm install
npm run dev -- --host 127.0.0.1

Open http://127.0.0.1:5173.

The frontend reads the live API through Vite's /api proxy. Paper writes require the operator to unlock an HttpOnly browser session using the backend PAPER_WRITE_TOKEN; the token is never embedded in the frontend bundle. The paper-entry action records an assumed fill in the in-memory paper ledger only.

For HTTPS/non-local deployment, set PAPER_COOKIE_SECURE=1. The M0 session store is intentionally in-memory and single-process; use a shared session store before running multiple workers or replicas.

Run with the real BOT Tourism source

Use the BOT-backed adapter when network access is available:

PAPER_WRITE_TOKEN=local-paper-token TOURISM_SOURCE=bot PYTHONPATH=backend .venv/bin/python backend/run.py

At startup the adapter performs a read-only GET/POST against the BOT Tourism Indicators report, parses the available monthly history, computes the latest year-over-year arrival observation against a trailing 12-point baseline, and stores the raw HTML plus normalized snapshot under backend/data/ (ignored by git). The dashboard labels provisional BOT data as provisional, not high.

Data-health and replay endpoints:

GET /api/v1/data-health
GET /api/v1/vintages?as_of=<ISO-8601 timestamp>
GET /api/v1/replay/tourism?vintage_id=<vintage_id>
GET /api/v1/backtest/tourism?min_events=12

Source: https://app.bot.or.th/BTWS_STAT/statistics/ReportPage.aspx?reportID=875&language=eng

Collect a vintage manually

The collector is intentionally one-shot and idempotent. Run it after a source update; it preserves raw bytes, normalized snapshots, and a manifest under backend/data/:

PYTHONPATH=backend .venv/bin/python backend/scripts/collect_tourism_vintage.py --root backend/data

Repeated collection of the same source hash keeps one vintage_id and increments seen_count without changing first_seen_at. A new hash for the same publication timestamp is recorded as a separate revised vintage.

Tests and build

PYTHONPATH=backend .venv/bin/python -m unittest discover -s backend/tests -v
cd frontend && npm run build

Current M2 boundary

  • English UI and analysis vocabulary
  • Research mode and paper mode only
  • Tourism Pulse fixture adapter and BOT Tourism Indicators adapter
  • Data lineage: source, publication time, retrieval time, vintage
  • Raw response hash and normalized snapshot persistence
  • Immutable vintage manifest with first-seen/revision metadata
  • Read-only data-health, vintage timeline and vintage replay endpoints
  • Deterministic event-study engine with benchmark and cost inputs
  • Backtest readiness gate that blocks without independent vintages and prices
  • Deterministic surprise × exposure × confidence score
  • Paper ledger endpoint
  • No LLM call yet; the deterministic result is the source of truth
  • No webhook receiver yet
  • No MT5 bridge yet

The next implementation step is the event-study/backtest layer using only vintages whose published_at is known at each test date.

The event-study gate is now exposed through /api/v1/backtest/tourism. It returns HTTP 409 with status=blocked when the independent-vintage minimum is not met, and it explicitly reports that a point-in-time daily price series is still required. The pure engine accepts events, daily prices, benchmark prices, event windows, and cost assumptions; it does not fetch or invent market prices.

Description
No description provided
Readme 2.6 MiB
Languages
Python 90.1%
Vue 6.4%
CSS 3.1%
Dockerfile 0.2%
Shell 0.1%