Introducing OpenCandle
The open schema for modern algorithmic trading
Write Once. Trade Everywhere.
Every exchange speaks a different language. Binance uses data['c'], Kraken uses data['c'][0], Coinbase uses data['close']. OpenCandle provides a strictly typed Protocol Buffer schema that normalizes market data into a single, immutable contract.
Why OpenCandle?
Universal Semantics
candle frees you from time-based assumptions. Works for Time bars, Tick bars, Volume bars, or any interval type you need.
Zero Ambiguity
Protocol Buffers enforce strict types at compile time. No more float(data['c']) vs data.close confusion.
Schema-First
Decouple your trading logic from data source details. Write your strategy once, connect to any exchange or data provider.
Escape Adapter Hell
See how OpenCandle eliminates exchange-specific code
# Every exchange, a new if-statement
if exchange == 'binance':
price = float(data['c'])
elif exchange == 'kraken':
price = float(data['c'][0])
elif exchange == 'coinbase':
price = float(data['close'])
elif exchange == 'ftx':
price = float(data['last'])
# ... and on and on ...
# Fragile. Error-prone. Unmaintainable.# One schema. Every exchange.
from opencandle import Candle
for candle in feed:
price = candle['~bar'].close
volume = candle['~bar'].volume
timestamp = candle['~bar'].timestamp
# Clean. Type-safe. Universal.The Interface
Any object with a ~bar property implementing this interface is a valid OpenCandle, regardless of how it stores data internally.
interface OpenCandleV1 {
readonly '~bar': {
readonly version: 1;
readonly vendor: string;
// OHLCV Accessors
get timestamp(): number; // Unix ms (bar start)
get open(): number;
get high(): number;
get low(): number;
get close(): number;
get volume(): number;
// Extensible metadata
meta<T = unknown>(key: string): T | undefined;
};
}Protocol Buffer schema available: opencandle.proto
Design Philosophy
Interface over storage
Any storage format (array, object, protobuf) can implement transparently. Your code works with all of them.
LLM token efficiency
60% reduction vs. object-based formats. Array storage: 48 bytes/candle vs object: 120 bytes/candle. Real cost savings for AI trading agents.
Zero-cost abstraction
Getters enable JIT inlining with zero runtime overhead. Compile-time polymorphism without performance penalty.
Non-breaking extensibility
.meta() pattern for future additions without version bumps. Crypto funding rates, forex spreads, ML signals - all supported.
Built on proven patterns
Follows the hidden property pattern pioneered by Standard Schema.
Implementations & Integrations
OpenCandle is a young specification, designed for universal adoption. Matchstick provides the reference implementation.
Storage Implementations
Libraries that provide OpenCandle-compliant candle objects
| Name | Description | Details | Link |
|---|---|---|---|
| Matchstick (Array) | Array-backed implementation optimized for LLM token efficiency | 48 bytes/candle | View |
| Matchstick (Object) | Object-backed implementation with descriptive property names | 120 bytes/candle | View |
Building an implementation? Submit a PR to be listed here
Framework Integrations
Tools and platforms that accept OpenCandle format
| Name | Description | Details | Link |
|---|---|---|---|
| Matchstick Stream | Real-time tick aggregation service | Aggregation engine | View |
| Matchstick CLI | Strategy backtesting and analysis tools | Command-line tools | View |
| Matchstick Data | Data processing and analytics components | Data pipelines | View |
Building an integration? Submit a PR to be listed here
Quickstart Guides
Get started in minutes
For Library Authors
Creating an OpenCandle implementation in your library
Copy the interface definition
OpenCandle is just a specification - no installation required. Copy the interface to your project:
// openCandle.ts
export interface OpenCandleV1 {
readonly '~bar': {
readonly version: 1;
readonly vendor: string;
get timestamp(): number;
get open(): number;
get high(): number;
get low(): number;
get close(): number;
get volume(): number;
meta<T = unknown>(key: string): T | undefined;
};
}Implement for your storage format
Wrap your existing data structure with the OpenCandle interface:
export function createMyCandle(data: MyCandleData): OpenCandleV1 {
return {
'~bar': {
version: 1,
vendor: 'MyLibrary',
get timestamp() { return data.time },
get open() { return data.o },
get high() { return data.h },
get low() { return data.l },
get close() { return data.c },
get volume() { return data.v },
meta(key) {
// Optional: expose your metadata
return data.metadata?.[key];
}
}
};
}Add type guard (optional)
Provide a type guard for consumers to validate OpenCandle compliance:
export function isOpenCandle(value: unknown): value is OpenCandleV1 {
return (
typeof value === 'object' &&
value !== null &&
'~bar' in value &&
typeof (value as any)['~bar'] === 'object'
);
}Questions?
Join the discussion on GitHub or check out the full specification for advanced usage.
For Tool Integrators
Accepting OpenCandle in your trading tools
Accept OpenCandle in your API
Use the OpenCandle interface in your function signatures:
import { OpenCandleV1 } from './openCandle';
export function analyzeCandle(candle: OpenCandleV1) {
const price = candle['~bar'].close;
const volume = candle['~bar'].volume;
// Your analysis logic
return {
price,
volume,
timestamp: candle['~bar'].timestamp
};
}Handle legacy formats with upcast
Support existing code by auto-converting unknown formats:
// Upcast utility (handles arrays, objects, etc.)
function upcast(data: unknown): OpenCandleV1 | null {
// Array format: [timestamp, open, high, low, close, volume]
if (Array.isArray(data) && data.length === 6) {
return {
'~bar': {
version: 1,
vendor: 'array-upcast',
get timestamp() { return data[0] },
get open() { return data[1] },
get high() { return data[2] },
get low() { return data[3] },
get close() { return data[4] },
get volume() { return data[5] },
meta() { return undefined }
}
};
}
// Object format: {timestamp, open, high, low, close, volume}
if (typeof data === 'object' && data !== null && 'close' in data) {
return {
'~bar': {
version: 1,
vendor: 'object-upcast',
get timestamp() { return (data as any).timestamp },
get open() { return (data as any).open },
get high() { return (data as any).high },
get low() { return (data as any).low },
get close() { return (data as any).close },
get volume() { return (data as any).volume },
meta() { return undefined }
}
};
}
return null;
}
// Use in your API
export function analyzeAnyCandle(data: unknown) {
const candle = upcast(data);
if (!candle) throw new Error('Invalid candle format');
return analyzeCandle(candle);
}Document OpenCandle support
Let users know your tool accepts OpenCandle format:
/**
* Analyzes OHLCV candle data.
*
* @param candle - OpenCandle V1 compliant candle object
* @returns Analysis results
*
* @example
* import { createCandle } from '@mylib/opencandle';
*
* const candle = createCandle(timestamp, o, h, l, c, v);
* const result = analyzeCandle(candle);
*/
export function analyzeCandle(candle: OpenCandleV1) {
// ...
}Questions?
Join the discussion on GitHub or check out the full specification for advanced usage.
Who Benefits?
For Traders & Analysts
Write strategies once, run on any data source. No more vendor lock-in or format conversion headaches.
For Library Authors
One integration, work with entire ecosystem. Consumers automatically get compatibility with all vendors.
For LLM Applications
2.5x more data in same context window. Array-backed storage saves 60% tokens, reducing AI inference costs dramatically.
For Performance
Zero-cost abstraction with JIT inlining. Getter functions compile to direct memory access - no runtime overhead.
Frequently Asked Questions
Why the ~bar prefix?
Follows Standard Schema's hidden property pattern (~standard), which signals special behavior and prevents collision with existing properties in legacy code.
Why "candle" instead of "bar"?
"Candle" is a universal term that works for Time bars, Tick bars, Volume bars, and any other interval type. It frees you from time-based assumptions while remaining familiar to traders worldwide.
Is this compatible with my existing code?
Yes! Use upcast() for automatic conversion from arrays, CCXT objects, Binance arrays, or generic JSON formats. Your legacy code continues to work.
What about Protocol Buffers?
OpenCandle includes a .proto schema for teams who want compile-time type safety and efficient binary serialization.
Can I add custom fields?
Yes, via .meta<T>(key) pattern. Add crypto funding rates, forex spreads, ML signals, or debugging info without breaking existing consumers.
Is this just for Matchstick?
No, it's an open specification designed for universal adoption. Matchstick provides the reference implementation, but anyone can implement OpenCandle in their libraries.
OpenCandle follows the interface-over-storage pattern pioneered by Standard Schema.
Designed and maintained by Matchstick
© 2026 Matchstick. Licensed under MIT.