Real-time technical indicator computation service for Chinese A-share market data. Reads 1-minute K-line and L2 tick snapshots from Lindorm TSDB, computes 37 indicators, and writes results back to Lindorm.
Built in Rust. Compiles to a single 3.9 MB statically-linked binary. Memory footprint under 3 MB at runtime.
| Category | Indicators |
|---|---|
| Trend | EMA(5, 10, 20), MACD(12, 26, 9) |
| Momentum | RSI(14), KDJ(9, 3, 3) |
| Volatility | ATR(14), Bollinger Bands(20, 2), Spread |
| Volume | VWAP, Net Flow, Volume Ratio(5) |
| Returns | Close Return, Log Return |
| Category | Indicators |
|---|---|
| Spread | Bid-Ask Spread, Weighted Spread (5-level) |
| Imbalance | Order Imbalance (L1), Depth Imbalance (L1-L5) |
| Flow | Cumulative Buy/Sell Volume & Amount (Lee-Ready classification) |
| Activity | Avg Tick Size, Large Order Count |
Lindorm TSDB Lindorm TSDB
┌─────────────────┐ poll (HTTP) ┌──────────────────┐
│ ads_stock_ │ ──────────────► │ │
│ kline_1min │ │ KlineEngine │──► kline_feature
│ │ 60s interval │ (23 indicators)│
└─────────────────┘ │ │
│ IndicatorService│
┌─────────────────┐ poll (HTTP) │ │
│ ods_tl_*_market_ │ ──────────────► │ TickEngine │──► tick_feature
│ conditions │ 5s interval │ (14 indicators)│
└─────────────────┘ └──────────────────┘
The computation engine uses Rust's type system to enforce correctness at compile time:
- Const-generic
RingBuffer<T, N>— fixed-size circular buffer withExactSizeIterator. The capacityNis a compile-time constant (e.g.,RingBuffer<f64, 9>for KDJ's 9-period window). - Sealed
SmoothingKerneltrait — two kernel types (Exponential: α = 2/(P+1),Wilder: α = 1/P) that cannot be implemented outside this crate. - Type-level
Ema<K, PERIOD>— the smoothing kernel and period are encoded as type parameters, resolved at compile time.ExpEma<5>andWilderEma<14>are distinct types — you cannot accidentally mix them.
pub type ExpEma<const P: usize> = Ema<Exponential, P>; // α = 2/(P+1)
pub type WilderEma<const P: usize> = Ema<Wilder, P>; // α = 1/P
struct CloseEmas {
e5: ExpEma<5>,
e10: ExpEma<10>,
e20: ExpEma<20>,
e12: ExpEma<12>, // MACD fast
e26: ExpEma<26>, // MACD slow
}UpsertRowtrait — static-dispatch serialization for batch UPSERT. BothKlineFeatureandTickFeatureimplement it;LindormClient::batch_upsert<R: UpsertRow>()is generic over the row type with zero runtime overhead.
cargo build --release
# Binary at target/release/rt-indicatordocker build -f Dockerfile.build -t rt-indicator-build .
docker create --name rt-build rt-indicator-build
docker cp rt-build:/rt-indicator ./rt-indicator-linux
docker rm rt-buildProduces a statically-linked musl binary (~3.9 MB, stripped).
cp config/indicator.yaml.example config/indicator.yaml
# Edit with your Lindorm credentialsKey fields:
| Field | Description |
|---|---|
lindorm.endpoint |
Lindorm TSDB HTTP SQL endpoint |
lindorm.read_database |
Source database for market data |
lindorm.write_database |
Target database for computed indicators |
polling.kline_interval_sec |
K-line polling interval (default: 60s) |
polling.tick_interval_sec |
Tick polling interval (default: 5s) |
tick_tables |
List of Lindorm tables containing L2 snapshots |
market_hours |
Trading session windows as [start_h, start_m, end_h, end_m] |
# With default config path (config/indicator.yaml)
./rt-indicator
# With explicit config
./rt-indicator /path/to/indicator.yaml
# Adjust log level
RUST_LOG=rt_indicator=debug ./rt-indicator# Copy binary and config
mkdir -p /opt/rt-indicator/config
cp rt-indicator /opt/rt-indicator/
cp config/indicator.yaml /opt/rt-indicator/config/
# Install systemd unit
cp deploy/rt-indicator.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now rt-indicator
# Check status
systemctl status rt-indicator
journalctl -u rt-indicator -fsrc/
├── main.rs # Entry point, tokio runtime
├── config.rs # YAML config with serde defaults
├── error.rs # Error types (thiserror)
├── lindorm.rs # HTTP client, UpsertRow trait, DDL
├── service.rs # Async polling loop, market hours
└── engine/
├── mod.rs # RingBuffer, SmoothingKernel, Ema
├── kline.rs # 23 K-line indicators
└── tick.rs # 14 tick microstructure indicators
config/
└── indicator.yaml.example
deploy/
└── rt-indicator.service # systemd unit
Dockerfile.build # Multi-stage Alpine build for Linux
| Field | Type | Maps to |
|---|---|---|
stock_full_code |
VARCHAR | symbol |
window_end_time |
TIMESTAMP | bar_time |
open_price / highest_price / lowest_price / close_price |
DOUBLE | OHLC |
turnover_volume / turnover_value |
DOUBLE | volume / amount |
| Field | Type | Maps to |
|---|---|---|
SecurityID |
VARCHAR | security_id |
UpdateTime |
TIMESTAMP | event_time |
LastPrice |
DOUBLE | last_price |
BidPrice1..5 / AskPrice1..5 |
DOUBLE | 5-level quotes |
BidVolume1..5 / AskVolume1..5 |
BIGINT | 5-level depth |
20 DOUBLE columns: vwap, net_flow, spread, rsi, macd, macd_signal, ema_5/10/20, close_return, log_return, atr, kdj_k/d/j, bb_mid/upper/lower, volume_ratio.
10 columns: bid_ask_spread, weighted_spread, order_imbalance, depth_imbalance, cum_buy/sell_volume/amount, avg_tick_size, large_order_count.