<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maxim Molchanov</title>
    <description>The latest articles on DEV Community by Maxim Molchanov (@molchanov).</description>
    <link>https://dev.to/molchanov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F157700%2F244f9e89-0054-4c17-813d-70d17cd77e0d.jpeg</url>
      <title>DEV Community: Maxim Molchanov</title>
      <link>https://dev.to/molchanov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/molchanov"/>
    <language>en</language>
    <item>
      <title>5 things I wish I knew before writing a financial exchange</title>
      <dc:creator>Maxim Molchanov</dc:creator>
      <pubDate>Thu, 13 Feb 2020 10:09:11 +0000</pubDate>
      <link>https://dev.to/molchanov/5-things-i-wish-i-knew-before-writing-an-financial-exchange-14p1</link>
      <guid>https://dev.to/molchanov/5-things-i-wish-i-knew-before-writing-an-financial-exchange-14p1</guid>
      <description>&lt;p&gt;“Tell me, and I will forget. Show me, and I may remember. Involve me, and I will understand.”&lt;/p&gt;

&lt;p&gt;When I and my cat set off to developing a financial exchange engine, this very quote of Confucius was ringing in my head. We were a dream team: motivated, solid, result-oriented. Our condition could be best described as interest and readiness to take on challenges on our way to MVP. All those challenges and discoveries, met during development, have adjusted the whole process and were well worth the time spent. Today I’d like to share some of them with you — and tell about the results of this experiment as well.&lt;/p&gt;

&lt;p&gt;For the impatient, here is the link to the &lt;a href="https://github.com/Vonmo/trade_demo" rel="noopener noreferrer"&gt;self-hosted demo&lt;/a&gt;. The rest, please, welcome to read further.&lt;/p&gt;

&lt;p&gt;The main challenge of this project was the limited resources. If this limit hadn’t been that critical, the following list would, probably, look a bit different. My insights might seem banal, but each question can be treated differently. There is a huge divide between just ‘hearing about a problem’, ‘becoming aware of the problem’ and ‘understanding the problem’. &lt;/p&gt;

&lt;p&gt;So, here is the list: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s way harder to bring a project to MVP than just to start it&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;When you are working on a project single-handedly and, what’s more, in your free time only, it’s very easy to shift the deadlines. I personally had to stop for half a year because of the main workload and family issues. &lt;/p&gt;

&lt;p&gt;The project implementation was divided into two parts: March-April 2019 and November-December 2019. In November I wasn’t sure that I would come to MVP. However, all the internal interfaces were ready, as well as the storage system and order processing. All I had to do was create user interfaces. So, I decided to move on. &lt;/p&gt;

&lt;p&gt;As a result, I realized that it’s better not to put projects on hold as the initial motivation is lost and the focus can be shifted. To keep going, I had to put a real effort in finding time for the project again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marketing can be different&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This insight appeared after I’d been researching and choosing the frontend stack. Now there is a wide variety of libraries and frameworks whose APIs are changing so rapidly that it might make you stunned. &lt;/p&gt;

&lt;p&gt;The hype around any of them is not an indication of quality. The size of the community around a project often doesn’t correlate to the quality of the solution. If a project/solution/technology position themselves as the best of the best and are described as ‘magical’ and ‘amazing’, it’s better to take it with a grain of salt or even avoid. It is likely to save you some time in future.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You never know which problem of failure in third-party software you might encounter&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Even proven technologies can let you down. It happens so that an instrument you’ve been using for a long time starts acting not the way you want. To my surprise, it happened to me while I was working on this project too. There was Timescale and its time_bucket which didn’t support big intervals; Tarantool and replication issues and manual control of transactions; Rustler which started copying more data while calling functions...this list can be continued. What I realized here was that in the open source world you can always manage to fix the problem or find a way to avoid it by digging deeper into the source code of the app. &lt;/p&gt;

&lt;p&gt;When resources are limited, you can’t but plan the amount of work carefully. If a problem turns up, you have do deal with it on your own, be it a small one or big one. You just have noone to delegate it to. &lt;/p&gt;

&lt;p&gt;So, every technical difficulty — for instance, a mistake in a third-party library, or a limitation in a technology you are using, or sometimes an issue of architecture — can seriously shift the deadline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your eyes might glaze over and affect decision making&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually, in case of commercial development, we communicate with other people. Any project involves people who are making it or using it. During development, we constantly get updates from other members of the process which add up to our perspective. Making any technically challenging decision is a complex process. When you are absorbed in a problem and spend hours or even days trying to find the ways of addressing it, you sometimes can’t keep your focus. &lt;/p&gt;

&lt;p&gt;I’ve realized how important and valuable working in a team is. An opportunity to discuss a question with a colleague who isn’t working on the same task but is still in the know can be really useful. They will be able to assess your approach or solution critically, and it will save your time and reduce the likelihood of mistakes. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Teaching programming to a cat is a tricky business&lt;/strong&gt;&lt;br&gt;
    &lt;/p&gt;
&lt;center&gt;
&lt;br&gt;
       &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffzs61h8opvypd3366p05.jpg" alt="cat"&gt;&lt;br&gt;
    &lt;/center&gt;
&lt;br&gt;
   If you want to repeat my experiment, here is something to bear in mind. Despite the fact that my four-pawed buddy demonstrated genuine interest to the project, it didn’t stop him from getting lazy or distracted all the time. After a couple of weeks I realized that there is no way he’d become a programmer. However, his teamwork skills can’t be underestimated. He was in charge of maintaining the team spirit and, needless to say, he did his best.
&lt;h2&gt;
  
  
  Project outcomes
&lt;/h2&gt;

&lt;p&gt;Before I started this experiment, I’d set 2 main goals: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A deeper understanding of the subject and improvement of technical expertise;&lt;/li&gt;
&lt;li&gt;Identifying strengths and weaknesses of functional languages and open source projects in trading systems development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During the experiment I definitely managed to deepen and systematize my knowledge and expertise and even tried to share the outcomes with the community — by the way, you will find the links to the previous boringly theoretical articles in my profile. So, this goal has been achieved successfully. &lt;/p&gt;

&lt;p&gt;As to the functional programming...well, I’m looking from Erlang/OTP perspective now. Erlang as a language and OTP as a framework are suited to industrial programming and financial systems implementation. First, the ecosystem is quite mature. Second, minimum syntactic basis of the language, immutability, and approaches embodied in OTP enable for quick implementation of scalable, distributed and reliable systems.&lt;br&gt;
Unfortunately, I didn’t keep a time log, but the whole process took me about 4 months while working after having done my day job. Meanwhile, there are more than 200 integration tests in the project.  &lt;/p&gt;

&lt;p&gt;Sounds good and all, you will say. However, there still is a small fly in the ointment. Erlang is pretty similar to Python in terms of performance. Unlike Python, it lets us write highly concurrent systems with an adjustable level of parallelism. Also, such systems are easy to scale, and the components can be distributed within a cluster. The systems turn out to be of high capacity, but not superfast. &lt;br&gt;
Basing on the last conclusion, I realized that the most effective and productive approach would be not distributing market handlers and client accounts to different machines within a cluster, but processing all markets and accounts within a single machine with data replication for the sake of reliability.   &lt;/p&gt;

&lt;p&gt;Due to the limited performance of Erlang, market processing should be done with the help of lower-level languages without GC and fair schedulers. In current implementation I have already moved some part of the functional to NIF on Rust.&lt;br&gt;
This way, thanks to the optimized work with network and binary structures, Erlang perfectly solves the issues of organizing a reliable and fast platform. An issue of working with orders and accounts, though, is better to be solved with the help of Rust/C/C++ (to your taste). &lt;/p&gt;

&lt;p&gt;Last but not least is the choice of the language for frontend. Old habits die hard — I opted for Vue + Js. However, if I were choosing the stack now, I would prefer Vue + Typescript. Stricter typing would make it possible to accelerate the development. &lt;br&gt;
All in all, thanks to everyone who was following the experiment. I hope it wasn’t too boring, or was it? Stay tuned — it’s not goodbye! :-) &lt;/p&gt;

</description>
      <category>development</category>
      <category>programming</category>
      <category>erlang</category>
      <category>vue</category>
    </item>
    <item>
      <title>Vonmo Trade Experiment. Part 4: Stock charts</title>
      <dc:creator>Maxim Molchanov</dc:creator>
      <pubDate>Tue, 21 Jan 2020 14:23:33 +0000</pubDate>
      <link>https://dev.to/molchanov/vonmo-trade-experiment-part-4-stock-charts-3oe7</link>
      <guid>https://dev.to/molchanov/vonmo-trade-experiment-part-4-stock-charts-3oe7</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iuMHsaOc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/9bwj8rxp95j9cculzjvl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iuMHsaOc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/9bwj8rxp95j9cculzjvl.jpg" alt="Vonmo trade"&gt;&lt;/a&gt;&lt;br&gt;
In the previous articles we dealt with order creating and processing. Today’s one will focus on processing and storage of the information which is essential for graphic instruments of market analysis – or just stock charts. &lt;/p&gt;

&lt;p&gt;To assess the market condition properly, we can’t manage with just an order book and trade history. We need an instrument which will help to see the market price trend clearly and fast. Charts can be divided into two types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Line charts &lt;/li&gt;
&lt;li&gt;Interval graphs&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Line charts
&lt;/h2&gt;

&lt;p&gt;These are the easiest and the do not require any preparation to be understood. Line charts show the dependence of a financial instrument price on time. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--64wYLpyg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cnfzyg23qcvqvmwbt7zb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--64wYLpyg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cnfzyg23qcvqvmwbt7zb.png" alt="line chart"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Chart resolution
&lt;/h2&gt;

&lt;p&gt;If we start building a chart based on all the price changes, i.e. every closed deal gets to the chart, it will be really difficult to take. Also, the capacities used for this chart processing and delivery will be wasted. &lt;br&gt;
That’s why data should be decimated: the timeline is split into intervals, and the prices within these intervals are aggregated.  Resolution is the size of a basic interval of timeline splitting: second, minute, hour, day etc.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Bar chart
&lt;/h2&gt;

&lt;p&gt;They belong to interval graphs. To increase informativeness, we need to show the price information at the beginning and at the end of the interval for every single interval. We also need to show the maximum and the minimum price. The graphic representation of this set is called a bar. Let’s look at a bar scheme:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PsI_-Orh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/534c0iadat70xtsv5sw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PsI_-Orh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/534c0iadat70xtsv5sw7.png" alt="bar"&gt;&lt;/a&gt;&lt;br&gt;
A sequence of bars makes a bar chart: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S6jxrDA6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/4vkgrtzpmk8ldq0qgu7f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S6jxrDA6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/4vkgrtzpmk8ldq0qgu7f.png" alt="bar chart"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Candlestick chart
&lt;/h2&gt;

&lt;p&gt;Like bar charts, they belong to interval graphs. They are the most popular type of graph in technical analysis. A ‘candlestick’ consists of black or white body and shadows – higher one and lower one. Sometimes a shadow is called a wick. The highest and the lowest borders of a candlestick illustrate the highest and the lowest price during the time interval represented. The body borders show the opening price and the closing price. Let’s draw a candlestick:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tcsn-lAC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xatjhtchl27vfckobms5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tcsn-lAC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xatjhtchl27vfckobms5.png" alt="candle"&gt;&lt;/a&gt;&lt;br&gt;
A sequence of candlesticks make a candlestick chart:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lqtm4Rmv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/qyidhh71rqju6vl18vry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lqtm4Rmv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/qyidhh71rqju6vl18vry.png" alt="candlestick chart"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  OHLCV notation
&lt;/h2&gt;

&lt;p&gt;In the previous article we dealt with the scheme of data storage for a graph in postgresql and built a table for data source which will store aggregated data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;without&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;df_resolution&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'1m'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;df_resolution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;df_pk&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;These fields don’t need to be explained except, probably, the &lt;code&gt;r&lt;/code&gt; field which is the row resolution. In postgresql there are enumerations which are great to use when we know the value set for a field in advance. Let’s define a new type for permissible chart resolutions via listings. Let it be a row from 1 minute to 1 month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;df_resolution&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;ENUM&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'3m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'5m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'15m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'30m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'45m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'4h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'6h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'8h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'12h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'3d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1M'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It’s important to find a balance between IO performance, CPU performance, and final cost of ownership. At the moment, there are 16 resolutions defined in the system.  Two obvious solutions present themselves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can calculate and store all resolutions in the base. This option is convenient because, while selecting, we don’t spend capacity on interval aggregating and all data are ready for output straightaway. There will be slightly more than 72k records for one instrument per month. Looks easy and convenient, doesn’t it? However, such table will change too often because for each price update we need to create or update 16 records in the table and rebuild the index. There might be an issue in postgresql with garbage collecting too.&lt;/li&gt;
&lt;li&gt;Another option is to store a single basic resolution. While selecting from basic resolution, it’s necessary to build the resolutions required. For example, if we store a minute resolution as the basic one, 43k records will be created for each instrument per month. This way, compared to the previous option, we are reducing the record volume and overhead costs by 40%. However, CPU load is increasing at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned before, it’s really vital to strike a balance. That’s why a middle ground would be to store not just one basic resolutions, but several: 1 minute, 1 hour, 1 day.  Under this scheme, there are 44,6k records created for each instrument per month. Optimization of record volume will be 36% and CPU load will be acceptable as well. For instance, to build week intervals we’ll need to read from the disc and aggregate the data of 7 day resolutions instead of reading and aggregating 10080 records with minute basic resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  OHLCV storage
&lt;/h2&gt;

&lt;p&gt;OHLCV is a time series in its nature. As is known, relational database is not suitable for such data storage and processing. Our project deals with this problem with the help of &lt;a href="https://timescale.com"&gt;Timescale&lt;/a&gt;.&lt;br&gt;
Timescale improves the performance of insert and update operations, and enables to configure partitioning, and provides the analytical functions which are specially optimized for working with time series.&lt;/p&gt;

&lt;p&gt;To create and update the bars, we’ll need standard functions only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;date_trunc&lt;/code&gt;(‘minute’ | ’hour’ | ’day’, transaction_ts) – to find the beginning of the interval of a minute, hour, and day resolution respectively.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;greatest&lt;/code&gt; и &lt;code&gt;least&lt;/code&gt; – to define the maximum and minimum price.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to upsert api, only one update request is performed per each transaction.&lt;br&gt;&lt;br&gt;
What I got in the end is the following SQL for fixing market changes in basic resolutions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="n"&gt;array_upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_resolutions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;LOOP&lt;/span&gt;
    &lt;span class="n"&gt;resolution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage_resolutions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1m'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'minute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;bar_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ELSIF&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1h'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hour'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;bar_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ELSIF&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1d'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;bar_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'INSERT INTO %I (t,r,o,h,l,c,v)
    VALUES (%L,%L,%L::numeric,%L::numeric,%L::numeric,%L::numeric,%L::numeric)
    ON CONFLICT (t,r) DO UPDATE
    SET h = GREATEST(%I.h, %L::numeric), l = LEAST(%I.l, %L::numeric), c = %L::numeric, v = %I.v + %L::numeric;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;df_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bar_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;df_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;
 &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;While selecting, the following functions will serve us for aggregating the intervals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;time_bucket&lt;/code&gt; – to split into intervals&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;first&lt;/code&gt; – to find the opening price – &lt;code&gt;O&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max&lt;/code&gt; – the maximum price per interval – &lt;code&gt;H&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;min&lt;/code&gt; – the minimum price per interval – &lt;code&gt;L&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;last&lt;/code&gt; – to find the closing price – &lt;code&gt;C&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sum&lt;/code&gt; – to find the trade volume – &lt;code&gt;V&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only problem with Timescale is the limitation of &lt;code&gt;time_bucket&lt;/code&gt; function. It enables to manipulate the intervals less than a month only. To build a month resolution, we should use the standard function &lt;code&gt;date_trunc&lt;/code&gt;.  &lt;/p&gt;

&lt;h2&gt;
  
  
  API
&lt;/h2&gt;

&lt;p&gt;To display the graphs on the client, we’ll use &lt;a href="https://github.com/tradingview/lightweight-charts"&gt;lightweight-charts&lt;/a&gt; from Tradingview. This library enables to configure the appearance of the charts and is quite convenient to exploit. I’ve got the following charts:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o-9CVndt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/45t1zmhzsw50v0fp1rcu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o-9CVndt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/45t1zmhzsw50v0fp1rcu.png" alt="chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the main part of the browser-platform interaction is carried out via websocket, there are no problems with interactivity. &lt;/p&gt;
&lt;h2&gt;
  
  
  Data source
&lt;/h2&gt;

&lt;p&gt;The data source for charts (or data feed) must return the required part of the time series in needed resolution. To save the traffic and processing time on the client, the server must pack the points. &lt;br&gt;
API for data feed should be designed so that you can request multiple graphs in a single request and subscribe to their updates. This will reduce the number of commands and responses in a channel. Let’s consider a request of the last 50 minutes for &lt;code&gt;USDGBP&lt;/code&gt; with automatic subscription to the graph updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"market"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"get_chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"charts"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"USDGBP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"cnt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"send_updates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For sure, we can request the date range (from, to), but as we know each bar interval, declarative API indicating the moment and the number of bars seems more convenient to me.&lt;br&gt;
Data feed will respond to this request in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"market"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bar_fields"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="s2"&gt;"t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"uts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"l"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"v"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"USDGBP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"bars"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="s2"&gt;"2019-12-13 13:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1576242000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"0.75236800"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="s2"&gt;"0.76926400"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"0.75236800"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"0.76926400"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"138.10000000"&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bar_fields&lt;/code&gt; field contains the information about the position of the elements. Further optimization involves putting this field into the client configuration which it gets from the server during the client initialization. This way, the client gets a necessary part of historical data and builds the initial state of the graph. If the state changes, the clients gets an update on the last bar only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"market"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"chart_tick"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"USDGBP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"140.600"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"ut"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1576242000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"t"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2019-12-13T13:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0.752368"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"l"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0.752368"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0.770531"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0.770531"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Interim conclusion
&lt;/h2&gt;

&lt;p&gt;Over the whole series of articles we went through the theory and practice of building an exchange. Now it’s time to put the system together. &lt;br&gt;
The following article will tell about the development of user graphic interfaces: service UI to operate the platform and UI for end users. Also, you’ll be able to take a look at the demo version of Vonmo Trade. &lt;/p&gt;

&lt;p&gt;Thanks to Nadezda Popova for proofreading.&lt;/p&gt;

</description>
      <category>erlang</category>
      <category>rust</category>
      <category>functional</category>
      <category>programming</category>
    </item>
    <item>
      <title>Vonmo Trade Experiment. Part 3: Order book. Processing and storing trade info</title>
      <dc:creator>Maxim Molchanov</dc:creator>
      <pubDate>Fri, 17 Jan 2020 12:59:31 +0000</pubDate>
      <link>https://dev.to/molchanov/vonmo-trade-experiment-part-3-order-book-processing-and-storing-trade-info-gm8</link>
      <guid>https://dev.to/molchanov/vonmo-trade-experiment-part-3-order-book-processing-and-storing-trade-info-gm8</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--24vSQViP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/du2ye5hbuw4y4cir89v5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--24vSQViP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/du2ye5hbuw4y4cir89v5.jpg" alt="Vonmo Trade"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the previous article of the series we got familiar with order types. Today we’ll take a closer look at an order book, processing orders, and dealing with issues of trade information storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supply and demand
&lt;/h2&gt;

&lt;p&gt;I’m pretty sure you remember the law of supply and demand from economics. It shows the market mechanics in pricing:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CvAhiEXf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/4ps0nolv4hi74rorp6qv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CvAhiEXf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/4ps0nolv4hi74rorp6qv.png" alt="pd chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same mechanics works at exchanges. &lt;/p&gt;

&lt;p&gt;An order book is a list where all limit orders of buyers and sellers go. This way, the current interest to a particular financial instrument is demonstrated. &lt;/p&gt;

&lt;p&gt;If we transform the previous graph with regard to an order book, we’ll have something like that: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--quFNUTmX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ap30psqtk4ehkahv96y3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--quFNUTmX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ap30psqtk4ehkahv96y3.jpg" alt="bg chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that the market price appears when the maximum bid price equals the minimum offer price. The difference between these prices is called spread. Spread is an important indicator as it is connected with an instrument liquidity. The less the spread is, the more liquidity the instrument has. To provide liquidity within exchange trading process, a limit on maximum spread is often imposed. If this limit is exceeded, bidding is stopped.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Creating an order
&lt;/h2&gt;

&lt;p&gt;Let’s have a look at an order life cycle, from getting to the exchange to being executed or cancelled. For ease, we’ll focus on a foreign exchange market. A special process is responsible for order processing logic. Let’s call it a market controller. &lt;/p&gt;

&lt;p&gt;So, a trader creates an order and it gets to the exchange. The controller must make sure that the trader has enough liquidity to create the requested type of order. As a source of information, an internal accounting server or any external APIs can be used. &lt;/p&gt;

&lt;p&gt;To fulfil this order immediately, the market must have a counterbid, or a so-called matching order. If the counterbid exists, then a smaller order from the pair is executed fully, and the bigger one partially — of course, as long as partial order fulfillment is allowed by the order trade instructions. If the counterbid doesn’t exist, a new order gets into the order book and takes its place in the list of the orders of its type. &lt;br&gt;
Since only pending orders can get into the order book, we need to create special lists for other order types. &lt;/p&gt;

&lt;p&gt;In all order lists the buy side should be sorted in descending order while the sell one — in ascending order. The first item of the limit orders list shapes the best ask and bid prices respectively. &lt;/p&gt;

&lt;p&gt;One more important point is the order of execution. The controller must implement FIFO. That is why, if the prices of two orders are identical, the first one to fulfil is the one which has been created first. &lt;/p&gt;

&lt;p&gt;In user interface the order book looks like a table with a set of price levels. It shows both buy limit orders and sell limit orders.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QVE3Y7g0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/3o8ygte2plyhxyfytib2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QVE3Y7g0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/3o8ygte2plyhxyfytib2.png" alt="book"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Buy orders and sell orders are in different colours to be distinguished easier.&lt;/p&gt;
&lt;h2&gt;
  
  
  Price levels aggregation
&lt;/h2&gt;

&lt;p&gt;The order book depth is a number of price levels. For active markets with lots of pending orders, which differ as little as they can, this depth can be too big to be shown in the trader’s terminal. We need a tool for grouping the levels to assess the whole book. &lt;br&gt;
By cutting off one decimal place at a time and grouping the levels, we can reduce the number of levels with each step. &lt;/p&gt;
&lt;h2&gt;
  
  
  Fulfilling and cancelling orders
&lt;/h2&gt;

&lt;p&gt;After an order in the order book has been executed or cancelled, the controller must update the book and delete the order. It also notifies all interested parties of the changes in the book. &lt;/p&gt;
&lt;h2&gt;
  
  
  Handlers architecture and scaling
&lt;/h2&gt;

&lt;p&gt;Taking into account required performance and reliability of the system, we need to define the approaches to scale applications and data storage layers. &lt;/p&gt;

&lt;p&gt;Normally, vertical scalability is used for exchanges. The code of order processing and user accounts processing is run on one machine as a monolith process. Such approach demonstrate good performance but has certain limitations — in any case, vertical scalability has its limits in terms of CPU and storage capacity. &lt;/p&gt;

&lt;p&gt;As part of the experiment, I decided that market processing should be scaled horizontally. Each individual instrument is handled by its own process. The processes are automatically distributed between cluster nodes. In case of failure, a market is moved to another node without loss of state.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k-EKitOf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2pvqy2zovsoo4gcpcrwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k-EKitOf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2pvqy2zovsoo4gcpcrwf.png" alt="market app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The system formula is quite simple: M handlers are distributed on K cluster nodes and use L data storages. &lt;br&gt;
This model lets us scale a system up to approximately 150k nodes, and each market controller can process about 30k RPS.  &lt;/p&gt;

&lt;p&gt;As the flow of orders is different at different markets and depends on users participation, we can divide all markets into several groups: big, medium, and small ones. Each node has the settings enabling us to specify the limits of the markets which it can process. The master is evenly and automatically distributing the markets of the same type between the cluster nodes. If the cluster composition has been changed, the markets are redistributed. This way we can achieve more or less equal distribution of the system load.  &lt;/p&gt;

&lt;p&gt;Nodes in management UI look something like this: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NLTlL2EG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ewp7p0cfeb3yddluzn4s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NLTlL2EG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ewp7p0cfeb3yddluzn4s.png" alt="node admin"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Data storage
&lt;/h2&gt;

&lt;p&gt;The order book is constantly changing and must be stored in memory. For MVP I’ve opted for Tarantool with WAL as an in-memory storage. All historical data will be stored in PostgreSQL.&lt;/p&gt;

&lt;p&gt;The storage scheme for current and historical data must comply with the chosen scheme of code scalability of the handlers. Each market can use its own postgres and tarantool. To make it possible, let’s combine postgresql and tarantool into a single entity — market data storage. While setting the market, an administrator is able to manage the storages. To retain flexibility, we’ll be indicating the unique id of the connection pool instead of credentials to certain postgresql and tarantool instances. The pool interface is supported by the platform. So, the storage looks like this in admin UI: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SIY1D0uy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xz54k1y9og95wq7wasux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SIY1D0uy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xz54k1y9og95wq7wasux.png" alt="storage admin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While setting the market, the administrator must indicate at least one storage for each market. If several are indicated, we’ll have a market with logical replication of data. This function enables us to configure the reliability and performance of the data storage scheme. &lt;/p&gt;
&lt;h2&gt;
  
  
  Order book data
&lt;/h2&gt;

&lt;p&gt;Tarantool uses spaces for stored data organisation. The declaration of spaces necessary for the order book looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'book_state'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;buy_orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'limit_buy_orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;sell_orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'limit_sell_orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;market&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;buy_orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'market_buy_orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;sell_orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'market_sell_orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;orders_mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'orders_mapping'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As several markets can store their data on one tarantool instance, let’s add market id to all instances. Current book implementation is based on the so-called precalculated principle. When the book is being updated, the groups are automatically recalculated. For example, we add an order to the market with 6 digit precision for prices. We can have 6 possible groups of prices + one slice with the order raw data which need to be updated. &lt;br&gt;
There is a number of orders_mapping orders for getting the client’s active orders. &lt;/p&gt;

&lt;p&gt;Thanks to tarantool’s indexes and iterators, lua-code for the order book storage consists of 600 lines only (together with initialization). &lt;/p&gt;
&lt;h2&gt;
  
  
  Historical data
&lt;/h2&gt;

&lt;p&gt;Market data are stored in tables, separately for each market. Let’s take a look at the set of basic tables.&lt;/p&gt;
&lt;h3&gt;
  
  
  Orders execution history
&lt;/h3&gt;

&lt;p&gt;To save the results of order processing, we have history table. All the orders that are completely fulfilled and the orders which are cancelled but partially fulfilled go there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;without&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;owner&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"default"&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_type&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_side&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;commission&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;history_pkey&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Reports for customers are computed based on their trade history which is stored in this table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Historical data feed
&lt;/h3&gt;

&lt;p&gt;For analytical purposes and historical data feed building, the market controller must save the information about the event after every transaction. The ticks table is used to commit all market changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticks&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;without&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bid&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ask&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;last&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bid_vol&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ask_vol&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;last_vol&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;ticks_pk&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It contains prices and market volume after a transaction has been made. The opts field contains service-based data, for example, the description of the orders involved in the transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data feed for charts
&lt;/h3&gt;

&lt;p&gt;The ticks table is enough to be able to build stock charts. It contains the so-called raw data stream, but postgresql has powerful analytical functions and enables us to aggregate data on request.&lt;br&gt;
Troubles start when there is too much of data and lack of available computing resources. To solve it, let’s create a table with pre-calculated data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;without&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;df_resolution&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'1m'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;df_resolution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;df_pk&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the next article we’ll talk about working with time series in Postgresql, preparing data for df table, and building charts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This article has given us a better understanding of the main moments of order book organisation, as well as order processing mechanism. We have also had some practice of working with market data. &lt;br&gt;
The chosen storage scheme enables us to start with just one storage for all markets and, as the project grows, spread the markets to different storages and place them as close as possible to market handlers. &lt;/p&gt;

</description>
      <category>erlang</category>
      <category>rust</category>
      <category>functional</category>
      <category>programming</category>
    </item>
    <item>
      <title>Vonmo Trade Experiment. Part 2: Orders. Types and processing features</title>
      <dc:creator>Maxim Molchanov</dc:creator>
      <pubDate>Thu, 09 Jan 2020 15:40:56 +0000</pubDate>
      <link>https://dev.to/molchanov/vonmo-trade-experiment-part-2-orders-types-and-processing-features-3ck8</link>
      <guid>https://dev.to/molchanov/vonmo-trade-experiment-part-2-orders-types-and-processing-features-3ck8</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AEADCgFo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dg1fl7pouzqymqyz9s59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AEADCgFo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dg1fl7pouzqymqyz9s59.png" alt="Vonmo Trade"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To figure out how exchanges work, we need to look into exchange orders and the way broker processes them. &lt;br&gt;
This article deals with order types, the way they are executed at an exchange, and limitations imposed by trading system.&lt;br&gt;&lt;br&gt;
If you are curious to know what all those DAY, GTC, FOK, IOC, GTD, GAT, MOO, MOC, LOO, LOC, MIT, OCO, OSO, PEG mean, you are welcome.&lt;/p&gt;
&lt;h2&gt;
  
  
  Orders
&lt;/h2&gt;

&lt;p&gt;All market players are interested in a good deal. To start bidding, we need to create a sell or a buy order for a required financial instrument. Consequently, a set of orders placed on the exchange creates current supply and demand. A market order is instructions from a client to their broker to buy or sell financial instruments at the exchange. &lt;br&gt;
A typical order contains the following information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Financial Instrument Identifier &lt;/li&gt;
&lt;li&gt;Order type &lt;/li&gt;
&lt;li&gt;Order side. Buy or sell. &lt;/li&gt;
&lt;li&gt;Price&lt;/li&gt;
&lt;li&gt;Quantity &lt;/li&gt;
&lt;li&gt;Order duration type (optional) &lt;/li&gt;
&lt;li&gt;Additional instructions (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can break all the orders into three big groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Market orders&lt;/strong&gt;. This order must be executed immediately at the current market price. If there is no necessary amount, then the order is executed as soon as possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pending orders&lt;/strong&gt;. A request enables to control  the limit prices for buying and selling. Sometimes such orders can not be executed straightaway or at all. The price is always better for limit orders.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional orders&lt;/strong&gt;. These are all requests except limit ones that require extra conditions to be activated and executed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Time in force
&lt;/h2&gt;

&lt;p&gt;You have, for sure, seen acronyms like DAY, GTC, FOK, IOC, GTD, GAT All of them indicate how long an order will remain active before it is executed or expires.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DAY&lt;/strong&gt;. These orders expire at the end of the trading day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GTC&lt;/strong&gt; (Good till canceled). This order is valid until it is cancelled. To make sure they don’t stay at the market indefinitely, a special limitation is imposed, which is usually from 30 to 90 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FOK&lt;/strong&gt; (Fill Or Kill). The order must be fully filled or immediately cancelled.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IOC&lt;/strong&gt; (Immediate Or Cancel). The order is immediately executed or cancelled by the exchange. Unlike FOK, this type allows for partial fulfillment. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GTD&lt;/strong&gt; (Good-til-Date/Time). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GAT&lt;/strong&gt; (Good-after-Time/Date).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Conditions of order execution
&lt;/h2&gt;

&lt;p&gt;By combining instructions on prices, time in force and extra limitations, we can get various types of order execution. To get a better idea, let’s get back to the order types structure which is presented at the beginning of this article.&lt;/p&gt;
&lt;h2&gt;
  
  
  Market-on-open/close orders
&lt;/h2&gt;

&lt;p&gt;If we limit the duration of period when this market order is valid, we get the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MOO&lt;/strong&gt; (Market-On-Open). A market order of such type can be executed only at the opening of the trading day. Its execution is guaranteed provided there is liquidity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LOO&lt;/strong&gt; (Limit-On-Open). Like MOO, LOO can be executed at the opening of the trading day only. However, limit orders of this type enable for setting instructions for the price. The execution is not guaranteed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MOC&lt;/strong&gt; (Market-On-Close). These orders are similar to MOO, but the market order can be executed at the closing of the trading day only. The execution is guaranteed provided there is liquidity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LOC&lt;/strong&gt; (Limit-On-Close). These orders are similar to LOO, but the market order can be executed at the closing of the trading day only. The execution is not guaranteed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Conditional orders
&lt;/h2&gt;

&lt;p&gt;When we want to tailor our orders for some trading strategy, we can do it with the help of conditional orders – the ones which are activated only after the condition has been satisfied. Thus, their main aim is to minimize the risk of significant financial losses.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stop order
&lt;/h3&gt;

&lt;p&gt;Stop order is a bid or an ask for a certain amount of a financial asset at a specified price or worse. As soon as the price equals or exceeds the specified stop price for a bid order, it automatically turns into a market order and is executed on general grounds. It works the same way with a stop order for selling. As soon as the price reaches the level set in the order and continues to fall, the order turns into a market one. This way, execution is guaranteed for active stop orders. Stop orders let us minimize potential losses, that’s why stop loss is the synonym for the stop order.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stop-limit order
&lt;/h3&gt;

&lt;p&gt;Unlike usual stop orders, where just the stop price is specified, stop-limit orders require also a limit price. &lt;br&gt;
As soon as the asset price is equal or worse than the stop price, a limit order with the specified limit price is automatically created. The execution of such order is not guaranteed.&lt;/p&gt;
&lt;h3&gt;
  
  
  Trailing stop order
&lt;/h3&gt;

&lt;p&gt;Unlike usual stop orders, where the stop price is shown in absolute units, trailing orders use percentage. After activation, it turns into a usual market order. This way, the stop price is linked to the market price and grows with it. For example, we’ve placed an order for 10 dollars and set the stop price at 10% from the current one, i.g. the absolute stop price equals 9 dollars. Then, the asset grew to 15 dollars alongside with the stop price which grew to 13.5 dollars.&lt;br&gt;
This order type is used to minimize losses as well as maximize profit.&lt;/p&gt;
&lt;h3&gt;
  
  
  Trailing stop-limit order
&lt;/h3&gt;

&lt;p&gt;Unlike trailing stop orders, this order turns into a limit one, not a market one, after activation. &lt;/p&gt;
&lt;h3&gt;
  
  
  PEG order
&lt;/h3&gt;

&lt;p&gt;This is a kind of limit orders. It changes its price automatically after the change of bid or ask prices. In price instructions a shift for the worse from the current best bid/ask price is indicated.&lt;/p&gt;
&lt;h3&gt;
  
  
  OCO order (one cancels other)
&lt;/h3&gt;

&lt;p&gt;This is a pair of orders. It usually consists of a stop order and a limit order. Both orders work with the same instrument. If one of them is executed, the second is automatically cancelled. &lt;br&gt;
A simple example: let’s imagine that we’ve bought an asset for ＄10. We aim at getting at least ＄3 of profit and limiting the losses to ＄2 at most. Then our stop price will be ＄8 and our limit price ＄13.&lt;/p&gt;
&lt;h3&gt;
  
  
  OSO order (one sends other)
&lt;/h3&gt;

&lt;p&gt;This order consists of the main order and a group of linked orders. Orders can work with different instruments. If the main order is executed,all linked orders are then sent.&lt;/p&gt;

&lt;p&gt;Now when we’ve briefly reviewed orders theory, let’s get down to practice.&lt;/p&gt;
&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;From the system point view, it’s crucial to define the basic structure for all orders in a flexible, but precise way. Erlang enables us to do that with the help of records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;exchange_order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;orig_id&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
  &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;trader&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;non_neg_integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;side&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;non_neg_integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;time_in_force&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;non_neg_integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;trigger_price&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;orig_qty&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;created_ts&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;non_neg_integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;updated_ts&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;non_neg_integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;deals_history&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nl"&gt;#exchange_deal&lt;/span&gt;&lt;span class="p"&gt;{}],&lt;/span&gt;
  &lt;span class="n"&gt;linked_orders&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nl"&gt;#exchange_order&lt;/span&gt;&lt;span class="p"&gt;{}],&lt;/span&gt;
  &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Order identification
&lt;/h3&gt;

&lt;p&gt;After an order has been placed on the exchange, it gets an internal identifier – &lt;code&gt;id&lt;/code&gt; field. &lt;br&gt;
To improve user’s experience, when we are coordinating the exchange with external systems, we should enable an external system to set its own id to the order being created. It will give us a possibility to request orders both by id and by &lt;code&gt;orig_id&lt;/code&gt;. Also, it will enable us not to use extra order mapping in the external system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Order ownership
&lt;/h3&gt;

&lt;p&gt;Let’s provide a possibility to create an agent (external broker) in our system. Orders can come directly from traders or from a broker that provides services to clients who don’t have direct access to the exchange. &lt;br&gt;
In case of a direct order owner and trader coincide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instruction encoding
&lt;/h3&gt;

&lt;p&gt;Let’s look at the rest of the fields. There are two fields for encoding the instructions by price: &lt;code&gt;price&lt;/code&gt;, which is the limit price for limit orders, and &lt;code&gt;trigger_price&lt;/code&gt;, which is the trigger price for custom types. For instance, in case of stop orders this field will encode the stop price.  &lt;/p&gt;

&lt;p&gt;We use &lt;code&gt;qty&lt;/code&gt; and &lt;code&gt;qty_orig&lt;/code&gt; for encoding the volume. &lt;code&gt;Qty_orig&lt;/code&gt; sets the initial order volume and &lt;code&gt;qty&lt;/code&gt; – the order volume while being fulfilled. &lt;br&gt;
&lt;code&gt;deals_history&lt;/code&gt; array will help to have precise information about partial fulfillments. With its help we can, for example, calculate the average price of the order after it’s been fully filled. &lt;br&gt;
&lt;code&gt;linked_orders&lt;/code&gt; field is necessary for encoding the information about linked orders for OCO and OSO. &lt;/p&gt;

&lt;p&gt;To enlarge the system without changing &lt;code&gt;exchange_order&lt;/code&gt; record, let’s add &lt;code&gt;opts&lt;/code&gt; field which is a dynamic dictionary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interim conclusion
&lt;/h2&gt;

&lt;p&gt;To be honest, I thought that the second part of the series would be more compact. However, I had to be more detailed to elaborate on the topic. &lt;br&gt;
The theoretical part of the following article will shed the light on order book and the rules of order processing. The technical part will deal with the scheme of market data processing and issues of historical data storage. &lt;/p&gt;

&lt;p&gt;Thanks to Nadezda Popova for proofreading.&lt;/p&gt;

</description>
      <category>erlang</category>
      <category>rust</category>
      <category>functional</category>
      <category>programming</category>
    </item>
    <item>
      <title>Vonmo Trade Experiment. Part 1: Exchanges and modern technologies</title>
      <dc:creator>Maxim Molchanov</dc:creator>
      <pubDate>Sun, 05 Jan 2020 17:37:52 +0000</pubDate>
      <link>https://dev.to/molchanov/vonmo-trade-experiment-part-1-exchanges-and-modern-technologies-1ao7</link>
      <guid>https://dev.to/molchanov/vonmo-trade-experiment-part-1-exchanges-and-modern-technologies-1ao7</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e93x8r6V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pe4vh9azi1vb5zhvbu61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e93x8r6V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pe4vh9azi1vb5zhvbu61.png" alt="Vonmo Trade"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This series of articles describes an attempt to create a reactive system performed by one person, with a minimum budget and in the shortest time possible. &lt;/p&gt;

&lt;p&gt;The experiment aims at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A deeper understanding of the subject and improvement of technical expertise;&lt;/li&gt;
&lt;li&gt;Identifying strengths and weaknesses of functional languages and open source projects in trading systems development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article presents the motivational part and task decomposition. &lt;/p&gt;

&lt;p&gt;I was planning to start this experiment in spring 2019, but then – life happened – and I had to reschedule. That is why here are my apologies to all those few followers of mine who have been waiting for the articles on this topic. &lt;/p&gt;

&lt;h2&gt;
  
  
  What brought me here?
&lt;/h2&gt;

&lt;p&gt;When I realized that I’d got tired of day-to-day project routine, I wanted to create something unusual. I was thinking of various topics and directions which looked challenging enough for me. This way, the main requirements to the project-to-be appeared: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field of studies that I find interesting; &lt;/li&gt;
&lt;li&gt;Distributed and high-availability nature of the app;&lt;/li&gt;
&lt;li&gt;High information capacity and big volume of stored data, typical for data-intensive apps;&lt;/li&gt;
&lt;li&gt;Maximum use of open source projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As I’ve already had some experience in developing object storages, I came up with an idea. Why don’t I write a new implementation of s3-compatible storage using erasure coding and fast hash functions? But then I wanted something even more exciting. From a developer’s point of view, financial system are interesting due to their near real-time work, huge number of updates and volume of stored data. So, I set my mind on creating my own implementation of an exchange system.  &lt;/p&gt;

&lt;h2&gt;
  
  
  About exchanges
&lt;/h2&gt;

&lt;p&gt;A stock exchange is also known as bourse (from Latin “bursa” – “purse”). In a broad sense, this is a market where sellers and buyers conduct transactions. A traded asset defines the exchange type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;commodities exchange, including labour exchange&lt;/li&gt;
&lt;li&gt;stock exchange / market&lt;/li&gt;
&lt;li&gt;currency exchange&lt;/li&gt;
&lt;li&gt;futures exchange&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Historically, exchanges were literally a place which, at the appointed time, brought together sellers, buyers and brokers, or middlemen, who assisted with transactions.&lt;/p&gt;

&lt;p&gt;The main functions of any exchange are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organizing the exchange trade:

&lt;ul&gt;
&lt;li&gt;Defining trade rules under current legislation&lt;/li&gt;
&lt;li&gt;Setting product standards&lt;/li&gt;
&lt;li&gt;Staffing and material provision&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Information activity. Providing information about stock instrument prices, markets and companies;
&lt;/li&gt;
&lt;li&gt;Drawing up standard contracts;&lt;/li&gt;
&lt;li&gt;Settlement of disputes (arbitration);&lt;/li&gt;
&lt;li&gt;Providing certain guarantees of order execution. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern exchanges are well-organized trading platforms which work by certain rules and create supply and demand. Exchange economy is crystal clear: the more transactions there are on the exchange, the more fees and commissions it gets for its service. &lt;/p&gt;

&lt;p&gt;Now when we have a general idea, let's limit tasks to do for MVP realization.&lt;/p&gt;

&lt;h2&gt;
  
  
  MVP
&lt;/h2&gt;

&lt;p&gt;Limited resources lead to limited functionality of the prototype.However, vital and fundamental things should be fully implemented.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task decomposition and architecture choice
&lt;/h3&gt;

&lt;p&gt;Technically speaking, an exchange is a public service system where its participants generate a flow of requests – or orders. The system, in its turn, operates and executes them by the rules defined in advance. Order service must be performed with minimum waste of time. The exchange must be fault-tolerant and highly available.  &lt;/p&gt;

&lt;p&gt;Let’s divide the project into a system part and a public part. The system part enables us to manage platform components and exchange trading process. The public part includes client interfaces: Web API, Trading API, notifications subsystem. Both parts will be based on distributed apps platform which is responsible for system scalability and reliability. As we are limited in budget, we’ll opt for tested open-source solutions for data storage organization. Tarantool is used for the system hot data, PostgreSQL for the cold data. &lt;/p&gt;

&lt;p&gt;Let’s draw the system structure: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QzdYiyTk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/v4qiviygigalqlq9ktfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QzdYiyTk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/v4qiviygigalqlq9ktfg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It might seem that both the architecture and the project itself are no different from any usual system. Where is the challenge, you may ask. To answer, let’s take a look at the process of creating orders and filling orders for a currency exchange.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating an order&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checking authorisation&lt;/li&gt;
&lt;li&gt;Making sure that a user can now afford to place an order&lt;/li&gt;
&lt;li&gt;Creating an order&lt;/li&gt;
&lt;li&gt;Informing the user on success of the operation or failure&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Filling an order&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finding a match&lt;/li&gt;
&lt;li&gt;Checking fund sufficiency for both parties of the deal&lt;/li&gt;
&lt;li&gt;Making the deal: money transfer between accounts + exchange fee transfer to the exchange commission account&lt;/li&gt;
&lt;li&gt;Saving the history of order execution&lt;/li&gt;
&lt;li&gt;Updating the information data: raw data and aggregated data for graphs&lt;/li&gt;
&lt;li&gt;Notifying all parties concerned about the deal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The trouble starts when we move from a monolith, working on a single machine, to a distributed fault-tolerant system deployed on a cluster. I think, the minimum performance level should make about 5-7k RPS completed transactions per market. This puts extra limitations on architecture and work with data. &lt;/p&gt;

&lt;p&gt;Following the KISS principle, each app must perform only the functions which are necessary for processing the entity implemented in it: market, account, authorization etc. &lt;/p&gt;

&lt;p&gt;VonmoTrade is based on horizontal scalability principle. Each application could have many instances to provide fault-tolerance and required performance level. &lt;/p&gt;

&lt;h2&gt;
  
  
  Technology stack
&lt;/h2&gt;

&lt;p&gt;To avoid generating entropy, let’s limit the languages and approaches used. &lt;br&gt;
For server, this set will include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Erlang. To my mind, a perfect language to deal with infrastructure things.&lt;/li&gt;
&lt;li&gt;Rust. Excellent for system-related things and optimization issues.&lt;/li&gt;
&lt;li&gt;PostgreSQL. Serves as the main base for long-term data storage. &lt;/li&gt;
&lt;li&gt;Tarantool. Serves as hot data storage (for MVP only)&lt;/li&gt;
&lt;li&gt;Clickhouse. For logs analysis and deep analytics. &lt;/li&gt;
&lt;li&gt;Linux. The system must support modern distributions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The client software is implemented with the help of Vue framework and Vuex. &lt;/p&gt;

&lt;p&gt;As the project presupposes a big number of components, we will write a lot of property-based tests and integrative tests to ensure an adequate level of quality of the final solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plans
&lt;/h2&gt;

&lt;p&gt;In foreseeable future I’m planning to review the basics of the theory and delve into practice in the following directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orders. Types, processing features, aspects of trade information storing.&lt;/li&gt;
&lt;li&gt;Order book. Visual representation of the market. &lt;/li&gt;
&lt;li&gt;Bidding history. Prints, graphs, personal history. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As they say... Like, Share, Subscribe :))&lt;/p&gt;

&lt;p&gt;Thanks to Nadezda Popova for proofreading.&lt;/p&gt;

</description>
      <category>erlang</category>
      <category>rust</category>
      <category>functional</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
