TimeBase messages overview.

What Are Messages?

TimeBase is designed to work with records of different types, we call them messages. In object-oriented programing languages messages can be seen as classes, each with a specific set of fields.

Messages are arranged chronologically in streams. Each stream has a unique schema, which declares what types of messages (including their hierarchy) a specific stream may store. This differs TimeBase from traditional relational databases with flat structure. Uniqueness of stream schema means, that identical classes defined by different stream schemas are in fact independent classes and are not treated as identical on a TimeBase level. Binding to target languages is made per-field and such identical classes on a TimeBase level are bound to one common class on a target language level. Refer to binding for more information.

There is a “master” parent class called Instrument Message, which defines fields timestamp and symbol, which will be inherited by all child classes. Timestamp defines a message time with up to a nanosecond resolution. Symbol defines a message key (e.g. in trading domain it may be a trading instrument id, in IoT it may be a sensor id). NON INSTANTIABLE (equivalent to abstract in other terminologies) classes serve to define properties inherited by child classes and cannot be instantiated.

On the illustration below, financial market data for the specific trading instrument (symbol) is represented in a form of Trade and Best Bid/Offer (BBO) messages. Each message class has an individual set of fields and two attributes inherited from the parent class: a symbol (trading instrument identifier) and a timestamp.

Binding

In target languages we can consume data two ways bound and unbound. Unbound data we receive as a collection of fields. Bound data is received already decoded into target language objects. Binding is performed per field in a class between TimeBase and a target language classes. Hierarchy of classes in a target language can, but does not have to, follow the original TimeBase hierarchy, though in many cases it may be the most convenient way. Refer to Encoding for more information.

In the following code examples you can see how original TimeBase classes can be represented in other languages.

public class BBO extends InstrumentMessage {
    public double askPrice;
    public double askSize;
    public double bidPrice;
    public double bidSize;
}
public class BBO : InstrumentMessage
{
    public double AskPrice { get; set; }
    public double AskSize { get; set; }
    public double BidPrice { get; set; }
    public double BidSize { get; set; }
}
class BBO(InstrumentMessage):
    def __init__(self, timestamp, symbol, askPrice, askSize, bidPrice, bidSize):
        InstrumentMessage.__init__(self, timestamp, symbol)
        self.askPrice = askPrice
        self.askSize = askSize
        self.bidPrice = bidPrice
        self.bidSize = bidSize

Smart Messages and Message Interfaces

Because binging is made per field in a class, a minimal requirement for binding (fully satisfied by basic POJO/POJO classes) is to have a set of fields and properties. In real life you may need to perform repetitive routine tasks such as produce properties, per-filed comparison, toString and other. In TimeBase we consider a best practice to generate and use more advanced classes (we call them “smart”) that already include methods to perform such tasks. Classes in TimeBase libraries follow this principle.

Properties

Use getKey and setKey methods to provide properties.

public class MyBBO extends InstrumentMessage implements MyBBOInterface {
    private double askPrice;
    private double askSize;
    private double bidPrice;
    private double bidSize;
  @SchemaElement
  @SchemaType(isNullable = false)
  public int getAskPrice() {​​​​​​​
    return key;
  }​​​​​​​
  public void setAskPrice(int value) {​​​​​​​
    this.key = value;
  }​​​​​​​
}

equals and hashCode

Use equals() for per-field comparisons. Use hashCode() to calculate integer hash codes for objects.

  public boolean equals(Object obj) 
  public int hashCode() 

toString

Use toString method to generate a valid JSON object.

public class MyBBO extends InstrumentMessage implements MyBBOInterface {
    private double askPrice;
    private double askSize;
    private double bidPrice;
    private double bidSize;
    @Override
    public StringBuilder toString(StringBuilder sb) {
        sb.append("{ \"$type\":  \"MyBBO\"");
        sb.append(", \"askPrice\": ").append(askPrice);
        sb.append(", \"askSize\": ").append(askSize);
        sb.append(", \"bidPrice\": ").append(bidPrice);
        sb.append(", \"bidSize\": ").append(bidSize);
        return sb;
    }
}

Primitive Data Types Nullability

When working with primitive data types, TimeBase represents NULL with a pre-defined constant value (taken from the field’s data type values range) to optimize memory consumption.

You can use has method in Java/.NET classes to check for such constant values in a combination with nullify method that assigns corresponding NULL constants to nullable fields’.

public class MyBBO extends InstrumentMessage implements MyBBOInterface {
    private double askPrice;
    private double askSize;
    private double bidPrice;
    private double bidSize;

  public boolean hasAskPrice() {​​​​​​​
    return value != null;
  }
  
  public void nullifyAskPrice() {​​​​​​​
    this.value = null;
  }​​​​​​​
  public CustomAttribute nullify() {​​​​​​​
    nullifyKey();
    nullifyAskPrice();
    return this;
  }​​​​​​​
}

Mutable/Immutable Interfaces

Frequently, when writing code, you may need to write it against interfaces and not specific classes or you may want to restrict access to read-only and read-write objects in your code. Interfaces are supplied with TimeBase libraries and can be used instead of classes. For example, mutable and immutable interfaces can be used to restrict access to read-only classes.

  • Immutable interfaces are identified like this: classInfo

public class MyBBO extends InstrumentMessage implements MyBBOInfo {
    private double askPrice;
    private double askSize;
    private double bidPrice;
    private double bidSize;
  @SchemaElement
  @SchemaType(isNullable = false)
  public int getKey() {​​​​​​​
    return key;
  }​​​​​​​
}

  • Mutable interfaces are identified like this: classInterface

public class MyBBO extends InstrumentMessage implements MyBBOInterface {
    private double askPrice;
    private double askSize;
    private double bidPrice;
    private double bidSize;
  @SchemaElement
  @SchemaType(isNullable = false)
  public int getKey() {​​​​​​​
    return key;
  }​​​​​​​
  public void setKey(int value) {​​​​​​​
    this.key = value;
  }​​​​​​​
}

Introspection and Code Generation

Working with TimeBase, it is a routine task to create TimeBase classes from a target language schema and the other way around. Doing it manually may be error-prone and time consuming. TimeBase comes with tools that allow automating this task and thus save you time and efforts.

  • Libraries for Java and .NET offer a special class called Introspector. You can use this class to create TimeBase schemas based on the current runtime classes.
  • In case you already have a TimeBase schema and you need to create messages in a target language, you can use a tool called Solution Generator to do that.