Time-Series Storage Choices for Backtesting and Data Alignment
Summary
The document compares ways to represent time series in a backtesting engine, with attention to memory use, access speed, iteration, aggregation, and alignment across instruments. It contrasts time-keyed structures with integer-indexed arrays and describes alternatives such as paired value and timestamp arrays, arrays of records, and established time-series libraries. One answer recommends sorted timestamps with binary search; others favor contiguous storage for sequential operations or suggest choosing a database layout around expected queries and updates.
The practical concern is synchronization: when one instrument lacks a bar, matching series by position can accidentally compare different times. Proposed approaches include retaining timestamps, filling missing daily observations with nulls so row offsets map to dates, and merging streams by time. The discussion offers implementation opinions rather than benchmark results or a universal best choice. Its recommendations depend on data frequency, missing observations, language, access patterns, and whether convenient indexing or low-overhead sequential processing matters most.
Key ideas
- Contiguous arrays can make sequential time-series access more efficient than hash-based lookup.
- Timestamps can keep comparisons across instruments aligned when observations are missing.
- Sorted timestamp arrays support binary search while keeping values in a parallel array.
- Filling missing daily rows with null values can allow dates to be derived from row offsets.
- Storage and indexing choices should reflect the system’s query and update patterns.
Tags
Full text
# What is the best data structure/implementation for representing a time series? # What is the best data structure/implementation for representing a time series? I was wondering what is best practice for representing elements in a time series, especially with large amounts of data. The focus/context is in a back testing engine and comparing multiple series. It seems there are two options: 1) Using an integer index, or 2) Using a date-based index At the moment I am using dates, but this impacts on performance & memory usage in that I am using a hash table rather than an array, and it requires some overhead in iteration (either forward or backwards) as I have to determine the next/previous valid date before I can access it. However, it does let me aggregate data on the fly (e.g. building the ohlc for the previous week when looking at daily bars) and most importantly for me allows me to compare different series with certainty I am looking at the same date/time. If I am looking at an equity issue relative to a broader index, and say the broader index is missing a few bars for whatever reason, using an integer indexed array would mean I'm looking at future data for the broad index vs present data for the given security. I don't see how you could handle these situations unless you're using date/times. Using integer indexes would be a lot easier code wise, so I was just wondering what others are doing or if there is best practice with this. ## Answer by wburzyns (score 27, accepted) https://quant.stackexchange.com/a/619 Representing time series (esp. tick data) using elaborate data structures may be not the best idea. You may want to try to use two arrays of the same length to store your time series. The first array stores values (e.g. price) and the second array stores time. Note that the second series is monotonically increasing (or at least non-decreasing), i.e. it's sorted. This property enables you to search it using the binary search algorithm. Once you get an index of a time of interest in the second array you also have the index of the relevant entry in the first array. If you wrap the two arrays and the search algorithm e.g. in a class you will have the whole implementation complexity hidden behind a simple interface. ## Answer by Michael WS (score 11) https://quant.stackexchange.com/a/1995 I really wouldn't implement time series on my own unless I had a good reason to. AQR uses pandas, almost everyone in R using zoo or xts. I never like multiple parallel arrays, if it breaks everything is broken, plus it gets uglier as you increment data. If you are doing something in C++, why not have an array of structs for each object where you have quote,time and all other data you need? ## Answer by Logistic (score 10) https://quant.stackexchange.com/a/2213 If you are serious about performance and flexibility, you have to take a look at data.table package in R. Here is the crantastic review. It is lighting fast! I think this is the best package addressing performance and memory issues. ## Answer by glyphard (score 8) https://quant.stackexchange.com/a/617 It's usually more efficient to have timeseries objects located sequentially in contiguous memory. A hashtable doesn't provide this. As good as it is, from a complexity standpoint, it's not faster than a fixed array when accessing items in a [i+1] or [i-lag] kind of operation that is typical in timeseries code. (For the most part you can estimate the array size needed to do timeseries before you start your operate, so array resizing operations can be optimized out.) ## Answer by Ram Ahluwalia (score 7) https://quant.stackexchange.com/a/1737 There is a third option -- use the ZOO packages for time-series objects. You will have the benefit of index and time-series indexes. Per Ulrich's comment -- XTS is the way to go! ## Answer by alpha (score 3) https://quant.stackexchange.com/a/3172 Best is to learn from other software packages that have been in business for a while. The common data structure is to store it in rows; each row contains: Date (as long), OHLC, Volume. This way you will avoid human errors; or software bugs. The worst nightmare is to have look ahead bias in your data. the following software packages uses the above: - R - Tradestation - MetaTrader - MetaStocks ... I personally store the data as above; in a binary file (not text file). reading from binary files is much faster; as the size of the file is much smaller compared to CSV. ## Answer by Erwin Mayer (score 1) https://quant.stackexchange.com/a/9971 At least for daily data: if you can afford to replace holes in your data by lines with N/A values (e.g. null), a possible approach is to simply store the date of the first row as a comment in your file, and then deterministically compute the date of any other row using its offset to the first row. A key benefit is being able to use such a file as a time-indexed memory-mapped file (no need to load into memory), without involving a hashmap. Works like a charm (I implemented it in C#). For a great, R-compatible (among others), memory-mapped file implementation, I can highly recommend TeaFiles (free an open source). ## Answer by rdalmeida (score 0) https://quant.stackexchange.com/a/16022 If your language is Java, CoralStore can persist time series for a fraction of the price of KDB. It provides very fast write access (~ 70 nanos / msg) so you can dump huge amounts of data to disk. When it comes to read access, you can fetch messages by sequence and it uses paging/swapping technology for very fast read access. It also allows simultaneous read/write access and comes with an asynchronous implementation for extra low variance. Disclaimer: I am one of the developers of CoralStore. ## Answer by DangerMouse (score 0) https://quant.stackexchange.com/a/73289 Far too late to the show but I'd suggest first collating the sorts of queries (analysis) and operations (particularly destructive updates) you need from the system first. Then figure what's needed to optimise those. Timeseries joins are really about merging multiple streams (of data subsets) then processing the result. Cache management is the key here. Merging streams doesn't require indexes. A timeseries "database" is very different beast to a relational database. Column based storage is almost certainly the way to go - and as I said think about access patterns first then architecture.
Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.