Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

orb-rust

A small, stateful intraday opening-range breakout (ORB) engine for the Indian market (IST), built natively in Rust on top of a TimescaleDB tick store.

The project is split in two:

  • rust_candle_fetcher (library) — owns the database. It exposes a clean PivotEngine interface plus the public types (Candle, AppError, and a re-exported BigDecimal). All sqlx / Postgres mechanics are hidden inside; the binary never touches a database driver.
  • orb-rust (strategy binary, main.rs) — holds all the trading logic and per-symbol day state. It polls the library on 5-minute boundaries and fires order webhooks.

No thiserror / anyhow; errors use only std traits. No futures::join_all; concurrent queries use a native tokio::task::JoinSet.


Strategy

Step Rule
Timeframe 5-minute candles
Opening range Accumulate the first three candles — buckets 09:15, 09:20, 09:25 (covering 09:15–09:30). Day high = max of their highs, day low = min of their lows
Watch window From the 09:30 candle onward, evaluate every 5-minute candle
Long candle high OR close > day high
Short candle low OR close < day low (long is checked first)
Orders One order per symbol per day, sent as an HTTP POST with an empty body to the symbol's webhook URL
Last signal No new signals after 14:00; the process shuts down completely at 14:00

Because a candle is only readable once it closes, the timeline runs one interval behind the bucket label:

09:20 poll → bucket 09:15   (opening #1)
09:25 poll → bucket 09:20   (opening #2)
09:30 poll → bucket 09:25   (opening #3)  → opening range set here
09:35 poll → bucket 09:30   (first breakout candle evaluated)
   ...
13:55 poll → bucket 13:50   (last breakout candle evaluated)
14:00      → shutdown

The last candle that can produce a signal is the 13:50 bucket (read at 13:55).


Requirements

  • Rust (stable) + Cargo
  • A reachable TimescaleDB / PostgreSQL instance with a ticks hypertable (symbol, ltt, ltp, cum_volume)
  • Network access to the Fyers webhook URLs

Configuration

The engine reads two environment variables and one JSON file.

Variable Required Default Purpose
DATABASE_URL yes Postgres/TimescaleDB connection string
SYMS_FILE no syms.json Path to the symbol → webhook map

The symbol list is taken from the keys of syms.json — it is the single source of truth for both which symbols to watch and where to send their orders.

syms.json

{
  "NSE:BAJAJ-AUTO-EQ": { "short": "https://.../bajaj-short", "long": "https://.../bajaj-long" },
  "NSE:BHEL-EQ":       { "short": "https://.../bhel-short",  "long": "https://.../bhel-long"  },
  "NSE:BSE-EQ":        { "short": "https://.../bse-short",   "long": "https://.../bse-long"   }
}

Cargo dependencies

The library keeps its existing sqlx (with the bigdecimal and chrono features), tokio, tracing, and chrono deps, and must re-export the decimal type so the binary can name it without depending on sqlx:

// in src/lib.rs
pub use sqlx::types::BigDecimal;

The binary needs:

[dependencies]
rust-candle-fetcher = { path = "../rust-candle-fetcher" }   # adjust to your layout

chrono              = "0.4"
serde               = { version = "1", features = ["derive"] }
serde_json          = "1"
reqwest             = { version = "0.12", default-features = false, features = ["rustls-tls"] }
tracing             = "0.1"
tracing-subscriber  = "0.3"
tokio               = { version = "1", features = ["full"] }

The binary does not depend on sqlx or bigdecimal directly. It uses BigDecimal purely through the library's re-export (rust_candle_fetcher::BigDecimal) with Clone and PartialOrd only — no extra crate needed.


Build & run

# via cargo
export DATABASE_URL="postgres://user:pass@host:5432/db"
export SYMS_FILE="syms.json"     # optional
cargo run --release

A Makefile is provided to route configuration (DATABASE_URL, symbol list):

make run

How it works

  1. Boundary sync. The loop sleeps until each 5-minute wall-clock boundary, then polls PivotEngine::fetch_latest_candles, which runs one concurrent query per symbol via JoinSet and returns the latest closed candle each.
  2. Dedup. Each symbol remembers its last processed bucket_ist, so a bucket is never acted on twice.
  3. Opening range. Candles whose bucket falls in [09:15, 09:30) are accumulated. The range is frozen and logged the moment the third one arrives (the 09:30 poll).
  4. Breakout. From the first ≥ 09:30 candle on, each candle is tested against the frozen range. On a break, the matching webhook is POSTed.
  5. Order bookkeeping. A symbol is marked done only after a 2xx webhook response, so a transient network failure simply retries on the next candle. Once done, no further orders fire for that symbol that day.
  6. Shutdown. At 14:00 IST the loop exits cleanly.

Logging

All timestamps are emitted in IST via a custom IstTimer:

2026-06-30 09:30:00 IST  INFO  [NSE:BHEL-EQ] opening range set H=415.6500 L=411.9500 (from 3 candles)
2026-06-30 09:40:00 IST  INFO  [NSE:BHEL-EQ] LONG breakout at 09:35:00 (close 416.2000)
2026-06-30 09:40:00 IST  INFO  Webhook OK -> https://.../bhel-long (200 OK)

Notes & limitations

  • Stateful accumulation. The opening range is built in memory as candles arrive. If the process starts after 09:30 it will have missed part of the opening window and that day's range may be incomplete (a symbol with no collected opening candles is skipped for the day, with a warning).
  • Latest closed candle only. The library returns one candle per symbol (LIMIT 1). The binary keys all logic off bucket_ist, so timing is driven by bucket timestamps rather than poll order.
  • Prices as exact decimals. Highs/lows/closes are compared as BigDecimal (exact decimal), avoiding floating-point rounding on breakout boundaries.
  • No weekend/holiday calendar. The engine assumes it is launched on a trading day; it does not consult an exchange holiday list.

About

ORB strategy in Stocks

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages