FlowscopeDocs

User-defined types

Give related values a name and behaviour with records, methods and typed collections.

User-defined types give a name and behaviour to values that travel together: a price zone, a signal, a risk plan or the running state of a pattern.

Declare a type

type lists every field with its type:

Flowscope Script
type Zone {
  low: float
  high: float
  touches: int
}

Create a value by giving every field, one per line or on one line: Zone { low: 1.0, high: 2.0, touches: 0 }. Types are nominal: two types with the same fields are still different types.

Methods

Methods keep operations next to the data. The current value is self:

Flowscope Script
script "Zone touches"

data chart = subscribe(data.ohlcv)

type Zone {
  low: float
  high: float
  touches: int

  fn width() -> float {
    return self.high - self.low
  }

  fn contains(price: float) -> bool {
    return price >= self.low && price <= self.high
  }

  fn recordTouch() {
    self.touches += 1
  }
}

state zone = Zone { low: 0.0, high: 0.0, touches: 0 }
state boxes = entities.boxPool(max: 1)

on chart.close {
  let hi = ta.highest(chart.high, 50)
  let lo = ta.lowest(chart.low, 50)
  if zone.width() == 0.0 && hi != null && lo != null {
    // A zone at the bottom fifth of the 50-bar range.
    zone = Zone { low: lo, high: lo + (hi - lo) * 0.2, touches: 0 }
  }
  if zone.contains(chart.low ?? 0.0) {
    zone.recordTouch()
  }
}

on chart.update {
  let t = chart.time
  if chart.isLast && t != null && zone.width() > 0.0 {
    let label = str.format("{0} touches", zone.touches)
    boxes.get("zone").set(t - time.bars(50), zone.high, t, zone.low, color: #26A69A22, extend: extend.right, text: label)
  }
}

Read-only methods return information; a method that assigns to self updates the state or local it was called on.

Typed collections

Types make collections easy to read:

Flowscope Script
script "Recent signals"

data chart = subscribe(data.ohlcv)

type Signal {
  price: float
  strength: float
  bullish: bool
}

state recent = Signal[](50)

pane countPane = pane(title: "Bullish of the last 50", height: 0.2)
plot bullish = plot.histogram(title: "Bullish", color: color.green, on: countPane)

on chart.close {
  if recent.len >= recent.capacity {
    recent.shift()
  }
  let open = chart.open ?? 0.0
  let close = chart.close ?? 0.0
  recent.push(Signal { price: close, strength: math.abs(close - open), bullish: close >= open })
  let ups = recent.filter(|s| s.bullish)
  bullish.plot(float(ups.len))
}

Copying

Assignment and function arguments copy a record; changing the copy leaves the original alone. Methods are the exception: self is the value the method was called on. Iterating an array also gives copies, so write a changed element back by index:

Flowscope Script
script "Write back"

data chart = subscribe(data.ohlcv)

type Level {
  price: float
  hits: int
}

state levels = Level[](8)

on chart.close {
  let close = chart.close ?? 0.0
  if levels.len == 0 {
    levels.push(Level { price: close, hits: 0 })
  }
  for i, level in levels {
    if math.abs(close - level.price) < context.tickSize * 10.0 {
      levels[i] = Level { price: level.price, hits: level.hits + 1 }
    }
  }
}

When to introduce a type

Create a type when it improves the model of the script: a result with several related parts, state with its own update rules, items stored in a collection, or logic that reads better as methods such as contains or reset. For a one-off pair of values, two let bindings are simpler.