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?

1

Universal Semantics

candle frees you from time-based assumptions. Works for Time bars, Tick bars, Volume bars, or any interval type you need.

2

Zero Ambiguity

Protocol Buffers enforce strict types at compile time. No more float(data['c']) vs data.close confusion.

3

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

The Old Way (Adapter Hell)
# 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.
The OpenCandle Way
# 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

1

Interface over storage

Any storage format (array, object, protobuf) can implement transparently. Your code works with all of them.

2

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.

3

Zero-cost abstraction

Getters enable JIT inlining with zero runtime overhead. Compile-time polymorphism without performance penalty.

4

Non-breaking extensibility

.meta() pattern for future additions without version bumps. Crypto funding rates, forex spreads, ML signals - all supported.

5

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

Matchstick (Array)

Array-backed implementation optimized for LLM token efficiency

48 bytes/candle

Matchstick (Object)

Object-backed implementation with descriptive property names

120 bytes/candle

Building an implementation? Submit a PR to be listed here

Framework Integrations

Tools and platforms that accept OpenCandle format

Matchstick Stream

Real-time tick aggregation service

Aggregation engine

Matchstick CLI

Strategy backtesting and analysis tools

Command-line tools

Matchstick Data

Data processing and analytics components

Data pipelines

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

1

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;
  };
}
2

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];
      }
    }
  };
}
3

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

1

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
  };
}
2

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);
}
3

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.