Expérience VonmoTrade. Partie 4: Tableaux de négociation


Dans des articles antérieurs, nous avons compris comment les commandes commerciales étaient créées et traitées. Le sujet de cet article sera les questions de traitement et de stockage des informations nécessaires aux outils graphiques d'analyse de marché - graphiques boursiers.


Avant de commencer, je veux faire une petite digression. Pour les projets Vonmo internes, le schéma de dénomination habituel des mots V + est utilisé, qui décrit le plus succinctement les fonctions du projet. Aujourd'hui, j'ai découvert que VTrade est une entreprise existante. Afin d'éviter toute confusion, j'ai renommé l'expérience en VonmoTrade.


Pour évaluer l'état du marché, un seul carnet de commandes et l'historique des transactions ne suffisent pas. Nous avons besoin d'un outil qui nous permette d'identifier clairement et rapidement la tendance du prix du marché. Les graphiques de trading peuvent être divisés en deux types:


  1. Linéaire;
  2. Intervalle

Graphiques linéaires


L'horaire le plus simple et le plus compréhensible sans préparation. Affiche la dépendance du prix d'un instrument financier sur le temps.



Le principal avantage de ce type de graphique est la simplicité. Le principal inconvénient en découle - une faible teneur en informations.


Si le graphique est basé sur des données brutes, le prix de la dernière clôture est pris. Mais généralement, les graphiques sont construits sur la base de données agrégées. Dans ce cas, le cours de clôture de chaque intervalle est pris. Puisque nous rejetons tout ce qui s'est passé dans l'intervalle et prenons uniquement le prix de clôture de l'intervalle, de ce fait, le contenu des informations est perdu.


Résolution graphique


Si nous commençons à construire un graphique sur la base de tous les changements de prix, c'est-à-dire que chaque transaction clôturée tombera sur le graphique, alors il sera difficile pour une personne de le percevoir. Et l'énergie dépensée pour le traitement et la livraison d'un tel calendrier sera dépensée de manière inefficace.


Par conséquent, les données sont affinées en divisant l'axe du temps en intervalles et en agrégeant les prix dans ces intervalles.
Résolution - la taille de l'intervalle élémentaire de division de l'axe du temps: seconde, minute, heure, jour, etc.


Bars


Se rapportent aux graphiques d'intervalle. Pour augmenter le contenu des informations, il est nécessaire pour chaque intervalle de temps d'afficher les informations de prix au début et à la fin de l'intervalle, ainsi que le prix maximum et minimum. L'affichage graphique de cet ensemble est appelé une barre. Considérez le schéma d'une barre:



La séquence de barres forme un graphique:



Bougies japonaises


Comme les barres, reportez-vous aux tableaux d'intervalles. Ils sont le type de graphique le plus populaire en analyse technique. Une bougie se compose d'un corps noir ou blanc et d'ombres: supérieures et inférieures. Parfois, une ombre s'appelle une mèche. La bordure supérieure et inférieure de l'ombre affiche les prix maximum et minimum pour la période correspondante. Les limites du corps affichent les prix d'ouverture et de clôture. Tirons une bougie:



La séquence des bougies forme un graphique:



Notation OHLCV


Dans le dernier article, nous avons déterminé le schéma de stockage des données pour le graphique dans postgresql et créé un tableau pour la source de données qui stockera les données agrégées:


CREATE TABLE df ( t timestamp without time zone NOT NULL, r df_resolution NOT NULL DEFAULT '1m'::df_resolution, o numeric(64,32), h numeric(64,32), l numeric(64,32), c numeric(64,32), v numeric(64,32), CONSTRAINT df_pk PRIMARY KEY (t, r) ) 

Les champs ne nécessitent pas d'explication, sauf que le champ r est la résolution de la série. Il existe des énumérations dans postgresql, il est pratique de les utiliser lorsqu'un ensemble de valeurs pour un champ est connu à l'avance. A travers les énumérations, nous définissons un nouveau type pour les résolutions de graphes autorisées. Que ce soit une série d'une minute à un mois:


 CREATE TYPE df_resolution AS ENUM ('1m', '3m', '5m', '15m', '30m', '45m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w', '1M'); 

Il est important de trouver un équilibre entre les performances du système de disques, du processeur et le coût total de possession. Le système définit actuellement 16 résolutions. Deux solutions sont évidentes:


  • Nous pouvons compter et stocker toutes les résolutions dans la base de données. L'option est pratique dans la mesure où lors de l'échantillonnage, nous ne dépensons pas d'énergie pour l'agrégation des intervalles, toutes les données sont immédiatement prêtes pour la sortie. En un mois, un peu plus de 72 000 enregistrements seront créés pour un instrument. Cela semble simple et pratique, cependant, une telle table changera trop souvent, car pour chaque mise à jour de prix, il est nécessaire de créer ou de mettre à jour 16 entrées dans la table et de reconstruire l'index. Dans postgresql, il peut également y avoir un problème avec la récupération de place.
  • Une autre option consiste à stocker une seule résolution de base. Lors de la sélection d'une résolution de base, les résolutions requises doivent être créées. Par exemple, lors du stockage de la résolution des minutes comme base par mois, 43 000 enregistrements seront créés pour chaque instrument. Ainsi, par rapport à la version précédente, le volume d'enregistrement et les frais généraux sont réduits de 40%. Cependant, la charge du processeur augmente.

Comme mentionné ci-dessus, il est important de trouver un équilibre. Par conséquent, une option de compromis n'est pas de stocker une résolution de base, mais plusieurs: 1 minute, 1 heure, 1 jour. Avec ce schéma, 44,6 mille enregistrements seront créés pour chaque instrument par mois. L'optimisation du volume d'enregistrement sera de 36%, mais la charge sur le processeur sera acceptable. Par exemple, pour créer des intervalles hebdomadaires au lieu de lire et d'agréger 10 080 enregistrements dans le cas d'une résolution de base minute, nous devons lire à partir du disque et agréger les données des résolutions de 7 jours seulement.


Stockage OHLCV


Par nature, OHLCV est une série chronologique. Comme vous le savez, une base de données relationnelle n'est pas très appropriée pour stocker et traiter de telles données. Pour résoudre ces problèmes, le projet utilise l'extension Timescale .


L'échelle de temps améliore les performances des opérations d'insertion et de mise à jour, vous permet de configurer le partitionnement et fournit des fonctions analytiques optimisées spécifiquement pour travailler avec des séries chronologiques.


Pour créer et mettre à jour des barres, nous n'avons besoin que de fonctions standard:


  • date_trunc('minute' | 'hour' | 'day', transaction_ts) - pour trouver le début de l'intervalle de résolution des minutes, heures et jours, respectivement.
  • greatest et le least pour déterminer les prix maximum et minimum.

Grâce à l'API upsert, une seule demande de mise à jour est exécutée par transaction.
J'ai obtenu ce type de SQL pour corriger les changements du marché dans les résolutions de base:


 FOR i IN 1 .. array_upper(storage_resolutions, 1) LOOP resolution = storage_resolutions[i]; IF resolution = '1m' THEN SELECT DATE_TRUNC('minute', ts) INTO bar_start; ELSIF resolution = '1h' THEN SELECT DATE_TRUNC('hour', ts) INTO bar_start; ELSIF resolution = '1d' THEN SELECT DATE_TRUNC('day', ts) INTO bar_start; END IF; EXECUTE format( '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(%Ih, %L::numeric), l = LEAST(%Il, %L::numeric), c = %L::numeric, v = %Iv + %L::numeric;', df_table, bar_start, resolution, price, price, price, price, volume, df_table, price, df_table, price, price, df_table, volume ); END LOOP; 

Lors de l'échantillonnage, pour l'agrégation des intervalles, nous avons besoin des fonctions suivantes:


  • time_bucket - pour diviser en intervalles
  • first - pour trouver le prix d'ouverture - O
  • max - le prix le plus élevé pour l'intervalle - H
  • min - le prix le plus bas par intervalle - L
  • last - pour trouver le cours de clôture - C
  • sum - pour trouver le volume des transactions - V

Le seul problème rencontré lors de l'utilisation de Timescale est les limitations de la fonction time_bucket . Il vous permet de fonctionner uniquement à des intervalles inférieurs à un mois. Pour créer une résolution mensuelle, vous devez utiliser la fonction standard date_trunc .


API


Pour afficher des graphiques sur le client, nous utiliserons des graphiques légers de Tradingview. La bibliothèque vous permet de personnaliser complètement l'apparence des graphiques et est pratique à utiliser. J'ai obtenu les graphiques suivants:



Étant donné que la majeure partie de l'interaction entre le navigateur et la plate-forme se fait via Websocket, il n'y a aucun problème d'interactivité.


Source de données


La source de données des graphiques (flux de données) doit renvoyer la partie nécessaire de la série chronologique dans la résolution requise. Dans le même temps, pour économiser du trafic et réduire le temps de traitement sur le client, le serveur doit emballer les points.


L'API du flux de données doit initialement être conçue de sorte que vous puissiez demander plusieurs graphiques en une seule demande et vous abonner à leurs mises à jour. Cela réduira le nombre de commandes et de réponses dans le canal.


Prenons un exemple de la dernière demande de 50 minutes pour USDGBP, avec un abonnement automatique aux mises à jour des graphiques:


 { "m":"market", "c":"get_chart", "v":{ "charts":[ { "ticker":"USDGBP", "resolution":"1h", "from":0, "cnt":50, "send_updates":true } ] } } 

Vous pouvez bien sûr demander une plage de dates (de, à), mais comme l'intervalle de chaque barre est connu, l'API déclarative indiquant le moment et le nombre de barres me semble plus pratique.
Le flux de données pour cette demande répondra de la même manière:


 { "m":"market", "c":"chart", "v":{ "bar_fields":[ "t","uts","o","h","l","c","v" ], "items":[ { "ticker":"USDGBP", "resolution":"1h", "bars":[ [ "2019-12-13 13:00:00",1576242000,"0.75236800", "0.76926400","0.75236800","0.76926400","138.10000000" ], .... ] } ] } } 

Le champ bar_fields contient des informations sur les positions des éléments. Une optimisation supplémentaire consiste à placer ce champ dans la configuration client qu'il reçoit du serveur au démarrage.


Ainsi, le client reçoit la partie nécessaire des données historiques et construit l'état initial du graphique. Si l'état change, il reçoit une mise à jour qui n'affecte que la dernière barre.


 { "m":"market", "c":"chart_tick", "v":{ "ticker":"USDGBP", "resolution":"1h", "items":{ "v":"140.600", "ut":1576242000, "t":"2019-12-13T13:00:00", "o":"0.752368", "l":"0.752368", "h":"0.770531", "c":"0.770531" } } } 

Résultat préliminaire


Tout au long de la série d'articles, nous avons analysé la théorie et la pratique de la construction d'un échange. Il est temps de remonter le système.


Dans le prochain article, nous aborderons le développement des interfaces utilisateurs graphiques: UI de service pour la gestion de plateforme et UI pour les utilisateurs finaux. Une version de démonstration de Vonmo Trade sera également présentée.

Source: https://habr.com/ru/post/fr483308/


All Articles