oliver-morrow/trading-sentiment

Python

0

11 commits

updated Aug 7, 2026

See the code

README

Cyclicus

Cyclicus is a point-in-time market-sentiment research workbench. It asks whether financial sentiment known at a declared historical decision time adds out-of-sample information beyond market and technical features.

The project reports historical research evidence. It does not provide investment advice, live recommendations, or claims of investment performance.

Current implementation

This repository is a greenfield revival of a small 2024 prototype. The legacy source files remain in src/ as historical evidence while the tested application lives under src/cyclicus/.

The executable revival includes:

  • deterministic synthetic market and headline observations;
  • explicit publication, availability, decision, and label timestamps;
  • a lazy, optional FinBERT adapter plus a deterministic lexicon fixture scorer;
  • a point-in-time feature/label dataset with enforced leakage invariants;
  • majority, technical-only, sentiment-only, combined logistic, and seeded Random Forest comparisons over identical chronological folds;
  • immutable JSON evidence bundles;
  • a read-only FastAPI adapter for those bundles;
  • a static Vue/Tailwind evidence explorer with point-in-time forecast replay;
  • root-domain and /cyclicus/ production builds; and
  • container and CI definitions for repeatable delivery.

The checked-in flagship run is deliberately synthetic. Its purpose is to make the research and presentation paths reproducible without credentials or licensed data. Live and scheduled provider ingestion is intentionally outside the scope of the public demo.

The optional BERT path is intentionally not used to fabricate the checked-in fixture. Install it with uv sync --extra nlp, then construct FinBertSentimentScorer.from_pretrained(). Production research should pass a pinned model revision and record that revision in its evidence manifest.

Local research demo

Prerequisites: Python 3.12 or 3.13 and uv.

uv sync --dev
uv run pytest
uv run cyclicus build-demo \
  --output artifacts/demo/flagship.json \
  --web-copy web/public/data/flagship.json
uv run cyclicus verify-demo
uv run cyclicus serve

The FastAPI evidence document is available at http://127.0.0.1:8000/api/v1/showcase and interactive documentation at http://127.0.0.1:8000/docs.

Run the dashboard in a second terminal:

cd web
corepack enable
pnpm install --frozen-lockfile
pnpm dev

The local dashboard is available at http://127.0.0.1:5173. It uses the checked-in fixture by default. To exercise the FastAPI boundary instead, start the API and run VITE_DATA_MODE=backend pnpm dev.

Presentation modes

Both modes consume the same versioned evidence schema:

  1. Static showcase — deploy the built web/dist/ directory at a root domain or a subpath such as /cyclicus/. It loads a precomputed evidence bundle and needs no backend or credentials.
  2. Interactive deployment — point the frontend at the FastAPI adapter, preferably on a subdomain such as cyclicus.example.com.

Build either static shape with:

pnpm --dir web build
pnpm --dir web build:subpath

See docs/deployment.md for the hosting contract, environment variables, container workflow, and same-origin reverse-proxy layout.

Verification

make check
make reproducibility

make check runs the Python tests, lint, strict type checks, frontend tests, and both production builds. make reproducibility builds the fixture evidence twice and proves byte-for-byte determinism within the current pinned environment, then validates the checked-in public bundle. Seeded scikit-learn outputs may differ across operating systems, so the project does not claim cross-platform bit identity.

Research contract

The shared vocabulary and non-negotiable invariants live in CONTEXT.md. The preregistered fixture experiment is documented in docs/research-protocol.md, and public claims are tracked in docs/claims-ledger.md.

The load-bearing rule is:

max_source_available_at <= decision_time < label_end_at

No model or presentation adapter is allowed to bypass the dataset module that enforces it.

oliver-morrow/trading-sentiment

Python

0

11 commits

updated Aug 7, 2026

See the code

README

Cyclicus

Cyclicus is a point-in-time market-sentiment research workbench. It asks whether financial sentiment known at a declared historical decision time adds out-of-sample information beyond market and technical features.

The project reports historical research evidence. It does not provide investment advice, live recommendations, or claims of investment performance.

Current implementation

This repository is a greenfield revival of a small 2024 prototype. The legacy source files remain in src/ as historical evidence while the tested application lives under src/cyclicus/.

The executable revival includes:

  • deterministic synthetic market and headline observations;
  • explicit publication, availability, decision, and label timestamps;
  • a lazy, optional FinBERT adapter plus a deterministic lexicon fixture scorer;
  • a point-in-time feature/label dataset with enforced leakage invariants;
  • majority, technical-only, sentiment-only, combined logistic, and seeded Random Forest comparisons over identical chronological folds;
  • immutable JSON evidence bundles;
  • a read-only FastAPI adapter for those bundles;
  • a static Vue/Tailwind evidence explorer with point-in-time forecast replay;
  • root-domain and /cyclicus/ production builds; and
  • container and CI definitions for repeatable delivery.

The checked-in flagship run is deliberately synthetic. Its purpose is to make the research and presentation paths reproducible without credentials or licensed data. Live and scheduled provider ingestion is intentionally outside the scope of the public demo.

The optional BERT path is intentionally not used to fabricate the checked-in fixture. Install it with uv sync --extra nlp, then construct FinBertSentimentScorer.from_pretrained(). Production research should pass a pinned model revision and record that revision in its evidence manifest.

Local research demo

Prerequisites: Python 3.12 or 3.13 and uv.

uv sync --dev
uv run pytest
uv run cyclicus build-demo \
  --output artifacts/demo/flagship.json \
  --web-copy web/public/data/flagship.json
uv run cyclicus verify-demo
uv run cyclicus serve

The FastAPI evidence document is available at http://127.0.0.1:8000/api/v1/showcase and interactive documentation at http://127.0.0.1:8000/docs.

Run the dashboard in a second terminal:

cd web
corepack enable
pnpm install --frozen-lockfile
pnpm dev

The local dashboard is available at http://127.0.0.1:5173. It uses the checked-in fixture by default. To exercise the FastAPI boundary instead, start the API and run VITE_DATA_MODE=backend pnpm dev.

Presentation modes

Both modes consume the same versioned evidence schema:

  1. Static showcase — deploy the built web/dist/ directory at a root domain or a subpath such as /cyclicus/. It loads a precomputed evidence bundle and needs no backend or credentials.
  2. Interactive deployment — point the frontend at the FastAPI adapter, preferably on a subdomain such as cyclicus.example.com.

Build either static shape with:

pnpm --dir web build
pnpm --dir web build:subpath

See docs/deployment.md for the hosting contract, environment variables, container workflow, and same-origin reverse-proxy layout.

Verification

make check
make reproducibility

make check runs the Python tests, lint, strict type checks, frontend tests, and both production builds. make reproducibility builds the fixture evidence twice and proves byte-for-byte determinism within the current pinned environment, then validates the checked-in public bundle. Seeded scikit-learn outputs may differ across operating systems, so the project does not claim cross-platform bit identity.

Research contract

The shared vocabulary and non-negotiable invariants live in CONTEXT.md. The preregistered fixture experiment is documented in docs/research-protocol.md, and public claims are tracked in docs/claims-ledger.md.

The load-bearing rule is:

max_source_available_at <= decision_time < label_end_at

No model or presentation adapter is allowed to bypass the dataset module that enforces it.