Skip to content
DiscordTwitter

Data Types

Data types define what kind of information flows through your edges. Think of them like different pipes—you can’t pump oil through a water pipe!

When you connect nodes, GrailHub checks if the data types match:

  • Number output → Number input: Works perfectly!
  • Number output → Text input: Won’t connect!
  • Text output → Dynamic input: Works (dynamic accepts anything)

This prevents silly mistakes like sending “hello” to a node expecting a price.

Any numerical value—integers or decimals.

Examples:

  • 150.75 (stock price)
  • 1000 (quantity)
  • 14.5 (RSI indicator value)
  • -5.2 (profit/loss)

Used for: Prices, quantities, indicators, calculations

Any text value wrapped in quotes.

Examples:

  • "AAPL" (stock symbol)
  • "BUY" (order side)
  • "Price is too high!" (alert message)

Used for: Symbols, messages, commands, labels

Just two values: true or false.

Examples:

  • true (condition met!)
  • false (not yet…)

Used for: Conditions, filters, decision points

The flexible type—accepts anything! Use when you’re not sure what type you’ll get.

Examples:

  • Can receive a number: 150.75
  • Can receive text: "AAPL"
  • Can receive complex data: Ticker object

Used for: Debug nodes, flexible processors, generic handlers

These are specialized data structures for trading workflows.

Real-time market price data.

What’s inside:

{
  "symbol": "C_BTC_USDT",
  "price": 95432.50,
  "timestamp": 1703001234567,
  "datasource": "binance"
}

Used for: Live price monitoring, strategy triggers

Produced by: Market Ticker components
Consumed by: Indicators, conditions, calculators

OHLCV candlestick data—the classic trading chart bar.

What’s inside:

{
  "ticker": "AAPL",
  "open": 150.00,
  "high": 152.50,
  "low": 149.75,
  "close": 151.25,
  "volume": 1500000,
  "timestamp": 1703001234567
}

Used for: Chart patterns, technical indicators, backtesting

Produced by: Historical data fetchers, aggregators
Consumed by: Pattern detectors (Doji, Hammer), RSI, moving averages

Time series data—arrays of values with timestamps.

What’s inside:

{
  "values": [150.0, 151.2, 149.8, 152.1],
  "timestamps": [1703001000000, 1703002000000, 1703003000000, 1703004000000]
}

Used for: Multi-period calculations, trend analysis

Produced by: Indicators (RSI, MA), aggregators
Consumed by: Chart displays, complex calculators

Upper and lower boundaries—like Bollinger Bands.

What’s inside:

{
  "upper": {
    "values": [152.0, 153.0, 154.0],
    "timestamps": [1703001000000, 1703002000000, 1703003000000]
  },
  "lower": {
    "values": [148.0, 147.5, 147.0],
    "timestamps": [1703001000000, 1703002000000, 1703003000000]
  }
}

Used for: Bollinger Bands, support/resistance zones

Produced by: Band indicators
Consumed by: Strategy conditions, chart displays

Result of a trading order execution.

What’s inside:

{
  "order_id": 123456789,
  "symbol": "BTCUSDT",
  "side": "BUY",
  "type": "MARKET",
  "quantity": 0.05,
  "price": 95432.50,
  "status": "FILLED",
  "timestamp": 1703001234567
}

Used for: Order tracking, PnL calculation, portfolio management

Produced by: Order execution components
Consumed by: PnL calculators, order loggers, alerts

Market depth—all buy and sell orders at different price levels.

What’s inside:

{
  "symbol": "BTCUSDT",
  "bids": [
    {"price": 95400.0, "quantity": 1.5},
    {"price": 95390.0, "quantity": 2.3}
  ],
  "asks": [
    {"price": 95410.0, "quantity": 0.8},
    {"price": 95420.0, "quantity": 1.2}
  ],
  "timestamp": 1703001234567
}

Used for: Market depth analysis, liquidity checks, spread calculations

Produced by: OrderBook stream components
Consumed by: Spread analyzers, liquidity filters

Generic message from any chat platform (Telegram, Discord, Slack).

What’s inside:

{
  "platform": "telegram",
  "type": "command",
  "content": "/buy",
  "args": "AAPL 100",
  "channel_id": "123456",
  "user_id": "789012",
  "metadata": {}
}

Used for: Bot commands, chat integrations, user interactions

Produced by: Telegram/Discord listeners
Consumed by: Command parsers, chat responders

Time period specification for historical data.

What’s inside:

{
  "unit": "minute",
  "amount": 5
}

Valid units: minute, hour, day, week, month, year

Examples:

  • 5 minutes: {unit: "minute", amount: 5}
  • 1 hour: {unit: "hour", amount: 1}
  • 1 day: {unit: "day", amount: 1}

Used for: Historical data fetching, indicator calculations

Configured in: Pattern detectors, RSI components, data fetchers

Hover over any input or output dot—a tooltip shows the type!

  • 🔵 Blue dot: Number
  • 🟢 Green dot: String
  • 🟣 Purple dot: Boolean
  • ⚪ White dot: Dynamic (accepts any)
  • 🟠 Orange dot: Complex type (hover to see which)
  1. Exact Match: Number → Number ✅
  2. Dynamic Target: Anything → Dynamic ✅
  3. Type Mismatch: Number → String ❌
  4. Transform Available: Different types can connect if you add a transform expression

Can’t connect two different types? Use edge transformations!

Example: Convert number to string

  • Output: 150.75 (Number)
  • Transform: String(value)
  • Input receives: "150.75" (String)

Example: Extract price from Ticker

  • Output: Ticker object
  • Transform: value.price
  • Input receives: 95432.50 (Number)

Example: Format text

  • Output: 150.75 (Number)
  • Transform: "Price: $" + String(value)
  • Input receives: "Price: $150.75" (String)

Problem: You’re trying to connect Number to String input.

Solution:

  1. Check if you’re connecting the right outputs/inputs
  2. Use a transform expression to convert types
  3. Find a node that accepts Dynamic type

Problem: Your transform expression expects wrong type.

Solution:

  • If input is Number: Use math operators (value * 2)
  • If input is String: Use string functions (value.toUpperCase())
  • If input is Object: Use dot notation (value.price)

Problem: Component received "150" but needs 150.

Solution: Add transform: Number(value) or parseFloat(value)

Add a Printer node with Dynamic input—it accepts and logs anything!

Click any component to see:

  • What types its inputs accept
  • What types its outputs produce
  • Example data structures

Build flows left-to-right:

  1. Start with data source (Ticker, Bar)
  2. Process with type-compatible nodes
  3. End with action (Order, Alert)

In flow settings, rename inputs/outputs to show their purpose:

  • ticker_data instead of input1
  • rsi_value instead of output1

Q: Can I convert any type to any other type?
A: You can try with transforms, but some conversions don’t make sense (like converting a Ticker object to a Boolean).

Q: What happens if I send the wrong type?
A: The node receiving it will turn red and show an error. Check the logs!

Q: Why can’t I see my custom type in the list?
A: Complex types like Ticker, Order, etc. are internal—you don’t create them directly. Components produce them.

Q: Is Dynamic type slower?
A: Slightly, but the difference is negligible for most flows.

Q: Can I create my own complex types?
A: Not in the UI—custom types are defined in component code. But you can use JavaScript objects with Dynamic type!