MMTMMT Docs

User-defined types

Group related values into reusable records with fields and methods.

User-defined types let a script give a name and behaviour to a group of related values. They are useful when several values always travel together: a price zone, a signal result, a risk plan, or the running state of a pattern.

Declare a type

Use type with a type annotation for every field:

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

Create a value by supplying every field inside a handler or function:

fn makeZone(low: float, high: float) -> Zone {
  return Zone {
    low: low
    high: high
    touches: 0
  }
}

Types are nominal. Two types with the same fields are still different types, which prevents accidentally passing one kind of record where another was expected.

Add methods

Methods keep operations beside the data they work on. The current value is available as self:

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

Read-only methods return information without changing the value:

let inside = activeZone.contains(chart.close ?? 0.0)
let width = activeZone.width()

A method that assigns to self updates a state or local value in place:

state activeZone = Zone { low: 0.0, high: 0.0, touches: 0 }

on chart.close {
  if activeZone.contains(chart.close ?? 0.0) {
    activeZone.recordTouch()
  }
}

Store typed values in collections

Types make arrays and rolling buffers much easier to understand:

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

state recent = Signal[](50)

on chart.close {
  if recent.len >= recent.capacity {
    recent.shift()
  }

  recent.push(Signal {
    price: chart.close ?? 0.0
    strength: math.abs((chart.close ?? 0.0) - (chart.open ?? 0.0))
    bullish: (chart.close ?? 0.0) >= (chart.open ?? 0.0)
  })
}

Understand copying

An ordinary assignment or function argument copies a user-defined value. Changing the copy does not change the original. Methods are the exception: self refers to the value on which the method was called, so a mutating method can update a state or local variable.

Array iteration also gives you a value copy. To change an array element, write the updated value back by index:

state zones = Zone[](32)

for i, zone in zones {
  if zone.contains(price) {
    zones[i] = Zone {
      low: zone.low
      high: zone.high
      touches: zone.touches + 1
    }
  }
}

This explicit write-back makes it clear which stored element changes.

When to introduce a type

Create a type when it improves the model of the script, not merely to shorten a few lines. Strong candidates include:

  • A result with several related outputs.
  • Persistent state with its own update rules.
  • Items stored in an array or map.
  • Logic that becomes clearer as methods such as contains, reset, or size.

For a one-off pair of local values, plain let bindings are usually simpler.

On this page