|
<< Click to Display Table of Contents >> Timeseries intro |
![]() ![]()
|
One of the fundamental building blocks of Gekko is the timeseries variable. Together with other Gekko variable types, timeseries live in a Gekko databank (which can be read/written as a .gbk file).
The Series class is a class designed for storing and retrieving timeseries (vectors/arrays of consecutive time data) in a fast and reliable way. Timeseries can be of different frequencies (for instance annual, quarterly, monthly, daily, etc.).
Figure 1. Databanks, Databank and Series design

A Series object is stored in a Databank object, and more than one databank can be open at the same time. So there is a Databanks container storing the open databanks by name (for instance 'Work'). A Databank contains Series by name, for instance fY!q for the variable fY with quarterly frequency (a quarterly variable has a !q appended to its real name, to distinguish it from for instance the monthly variable fY!m and so on).
In order to benefit from time series observations being consecutive in nature, the underlying storage container for the individual observations in a particular timeseries is a 0-based double[] array (that is, a double precision array of floating point numbers). Now the question is how to map from for instance 2001q1 (first quarter of 2001) to a particular position in this array? To do this, an 'anchor' is used (gray dot in the figure), that is, a particular index in the dataArray being mapped to a corresponding time period. In the figure, the array position 3 is the anchor, corresponding to 2000q1 (anchorSuper = 2000, anchorSub = 1). So dataArray[3] corresponds to fY[2000q1] which is a missing value (NaN). If instead we would like to query the period 1999q3, it is pretty simple to calculate that the offset relative to the anchor is -2 (quarters), so 1999q3 corresponds to the array slot 1 = 3 - 2, or the value 312.45. Since all the items in the figure are objects, memory allocation is quite flexible. If a queried date corresponds to being out of bounds of the dataArray, for instance querying 1999q1 or 2001q1 in the example, a NaN will be returned. If the user tries to put data into the dataArray at an out-of-bounds array position, the dataArray will be dynamically resized (50% larger than before), and the data is copied from the old to the new and larger dataArray (anchor pointers etc. are adjusted correspondingly). In that way, the user does not have to worry about limits on number of observations, since the number of observations for a particular timeseries is only limited by available RAM.
Missing values are double.NaN internally, and the dataArray is initialized with NaN's when a new Series object is created (the dataArray will typically start out with 200 observations). Gekko keeps track of the start and end period of the particular timeseries (in the example above, the data period corresponds to position 1-4 in the array, or 1999q3-2000q2). There is a method for compacting data, that is, trimming each dataArray to be of exactly the necessary size to contain the data (to save RAM or disk space). In the example, the array could be shaved off to length 4 instead of 7.
Individual observations are queried and altered by means of a GekkoTime object (which is in reality a struct for speed). The GekkoTime object contains information on frequency, super period (year), and subperiod (for instance quarter). The syntax for getting fY in 1999q3 from the 'Work' databank is is something very similar to the following:
GekkoTime t = new GekkoTime(EFreq.Q, 2020, 1); |
First, a GekkoTime object (struct) t is created, corresponding to the date 2020q1. Next, the databank Work is fetched, using GetDatabank(), and inside this databank, GetIVariable() is used to fetch fY!q. The method GetData() fetches the observation corresponding to 2020q1.
When getting or setting more than one observation at the time, it is often more efficient to operate with chunks (arrays) of data, rather than individual observations. For instance, you may want to set five consecutive values at a time via an array, or you may want to copy all observations in a given sample to a new array. For this purpose, there are methods like SetDataSequence() or GetDataSequence().
There is a Clone() method to clone a Series object, for instance for fast copying of a Databank object in RAM. There are also methods that return the start and end period of the Series: GetPeriodFirst(), and GetPeriodLast().
In addition to data values, Series objects can also store metadata such as a label, source, unit, etc. The name of the timeseries is also stored (name) in the Series object. This name is the name the timeseries is stored under in a Databank object, So in the example, the name field would be fY!q, and this would also be the key that the series is stored under in the Databank object (the databank object used a C# Dictionary to store variables).
There is also an 'undated' frequency. This frequency is convenient for consecutive data starting with observation 0 or 1 and running without end. It could for instance be hourly data, where 1 is the first hour of the first day, 24 is the last hour of the first day, 25 is the first hour of the second day, 48 is the last hour of the second day, etc.
Using a GekkoTime object (struct) for storing the time period has some conveniences, for instance it is easy to offset a GekkoTime t with n periods, as this is simply done by means of the method t.Add(n). If t is 2020q1 and n is -2, the result is a new GekkoTime corresponding to 2019q3. In the same manner, looping over for instance quarters becomes easy too, since it is possible to simply use this:
foreach (GekkoTime t in new GekkoTimeIterator(t1, t2)) |
which will loop through these periods (start and end included) and regardless of frequency (t1 and t2 are also GekkoTime objects).
Regarding non-timeseries data, there are for instance value, date and string objects (ScalarVal, ScalarDate, ScalarString).