Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollup candlesticks via @CandlestickColumn #20

Merged
merged 40 commits into from
Feb 12, 2025
Merged

Conversation

danstarns
Copy link
Collaborator

@danstarns danstarns commented Feb 10, 2025

Related:

This PR adds the @CandlestickColumn and @TimeColum to better support rolled-up candlesticks.

Setting Up a Stock Price Entity

Create a stock price entity that will serve as our base for candlestick and rollup operations:

import { Entity, Column } from 'typeorm';
import { Hypertable, TimeColumn } from '@timescaledb/typeorm';

@Entity('stock_prices')
@Hypertable({
  compression: {
    compress: true,
    compress_orderby: 'timestamp',
    compress_segmentby: 'symbol',
    policy: {
      schedule_interval: '7 days',
    },
  },
})
export class StockPrice {
  @PrimaryColumn({ type: 'varchar' })
  symbol!: string;

  @TimeColumn()
  timestamp!: Date;

  @Column({ type: 'decimal', precision: 10, scale: 2 })
  price!: number;

  @Column({ type: 'decimal', precision: 10, scale: 2 })
  volume!: number;
}

Generating 1-Minute Candlesticks

Create a continuous aggregate for 1-minute candlesticks:

import { ContinuousAggregate, BucketColumn, CandlestickColumn } from '@timescaledb/typeorm';
import { StockPrice } from './StockPrice';
import { Candlestick } from '@timescaledb/schemas';

@ContinuousAggregate(StockPrice, {
  name: 'stock_candlesticks_1m',
  bucket_interval: '1 minute',
  refresh_policy: {
    start_offset: '1 day',
    end_offset: '1 minute',
    schedule_interval: '1 minute',
  },
})
export class StockPrice1M {
  @BucketColumn({
    source_column: 'timestamp',
  })
  bucket!: Date;

  @PrimaryColumn()
  symbol!: string;

  @CandlestickColumn({
    time_column: 'timestamp',
    price_column: 'price',
    volume_column: 'volume',
  })
  candlestick!: Candlestick;
}

Creating 1-Hour Rollups

Create a rollup that aggregates the 1-minute candlesticks into 1-hour candlesticks:

import { Rollup, BucketColumn, CandlestickColumn } from '@timescaledb/typeorm';
import { StockPrice1M } from './StockPrice1M';
import { Candlestick } from '@timescaledb/schemas';

@Rollup(StockPrice1M, {
  name: 'stock_candlesticks_1h',
  bucket_interval: '1 hour',
  refresh_policy: {
    start_offset: '7 days',
    end_offset: '1 hour',
    schedule_interval: '1 hour',
  },
})
export class StockPrice1H {
  @BucketColumn({
    source_column: 'bucket',
  })
  bucket!: Date;

  @PrimaryColumn()
  symbol!: string;

  @CandlestickColumn({
    source_column: 'candlestick',
  })
  candlestick!: Candlestick;
}

Querying Candlesticks

1-Minute Candlesticks

import { AppDataSource } from './data-source';
import { StockPrice1M } from './models/StockPrice1M';

async function get1MinuteCandlesticks() {
  const repository = AppDataSource.getRepository(StockPrice1M);

  const candlesticks = await repository
    .createQueryBuilder()
    .where('bucket >= :start', { start: new Date('2025-01-01') })
    .andWhere('bucket < :end', { end: new Date('2025-01-02') })
    .andWhere('symbol = :symbol', { symbol: 'AAPL' })
    .orderBy('bucket', 'ASC')
    .getMany();

  console.log(JSON.stringify(candlesticks, null, 2));
  // Example output:
  // [
  //   {
  //     "bucket": "2025-01-01T00:00:00.000Z",
  //     "symbol": "AAPL",
  //     "candlestick": {
  //       "open": 150.25,
  //       "high": 152.30,
  //       "low": 149.80,
  //       "close": 151.45,
  //       "volume": 1250000,
  //       "open_time": "2025-01-01T00:00:15.000Z",
  //       "close_time": "2025-01-01T00:59:45.000Z"
  //     }
  //   },
  //   ...
  // ]
}

1-Hour Rollup Candlesticks

import { AppDataSource } from './data-source';
import { StockPrice1H } from './models/StockPrice1H';

async function get1HourCandlesticks() {
  const repository = AppDataSource.getRepository(StockPrice1H);

  const candlesticks = await repository
    .createQueryBuilder()
    .where('bucket >= :start', { start: new Date('2025-01-01') })
    .andWhere('bucket < :end', { end: new Date('2025-02-01') })
    .andWhere('symbol = :symbol', { symbol: 'AAPL' })
    .orderBy('bucket', 'ASC')
    .getMany();

  console.log(JSON.stringify(candlesticks, null, 2));
  // Example output:
  // [
  //   {
  //     "bucket": "2025-01-01T00:00:00.000Z",
  //     "symbol": "AAPL",
  //     "candlestick": {
  //       "open": 150.25,
  //       "high": 155.60,
  //       "low": 149.50,
  //       "close": 153.20,
  //       "volume": 8750000,
  //       "open_time": "2025-01-01T00:00:15.000Z",
  //       "close_time": "2025-01-01T00:59:45.000Z"
  //     }
  //   },
  //   ...
  // ]
}

@danstarns danstarns force-pushed the candlestick-rollup branch 2 times, most recently from 801e637 to 0df1532 Compare February 10, 2025 23:07
@danstarns danstarns changed the base branch from feat/rollups to main February 11, 2025 01:33
@danstarns danstarns merged commit d2929cf into main Feb 12, 2025
2 checks passed
@jonatas jonatas deleted the candlestick-rollup branch February 17, 2025 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants