Compare commits

...
Author SHA1 Message Date
John Colvin 0e08bad534 cleanup: Remove unnecessary includes 2025-03-04 05:02:35 +00:00
John Colvin 09573167b8 feat: Add binance gw for cross margin orders
Spots only
2025-03-04 05:02:35 +00:00
John Colvin caa03dcfa7 refactor: Rename user_data_streams dir, ns without plural 's'
There is one user data stream for Binance Spots, and another for Usds
Futures. We model both of these user data streams in separate folders,
so each "user_data_stream" dir represents a single stream.
2025-03-03 19:32:16 +00:00
John Colvin d010ba7ae7 refactor: Abstract out common binance gw code
For all sub-venues to use
2025-03-03 19:32:16 +00:00
John Colvin c60df8bcfa refactor: Rename binance exe gw filenames so they are unique
The debugger gets confused when a particular lib contains multiple
source files with the same name (in different dirs).
2025-03-03 19:32:16 +00:00
John Colvin 634e69f72b fix: Trim account name string when converting to Fill proto 2025-03-03 19:32:16 +00:00
John Colvin 2397a15b23 fix: Disable CURLOPT_FAILONERROR to always get response body
Even when the http error code indicates failure. The payload may contain useful
information about the error.
2025-03-03 19:32:16 +00:00
John Colvin dfacf73289 Improve logging/error reporting in binance execution gw
In the process, consolidate binance exe report stores into single class.
2025-03-03 19:32:16 +00:00
John Colvin 0339fe9749 cleanup: Specify bts account directly in binance gw config
This eliminates the need for an ExchangeSubAccountResolver.
2025-03-03 19:32:16 +00:00
John Colvin ff1d71a36d cleanup: Replace includes w/ fwd declares in header 2025-03-03 19:32:16 +00:00
John Colvin 5b9fc54ea6 fix: Reject orders w/ invalid TimeInForce at binance
Do this internally (before sending them to the exchange)
2025-03-03 19:32:16 +00:00
John Colvin 24f0eef2eb fix: Report internal reject msgs to ExecutionReport
For Binance execution gateways (spots and usds futures)
2025-03-03 19:32:16 +00:00
John Colvin 01e90c2497 Add reconnect behavior to WebsocketClient 2025-03-03 19:32:16 +00:00
John Colvin a62b4af667 cleanup: Perform basic init in WebsocketClient ctr 2025-03-03 19:32:16 +00:00
John Colvin 3aebd25838 Move config profiles dir to server/sparkd 2025-03-03 19:32:16 +00:00
John Colvin c31b44ecfb diag: Log when config server starts and show url 2025-03-03 19:32:16 +00:00
John Colvin 19efb77e07 tool: Add some usds futures requests to binance.env 2025-02-24 15:12:45 +00:00
John Colvin 76de640421 diag: Show Binance sub-venue in log message prefix
Otherwise it's hard to tell which Binance venue the log is about (e.g.,
spots, usds futures, etc.).
2025-02-24 06:16:22 +00:00
John Colvin fb030742d3 Clean up binance_orders_client.sh a bit, add docs 2025-02-24 06:06:15 +00:00
John Colvin 73608f1b45 perf: Run each ccapi md session on its own thread 2025-02-24 06:04:52 +00:00
John Colvin 58aebe7002 diag: Reduce log noise in CCApiMarketDepthAdaptor 2025-02-24 06:04:52 +00:00
John Colvin 299cb0b29e diag: Log when curl_global_init/cleanup is called
It should only happen once for the whole app (so it shouldn't be noisy).
2025-02-23 20:08:52 +00:00
John Colvin 06663f711e improvement: Only call curl_global_init/cleanup once
Avoids unnecessary overhead (plus their docs say it only needs to be
done once for the whole app). These fns have "global" in their name for
a reason.

https://curl.se/libcurl/c/curl_global_init.html
2025-02-23 20:08:52 +00:00
John Colvin 51479f4885 fix: Disable signal handling in curl
This avoids a strange stack corruption bug related to set/longjmp.

https://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
2025-02-23 20:08:50 +00:00
John Colvin 5b520ba241 cleanup: Get rid of custom fmt::formatter for security proto
Security proto objects are large, so we shouldn't pass them by value to
the log system. The custom formatter's presence makes this easy to do by
accident.
2025-02-23 19:05:42 +00:00
John Colvin f0651a5341 tool: Add flag to roll logs without running sparkd 2025-02-23 15:36:10 +00:00
John Colvin 897e0990a2 diag: Improve logging in SecurityCache 2025-02-23 15:36:10 +00:00
John Colvin 30e1ae5c3c Add Binance execution to config profile 2025-02-23 15:36:10 +00:00
John Colvin efe83f81ec fix: Index live securities (as well as db ones) in SecurityCache 2025-02-23 15:36:10 +00:00
John Colvin 5fb21ada97 diag: Add sec id to PositionMap log 2025-02-23 15:36:10 +00:00
John Colvin 58edd07f6b fix: Check for empty string in strn fns 2025-02-23 15:36:10 +00:00
John Colvin 9d81354c3a Add binance_orders_client.sh for quick/easy troubleshooting 2025-02-23 15:36:10 +00:00
John Colvin 0f37be89a4 Fix typos in binance.env 2025-02-23 15:36:10 +00:00
John Colvin f6586fe636 Activate ccapi exchanges according to md config at runtime
Previously, the list of exchanges ccapi would activate was determined at
build time.
2025-02-23 15:36:08 +00:00
John Colvin 7827ccc0e7 Update externals to get ccapi v6.12.0 2025-02-23 15:35:35 +00:00
John Colvin af6a2acc0a perf: Send Binance md rest reqs on background thread 2025-02-23 06:10:36 +00:00
John Colvin 84abf59a55 feat: Add binance spots execution 2025-02-23 06:10:36 +00:00
John Colvin 2fc1d30bf1 Add binance_usds_futures_testnet config profile
To activate this config profile, run:

tools/sparkd_profile.sh binance_usds_futures_testnet
2025-02-23 06:10:36 +00:00
John Colvin 4dcd5d63a1 tool: Add script to easily switch between spark.conf profiles 2025-02-23 06:10:36 +00:00
John Colvin 63e5697b20 Unify conf and conf_json dirs
Put all files (.conf and .json) in sparkdata/conf. This allows us to
implement config profiles via symlink.
2025-02-23 06:10:36 +00:00
John Colvin 8e5cb100a7 fix: Assign BTS account to ExecutionReport
Not external exch account
2025-02-23 06:10:36 +00:00
John Colvin 938bee1817 Validate ExecutionReport before invoking callback 2025-02-23 06:10:36 +00:00
John Colvin d48af15480 diag: Log warning if ExecutionReport acct not same as order def 2025-02-23 06:10:36 +00:00
John Colvin e3b2e98cd9 fix build: Add JsonReader.cpp to client & api server builds 2025-02-20 00:55:48 -05:00
John Colvin ee53fb854c ui: Make buy/sell button bg darker in order ticket
The buttons looked washed out; too bright, yet pale.
2025-02-20 00:49:59 -05:00
Taha d36e2d61f6 Fixing PNL for both crypto and non crypto 2025-02-19 19:06:12 -05:00
John Colvin bfff3ea5fe fix (preemptive): Check for null sec in ExecutionReport::to_string
I don't think it's ever a nullptr in practice, but if that changes, this
fn shouldn't seg fault.
2025-02-19 23:14:18 +00:00
John Colvin 059c03d289 cleanup: Removing docker submodules again
...they're back. It's like Night of the Living Dead submodules!
2025-02-19 22:50:29 +00:00
John Colvin 8c561f7886 improvement: Pass account/order updates by move (not value) 2025-02-19 22:50:29 +00:00
John Colvin 288567d1eb cleanup: Pass JetFIX cfg directly to binance usds futs exe gw 2025-02-19 22:50:29 +00:00
John Colvin ecfc5405fe refactor: Move macro defined json fns to cpp file
Some fns are big (shouldn't be inlined); plus its best to avoid exposing
macros in headers.
2025-02-19 22:50:29 +00:00
John Colvin 8db34a2a11 Add json & fmt formatters for order & sec protos 2025-02-19 22:50:29 +00:00
John Colvin 91094c4d84 refactor: Make parse_uint support generic char iterator 2025-02-19 22:50:29 +00:00
John Colvin dae8c85d73 fix: Check strlen when making order/fill keys
Although the string data is stored in a fixed size array, it isn't
necessarily the full length.
2025-02-19 22:50:29 +00:00
John Colvin abdd98082f Move BrokerProxy do_work fn to cpp
Doesn't make sense to inline; it's too big.
2025-02-19 22:50:29 +00:00
John Colvin 2f6aa785ab Use object_pool wrapper to count allocations and assert if too many 2025-02-19 22:50:29 +00:00
John Colvin 7acf1f7043 diag: Improve logging in PositionMap 2025-02-19 22:49:30 +00:00
John Colvin 1c3c7a078a diag: Improve logging in binance usds futures exe gateway
Also propagate error msgs through the execution report (so we can see
them in the front end).
2025-02-19 22:49:30 +00:00
John Colvin 93ace52bfa diag: Periodically log ActorThread work cycle speed
So we can better detect slowness, blockage, etc.
2025-02-19 22:49:30 +00:00
John Colvin b5d97c0b1b refactor: Consolidate ExecutionGatewayProvider/Factory code
It's easier to read and manage code when you're not constantly jumping
to and fro across a bunch of files.
2025-02-19 22:49:30 +00:00
John Colvin 284aa53ad4 Add script to run sparkd and roll over log w/ timestamp suffix
So each run gets its own log file
2025-02-19 22:49:30 +00:00
John Colvin 8a9952f52d diag: Improve logging in ServiceBridge 2025-02-19 22:49:30 +00:00
John Colvin 748aafaab3 cleanup: Remove unnecessary includes 2025-02-19 22:49:30 +00:00
John Colvin 3ba30c70de cleanup: Normalize indentation in orderman_cfg.conf
It was all over the place before; unreadable
2025-02-19 22:49:30 +00:00
John Colvin e72f465999 diag: Improve logging in OrderManager 2025-02-19 22:49:30 +00:00
John Colvin f3d93f7749 fix leak: Replace object pool raw ptrs w/ shared ptrs 2025-02-19 22:49:30 +00:00
Mohammad Arham 8d142b8a74 Fix: forgot to push a file 2025-02-19 03:36:14 +00:00
Mohammad Arham 8210b9f35f Fix: change onix exe so it uses dispatch fix msgs 2025-02-19 03:33:00 +00:00
Mohammad Arham 12b20b05ce Fix: OKX executions sends er only when dispatch called 2025-02-18 16:02:10 +00:00
Mohammad Arham 72efbccdbd Fix: USDC swaps md working for okx 2025-02-14 23:02:49 +00:00
Mohammad Arham 33643e9eab Fix: make log resize work again 2025-02-14 22:59:35 +00:00
Mohammad Arham 0f6dc6f40b Fix: Typo in Okx execution 2025-02-13 23:38:27 +00:00
Mohammad Arham 8c778f5e41 Fix: Added tdmode to config for okx execution 2025-02-13 23:32:53 +00:00
Mohammad Arham f308654200 Fix: okx execution fix large spreader orderd 2025-02-13 23:27:32 +00:00
Mohammad Arham bdf5b980fc Add: config for sim trading 2025-02-13 22:06:23 +00:00
216 changed files with 10493 additions and 2167 deletions

No files matched your search

Submodule docker/linux deleted from 56c61231f3.
Submodule docker/windows deleted from de7d40c09d.
+161 -161
View File
@@ -1,161 +1,161 @@
<Window
x:Class="mbts.ui.spark.OrderTicket"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
Style="{StaticResource BTSWindowTitleTemplate}"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Sell" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.ask}"
Padding="2"
Height="Auto"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Buy" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.bid}"
Padding="2"
Height="Auto"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Esc" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
</Window.InputBindings>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="120"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition MinWidth="120"/>
</Grid.ColumnDefinitions>
<usp:OrderModificationControl Grid.ColumnSpan="3" />
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
</Grid>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource TextBlock.Dark}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Text="{Binding security, Mode=OneWay}" />
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
<Binding Path="bid_size" Mode="OneWay"/>
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="bid"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="ask"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
<Binding Path="ask_size" Mode="OneWay"/>
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
</TextBlock.Inlines>
</TextBlock>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
</Window>
<Window
x:Class="mbts.ui.spark.OrderTicket"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
Style="{StaticResource BTSWindowTitleTemplate}"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask_dark}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Sell" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.ask}"
Padding="2"
Height="Auto"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.bid_dark}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Buy" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.bid}"
Padding="2"
Height="Auto"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Esc" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
</Window.InputBindings>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="120"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition MinWidth="120"/>
</Grid.ColumnDefinitions>
<usp:OrderModificationControl Grid.ColumnSpan="3" />
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
</Grid>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource TextBlock.Dark}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Text="{Binding security, Mode=OneWay}" />
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
<Binding Path="bid_size" Mode="OneWay"/>
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="bid"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="ask"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
<Binding Path="ask_size" Mode="OneWay"/>
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
</TextBlock.Inlines>
</TextBlock>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
</Window>
+101 -30
View File
@@ -4,6 +4,9 @@ using bts.api;
using BCG;
using mbts.co2;
using System.Xml.Serialization;
using ZstdSharp.Unsafe;
using bts.proto;
namespace mbts.analytics {
// Pairs ITradeLogRow along with the data (theo, md) that is needed to compute
@@ -18,20 +21,87 @@ namespace mbts.analytics {
public SecurityImmutable security { get { return md.security; } }
private double _option_pnl(double trade_price, double now_price) {
double coin_terms = (now_price - trade_price) * tl_row.size;
double usd_terms = coin_terms * driver;
return usd_terms;
// private double _option_pnl(double trade_price, double now_price) {
// double coin_terms = (now_price - trade_price) * tl_row.size;
// double usd_terms = coin_terms * driver;
// return usd_terms;
// }
private bool _is_notional {
get {
return false;
}
}
private double _future_pnl(double trade_price, double now_price) {
// the qty is in USD, so this is the initial cash flow
double init_asset_value = tl_row.size;
double asset_size = init_asset_value / tl_row.price;
double now_asset_value = asset_size * now_price;
return now_asset_value - init_asset_value;
private bool _is_inverse_option_contract {
get {
return false;
}
}
private double _cpp {
get {
return 1.0;
}
}
private bool _is_reverse_option_contract {
get {
// check for Deribit options
return false;
}
}
private double _option_pnl {
get {
if (_is_notional) {
var now_price = theo;
double coin_terms = (now_price - tl_row.price) * tl_row.size;
double usd_terms = coin_terms * driver;
return usd_terms;
} else {
return _cpp * tl_row.size * (theo - tl_row.price);
}
}
}
private double _spot_pnl {
get {
// both are computed the same way
return _future_pnl;
}
}
private double _future_pnl {
get {
if (_is_notional) {
// TODO:
// let it fall through right now
// the qty is in USD, so this is the initial cash flow
var now_price = theo;
double init_asset_value = tl_row.size;
double asset_size = init_asset_value / tl_row.price;
double now_asset_value = asset_size * now_price;
return now_asset_value - init_asset_value;
} else {
// TODO: is position pnl, compute it wrt settlement price?
return tl_row.size * (theo - tl_row.price);
}
}
}
// private double _future_pnl(double trade_price, double now_price) {
// if (_is_notional) {
// // the qty is in USD, so this is the initial cash flow
// double init_asset_value = tl_row.size;
// double asset_size = init_asset_value / tl_row.price;
// double now_asset_value = asset_size * now_price;
// return now_asset_value - init_asset_value;
// } else {
// return tl_row.size * (theo - tl_row.price);
// }
// }
public double driver {
get {
@@ -46,9 +116,16 @@ namespace mbts.analytics {
public double theo {
get {
switch (tl_row.security_type) {
// TODO:
case SecurityType.Option:
return tl_row.call_put == PutOrCall.Call ?
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
if (_is_inverse_option_contract) {
return tl_row.call_put == PutOrCall.Call ?
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
} else {
return tl_row.call_put == PutOrCall.Call ?
opt_theo.call : opt_theo.put;
}
// future and options, just get mid market
case SecurityType.Future:
//return fut_theo.theo;
case SecurityType.Spot:
@@ -62,28 +139,22 @@ namespace mbts.analytics {
public double pnl {
get {
double rv = Double.NaN;
if (is_day_trade) {
if (tl_row.security_type == SecurityType.Future) {
rv = _future_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Option) {
rv = _option_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Spot) {
rv = tl_row.size * (theo - tl_row.price);
}
return rv;
switch (tl_row.security_type) {
case SecurityType.Option:
rv = _option_pnl;
break;
case SecurityType.Future:
rv = _future_pnl;
break;
case SecurityType.Spot:
// = tl_row.size * (theo - tl_row.price);
rv = _spot_pnl;
break;
}
// use settle price ?
if (tl_row.security_type == SecurityType.Future) {
rv = _future_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Option) {
rv = _option_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Spot) {
rv = tl_row.size * tl_row.price - settle_price;
}
return rv;
}
}
public double settle_price {
get {
return md.md == null ? Double.NaN : md.md.settlement_price;
@@ -216,8 +216,10 @@ namespace mbts.ui {
public static BTSColor ask_text => BTSCoreColors.red_dark;
public static BTSColor bid_text => BTSCoreColors.blue;
public static BTSColor bid => 0xff76b7fa;
public static BTSColor bid_dark => 0xff26239a;
public static BTSColor bid_alt => BTSCoreColors.blue_light.with(0x70);
public static BTSColor ask => 0xffff6666;
public static BTSColor ask_dark => 0xff7a2331;
public static BTSColor ask_alt => 0x70ffb4b4;
public static BTSColor last => BTSCoreColors.black;
public static BTSColor bid_ask_alt_blend => BTSCoreColors.purple_light;
@@ -19,7 +19,7 @@
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}"/>
<Setter Property="Background" Value="{u:BTSBrush MarketData.ask_dark}"/>
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}" />
<Setter Property="MinHeight" Value="40"/>
@@ -32,7 +32,7 @@
</Style>
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}"/>
<Setter Property="Background" Value="{u:BTSBrush MarketData.bid_dark}"/>
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}" />
+174
View File
@@ -0,0 +1,174 @@
#include "OrderService.h"
#include "lib/api/services/OrderService.pb.h"
namespace bts::api::orderman {
#define PARSE_STRING(name, dest, it) \
it = j.find(#name); \
if (it != j.end()) { \
dest.mutable_##name()->clear(); \
it->get_to(*dest.mutable_##name()); \
}
#define PARSE_VALUE(name, dest, it) \
it = j.find(#name); \
if (it != j.end()) { \
dest.set_##name(it->get<decltype(dest.name())>()); \
}
#define PARSE_STRUCT(name, dest, it) \
it = j.find(#name); \
if (it != j.end()) { \
it->get_to(*dest.mutable_##name()); \
}
#define PARSE_ARRAY(name, dest, it) \
it = j.find(#name); \
if (it != j.end() && it->is_array()) { \
c.mutable_##name()->Clear(); \
it->get_to(*c.mutable_##name()); \
}
#define PARSE_ONEOF(name, dest, it) \
if (!parsed_oneof) { \
it = j.find(#name); \
if (it != j.end()) { \
it->get_to(*dest.mutable_##name()); \
parsed_oneof = true; \
} \
}
#define PARSE_ONEOFS(for_each) \
{ \
bool parsed_oneof = false; \
for_each(PARSE_ONEOF) \
}
#define WRITE_VALUE(src, name) j[#name] = src.name()
#define WRITE_STRUCT(src, name) \
if (src.has_##name()) { \
j[#name] = src.name(); \
}
#define WRITE_IF_NOT_ZERO(src, name) \
if (src.name() != 0) { \
j[#name] = src.name(); \
}
#define WRITE_IF_NOT_NAN(src, name) \
if (!std::isnan(src.name())) { \
j[#name] = src.name(); \
}
#define WRITE_IF_NOT_EMPTY(src, name) \
if (!src.name().empty()) { \
j[#name] = src.name(); \
}
#define WRITE_TIMESTAMP(src, name) \
j[#name] = bts::core::TimeStamp::from_nanos( \
src.name().seconds() * 1'000'000'000 + src.name().nanos()) \
.to_iso_string()
#define PARSE_TIMESTAMP(name, dest, it) \
it = j.find(#name); \
if (it != j.end()) { \
bts::core::TimeStamp tmp_ts = \
bts::core::TimeStamp::from_iso_string(it->get<std::string>()); \
tmp_ts.to_protobuf(*dest.mutable_##name()); \
}
void to_json(nlohmann::json &j, const Fill &f) {
WRITE_IF_NOT_ZERO(f, order_key);
WRITE_IF_NOT_ZERO(f, security_key);
WRITE_IF_NOT_EMPTY(f, account);
WRITE_IF_NOT_NAN(f, last_price);
WRITE_IF_NOT_NAN(f, avg_price);
WRITE_VALUE(f, side);
WRITE_IF_NOT_ZERO(f, last_shares);
WRITE_IF_NOT_EMPTY(f, fill_key);
WRITE_TIMESTAMP(f, timestamp);
WRITE_IF_NOT_ZERO(f, internal_account_id);
WRITE_VALUE(f, fill_kind);
WRITE_VALUE(f, fill_status);
}
void from_json(const nlohmann::json &j, Fill &f) {
nlohmann::json::const_iterator it;
PARSE_VALUE(order_key, f, it);
PARSE_VALUE(security_key, f, it);
PARSE_STRING(account, f, it);
PARSE_VALUE(last_price, f, it);
PARSE_VALUE(avg_price, f, it);
PARSE_VALUE(side, f, it);
PARSE_VALUE(last_shares, f, it);
PARSE_STRING(fill_key, f, it);
PARSE_TIMESTAMP(timestamp, f, it);
PARSE_VALUE(internal_account_id, f, it);
PARSE_VALUE(fill_kind, f, it);
PARSE_VALUE(fill_status, f, it);
}
void to_json(nlohmann::json &j, const NativeOrderState &n) {
WRITE_VALUE(n, exchange_order_id);
WRITE_VALUE(n, last_msg_seq_num);
WRITE_VALUE(n, sub_error_code);
WRITE_VALUE(n, native_status);
WRITE_VALUE(n, routing_record);
WRITE_VALUE(n, native_type);
WRITE_VALUE(n, last_transact_time);
WRITE_VALUE(n, last_transact_time_unix_ns);
WRITE_VALUE(n, stop_px);
WRITE_VALUE(n, exchange_order_id_u64);
}
void from_json(const nlohmann::json &j, NativeOrderState &n) {
nlohmann::json::const_iterator it;
PARSE_STRING(exchange_order_id, n, it);
PARSE_VALUE(last_msg_seq_num, n, it);
PARSE_VALUE(sub_error_code, n, it);
PARSE_VALUE(native_status, n, it);
PARSE_STRUCT(routing_record, n, it);
PARSE_VALUE(native_type, n, it);
PARSE_STRING(last_transact_time, n, it);
PARSE_VALUE(last_transact_time_unix_ns, n, it);
PARSE_VALUE(stop_px, n, it);
PARSE_VALUE(exchange_order_id_u64, n, it);
}
void to_json(nlohmann::json &j, const RoutingRecord &r) {
WRITE_IF_NOT_EMPTY(r, session);
WRITE_IF_NOT_EMPTY(r, account);
WRITE_IF_NOT_EMPTY(r, operator_id);
WRITE_VALUE(r, cti_code);
WRITE_VALUE(r, customer_or_firm);
WRITE_IF_NOT_EMPTY(r, smp_id);
WRITE_VALUE(r, smp_instruction);
WRITE_IF_NOT_EMPTY(r, location);
WRITE_IF_NOT_EMPTY(r, cust_order_handing_inst);
WRITE_IF_NOT_EMPTY(r, sender_compid);
WRITE_IF_NOT_EMPTY(r, clearing_firm);
WRITE_IF_NOT_EMPTY(r, customer_account_ref_id);
WRITE_IF_NOT_EMPTY(r, account_code);
WRITE_IF_NOT_EMPTY(r, company_id);
WRITE_IF_NOT_EMPTY(r, authorized_trader);
WRITE_IF_NOT_EMPTY(r, authorized_group);
WRITE_IF_NOT_EMPTY(r, cust_order_handing_inst);
WRITE_IF_NOT_EMPTY(r, order_server_id);
}
void from_json(const nlohmann::json &j, RoutingRecord &r) {
nlohmann::json::const_iterator it;
PARSE_STRING(session, r, it);
PARSE_STRING(account, r, it);
PARSE_STRING(operator_id, r, it);
PARSE_VALUE(cti_code, r, it);
PARSE_VALUE(customer_or_firm, r, it);
PARSE_STRING(smp_id, r, it);
PARSE_VALUE(smp_instruction, r, it);
PARSE_STRING(location, r, it);
PARSE_STRING(cust_order_handling_inst, r, it);
PARSE_STRING(sender_compid, r, it);
PARSE_STRING(clearing_firm, r, it);
PARSE_STRING(customer_account_ref_id, r, it);
PARSE_STRING(account_code, r, it);
PARSE_STRING(company_id, r, it);
PARSE_STRING(authorized_trader, r, it);
PARSE_STRING(authorized_group, r, it);
PARSE_STRING(cust_order_handing_inst, r, it);
PARSE_STRING(order_server_id, r, it);
}
} // namespace bts::api::orderman
+61 -9
View File
@@ -1,6 +1,10 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/api/services/native_order_type.h"
#include "lib/api/services/order_status.h"
#include "lib/api/services/time_in_force.h"
#include "lib/api/services/trigger_price_type.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
@@ -10,22 +14,70 @@
namespace bts::api::orderman {
PB_ENUM_FROM_JSON(OrderStatus)
PB_ENUM_TO_JSON(OrderStatus)
PB_ENUM_FROM_JSON(Fill_FillKind)
PB_ENUM_TO_JSON(Fill_FillKind)
PB_ENUM_FROM_JSON(TriggerPriceType)
PB_ENUM_TO_JSON(TriggerPriceType)
PB_ENUM_FROM_JSON(Fill_FillStatus)
PB_ENUM_TO_JSON(Fill_FillStatus)
void to_json(nlohmann::json &j, const Fill &f);
void from_json(const nlohmann::json &j, Fill &f);
PB_ENUM_FROM_JSON(OrderSubType)
PB_ENUM_TO_JSON(OrderSubType)
PB_ENUM_FROM_JSON(SelfMatchPreventionInstruction)
PB_ENUM_TO_JSON(SelfMatchPreventionInstruction)
PB_ENUM_FROM_JSON(CustomerOrFirm)
PB_ENUM_TO_JSON(CustomerOrFirm)
void to_json(nlohmann::json &j, const RoutingRecord &r);
void from_json(const nlohmann::json &j, RoutingRecord &r);
PB_ENUM_FROM_JSON(NativeOrderStatus)
PB_ENUM_TO_JSON(NativeOrderStatus)
PB_ENUM_FROM_JSON(NativeOrderState_SubError)
PB_ENUM_TO_JSON(NativeOrderState_SubError)
void to_json(nlohmann::json &j, const NativeOrderState &n);
void from_json(const nlohmann::json &j, NativeOrderState &n);
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::OrderStatus,
s,
bts::api::orderman::Fill_FillKind,
k,
"{}",
bts::api::orderman::OrderStatus_Name(s));
bts::api::orderman::Fill_FillKind_Name(k));
FMT_IMPL(
bts::api::orderman::TriggerPriceType,
bts::api::orderman::Fill_FillStatus,
s,
"{}",
bts::api::orderman::Fill_FillStatus(s));
FMT_IMPL(
bts::api::orderman::OrderSubType,
t,
"{}",
bts::api::orderman::TriggerPriceType_Name(t));
bts::api::orderman::OrderSubType_Name(t));
FMT_IMPL(
bts::api::orderman::SelfMatchPreventionInstruction,
s,
"{}",
bts::api::orderman::SelfMatchPreventionInstruction_Name(s));
FMT_IMPL(
bts::api::orderman::NativeOrderStatus,
s,
"{}",
bts::api::orderman::NativeOrderStatus_Name(s));
FMT_IMPL(
bts::api::orderman::NativeOrderState_SubError,
s,
"{}",
bts::api::orderman::NativeOrderState_SubError_Name(s));
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
/**
* Parses NativeOrderType from a string. Unlike `bts::api::orderman::NativeOrderType_Parse`,
* this implementation is case insensitive, and makes the 'NATIVE_ORDER_TYPE_' prefix optional.
*/
bool native_order_type_parse_smart(
const std::string &str, NativeOrderType *result);
PB_ENUM_FROM_JSON(NativeOrderType)
PB_ENUM_TO_JSON(NativeOrderType)
std::ostream &operator<<(std::ostream &out, NativeOrderType t) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool native_order_type_parse_smart(
const std::string &str, NativeOrderType *result) {
if (NativeOrderType_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (NativeOrderType_Parse(s, result)) {
return true;
}
const char *prefix = "NATIVE_ORDER_TYPE_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return NativeOrderType_Parse(s, result);
}
inline std::ostream &operator<<(std::ostream &out, NativeOrderType t) noexcept {
out << NativeOrderType_Name(t);
return out;
}
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::NativeOrderType,
t,
"{}",
bts::api::orderman::NativeOrderType_Name(t));
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
/**
* Parses OrderStatus from a string. Unlike `bts::api::orderman::OrderStatus_Parse`,
* this implementation is case insensitive, and makes the 'ORDER_STATUS_' prefix optional.
*/
bool order_status_parse_smart(const std::string &str, OrderStatus *result);
PB_ENUM_FROM_JSON(OrderStatus)
PB_ENUM_TO_JSON(OrderStatus)
std::ostream &operator<<(std::ostream &out, OrderStatus s) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool order_status_parse_smart(
const std::string &str, OrderStatus *result) {
if (OrderStatus_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (OrderStatus_Parse(s, result)) {
return true;
}
const char *prefix = "ORDER_STATUS_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return OrderStatus_Parse(s, result);
}
inline std::ostream &operator<<(std::ostream &out, OrderStatus s) noexcept {
out << OrderStatus_Name(s);
return out;
}
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::OrderStatus,
s,
"{}",
bts::api::orderman::OrderStatus_Name(s));
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
/**
* Parses TimeInForce from a string. Unlike `bts::api::orderman::TimeInForce_Parse`,
* this implementation is case insensitive, and makes the 'TIME_IN_FORCE_' prefix optional.
*/
bool time_in_force_parse_smart(const std::string &str, TimeInForce *result);
PB_ENUM_FROM_JSON(TimeInForce)
PB_ENUM_TO_JSON(TimeInForce)
std::ostream &operator<<(std::ostream &out, TimeInForce tif) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool time_in_force_parse_smart(
const std::string &str, TimeInForce *result) {
if (TimeInForce_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (TimeInForce_Parse(s, result)) {
return true;
}
const char *prefix = "TIME_IN_FORCE_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return TimeInForce_Parse(s, result);
}
inline std::ostream &operator<<(std::ostream &out, TimeInForce tif) noexcept {
out << TimeInForce_Name(tif);
return out;
}
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::TimeInForce,
v,
"{}",
bts::api::orderman::TimeInForce_Name(v));
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
/**
* Parses TriggerPriceType from a string. Unlike `bts::api::orderman::TriggerPriceType_Parse`,
* this implementation is case insensitive, and makes the 'TRIGGER_PRICE_TYPE_' prefix optional.
*/
bool trigger_price_type_parse_smart(
const std::string &str, TriggerPriceType *result);
PB_ENUM_FROM_JSON(TriggerPriceType)
PB_ENUM_TO_JSON(TriggerPriceType)
std::ostream &operator<<(std::ostream &out, TriggerPriceType t) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool trigger_price_type_parse_smart(
const std::string &str, TriggerPriceType *result) {
if (TriggerPriceType_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (TriggerPriceType_Parse(s, result)) {
return true;
}
const char *prefix = "TRIGGER_PRICE_TYPE_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return TriggerPriceType_Parse(s, result);
}
inline std::ostream &operator<<(
std::ostream &out, TriggerPriceType t) noexcept {
out << TriggerPriceType_Name(t);
return out;
}
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::TriggerPriceType,
t,
"{}",
bts::api::orderman::TriggerPriceType_Name(t));
@@ -75,6 +75,7 @@ function(get_cpp_sources dest)
expression/ParserExceptions.cpp
expression/Value.cpp
expression/tests/expression_parser_tests.cpp
io/JsonReader.cpp
logging/LoggerHub.cpp
logging/Sink.cpp
logging/logging.cpp
@@ -74,6 +74,7 @@ function(get_cpp_sources dest)
expression/ParserExceptions.cpp
expression/Value.cpp
expression/tests/expression_parser_tests.cpp
io/JsonReader.cpp
logging/LoggerHub.cpp
logging/Sink.cpp
logging/logging.cpp
+98
View File
@@ -0,0 +1,98 @@
#pragma once
#include "lib/utils/LockBox.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/type_name.h"
#include <functional>
#include <memory>
namespace bts::collections {
template <class KeyT, class ValueT>
class ConcurrentFactory {
utils::LockBox<std::unordered_map<KeyT, std::shared_ptr<ValueT>>> store;
public:
std::shared_ptr<ValueT> add(
const KeyT &key,
std::function<void(std::shared_ptr<ValueT> &)> callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key, std::make_shared<ValueT>());
bts_assert_msgf(
is_new, "{}::add. Duplicate key '{}'",
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
callback(it->second);
return it->second;
});
}
std::size_t erase(const KeyT &key) {
return store.lock([&](auto &map) {
map.erase(key);
return map.size();
});
}
std::size_t size() const {
return store.lock([&](auto &map) { return map.size(); });
}
template <
typename CallbackT = std::function<void(std::shared_ptr<ValueT> &)>>
std::shared_ptr<const ValueT> get(const KeyT &key, CallbackT callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key, std::make_shared<ValueT>());
bts_assert_msgf(
!is_new, "{}::get. Key not found '{}'",
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
callback(it->second);
return it->second;
});
}
template <
typename CallbackT =
std::function<void(std::shared_ptr<ValueT> &, bool)>>
std::shared_ptr<const ValueT> get_or_add(
const KeyT &key, CallbackT callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key);
if (is_new) {
it->second = std::make_shared<ValueT>();
}
callback(it->second, is_new);
return it->second;
});
}
template <
typename CallbackT =
std::function<void(std::shared_ptr<ValueT> &, bool)>>
std::shared_ptr<const ValueT> replace(const KeyT &key, CallbackT callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key);
bts_assert_msgf(
!is_new, "{}::replace. Key not found '{}'",
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
it->second = std::make_shared<ValueT>();
callback(it->second, is_new);
return it->second;
});
}
template <
typename CallbackT =
std::function<void(std::shared_ptr<ValueT> &, bool)>>
std::shared_ptr<const ValueT> replace_or_add(
const KeyT &key, CallbackT callback) {
return store.lock([&](auto &map) {
auto [it, is_new] = map.try_emplace(key);
it->second = std::make_shared<ValueT>();
callback(it->second, is_new);
return it->second;
});
}
};
} // namespace bts::collections
@@ -11,5 +11,7 @@ OkxExeCfg {
ws_ping_interval_seconds : 25
ws_reconnect_interval_seconds : 5
ws_reconnect_max_count : 5
simulated_trading : true
tdmode : "cross"
}
}
@@ -1,34 +1,34 @@
OrdermanCfg {
rule: "DeploymentType.type == 'dev'"
value {
executor {
thread_name: "oman_orders"
poll: false
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
}
rule: "DeploymentType.type == 'dev'"
value {
executor {
thread_name: "oman_orders"
poll: false
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
}
}
OrdermanCfg {
value {
executor {
thread_name: "oman_orders"
poll: true
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
}
}
value {
executor {
thread_name: "oman_orders"
poll: true
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
}
}
+2
View File
@@ -12,5 +12,7 @@ message OkxExeCfg {
uint32 ws_ping_interval_seconds = 9;
uint32 ws_reconnect_interval_seconds = 10;
uint32 ws_reconnect_max_count = 11;
bool simulated_trading = 12;
string tdmode = 13;
}
+14
View File
@@ -110,6 +110,19 @@
ENUM_TO_JSON(type) \
ENUM_EQUALITY_OPS(type)
#define ENUM_CLASS_DEF_NO_JSON(type, for_all) \
class type { \
public: \
enum Value { for_all(ENUM_DECLARE_VALUE) }; \
\
ENUM_BASIC_MEMBERS(type) \
}; \
\
ENUM_OUTPUT_OPS(type) \
ENUM_NAME_DEF(type, for_all) \
ENUM_PARSE_DEF(type, for_all) \
ENUM_EQUALITY_OPS(type)
#define ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, op) \
constexpr enum_type operator op(enum_type lhs, enum_type rhs) noexcept { \
return static_cast<enum_type>(static_cast<numeric_type>(lhs) \
@@ -154,3 +167,4 @@
inline void to_json(nlohmann::json &j, const enum_type &value) { \
j = enum_type##_Name(value); \
}
+7
View File
@@ -1,6 +1,7 @@
#include "TimeStamp.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/time_formatters.hpp>
#include <google/protobuf/timestamp.pb.h>
namespace bts::core {
@@ -43,6 +44,12 @@ bool TimeStamp::from_ymd(
return true;
}
void TimeStamp::to_protobuf(google::protobuf::Timestamp &ts) const {
ts.set_seconds(seconds());
bts_assert(nanos() >= seconds() * 1'000'000'000);
ts.set_nanos(nanos() - seconds() * 1'000'000'000);
}
TimeStamp TimeStamp::from_iso_string(const std::string &iso_string) {
auto ptime = datetime::parse_iso_date_time_string(iso_string);
return from_ptime(ptime);
+2
View File
@@ -10,6 +10,7 @@
#include <boost/date_time/time.hpp>
#include <cstdint>
#include <fmt/format.h>
#include <google/protobuf/timestamp.pb.h>
#include <limits>
#include <string>
@@ -87,6 +88,7 @@ struct TimeStamp {
// Gets a TimeStamp representing this date at midnight.
TimeStamp date() const;
constexpr boost::posix_time::ptime to_ptime() const;
void to_protobuf(google::protobuf::Timestamp &ts) const;
std::string to_iso_string() const;
// current epoch ns
static uint64_t now_ns() {
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/string_utils.h"
#include <nlohmann/json.hpp>
namespace bts::api {
/**
* Parses PutOrCall from a string. Unlike `bts::api::SecurityType_Parse`, this
* implementation is case insensitive, and makes the 'PUTORCALL_' prefix optional.
*/
bool put_or_call_parse_smart(const std::string &str, PutOrCall *result);
PB_ENUM_FROM_JSON(PutOrCall)
PB_ENUM_TO_JSON(PutOrCall)
std::ostream &operator<<(std::ostream &out, const PutOrCall &poc) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline bool put_or_call_parse_smart(const std::string &str, PutOrCall *result) {
if (PutOrCall_Parse(str, result)) {
return true;
}
std::string s = bts::system::string_utils::copy_to_upper(str);
if (PutOrCall_Parse(s, result)) {
return true;
}
const char *prefix = "PUTORCALL_";
if (!s.starts_with(prefix)) {
s.insert(0, prefix);
}
return PutOrCall_Parse(s, result);
}
inline std::ostream &operator<<(
std::ostream &out, const PutOrCall &poc) noexcept {
out << PutOrCall_Name(poc);
return out;
}
} // namespace bts::api
FMT_IMPL(bts::api::PutOrCall, poc, "{}", bts::api::PutOrCall_Name(poc));
+389
View File
@@ -2,10 +2,13 @@
#include "lib/config/config.h"
#include "lib/config/protos/orders_fills_cleanup.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonMacros.h"
#include "lib/md/common.h"
#include "lib/utils/pb_json_macros.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <google/protobuf/text_format.h>
#include <nlohmann/json.hpp>
namespace bts {
namespace securities {
@@ -191,3 +194,389 @@ std::string unique_descriptor(const api::Security &security) {
} // namespace securities
} // namespace bts
namespace bts::api {
using nlohmann::json;
using std::uint32_t;
void to_json(json &j, const api::ContractQuantityInfo &cqi) {
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, min_value);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, max_value);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, tick_unit);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, tick_base);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, multiplier);
PB_JSON_WRITE_IF_NOT_ZERO(j, cqi, max_price);
}
void from_json(const json &j, api::ContractQuantityInfo &cqi) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, cqi, min_value, it);
PB_JSON_READ_VALUE(j, cqi, max_value, it);
PB_JSON_READ_VALUE(j, cqi, max_value, it);
PB_JSON_READ_VALUE(j, cqi, tick_unit, it);
PB_JSON_READ_VALUE(j, cqi, tick_base, it);
PB_JSON_READ_VALUE(j, cqi, max_price, it);
}
void to_json(json &j, const api::ContractSpecification &spec) {
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, low_tick);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, price_break);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, high_tick);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, cash_per_point);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, price_multiplier);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, main_fraction);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, sub_fraction);
PB_JSON_WRITE_IF_NOT_ZERO(j, spec, price_display_format);
PB_JSON_WRITE_IF_NOT_EMPTY(j, spec, quantity_infos);
PB_JSON_WRITE_IF_NOT_EMPTY(j, spec, notional_quantity_infos);
}
void from_json(const json &j, api::ContractSpecification &spec) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, spec, low_tick, it);
PB_JSON_READ_VALUE(j, spec, price_break, it);
PB_JSON_READ_VALUE(j, spec, high_tick, it);
PB_JSON_READ_VALUE(j, spec, cash_per_point, it);
PB_JSON_READ_VALUE(j, spec, price_multiplier, it);
PB_JSON_READ_VALUE(j, spec, main_fraction, it);
PB_JSON_READ_VALUE(j, spec, sub_fraction, it);
PB_JSON_READ_VALUE(j, spec, price_display_format, it);
PB_JSON_READ_ARRAY(j, spec, quantity_infos, it);
PB_JSON_READ_ARRAY(j, spec, notional_quantity_infos, it);
}
void to_json(json &, const SpotContract &) {}
void from_json(const json &, SpotContract &) {}
template <class ExpContract>
static void exp_to_json(json &j, const ExpContract &ec) {
j = bts::core::TimeStamp::from_ymd(
ec.expiration_year(), ec.expiration_month(), ec.expiration_day())
.to_iso_string();
}
template <class ExpContract>
static void exp_from_json(const json &j, ExpContract &ec) {
const auto dt = bts::core::TimeStamp::from_iso_string(j.get<std::string>());
uint32_t y, m, d;
dt.to_date(y, m, d);
ec.set_expiration_year(y);
ec.set_expiration_month(m);
ec.set_expiration_day(d);
}
void to_json(json &j, const FutureContract &f) {
exp_to_json(j["expiration"], f);
}
void from_json(const json &j, FutureContract &f) {
const auto it = j.find("expiration");
if (it != j.end()) {
exp_from_json(*it, f);
}
}
void to_json(json &j, const OptionContract &opt) {
exp_to_json(j["expiration"], opt);
PB_JSON_WRITE_VALUE(j, opt, strike);
PB_JSON_WRITE_VALUE(j, opt, call_put);
}
void from_json(const json &j, OptionContract &opt) {
json::const_iterator it;
it = j.find("expiration");
if (it != j.end()) {
exp_from_json(*it, opt);
}
PB_JSON_READ_VALUE(j, opt, strike, it);
PB_JSON_READ_VALUE(j, opt, call_put, it);
}
void to_json(json &j, const ContractLeg &leg) {
PB_JSON_WRITE_STRUCT(j, leg, security);
PB_JSON_WRITE_VALUE(j, leg, quantity);
PB_JSON_WRITE_VALUE(j, leg, side);
PB_JSON_WRITE_VALUE(j, leg, price_multiplier);
PB_JSON_WRITE_IF_NOT_ZERO(j, leg, leg_delta);
PB_JSON_WRITE_VALUE(j, leg, price_calc);
}
void from_json(const json &j, ContractLeg &leg) {
json::const_iterator it;
PB_JSON_READ_STRUCT(j, leg, security, it);
PB_JSON_READ_VALUE(j, leg, quantity, it);
PB_JSON_READ_VALUE(j, leg, side, it);
PB_JSON_READ_VALUE(j, leg, price_multiplier, it);
PB_JSON_READ_VALUE(j, leg, leg_delta, it);
PB_JSON_READ_VALUE(j, leg, price_calc, it);
}
void to_json(json &j, const SpreadContract &spd) {
PB_JSON_WRITE_IF_NOT_EMPTY(j, spd, legs);
}
void from_json(const json &j, SpreadContract &spd) {
json::const_iterator it;
PB_JSON_READ_ARRAY(j, spd, legs, it);
}
void to_json(json &j, const SyntheticContract &syn) {
PB_JSON_WRITE_IF_NOT_EMPTY(j, syn, name);
PB_JSON_WRITE_IF_NOT_ZERO(j, syn, user_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, syn, legs);
PB_JSON_WRITE_VALUE(j, syn, price_calc);
}
void from_json(const json &j, SyntheticContract &syn) {
json::const_iterator it;
PB_JSON_READ_STRING(j, syn, name, it);
PB_JSON_READ_VALUE(j, syn, user_id, it);
PB_JSON_READ_ARRAY(j, syn, legs, it);
PB_JSON_READ_VALUE(j, syn, price_calc, it);
}
void to_json(json &j, const GLBXExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, cfi_code);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, cme_symbol);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, market_segment_id);
}
void from_json(const json &j, GLBXExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_STRING(j, data, cfi_code, it);
PB_JSON_READ_STRING(j, data, cme_symbol, it);
PB_JSON_READ_VALUE(j, data, market_segment_id, it);
}
void to_json(json &j, const CBOEExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, class_key);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, source);
}
void from_json(const json &j, CBOEExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_VALUE(j, data, class_key, it);
PB_JSON_READ_STRING(j, data, source, it);
}
void to_json(json &j, const CBOECFEExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, class_key);
}
void from_json(const json &j, CBOECFEExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_VALUE(j, data, class_key, it);
}
void to_json(json &j, const ICEExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, price_denominator);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, deal_denominator);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, settle_denominator);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, market_description);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, cleared_alias);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, product_name);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, hub_alias);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, strip_name);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, mic_code);
PB_JSON_WRITE_IF_TRUE(j, data, is_strip);
}
void from_json(const json &j, ICEExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_VALUE(j, data, price_denominator, it);
PB_JSON_READ_VALUE(j, data, deal_denominator, it);
PB_JSON_READ_VALUE(j, data, settle_denominator, it);
PB_JSON_READ_STRING(j, data, market_description, it);
PB_JSON_READ_STRING(j, data, cleared_alias, it);
PB_JSON_READ_STRING(j, data, product_name, it);
PB_JSON_READ_STRING(j, data, hub_alias, it);
PB_JSON_READ_STRING(j, data, strip_name, it);
PB_JSON_READ_STRING(j, data, mic_code, it);
PB_JSON_READ_VALUE(j, data, is_strip, it);
}
void to_json(json &j, const TAIFEXExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
PB_JSON_WRITE_IF_NOT_ZERO(j, data, class_key);
}
void from_json(const json &j, TAIFEXExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
PB_JSON_READ_VALUE(j, data, class_key, it);
}
void to_json(json &j, const DeribitExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, DeribitExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const FTXExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, FTXExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const BinanceExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, BinanceExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const BinanceUsdsFuturesExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, BinanceUsdsFuturesExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const BinanceCoinFuturesExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, BinanceCoinFuturesExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const BinanceUsExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, BinanceUsExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const OkxExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, OkxExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const CoinbaseExchangeData &data) {
PB_JSON_WRITE_IF_NOT_ZERO(j, data, exchange_id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, data, security_desc);
}
void from_json(const json &j, CoinbaseExchangeData &data) {
json::const_iterator it;
PB_JSON_READ_VALUE(j, data, exchange_id, it);
PB_JSON_READ_STRING(j, data, security_desc, it);
}
void to_json(json &j, const Security &s) {
// clang-format off
#define CONTRACT_ONEOF(oneof) \
oneof(j, s, Spot, spot) \
oneof(j, s, Future, future) \
oneof(j, s, Option, option) \
oneof(j, s, Spread, spread) \
oneof(j, s, Synthetic, synthetic)
#define EXCHANGE_DATA_ONEOF(oneof) \
oneof(j, s, Glbx, glbx) \
oneof(j, s, Cboe, cboe) \
oneof(j, s, Cfe, cfe) \
oneof(j, s, Ice, ice) \
oneof(j, s, Deribit, deribit) \
oneof(j, s, Ftx, ftx) \
oneof(j, s, Binance, binance) \
oneof(j, s, BinanceCoinFutures, binance_coin_futures) \
oneof(j, s, BinanceUsdsFutures, binance_usds_futures) \
oneof(j, s, BinanceUs, binance_us) \
oneof(j, s, Coinbase, coinbase) \
oneof(j, s, Okx, okx) \
oneof(j, s, Taifex, taifex)
// clang-format on
PB_JSON_WRITE_IF_NOT_ZERO(j, s, id);
PB_JSON_WRITE_IF_NOT_EMPTY(j, s, symbol);
PB_JSON_WRITE_VALUE(j, s, exchange);
PB_JSON_WRITE_STRUCT(j, s, contract_spec);
PB_JSON_WRITE_IF_NOT_EMPTY(j, s, currency);
PB_JSON_WRITE_ONEOF(j, s, Contract, CONTRACT_ONEOF);
PB_JSON_WRITE_ONEOF(j, s, ExchangeData, EXCHANGE_DATA_ONEOF);
}
void from_json(const json &j, Security &s) {
json::const_iterator it;
#define SEC_TYPE_ONEOF(oneof) \
oneof(j, s, future, it); \
oneof(j, s, option, it); \
oneof(j, s, spread, it); \
oneof(j, s, synthetic, it)
#define CONTRACT_CASE_ONEOF(oneof) \
oneof(j, s, glbx, it); \
oneof(j, s, cboe, it); \
oneof(j, s, cfe, it); \
oneof(j, s, ice, it); \
oneof(j, s, deribit, it); \
oneof(j, s, ftx, it); \
oneof(j, s, binance, it); \
oneof(j, s, binance_usds_futures, it); \
oneof(j, s, binance_coin_futures, it); \
oneof(j, s, binance_us, it); \
oneof(j, s, coinbase, it); \
oneof(j, s, okx, it); \
oneof(j, s, taifex, it);
PB_JSON_READ_VALUE(j, s, id, it);
PB_JSON_READ_STRING(j, s, symbol, it);
PB_JSON_READ_STRING(j, s, currency, it);
PB_JSON_READ_STRUCT(j, s, contract_spec, it);
PB_JSON_READ_ONEOFS(SEC_TYPE_ONEOF);
PB_JSON_READ_ONEOFS(CONTRACT_CASE_ONEOF);
}
} // namespace bts::api
+88 -19
View File
@@ -1,6 +1,6 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/PutOrCall.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/SecurityType.h"
#include "lib/domain_model/Side.h"
@@ -9,6 +9,7 @@
#include "lib/utils/bts_assert.h"
#include <cmath>
#include <nlohmann/json_fwd.hpp>
namespace boost {
namespace gregorian {
@@ -16,14 +17,83 @@ class date;
}
} // namespace boost
namespace bts {
namespace bts::api {
namespace api {
class Security;
class ICEExchangeData;
} // namespace api
namespace securities {
void to_json(nlohmann::json &j, const ContractQuantityInfo &c);
void from_json(const nlohmann::json &j, ContractQuantityInfo &c);
void to_json(nlohmann::json &j, const ContractSpecification &c);
void from_json(const nlohmann::json &j, ContractSpecification &c);
void to_json(nlohmann::json &j, const ContractLeg &c);
void from_json(const nlohmann::json &j, ContractLeg &c);
void to_json(nlohmann::json &j, const SpotContract &c);
void from_json(const nlohmann::json &j, SpotContract &c);
void to_json(nlohmann::json &j, const FutureContract &c);
void from_json(const nlohmann::json &j, FutureContract &c);
void to_json(nlohmann::json &j, const OptionContract &c);
void from_json(const nlohmann::json &j, OptionContract &c);
void to_json(nlohmann::json &j, const SpreadContract &c);
void from_json(const nlohmann::json &j, SpreadContract &c);
void to_json(nlohmann::json &j, const SyntheticContract_PriceCalc &c);
void from_json(const nlohmann::json &j, SyntheticContract_PriceCalc &c);
void to_json(nlohmann::json &j, const SyntheticContract &c);
void from_json(const nlohmann::json &j, SyntheticContract &c);
void to_json(nlohmann::json &j, const GLBXExchangeData &data);
void from_json(const nlohmann::json &j, GLBXExchangeData &data);
void to_json(nlohmann::json &j, const CBOEExchangeData &data);
void from_json(const nlohmann::json &j, CBOEExchangeData &data);
void to_json(nlohmann::json &j, const CBOECFEExchangeData &data);
void from_json(const nlohmann::json &j, CBOECFEExchangeData &data);
void to_json(nlohmann::json &j, const ICEExchangeData &data);
void from_json(const nlohmann::json &j, ICEExchangeData &data);
void to_json(nlohmann::json &j, const DeribitExchangeData &data);
void from_json(const nlohmann::json &j, DeribitExchangeData &data);
void to_json(nlohmann::json &j, const FTXExchangeData &data);
void from_json(const nlohmann::json &j, FTXExchangeData &data);
void to_json(nlohmann::json &j, const BinanceExchangeData &data);
void from_json(const nlohmann::json &j, BinanceExchangeData &data);
void to_json(nlohmann::json &j, const BinanceUsdsFuturesExchangeData &data);
void from_json(const nlohmann::json &j, BinanceUsdsFuturesExchangeData &data);
void to_json(nlohmann::json &j, const BinanceCoinFuturesExchangeData &data);
void from_json(const nlohmann::json &j, BinanceCoinFuturesExchangeData &data);
void to_json(nlohmann::json &j, const BinanceUsExchangeData &data);
void from_json(const nlohmann::json &j, BinanceUsExchangeData &data);
void to_json(nlohmann::json &j, const OkxExchangeData &data);
void from_json(const nlohmann::json &j, OkxExchangeData &data);
void to_json(nlohmann::json &j, const CoinbaseExchangeData &data);
void from_json(const nlohmann::json &j, CoinbaseExchangeData &data);
void to_json(nlohmann::json &j, const Security &s);
void from_json(const nlohmann::json &j, Security &s);
PB_ENUM_FROM_JSON(SyntheticContract_PriceCalc);
PB_ENUM_TO_JSON(SyntheticContract_PriceCalc);
} // namespace bts::api
namespace bts::securities {
/**
* Gets a descriptor string for a security (should be unique within a particular exchange).
@@ -60,7 +130,8 @@ inline api::Side other_side(api::Side side) {
return side;
}
}
template <class V> class TwoSided {
template <class V>
class TwoSided {
public:
TwoSided() : buy(), sell() {}
@@ -104,7 +175,8 @@ inline bool is_worse(api::Side s, Price p1, Price p2) {
return is_better(s, p2, p1);
}
template <class C> void apply_to_both_sides(C c) {
template <class C>
void apply_to_both_sides(C c) {
c(api::SIDE_BUY);
c(api::SIDE_SELL);
}
@@ -178,8 +250,8 @@ inline Price encode_exchange_price(api::Security const &sec, Price in) {
return in;
}
inline double
raw_round_to_quote_price(double raw_price, double tick_size, api::Side side) {
inline double raw_round_to_quote_price(
double raw_price, double tick_size, api::Side side) {
double factor = 1000000000;
auto i_price = llround(factor * raw_price);
auto i_tick_size = llround(tick_size * factor);
@@ -222,8 +294,8 @@ inline double checked_round_to_quote_price(
}
}
inline Price
round_to_quote_price(api::Security const &sec, Price in, api::Side side) {
inline Price round_to_quote_price(
api::Security const &sec, Price in, api::Side side) {
auto p = in.value();
bts_assert_d(!std::isnan(p));
if (p < -sec.contract_spec().price_break() ||
@@ -287,8 +359,8 @@ inline const std::string &descriptor(
// Looking to round md::IntPrice? Look in IntPrice.h
// computes price_2 - price_1 in ticks
inline int
price_diff_to_ticks(double price_1, double price_2, double tick_size) {
inline int price_diff_to_ticks(
double price_1, double price_2, double tick_size) {
return static_cast<int>(std::round((price_2 - price_1) / tick_size));
}
// returns the difference (in ticks) bw and order and a market.
@@ -312,8 +384,8 @@ inline int order_diff_market(
// simple double equality can not be used to compare prices.
// this function deems 2 prices to be equal if they are within
// 1 % of the tick size
inline bool
test_price_equality(double price_1, double price_2, double tick_size) {
inline bool test_price_equality(
double price_1, double price_2, double tick_size) {
return std::fabs(price_2 - price_1) < 0.01 * tick_size;
}
inline bool gr_than_eq(double p1, double p2, double tick_size) {
@@ -362,7 +434,4 @@ inline bool is_spread_of_strips(const bts::api::Security &sec) {
return false;
}
} // namespace securities
} // namespace bts
FMT_IMPL(bts::api::Security, s, "{}", bts::securities::descriptor(s));
} // namespace bts::securities
+5 -9
View File
@@ -3,18 +3,14 @@
#define _TO_JSON_FIELD(field) j[#field] = t.field;
#define _FROM_JSON_FIELD(field) bts::io::JsonReader{&j, #field}.read(t.field);
#define SPARK_TO_JSON_IMPL(type, for_each) \
inline void to_json(nlohmann::json &j, const type &t) { \
FOR_EACH(_TO_JSON_FIELD) \
}
#define SPARK_TO_JSON_IMPL(type, fields) \
void to_json(nlohmann::json &j, const type &t) { fields(_TO_JSON_FIELD) }
#define SPARK_FROM_JSON_IMPL(type, for_each) \
inline void from_json(const nlohmann::json &j, type &t) { \
FOR_EACH(_FROM_JSON_FIELD) \
}
#define SPARK_FROM_JSON_IMPL(type, fields) \
void from_json(const nlohmann::json &j, type &t) { fields(_FROM_JSON_FIELD) }
#define OSTREAM_JSON_IMPL(type) \
inline std::ostream &operator<<(std::ostream &out, const type &t) { \
std::ostream &operator<<(std::ostream &out, const type &t) { \
nlohmann::json j; \
to_json(j, t); \
out << j.dump(2); \
+39 -1
View File
@@ -94,7 +94,18 @@ class JsonReader {
* @return True if the read succeeds, otherwise false
*/
template <class T>
bool read_or(const std::string &name, T &result, T default_value) const;
bool read_or(const std::string &name, T &result, T default_value = {}) const;
template <class T>
bool read_optional_or(
std::optional<T> &result,
std::optional<T> default_value = std::nullopt) const;
template <class T>
bool read_optional_or(
const std::string &name,
std::optional<T> &result,
std::optional<T> default_value = std::nullopt) const;
private:
const nlohmann::json *json_;
@@ -269,6 +280,33 @@ inline bool JsonReader::read_or(
return false;
}
template <class T>
inline bool JsonReader::read_optional_or(
std::optional<T> &result, std::optional<T> default_value) const {
if (read_optional(result)) {
return true;
}
result = std::move(default_value);
return false;
}
template <class T>
inline bool JsonReader::read_optional_or(
const std::string &name,
std::optional<T> &result,
std::optional<T> default_value) const {
if (json_ && json_->is_object()) {
auto it = json_->find(name);
if (it != json_->end()) {
if (JsonReader{&it.value()}.read_optional(result)) {
return true;
}
}
}
result = std::move(default_value);
return false;
}
template <class T>
bool JsonReader::read_flat(
const std::vector<nlohmann::json> &items, std::vector<T> &result) {
+4
View File
@@ -1,5 +1,7 @@
#pragma once
#include "lib/logging/logging.h"
#include <atomic>
#include <curl/curl.h>
#include <curl/easy.h>
@@ -12,6 +14,7 @@ struct CurlSession {
CurlSession() {
if (++ref_count == 1) {
LOG(Http, Info, "Initializing curl (global)");
curl_global_init(CURL_GLOBAL_ALL);
}
curl = curl_easy_init();
@@ -20,6 +23,7 @@ struct CurlSession {
~CurlSession() {
curl_easy_cleanup(curl);
if (--ref_count == 0) {
LOG(Http, Info, "Cleaning up curl (global)");
curl_global_cleanup();
}
}
+4 -1
View File
@@ -117,7 +117,10 @@ void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
// Prepare req
//to debug curl requests
// curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_callback);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
// Disable signals to avoid strange stack corruption related to set/longjmp
// https://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0L);
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers.data());
if (response != nullptr) {
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <string>
namespace bts::io {
/**
* This wrapper struct provides a means to format arbitrary objects as json
* in templated strings (e.g., LOG macros).
*/
template <class T>
struct JsonFormatter {
T value;
std::string to_string() const { return fmt::format("{}", *this); }
};
template <class T>
JsonFormatter<T> json_formatter(T value) {
return JsonFormatter<T>{std::move(value)};
}
} // namespace bts::io
template <class T>
struct fmt::formatter<bts::io::JsonFormatter<T>> {
int indent = -1;
constexpr auto parse(fmt::format_parse_context &ctx) -> decltype(ctx.end()) {
const auto end = ctx.end();
auto it = ctx.begin();
if (it != end) {
indent = 0;
do {
int x = *it - '0';
if (x < 0 || x > 9) {
return it;
}
indent = indent * 10 + x;
} while (++it != end);
}
return it;
}
template <typename FormatContext>
auto format(const bts::io::JsonFormatter<T> &jf, FormatContext &ctx) const
-> decltype(ctx.out()) {
nlohmann::json j;
to_json(j, jf.value);
return fmt::format_to(ctx.out(), "{}", j.dump(indent));
}
};
+123 -73
View File
@@ -11,7 +11,10 @@
#include "lib/utils/bts_assert.h"
#include "lib/utils/formatters/exception_formatter.h"
#include <algorithm>
#include <atomic>
#include <boost/asio/io_context.hpp>
#include <chrono>
#include <exception>
#include <fmt/format.h>
#include <mutex>
@@ -19,6 +22,7 @@
#include <optional>
#include <stdexcept>
#include <string>
#include <system_error>
#include <thread>
#include <websocketpp/close.hpp>
#include <websocketpp/common/connection_hdl.hpp>
@@ -34,6 +38,7 @@ namespace bts::websocket {
struct WebsocketClientSnapshot {
int client_id;
WebsocketClientState state;
int n_reconnects;
int version;
};
@@ -42,9 +47,10 @@ struct WebsocketClientSnapshot {
FMT_IMPL(
bts::websocket::WebsocketClientSnapshot,
t,
"id: {}; state: {}; version: {}",
"id: {}; state: {}; n_reconnects: {}; version: {}",
t.client_id,
t.state,
t.n_reconnects,
t.version);
namespace bts::websocket {
@@ -62,6 +68,8 @@ using std::size_t;
using std::string;
using std::thread;
using std::uint32_t;
using std::chrono::nanoseconds;
using std::this_thread::sleep_for;
using websocketpp::config::asio_tls_client;
using WebsocketppClient =
@@ -86,9 +94,16 @@ using State = WebsocketClientState;
#define ERROR(...) LOG(Http, Error, __VA_ARGS__)
#define ERROR_FN(...) LOG(Http, Error, "WebsocketClient::" __VA_ARGS__)
static bts::core::TimeSpan reconnect_interval(std::uint32_t n_attempts) {
std::int64_t secs = 5 * n_attempts;
secs = std::clamp(secs, 5L, 300L);
return bts::core::TimeSpan::from_seconds(secs);
}
struct WebsocketClient::PImpl {
WebsocketClientConfig config;
WebsocketppClient client{};
boost::asio::io_context *io_context = nullptr;
WebsocketppClient::connection_ptr con;
optional<OpenHandler> open_handler;
optional<MessageHandler> message_handler;
@@ -98,8 +113,45 @@ struct WebsocketClient::PImpl {
optional<thread> io_thread;
int client_id{client_id_counter++};
mutex instance_mx;
State state;
State state = State::IDLE;
int version = 0;
int n_reconnects = 0;
PImpl() {
client.set_access_channels(websocketpp::log::alevel::all);
// client.clear_access_channels(websocketpp::log::alevel::frame_payload);
client.set_error_channels(websocketpp::log::elevel::all);
if (io_context == nullptr) {
client.init_asio();
} else {
client.init_asio(io_context);
}
client.set_message_handler(
[this](auto hdl, auto msg) { on_message(hdl, msg); });
client.set_open_handler([this](auto hdl) { on_open(hdl); });
client.set_ping_handler([](auto /*hdl*/, auto /*msg*/) {
LOG(Http, Info, "Received ping");
return true;
});
client.set_pong_timeout(0);
client.set_pong_handler([](auto /*hdl*/, auto /*msg*/) {
LOG(Http, Info, "Received pong");
return true;
});
client.set_tls_init_handler([&](auto hdl) { return on_tls_init(hdl); });
client.set_close_handler([=, this](auto hdl) {
on_close(hdl);
if (config.reconnect_on_close) {
sleep_for(nanoseconds{reconnect_interval(n_reconnects).nanos()});
connect();
}
});
client.set_fail_handler([this](auto hdl) {
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
ERROR_FN("fail_handler. {}; err: {}", snapshot(), ec.message());
on_fail(hdl);
});
}
~PImpl() {
if (io_thread) {
@@ -120,7 +172,7 @@ struct WebsocketClient::PImpl {
}
constexpr WebsocketClientSnapshot snapshot() const {
return {client_id, state, version};
return {client_id, state, n_reconnects, version};
}
void close(websocketpp::close::status::value code, std::string reason) {
@@ -164,8 +216,7 @@ struct WebsocketClient::PImpl {
/**
* Connects to the websocket server
*/
void connect(
const map<string, string> &headers, bool run_in_background = true) {
void connect() {
lock_guard<mutex> lock{instance_mx};
INFO_FN("connect. {}", snapshot());
switch (state.value) {
@@ -177,9 +228,6 @@ struct WebsocketClient::PImpl {
}
set_state(State::OPENING);
try {
init_logging();
init_handlers();
// Get connection
websocketpp::lib::error_code ec;
con = client.get_connection(config.url, ec);
if (ec) {
@@ -187,20 +235,11 @@ struct WebsocketClient::PImpl {
return;
}
// Add headers
for (const auto &[k, v] : headers) {
for (const auto &[k, v] : config.headers) {
con->append_header(k, v);
}
// Connect
client.connect(con);
if (run_in_background) {
bts_assert_msgf(
!io_thread, "Websocket io_thread already instantiated. {}",
snapshot());
io_thread = std::thread{[this]() { run(); }};
if (config.connect_timeout > core::TimeSpan::zero()) {
wait_for_connect(config.connect_timeout);
}
}
} catch (const std::exception &ex) {
std::string str = ex.what();
ERROR_FN(
@@ -212,22 +251,11 @@ struct WebsocketClient::PImpl {
}
}
void wait_for_connect(const TimeSpan timeout) {
INFO_FN("wait_for_connect. {}; timeout: {}", snapshot(), timeout);
const auto start = TimeStamp::now();
while (state == State::OPENING) {
const auto elapsed = TimeStamp::now() - start;
const auto rem = timeout - elapsed;
if (rem < TimeSpan::zero()) {
throw std::runtime_error{fmt::format(
"Timed out waiting for connection after {} secs. {}",
elapsed.total_seconds(), snapshot())};
}
WARN_FN(
"wait_for_connect. Still waiting. {}; timeout: {}; rem: {}",
snapshot(), timeout, rem);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
void reconnect() {
WARN_FN("reconnect. {}", snapshot());
sleep_for(nanoseconds{reconnect_interval(n_reconnects).nanos()});
++n_reconnects;
connect();
}
void run() {
@@ -258,10 +286,33 @@ struct WebsocketClient::PImpl {
INFO("Websocket 'run' thread end. {}", snapshot());
}
void run_one() {
try {
client.run_one();
} catch (const websocketpp::exception &ex) {
ERROR_FN(
"io_thread (run_one). Websocket io threw websocketpp::exception. "
"{}; err: {}",
snapshot(), ex);
} catch (const std::exception &ex) {
ERROR_FN(
"io_thread (run_one). Websocket io threw std::exception. {}; err: "
"{}",
snapshot(), ex);
} catch (...) {
bts_assert_msgf(
false,
"io_thread (run_one). Websocket io threw unknown exception. Could "
"this have been thrown by a callback delegate? {}",
snapshot());
}
}
private:
static std::atomic<int> client_id_counter;
auto on_tls_init(ConnectionHdl /*hdl*/) {
std::shared_ptr<boost::asio::ssl::context> on_tls_init(
ConnectionHdl /*hdl*/) {
INFO_FN("on_tls_init. state: {}; version: {}", state, version);
auto result =
websocketpp::lib::make_shared<SslContext>(SslContext::sslv23_client);
@@ -269,38 +320,6 @@ struct WebsocketClient::PImpl {
return result;
}
void init_handlers() {
INFO_FN("init_handlers. state: {}; version: {}", state, version);
bts_assert(!io_thread);
client.init_asio();
client.set_message_handler(
[this](auto hdl, auto msg) { on_message(hdl, msg); });
client.set_open_handler([this](auto hdl) { on_open(hdl); });
client.set_ping_handler([](auto /*hdl*/, auto /*msg*/) {
LOG(Http, Info, "Received ping");
return true;
});
client.set_pong_timeout(0);
client.set_pong_handler([](auto /*hdl*/, auto /*msg*/) {
LOG(Http, Info, "Received pong");
return true;
});
client.set_tls_init_handler(
[this](auto hdl) { return on_tls_init(hdl); });
client.set_close_handler([this](auto hdl) { on_close(hdl); });
client.set_fail_handler([this](auto hdl) {
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
ERROR_FN("fail_handler. {}; err: {}", snapshot(), ec.message());
});
}
void init_logging() {
INFO_FN("init_logging. {}", snapshot());
client.set_access_channels(websocketpp::log::alevel::all);
// client.clear_access_channels(websocketpp::log::alevel::frame_payload);
client.set_error_channels(websocketpp::log::elevel::all);
}
void on_open(ConnectionHdl hdl) {
lock_guard<mutex> lock{instance_mx};
INFO_FN("on_open. {}", snapshot());
@@ -358,11 +377,37 @@ struct WebsocketClient::PImpl {
if (close_handler) {
(*close_handler)(ec);
}
if (config.reconnect_on_close) {
reconnect();
}
}
void on_fail(ConnectionHdl hdl) {
lock_guard<mutex> lock{instance_mx};
ERROR_FN("on_fail. {}", snapshot());
set_state(State::IDLE);
const auto con = client.get_con_from_hdl(hdl);
const auto ec = con->get_ec();
if (ec) {
ERROR_FN(
"on_fail. {}; local_code: {}; "
"local_reason: {}; remote_code: {}; remote_reason: {}; err: {}",
snapshot(), con->get_local_close_code(),
con->get_local_close_reason(), con->get_remote_close_code(),
con->get_remote_close_reason(), ec.message());
}
if (config.reconnect_on_fail) {
reconnect();
}
}
};
WebsocketClient::WebsocketClient(WebsocketClientConfig config)
: me_(std::make_unique<PImpl>(std::move(config))) {}
WebsocketClient::WebsocketClient(
WebsocketClientConfig config, boost::asio::io_context *io_context)
: me_(std::make_unique<PImpl>()) {
me_->config = std::move(config);
me_->io_context = io_context;
}
WebsocketClient::~WebsocketClient() {
me_->close(websocketpp::close::status::going_away, "");
@@ -391,13 +436,16 @@ WebsocketClient::CloseHandler &WebsocketClient::close_handler() {
return *me_->close_handler;
}
void WebsocketClient::connect(
const std::map<std::string, std::string> &headers, bool run_in_background) {
me_->connect(headers, run_in_background);
}
void WebsocketClient::connect() { me_->connect(); }
void WebsocketClient::run() { me_->run(); }
void WebsocketClient::run_one() { me_->run_one(); }
boost::asio::io_context *WebsocketClient::io_context() {
return me_->io_context;
}
websocketpp::lib::error_code WebsocketClient::send_text(const string &text) {
return me_->send_text(text);
}
@@ -415,4 +463,6 @@ bool WebsocketClient::is_connected() const {
void WebsocketClient::ping(const string &payload) { me_->con->ping(payload); }
const string &WebsocketClient::url() const { return me_->config.url; }
} // namespace bts::websocket
+10 -4
View File
@@ -3,6 +3,7 @@
#include "lib/io/websocket/websocket_client_config.h"
#include "lib/utils/Delegate.h"
#include <boost/asio/io_context.hpp>
#include <memory>
#include <string>
#include <websocketpp/config/core_client.hpp>
@@ -12,28 +13,33 @@ namespace bts::websocket {
class WebsocketClient {
public:
explicit WebsocketClient(WebsocketClientConfig config);
WebsocketClient(
WebsocketClientConfig config,
boost::asio::io_context *io_context = nullptr);
~WebsocketClient();
using OpenHandler = bts::core::Delegate<void(const std::error_code &)>;
using MessageHandler = bts::core::Delegate<void(const std::string &)>;
using CloseHandler = bts::core::Delegate<void(const std::error_code &)>;
void connect(
const std::map<std::string, std::string> &headers = {},
bool run_in_background = true);
void connect();
void disconnect();
bool is_connected() const;
boost::asio::io_context *io_context();
void run();
void run_one();
CloseHandler &open_handler();
MessageHandler &message_handler();
CloseHandler &close_handler();
websocketpp::lib::error_code send_text(const std::string &text);
void ping(const std::string &payload = "");
const std::string &url() const;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::websocket
@@ -5,9 +5,12 @@
namespace bts::websocket {
struct WebsocketClientConfig {
std::string url;
core::TimeSpan connect_timeout;
core::TimeSpan pong_timeout;
std::string url{};
core::TimeSpan connect_timeout = core::TimeSpan::zero();
core::TimeSpan pong_timeout = core::TimeSpan::zero();
bool reconnect_on_close = false;
bool reconnect_on_fail = false;
std::map<std::string, std::string> headers{};
};
} // namespace bts::websocket
+2 -3
View File
@@ -107,7 +107,7 @@ Sink file_sink(LoggingConfig const &cfg) {
if (boost::filesystem::exists(path)) {
auto sz = boost::filesystem::file_size(path);
if (sz > max_sz && false) {
if (sz > max_sz) {
auto backup = boost::filesystem::path(path.string() + ".old");
boost::filesystem::rename(path, backup);
}
@@ -118,8 +118,7 @@ Sink file_sink(LoggingConfig const &cfg) {
return [=](LogMsg const &m) {
auto msg = f(m);
bytes_written += msg.size();
if (bytes_written >= max_sz && false) {
return;
if (bytes_written >= max_sz) {
bytes_written = 0;
stream.close();
auto backup = boost::filesystem::path(path.string() + ".old");
+2 -1
View File
@@ -435,7 +435,8 @@ class SyntheticConsumerInst {
void add_security(const api::Security &sec) {
Mutex::scoped_lock lock(_workmutex);
if (_data.find(sec.id()) == _data.end()) {
LOG(md, Info, "MDConsumer::add_security. {}", sec);
LOG(md, Info, "MDConsumer::add_security. {}",
bts::securities::descriptor(sec));
auto ptr = to_strategy(make_shared<api::Security>(sec));
vector<IncompleteStrategy> vec;
vec.push_back(ptr);
+28 -6
View File
@@ -14,6 +14,15 @@
namespace bts {
namespace md {
#define ASSERT_FN(test, fn, ...) \
bts_assert_msg(test, "SecurityCache::" fn ". " __VA_ARGS__)
#define LOG_FN(level, fn, ...) \
LOG(md, level, "SecurityCache::" fn ". " __VA_ARGS__)
#define DIAG_FN(fn, ...) LOG_FN(Diag, fn, __VA_ARGS__)
#define INFO_FN(fn, ...) LOG_FN(Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) LOG_FN(Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) LOG_FN(Error, fn, __VA_ARGS__)
using std::string;
using std::unordered_map;
@@ -99,6 +108,9 @@ struct SecurityCacheIndex {
// Insert but don't overwrite existing
auto result =
cache.insert(std::make_pair(SecurityKey(security->id()), security));
venue_type_ids.get_or_add(security->exchange())
.get_or_add(bts::securities::security_type(*security))
.try_emplace(bts::securities::descriptor(*security), security->id());
return result.second;
}
@@ -166,8 +178,8 @@ struct SecurityCacheImpl {
};
void SecurityCacheImpl::enqueue_update_db(const SecurityPtr &security) {
LOG(md, Diag, "SecurityCacheImpl::enqueue_update_db security: {}",
bts::securities::descriptor(*security));
DIAG_FN(
"enqueue_update_db", "security: {}", securities::descriptor(*security));
update_queue.push(security);
}
@@ -180,7 +192,13 @@ void SecurityCacheImpl::flush() {
work.push_back(temp);
}
if (!work.empty()) {
INFO_FN("flush", "Saving securities to db. count: {}", work.size());
if (db::insert_or_replace(work) == Result::error) {
WARN_FN(
"flush",
"Failed to save securities to db. Will retry on next flush. "
"count: {}",
work.size());
// If we fail to insert then retry later
for (auto &s : work)
update_queue.push(std::move(s));
@@ -270,14 +288,18 @@ std::vector<api::Security const *> SecurityCache::get_securities() {
}
bool SecurityCache::add_live_security(const SecurityPtr &security) {
bts_assert(security);
LOG(md, Diag, "SecurityCache::add_live_security security: {}",
bts::securities::descriptor(*security));
return _me->cache.lock([&](SecurityCacheIndex &cache) {
ASSERT_FN(security, "add_live_security", "security is null");
const bool is_new_sec = _me->cache.lock([&](SecurityCacheIndex &cache) {
const bool is_new_sec = cache.add_live_security(security);
_me->enqueue_update_db(security);
return is_new_sec;
});
if (is_new_sec) {
INFO_FN(
"add_live_security", "new security: {}",
bts::securities::descriptor(*security));
}
return is_new_sec;
}
void SecurityCache::flush_to_disk() { _me->flush(); }
-1
View File
@@ -7,7 +7,6 @@
#include "lib/utils/checked_arithmetic.h"
#include <boost/filesystem.hpp>
#include <boost/pool/object_pool.hpp>
#include <memory>
#include <sqlite/sqlite3.h>
#include <stdexcept>
+59 -1
View File
@@ -8,7 +8,10 @@ namespace mercury {
BrokerProxy::BrokerProxy(ILocalBroker &real_broker)
: _real_broker(real_broker), _id(real_broker.id()), _cycles(0),
_outgoing(config::get<config::Mercury>().queue_size()),
_incoming(config::get<config::Mercury>().queue_size()), _action_pool() {}
_incoming(config::get<config::Mercury>().queue_size()), _action_pool() {
_action_pool.set_max_count(4096 * 10);
_action_pool.set_log_freq(100);
}
BrokerProxy::~BrokerProxy() {}
@@ -96,5 +99,60 @@ void BrokerProxy::schedule_action(std::function<void()> const &action) {
send(*m);
}
void BrokerProxy::do_work(trace::PerfLog &log) {
_cycles++;
_outgoing.consume_all([this, &log](BrokerMsg *m) {
switch (m->msg_type) {
case BrokerMsgType::ADD_NEIGHBOUR:
_real_broker.add_neighbour(*m->neighbour);
break;
case BrokerMsgType::REMOVE_NEIGHBOUR:
_real_broker.remove_neighbour(m->broker_id);
break;
case BrokerMsgType::ADVERTISE:
_real_broker.advertise(m->broker_id, m->stream_id, m->topic);
break;
case BrokerMsgType::UNADVERTISE:
_real_broker.unadvertise(m->broker_id, m->stream_id);
break;
case BrokerMsgType::SUBSCRIBE:
_real_broker.subscribe(m->broker_id, m->sub_id, m->stream_id);
break;
case BrokerMsgType::UNSUBSCRIBE:
_real_broker.unsubscribe(m->broker_id, m->sub_id, m->stream_id);
break;
case BrokerMsgType::NOTIFY: {
LOG(Latency, Diag, "Notify broker {}. Cycles: {}", _id.value(),
_cycles);
auto scoped_do_work = trace::log_scope(log, {}, "BrokerProxy::notify");
_real_broker.notify(
m->broker_id, m->stream_id, m->sub_id, m->notification);
} break;
case BrokerMsgType::LOCAL_SUBSCRIBE:
_real_broker.local_subscribe(
m->local_sub_id, m->filter, m->local_handler);
break;
case BrokerMsgType::LOCAL_UNSUBSCRIBE:
_real_broker.local_unsubscribe(m->local_sub_id);
break;
case BrokerMsgType::EXECUTE_ACTION: {
auto start = Timer::now();
LOG(Latency, Diag, "Executing lambda on broker {}", _id.value());
m->action();
auto stop = Timer::now();
LOG(Latency, Diag,
"Finished executing lambda on broker {}. Took: {}us", _id.value(),
us_diff(stop, start));
break;
}
default:
bts_assert(false);
break;
}
_incoming.drain();
_incoming.push(m);
});
}
} // namespace mercury
} // namespace bts
+3 -57
View File
@@ -5,8 +5,8 @@
#include "lib/mercury/topics/Topic.h"
#include "lib/tracing/PerfLog.h"
#include "lib/utils/GrowableSPSCQueue.h"
#include "lib/utils/object_pool.h"
#include <boost/pool/object_pool.hpp>
#include <memory>
namespace bts {
@@ -80,61 +80,7 @@ class BrokerProxy : public ILocalBroker {
send(m);
}
void do_work(trace::PerfLog &log) {
_cycles++;
_outgoing.consume_all([this, &log](BrokerMsg *m) {
switch (m->msg_type) {
case BrokerMsgType::ADD_NEIGHBOUR:
_real_broker.add_neighbour(*m->neighbour);
break;
case BrokerMsgType::REMOVE_NEIGHBOUR:
_real_broker.remove_neighbour(m->broker_id);
break;
case BrokerMsgType::ADVERTISE:
_real_broker.advertise(m->broker_id, m->stream_id, m->topic);
break;
case BrokerMsgType::UNADVERTISE:
_real_broker.unadvertise(m->broker_id, m->stream_id);
break;
case BrokerMsgType::SUBSCRIBE:
_real_broker.subscribe(m->broker_id, m->sub_id, m->stream_id);
break;
case BrokerMsgType::UNSUBSCRIBE:
_real_broker.unsubscribe(m->broker_id, m->sub_id, m->stream_id);
break;
case BrokerMsgType::NOTIFY: {
LOG(Latency, Diag, "Notify broker {}. Cycles: {}", _id.value(),
_cycles);
auto scoped_do_work =
trace::log_scope(log, {}, "BrokerProxy::notify");
_real_broker.notify(
m->broker_id, m->stream_id, m->sub_id, m->notification);
} break;
case BrokerMsgType::LOCAL_SUBSCRIBE:
_real_broker.local_subscribe(
m->local_sub_id, m->filter, m->local_handler);
break;
case BrokerMsgType::LOCAL_UNSUBSCRIBE:
_real_broker.local_unsubscribe(m->local_sub_id);
break;
case BrokerMsgType::EXECUTE_ACTION: {
auto start = Timer::now();
LOG(Latency, Diag, "Executing lambda on broker {}", _id.value());
m->action();
auto stop = Timer::now();
LOG(Latency, Diag,
"Finished executing lambda on broker {}. Took: {}us",
_id.value(), us_diff(stop, start));
break;
}
default:
bts_assert(false);
break;
}
_incoming.drain();
_incoming.push(m);
});
}
void do_work(trace::PerfLog &log);
private:
ILocalBroker &_real_broker;
@@ -142,7 +88,7 @@ class BrokerProxy : public ILocalBroker {
uint64_t _cycles;
utils::GrowableSPSCQueue<BrokerMsg *> _outgoing;
utils::GrowableSPSCQueue<BrokerMsg *> _incoming;
boost::object_pool<BrokerMsg> _action_pool;
bts::utils::object_pool<BrokerMsg> _action_pool;
void send(BrokerMsg &m) {
_outgoing.push(&m);
+43
View File
@@ -1,5 +1,6 @@
#pragma once
#include "lib/api/services/OrderService.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/common.h"
@@ -24,6 +25,25 @@ struct CreateOrderMsg {
api::orderman::RoutingRecord const *routing_record;
};
/**
* A CreateOrderMsg wrapper with a custom fmt::formatter that doesn't access the
* raw security pointer. This is mainly used for logging purposes (because the
* security pointer may be invalid by the time the fmt::formatter is invoked).
*/
struct CreateOrderMsg_JsonData {
CreateOrderMsg_JsonData() = default;
CreateOrderMsg_JsonData(CreateOrderMsg source) : source(std::move(source)) {
if (this->source.security != nullptr) {
sec_name = securities::descriptor(*this->source.security);
}
}
CreateOrderMsg source;
std::string sec_name;
};
void to_json(nlohmann::json &j, const CreateOrderMsg &msg);
inline std::ostream &operator<<(std::ostream &out, CreateOrderMsg const &msg) {
out << "key: " << msg.key.value() << " price: " << msg.price.value()
<< " qty: " << msg.quantity.full_value() << " security: "
@@ -43,6 +63,29 @@ inline std::ostream &operator<<(std::ostream &out, CreateOrderMsg const &msg) {
return out;
}
inline void to_json(
nlohmann::json &j, const CreateOrderMsg_JsonData &msg_data) {
const auto &msg = msg_data.source;
j["key"] = msg.key.value();
j["price"] = msg.price.value();
j["quantity"] = msg.quantity.value();
if (msg.display_quantity.value() != 0) {
j["display_quantity"] = msg.quantity.value();
}
if (!msg_data.sec_name.empty()) {
j["security"] = msg_data.sec_name;
}
j["side"] = msg.side;
if (msg.stop_px.value() != 0) {
j["stop_px"] = msg.stop_px.value();
}
j["time_in_force"] = msg.time_in_force;
j["manual_order_indicator"] = msg.manual_order_indicator;
if (msg.routing_record != nullptr) {
j["routing_record"] = *msg.routing_record;
}
}
} // namespace bts::orderman
FMT_IMPL_OSTREAM(bts::orderman::CreateOrderMsg);
+45 -3
View File
@@ -8,6 +8,7 @@
#include "lib/config/config.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/config/protos/orderman_cfg.pb.h"
#include "lib/core/FMT.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/common.h"
#include "lib/logging/logging.h"
@@ -29,11 +30,11 @@
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
#include "lib/orderman/events/ExchangePositionEvent.h"
#include "lib/orderman/events/OrderOpResultEvent.h"
#include "lib/orderman/gateways/execution_gateway_provider.h"
#include "lib/orderman/gateways/i_execution_gateway_provider.h"
#include "lib/orderman/order_factory/DynamicOrderRegistry.h"
#include "lib/orderman/orderman_utils/ExchangeOpenOrders.h"
#include "lib/orderman/orders/Order.h"
#include "lib/os/os.h"
#include "lib/os/spark_data_directory.h"
#include "lib/plugin/SparkServices.h"
#include "lib/plugin/plugin.h"
@@ -41,7 +42,6 @@
#include "lib/utils/bts_chrono.h"
#include "lib/utils/thread_manager.h"
#include "lib/utils/type_name.h"
#include "lib_binance_execution/binance_execution/execution_gateway_provider.h"
#include <bits/stdint-uintn.h>
#include <boost/algorithm/string/predicate.hpp>
@@ -73,16 +73,58 @@ struct ActorThread {
void start(trace::PerfLog &log) {
if (cfg.poll())
thread = threading::make_thread(cfg.thread_name(), [this, &log]() {
for (;;)
const core::TimeSpan log_interval =
core::TimeSpan::from_seconds(30);
core::TimeStamp last_log_time{0};
core::TimeStamp now;
core::TimeSpan elapsed;
std::uint32_t last_count = 0;
for (;;) {
now = core::TimeStamp::now();
elapsed = now - last_log_time;
if (elapsed > log_interval) {
LOG(OrderMan, Info,
"OrderServiceServer ActorThread work speed is {:.2f} "
"cycles/sec",
static_cast<float>(last_count) / elapsed.total_seconds());
last_log_time = now;
last_count = 0;
}
executor.do_work(log);
last_count++;
}
});
else {
thread = threading::make_thread(cfg.thread_name(), [this, &log]() {
// for logging purposes
const std::string t_name = bts::os::get_thread_name();
const std::string actors_desc = FMT_JOIN(cfg.hosted_actor(), ", ");
const core::TimeSpan log_interval =
core::TimeSpan::from_seconds(30);
const auto inner_iter = 1000;
bts::core::TimeStamp last_log_time{0};
core::TimeStamp now;
core::TimeSpan elapsed;
std::uint32_t last_count = 0;
while (!done) {
now = core::TimeStamp::now();
elapsed = now - last_log_time;
auto log_elapsed = now - last_log_time;
if (log_elapsed > log_interval) {
LOG(OrderMan, Info,
"OrderServiceServer ActorThread {} work speed is {:.2f} "
"cycles/sec ({})",
t_name,
static_cast<float>(last_count) / elapsed.total_seconds(),
actors_desc);
last_log_time = now;
last_count = 0;
}
auto start = Timer::now();
for (auto i = 0; i < inner_iter; i++)
executor.do_work(log);
last_count += inner_iter;
auto stop = Timer::now();
loop_metric->observe(ns_diff(stop, start) / inner_iter);
std::this_thread::sleep_for(10ms);
+18 -3
View File
@@ -24,6 +24,12 @@
namespace bts {
namespace orderman {
#define LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "FillStore::" fn ". " __VA_ARGS__)
#define INFO_FN(fn, ...) LOG_FN(Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) LOG_FN(Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) LOG_FN(Error, fn, __VA_ARGS__)
auto const fills_ns = "fills";
struct FillStore::UnresolvedFills {
@@ -160,7 +166,7 @@ void FillStore::on_execution_report(
ev.data->to_string());
auto &er = *ev.data;
auto fk = fmt::format(
"{}:{}", er.order_key.value(), to_str(er.exchange_fill_id));
"{}:{}", er.order_key.value(), to_strn(er.exchange_fill_id));
auto is_known = me->known_fills.count(fk);
if (er.exec_type == api::orderman::EXEC_TYPE_PARTIAL_FILL ||
er.exec_type == api::orderman::EXEC_TYPE_FILL) {
@@ -176,7 +182,7 @@ void FillStore::on_execution_report(
fill.set_fill_kind(api::orderman::Fill::FILL_KIND_STANDARD);
fill.set_security_key(er.security->id());
}
fill.set_account(to_str(er.account_name));
fill.set_account(to_strn(er.account_name));
fill.set_last_price(er.last_price.value());
fill.set_avg_price(er.avg_price.value());
fill.set_side(er.side);
@@ -207,7 +213,15 @@ void FillStore::on_execution_report(
auto it = me->known_orders.find(er.order_key);
if (it != me->known_orders.end()) {
auto &order_def = it->second;
fill.set_account(order_def.account());
if (fill.account() != order_def.account()) {
WARN_FN(
"on_execution_report",
"ExecutionReport account '{}' does not match "
"OrderDefinition account '{}' (order_key: {}). "
"Setting fill account to the latter",
fill.account(), order_def.account(), er.order_key);
fill.set_account(order_def.account());
}
fill.set_internal_account_id(order_def.account_id());
} else
me->fills_with_unresolved_accounts[er.order_key]
@@ -215,6 +229,7 @@ void FillStore::on_execution_report(
auto fv = utils::pb::to_string(fill);
me->store->insert(fills_ns, fk, fv);
LOG(OrderMan, Info, "Publishing fill. {}", fv);
publish(me->fill_stream, fill, *er.security);
} else
LOG(OrderMan, Info,
+67 -45
View File
@@ -37,6 +37,12 @@
namespace bts {
namespace orderman {
#define _LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "ServiceBridge::" fn ". " __VA_ARGS__)
#define INFO_FN(fn, ...) _LOG_FN(Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) _LOG_FN(Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) _LOG_FN(Error, fn, __VA_ARGS__)
struct ServiceBridge::PImpl {
PImpl(
OnReady const &on_ready,
@@ -173,56 +179,72 @@ void ServiceBridge::init() {
} // namespace orderman
void ServiceBridge::on(mercury::Update<StoredFillEvent> const &update) {
if (update.events) {
std::vector<api::orderman::Fill> fills;
fills.reserve(update.events->size());
for (auto ev : *(update.events))
fills.push_back(ev.fill);
me->on_fills(fills);
if (!update.events) {
WARN_FN("on StoredFillEvent", "no event data");
return;
}
INFO_FN("on StoredFillEvent", "n_events: {}", update.events->size());
std::vector<api::orderman::Fill> fills;
fills.reserve(update.events->size());
for (auto ev : *(update.events))
fills.push_back(ev.fill);
me->on_fills(fills);
}
void ServiceBridge::on(mercury::Update<StoredOrderEvent> const &update) {
if (update.events) {
bool was_ready = is_ready();
me->have_orders = true;
std::vector<api::orderman::OrderRecord> orders;
orders.reserve(update.events->size());
for (auto ev : *(update.events)) {
me->known_orders.insert(OrderKey(ev.order_record.key()));
orders.push_back(ev.order_record);
}
me->on_orders(orders);
maybe_signal_ready(was_ready);
if (!update.events) {
WARN_FN("on StoredOrderEvent", "no event data");
return;
}
INFO_FN("on StoredOrderEvent", "n_events: {}", update.events->size());
bool was_ready = is_ready();
me->have_orders = true;
std::vector<api::orderman::OrderRecord> orders;
orders.reserve(update.events->size());
for (auto ev : *(update.events)) {
me->known_orders.insert(OrderKey(ev.order_record.key()));
orders.push_back(ev.order_record);
}
me->on_orders(orders);
maybe_signal_ready(was_ready);
}
void ServiceBridge::on(mercury::Update<OrderManagerReadyEvent> const &update) {
if (update.events) {
bool was_ready = is_ready();
me->order_manager_ready = true;
maybe_signal_ready(was_ready);
if (!update.events) {
WARN_FN("on OrderManagerReadyEvent", "no event data");
return;
}
INFO_FN("on OrderManagerReadyEvent", "n_events: {}", update.events->size());
bool was_ready = is_ready();
me->order_manager_ready = true;
maybe_signal_ready(was_ready);
}
void ServiceBridge::on(mercury::Update<NewAdminDataEvent> const &update) {
if (update.events)
for (auto ev : *(update.events))
me->order_router.update(*ev.admin_data);
if (!update.events) {
WARN_FN("on NewAdminDataEvent", "no event data");
return;
}
INFO_FN("on NewAdminDataEvent", "n_events: {}", update.events->size());
for (auto ev : *(update.events)) {
me->order_router.update(*ev.admin_data);
}
}
void ServiceBridge::on(mercury::Update<OrderOpResultEvent> const &update) {
if (update.events)
for (auto ev : *(update.events))
me->on_order_op_result(ev);
if (!update.events) {
WARN_FN("on OrderOpResultEvent", "no event data");
return;
}
INFO_FN("on OrderOpResultEvent", "n_events: {}", update.events->size());
for (auto ev : *(update.events)) {
me->on_order_op_result(ev);
}
}
void ServiceBridge::create_order(uint32_t rid, api::orderman::Create const &c) {
LOG(OrderMan, Info, "Received new order from client: {}",
utils::pb::to_string(c));
INFO_FN("create_order", "rid: {}; req: {}", rid, utils::pb::to_string(c));
me->service_call_marshaller.schedule_action([this, c, rid]() {
if (is_ready()) {
auto &order =
@@ -239,9 +261,7 @@ void ServiceBridge::create_order(uint32_t rid, api::orderman::Create const &c) {
}
void ServiceBridge::update_order(uint32_t rid, api::orderman::Update const &c) {
LOG(OrderMan, Info, "ServiceBridge::update_order update: {}",
utils::pb::to_string(c));
INFO_FN("update_order", "rid: {}; req: {}", rid, utils::pb::to_string(c));
me->service_call_marshaller.schedule_action([this, c, rid]() {
auto ok = OrderKey(c.order_key());
if (is_ready()) {
@@ -259,9 +279,7 @@ void ServiceBridge::update_order(uint32_t rid, api::orderman::Update const &c) {
}
void ServiceBridge::cancel_order(uint32_t rid, api::orderman::Cancel const &c) {
LOG(OrderMan, Info, "Received order cancel from client: {}",
utils::pb::to_string(c));
INFO_FN("cancel_order", "rid: {}; req: {}", rid, utils::pb::to_string(c));
me->service_call_marshaller.schedule_action([this, c, rid]() {
auto ok = OrderKey(c.order_key());
if (is_ready()) {
@@ -279,9 +297,7 @@ void ServiceBridge::cancel_order(uint32_t rid, api::orderman::Cancel const &c) {
}
void ServiceBridge::detach_order(uint32_t rid, api::orderman::Detach const &c) {
LOG(OrderMan, Info, "Received order detach from client: {}",
utils::pb::to_string(c));
INFO_FN("detach_order", "rid: {}; req: {}", rid, utils::pb::to_string(c));
me->service_call_marshaller.schedule_action([this, c, rid]() {
auto ok = OrderKey(c.order_key());
if (is_ready()) {
@@ -300,15 +316,13 @@ void ServiceBridge::detach_order(uint32_t rid, api::orderman::Detach const &c) {
void ServiceBridge::manually_bust_all_fills(
const api::orderman::ManualBustParameters &c) {
LOG(OrderMan, Info, "ServiceBridge::manually_bust_all_fills: {}",
utils::pb::to_string(c));
INFO_FN("manually_bust_all_fills", "req: {}", utils::pb::to_string(c));
me->service_call_marshaller.schedule_action(
[this, c]() { publish(me->manually_bust_fills_pub, c.account()); });
}
void ServiceBridge::send_rfq(api::orderman::RFQRequest const &rfq) {
LOG(OrderMan, Info, "ServiceBridge::send_rfq rfq: {}",
utils::pb::to_string(rfq));
INFO_FN("send_rfq", "req: {}", utils::pb::to_string(rfq));
me->service_call_marshaller.schedule_action(
[this, rfq]() { publish(me->rfq_pub, rfq); });
}
@@ -398,6 +412,11 @@ std::optional<FilledPositionEvent> make_pos_event(
void ServiceBridge::update_position(
api::position::Position_dataUpdate const &pos) {
INFO_FN(
"update_position",
"n_added: {}; n_changed: {}; n_removed: {}; n_total: {}",
pos.added().size(), pos.changed().size(), pos.removed().size(),
pos.all().size());
std::vector<FilledPositionEvent> evs;
for (auto const &pu : pos.added()) {
auto ev = make_pos_event(*pu, me->sec_db);
@@ -430,6 +449,9 @@ bool ServiceBridge::is_ready() const {
}
void ServiceBridge::maybe_signal_ready(bool old_ready) {
INFO_FN(
"maybe_signal_ready", "old_ready: {}; is_ready: {}", old_ready,
is_ready());
if (!old_ready && is_ready())
me->on_ready();
}
@@ -40,6 +40,13 @@
namespace bts {
namespace orderman {
#define _LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "OrderManager::" fn ". " __VA_ARGS__)
#define DIAG_FN(fn, ...) _LOG_FN(Diag, fn, __VA_ARGS__)
#define INFO_FN(fn, ...) _LOG_FN(Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) _LOG_FN(Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) _LOG_FN(Error, fn, __VA_ARGS__)
using namespace mercury;
enum class OrderManager::State {
@@ -80,6 +87,23 @@ struct OrderManager::PImpl {
mercury::Stream<OrderManagerReadyEvent> pub_ready;
std::thread background_thread;
std::atomic<bool> shutdown = false;
constexpr std::string state_name() {
switch (state) {
case State::WAIT_ADMIN:
return "WAIT_ADMIN";
case State::WAIT_SEC:
return "WAIT_SEC";
case State::WAIT_FILLED_POSITION:
return "WAIT_FILLED_POSITION";
case State::WAIT_ORDERS:
return "WAIT_ORDERS";
case State::READY:
return "READY";
default:
return "UNKNOWN";
}
}
};
OrderManager::OrderManager(
@@ -108,6 +132,7 @@ OrderManager::~OrderManager() {
}
void OrderManager::init() {
INFO_FN("init", "state: {}", me->state_name());
subscribe<NewAdminDataEvent>(
[this](auto const &admin_update) { this->on_admin_data(admin_update); });
subscribe<AccountPnLUpdateEvent>(
@@ -119,301 +144,365 @@ void OrderManager::init() {
}
void OrderManager::on_admin_data(const mercury::Update<NewAdminDataEvent> &m) {
if (m.events) {
if (me->state == State::WAIT_ADMIN) {
LOG(OrderMan, Info,
"Order manager received admin data. Subscribing to security list "
"now ");
for (auto &osd : me->services_data.sessions)
osd->gw->on_admin_data(m);
if (!m.events) {
WARN_FN("on_admin_data", "no event data. state: {}", me->state_name());
return;
}
INFO_FN("on_admin_data", "state: {}", me->state_name());
if (me->state == State::WAIT_ADMIN) {
for (auto &osd : me->services_data.sessions)
osd->gw->on_admin_data(m);
me->state = State::WAIT_SEC;
subscribe<NewSecuritiesEvent>([this](auto const &sec_update) {
this->on_securities(sec_update);
});
}
me->state = State::WAIT_SEC;
INFO_FN("on_admin_data", "state: {}", me->state_name());
subscribe<NewSecuritiesEvent>(
[this](auto const &sec_update) { this->on_securities(sec_update); });
}
for (auto ev : *m.events) {
me->services_data.order_router.update(*ev.admin_data);
me->services_data.limiter.update(*ev.admin_data);
}
for (auto ev : *m.events) {
me->services_data.order_router.update(*ev.admin_data);
me->services_data.limiter.update(*ev.admin_data);
}
}
void OrderManager::on_pnl_update(
const mercury::Update<AccountPnLUpdateEvent> &u) {
if (u.events)
for (auto ev : *u.events)
me->services_data.limiter.set_day_pnl(ev.account_id, ev.day_pnl);
if (!u.events) {
WARN_FN("on_pnl_update", "no event data. state: {}", me->state_name());
return;
}
// This log is too noisy. It fires regularly on an interval (sometimes when
// the pnl hasn't actually changed).
// INFO_FN(
// "on_pnl_update", "state: {}; n_events: {}", me->state_name(),
// u.events->size());
for (auto ev : *u.events) {
me->services_data.limiter.set_day_pnl(ev.account_id, ev.day_pnl);
}
}
void OrderManager::on_excess_liquidity_update(
const mercury::Update<ExcessLiquidityUpdateEvent> &u) {
if (u.events)
for (auto ev : *u.events)
me->services_data.limiter.set_excess_liquidity(
ev.account_id, ev.excess_liquidity);
if (!u.events) {
WARN_FN(
"on_excess_liquidity_update", "no event data. state: {}",
me->state_name());
return;
}
INFO_FN(
"on_excess_liquidity_update", "state: {}; n_events: {}",
me->state_name(), u.events->size());
for (auto ev : *u.events) {
me->services_data.limiter.set_excess_liquidity(
ev.account_id, ev.excess_liquidity);
}
}
void OrderManager::on_rfq_send(const mercury::Update<RFQEvent> &rfq) {
if (rfq.events) {
std::vector<OrderMsg> msgs = {};
for (auto ev : *rfq.events) {
RFQMsg rfq_msg = RFQMsg(ev);
msgs.emplace_back(rfq_msg);
}
trace::CorID cid = {};
LOG(OrderMan, Info, "OrderManager::on_rfq_send n_events: {}",
rfq.events->size());
for (auto &osd : me->services_data.sessions)
osd->gw->send_msgs(msgs, cid);
if (!rfq.events) {
WARN_FN("on_rfq_send", "no event data. state: {}", me->state_name());
return;
}
INFO_FN(
"on_rfq_send", "state: {}; n_events: {}", me->state_name(),
rfq.events->size());
std::vector<OrderMsg> msgs = {};
for (auto ev : *rfq.events) {
RFQMsg rfq_msg = RFQMsg(ev);
msgs.emplace_back(rfq_msg);
}
trace::CorID cid = {};
for (auto &osd : me->services_data.sessions) {
osd->gw->send_msgs(msgs, cid);
}
}
void OrderManager::on_securities(
const mercury::Update<NewSecuritiesEvent> &update) {
if (!update.events) {
WARN_FN("on_securities", "no event data. state: {}", me->state_name());
return;
}
INFO_FN(
"on_securities", "state: {}; n_events: {}", me->state_name(),
update.events->size());
// forward to execution gateways
for (auto &osd : me->services_data.sessions)
osd->gw->on_securities(update);
if (update.events) {
// update our security db and accumulate for the sake of the limiter
std::vector<api::Security const *> secs;
for (auto const &ev : *(update.events)) {
auto sec_ptr = ev.security;
secs.push_back(sec_ptr);
}
me->services_data.limiter.add_securities(secs);
// update our security db and accumulate for the sake of the limiter
std::vector<api::Security const *> secs;
for (auto const &ev : *(update.events)) {
auto sec_ptr = ev.security;
secs.push_back(sec_ptr);
}
me->services_data.limiter.add_securities(secs);
if (me->state == State::WAIT_SEC) {
LOG(OrderMan, Info,
"Order manager received initial set of {} "
"securities. Waiting for the filled position now",
update.events->size());
me->state = State::WAIT_FILLED_POSITION;
me->filled_position_sub = subscribe<FilledPositionEvent>(
[this](auto const &pos) { this->on_filled_position(pos); });
}
if (me->state == State::WAIT_SEC) {
me->state = State::WAIT_FILLED_POSITION;
INFO_FN(
"on_securities", "received initial set of {} securities. state: {}",
update.events->size(), me->state_name());
me->filled_position_sub = subscribe<FilledPositionEvent>(
[this](auto const &pos) { this->on_filled_position(pos); });
}
}
void OrderManager::on_filled_position(
const mercury::Update<FilledPositionEvent> &update) {
if (update.events) {
if (me->state == State::WAIT_FILLED_POSITION) {
LOG(OrderMan, Info,
"Order manager received initial set of {} "
"stored filled position records. Waiting for stored orders now",
update.events->size());
me->state = State::WAIT_ORDERS;
me->stored_order_sub = subscribe<StoredOrderEvent>(
[this](auto const &orders) { this->on_order_recovery(orders); });
}
if (!update.events) {
WARN_FN(
"on_filled_position", "no event data. state: {}", me->state_name());
return;
}
INFO_FN(
"on_filled_position", "state: {}; n_events: {}", me->state_name(),
update.events->size());
if (me->state == State::WAIT_FILLED_POSITION) {
me->state = State::WAIT_ORDERS;
INFO_FN("on_filled_position", "new_state: {}", me->state_name());
me->stored_order_sub = subscribe<StoredOrderEvent>(
[this](auto const &orders) { this->on_order_recovery(orders); });
}
for (auto const &ev : *(update.events)) {
me->services_data.position.update(ev);
me->services_data.limiter.set_filled_position(
*ev.security, ev.account_id, ev.long_quantity, ev.short_quantity);
}
for (auto const &ev : *(update.events)) {
me->services_data.position.update(ev);
me->services_data.limiter.set_filled_position(
*ev.security, ev.account_id, ev.long_quantity, ev.short_quantity);
}
}
void OrderManager::on_order_recovery(
const mercury::Update<StoredOrderEvent> &update) {
if (update.events)
if (me->state == State::WAIT_ORDERS) {
if (!update.events) {
WARN_FN(
"on_order_recovery", "no event data. state: {}", me->state_name());
return;
}
if (me->state != State::WAIT_ORDERS) {
WARN_FN(
"on_order_recovery",
"Expected state WAIT_ORDERS. state: {}; n_events: {}",
me->state_name(), update.events->size());
return;
}
// Got the first batch of persisted orders which has to match the orders
// stored on disk (since we are the only ones producing new orders)
INFO_FN(
"on_order_recovery", "state: {}; n_events: {}", me->state_name(),
update.events->size());
// Don't need to monitor persisted orders anymore, all the new orders we care
// about will be generated by us.
unsubscribe(me->stored_order_sub);
// Got the first batch of persisted orders which has to match
// the orders stored on disk (since we are the only ones
// producing new orders)
LOG(OrderMan, Info,
"Order manager received initial set of {} persisted orders.",
update.events->size());
init_order_services();
// Don't need to monitor persisted orders anymore, all the
// new orders we care about will be generated by us.
unsubscribe(me->stored_order_sub);
// recover orders from disk
for (auto const &ev : *(update.events)) {
auto &order = me->services_data.order_factory.recover(ev.order_record);
auto const &def = order.def();
publish_order_def_update(OrderKey(ev.key()), def);
order.init_order_services(me->services_data);
init_order_services();
auto r = me->orders.emplace(order.key(), &order);
bts_assert(r.second);
auto const &ostate = ev.order_record.state();
order.on_activate(
Price(ostate.price()), Quantity(ostate.quantity()), *order._services);
}
// recover orders from disk
for (auto const &ev : *(update.events)) {
auto &order =
me->services_data.order_factory.recover(ev.order_record);
auto const &def = order.def();
publish_order_def_update(OrderKey(ev.key()), def);
order.init_order_services(me->services_data);
auto r = me->orders.emplace(order.key(), &order);
bts_assert(r.second);
auto const &ostate = ev.order_record.state();
order.on_activate(
Price(ostate.price()), Quantity(ostate.quantity()),
*order._services);
}
// signal that we are ready
LOG(OrderMan, Info, "Order manager is ready");
publish(me->pub_ready);
me->state = State::READY;
}
// signal that we are ready
INFO_FN("on_order_recovery", "OrderManager is ready");
publish(me->pub_ready);
me->state = State::READY;
}
void OrderManager::on_execution_report(
const mercury::Update<NewExecutionReportEvent> &m) {
if (m.events)
for (auto const &report : *m.events)
if (report.data->leg_security == nullptr) {
auto k = report.data->order_key;
auto it = me->orders.find(k);
if (it == me->orders.end()) {
LOG(OrderMan, Error,
"Received an execution report for an unknown order with key "
"{}. Order state USED TO be reconstructed from this report but "
"some "
"information will be lost, including CustomerOrFirm,CTI "
"code "
"and "
"parent order id but now these are IGNORED",
k.value());
continue;
auto order =
&me->services_data.order_factory.recreate_from_exe_report(
*report.data);
auto const &def = order->def();
publish_order_def_update(OrderKey(k), def);
order->init_order_services(me->services_data);
auto r = me->orders.emplace(order->key(), order);
bts_assert(r.second);
order->on_activate(
report.data->price, report.data->quantity,
*order->_services);
}
}
if (!m.events) {
WARN_FN("on_execution_report", "no event data");
return;
}
INFO_FN(
"on_execution_report", "state: {}; n_events", me->state_name(),
m.events->size());
for (auto const &report : *m.events) {
if (report.data->leg_security != nullptr) {
continue;
}
auto k = report.data->order_key;
auto it = me->orders.find(k);
if (it == me->orders.end()) {
ERROR_FN(
"on_execution_report",
"Received an execution report for an unknown order with key {}. "
"Order state USED TO be reconstructed from this report but some "
"information will be lost, including CustomerOrFirm,CTI code and "
"parent order id but now these are IGNORED",
k.value());
continue;
// auto order = &me->services_data.order_factory.recreate_from_exe_report(
// *report.data);
// auto const &def = order->def();
// publish_order_def_update(OrderKey(k), def);
// order->init_order_services(me->services_data);
// auto r = me->orders.emplace(order->key(), order);
// bts_assert(r.second);
// order->on_activate(
// report.data->price, report.data->quantity, *order->_services);
}
}
}
void OrderManager::on_activate_order(
const mercury::Update<ActivateOrderEvent> &m) {
if (m.events)
for (auto const &ev : *m.events) {
LOG(OrderMan, Info,
"OrderManager::on_activate_order req_id: {} p: {} q: {}",
ev.req_id, ev.price, ev.quantity);
auto order = ev.order;
auto const &def = order->def();
publish_order_def_update(order->key(), def);
order->init_order_services(me->services_data);
auto r = me->orders.emplace(order->key(), order);
bts_assert(r.second);
if (!m.events) {
WARN_FN("on_activate_order", "no event data");
return;
}
INFO_FN(
"on_activate_order", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
LOG(OrderMan, Info,
"OrderManager::on_activate_order req_id: {} p: {} q: {}", ev.req_id,
ev.price, ev.quantity);
auto order = ev.order;
auto const &def = order->def();
publish_order_def_update(order->key(), def);
order->init_order_services(me->services_data);
auto r = me->orders.emplace(order->key(), order);
bts_assert(r.second);
auto rid = ev.req_id;
auto k = order->key();
auto on_response = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
order->_services->activate(ev.price, ev.quantity, on_response);
}
auto rid = ev.req_id;
auto k = order->key();
auto on_response = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
order->_services->activate(ev.price, ev.quantity, on_response);
}
}
void OrderManager::on_update_order(const mercury::Update<UpdateOrderEvent> &m) {
if (m.events)
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id();
if (!m.events) {
WARN_FN("on_update_order", "no event data");
return;
}
INFO_FN(
"on_update_order", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id();
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
if (it != me->orders.end()) {
if (ev.is_full_update())
it->second->_services->update(
ev.full_update(), on_order_op_result);
else
it->second->_services->update(
Price(ev.price()), Quantity(ev.quantity()),
on_order_op_result);
if (it != me->orders.end()) {
if (ev.is_full_update()) {
it->second->_services->update(ev.full_update(), on_order_op_result);
} else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown order"});
LOG(OrderMan, Error,
"Received an update request for an unknown order {}. "
"This should not happen and likely indicates a serious bug. ",
ev.key().value());
it->second->_services->update(
Price(ev.price()), Quantity(ev.quantity()), on_order_op_result);
}
} else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown order"});
ERROR_FN(
"on_update_order",
"order not found (id={}). This likely indicates a serious bug.",
ev.key().value());
}
}
}
void OrderManager::on_cancel_order(const mercury::Update<CancelOrderEvent> &m) {
if (m.events)
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id;
if (!m.events) {
WARN_FN("on_cancel_order", "no event data");
return;
}
INFO_FN(
"on_cancel_order", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id;
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
if (it != me->orders.end())
it->second->_services->cancel(on_order_op_result);
else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown order"});
LOG(OrderMan, Error,
"Received a cancel request for an unknown order {}. "
"This should not happen and likely indicates a serious bug. ",
ev.key().value());
}
if (it != me->orders.end()) {
it->second->_services->cancel(on_order_op_result);
} else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown order"});
ERROR_FN(
"on_cancel_order",
"order not found (id={}). This likely indicates a serious bug.",
ev.key().value());
}
}
}
void OrderManager::on_detach_order(const mercury::Update<DetachOrderEvent> &m) {
if (m.events)
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id;
if (!m.events) {
WARN_FN("on_detach_order", "no event data");
return;
}
INFO_FN(
"on_detach_order", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
auto k = ev.key();
auto it = me->orders.find(k);
auto rid = ev.req_id;
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
auto on_order_op_result = [this, rid, k](auto const &order_op_result) {
publish(
me->services_data.report_order_op_result_pub, k, rid,
order_op_result);
};
if (it != me->orders.end())
it->second->_services->detach(on_order_op_result);
else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown Order"});
if (it != me->orders.end()) {
it->second->_services->detach(on_order_op_result);
} else {
on_order_op_result(OrderOpResult{
api::orderman::ORDER_ERROR_UNKNOWN_ORDER, "Unknown Order"});
LOG(OrderMan, Error,
"Received a detach request for an unknown order {}. "
"This should not happen and likely indicates a serious bug. ",
ev.key().value());
}
ERROR_FN(
"on_detach_order",
"order not found (id={}). This likely indicates a serious bug.",
ev.key().value());
}
}
}
void OrderManager::on_gateway_state(
const mercury::Update<GatewayStatusChangeEvent> &m) {
if (m.events)
for (auto const &ev : *m.events) {
LOG(OrderMan, Info,
" OrderManager::on_gateway_state gw: {} connected: {}", ev.gw_name,
ev.is_connected);
auto &sr = me->services_data.get_session(ev.gw_name.value());
sr.gw_connected = ev.is_connected;
}
if (!m.events) {
WARN_FN("on_gateway_state", "no event data");
return;
}
INFO_FN(
"on_gateway_state", "state: {}; n_events: {}", me->state_name(),
m.events->size());
for (auto const &ev : *m.events) {
LOG(OrderMan, Info,
" OrderManager::on_gateway_state gw: {} connected: {}", ev.gw_name,
ev.is_connected);
auto &sr = me->services_data.get_session(ev.gw_name.value());
sr.gw_connected = ev.is_connected;
}
}
void OrderManager::add_child_order(OrderServices &parent, Order &child) {
@@ -471,9 +560,12 @@ void OrderManager::init_order_services() {
return s->session;
}),
", "));
LOG(OrderMan, Info, "init_order_services. sessions: {}", session_names);
INFO_FN(
"init_order_services", "state: {}; sessions: {}", me->state_name(),
session_names);
for (auto &s : me->services_data.sessions) {
INFO_FN("init_order_services", "Initializing session {}", s->session);
auto gw_name = GatewayName(s->session);
// Now we are ready to subscribe to the execution gateway
@@ -536,9 +628,11 @@ void OrderManager::init_order_services() {
// reject evets
auto s_ptr = s.get();
s->exe_report_pub = make_stream<NewExecutionReportEvent>(gw_name);
s->gw->on_exe_report() = [s_ptr, this](auto const &er, auto const &t) {
publish(s_ptr->exe_report_pub, &er, t);
};
s->gw->on_exe_report() =
[s_ptr,
this](std::shared_ptr<const ExecutionReport> er, auto const &t) {
publish(s_ptr->exe_report_pub, std::move(er), t);
};
s->gw_status_change_pub = make_stream<GatewayStatusChangeEvent>(gw_name);
s->gw->on_gw_status_change() = [s_ptr, this, gw_name](bool b) {
publish(s_ptr->gw_status_change_pub, gw_name, b);
@@ -564,9 +658,10 @@ void OrderManager::init_order_services() {
s->gw->on_cancel_reject() = [s_ptr, this](auto const &cr) {
publish(s_ptr->cancel_reject_pub, cr);
};
INFO_FN("init_order_services", "Starting session {}", s->session);
// handlers are ready so we can start processing fix events
s->gw->start();
INFO_FN("init_order_services", "Started session {}", s->session);
reload_fix_buffer(*s);
}
@@ -677,9 +772,10 @@ void OrderManager::after_event() {
if (queued_ops.size()) {
auto &corid = osd->current_trace;
auto now = utils::TimeStamp::now();
LOG(Latency, Info,
"entry_type:after_event channel:{} sn:{} app_arrival:{} now:{} "
"delta:{}",
INFO_FN(
"after_event",
"channel: {} sn: {} app_arrival: {} now: {} "
"delta: {}",
std::string{corid.channel}, corid.sn,
corid.app_arrival.to_uint64(), now.to_uint64(),
(now - corid.app_arrival).nanoseconds());
@@ -695,9 +791,9 @@ void OrderManager::reload_fix_buffer(SessionServicesData &osd) {
osd.buffered_fix_msgs = osd.free_buffers.back();
osd.free_buffers.pop_back();
} else {
LOG(OrderMan, Warn,
"Ran out of fix buffers in {}. Will allocate directly. "
"This will have an impact on performance",
WARN_FN(
"reload_fix_buffer",
"Allocating new fix buffers in {}. This will impact performance",
osd.cfg.name());
osd.buffered_fix_msgs = std::make_shared<jetfix::OutFIX>(osd.cfg);
}
@@ -1,10 +1,6 @@
#include "OrderServicesData.h"
#include "lib/config/config.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/md/SecurityOracle.h"
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
#include "lib/orderman/gateways/ExecutionGatewayFactory.h"
#include "lib/os/spark_data_directory.h"
#include "lib/tracing/PerfLog.h"
namespace bts {
@@ -26,8 +22,6 @@ OrderServicesData::OrderServicesData(
for (auto &g : gateways) {
sessions.push_back(std::make_unique<SessionServicesData>(std::move(g)));
}
// spark::SparkDataDirectory data_dir;
// auto binance_j = data_dir.load_config_json("binance_execution");
}
} // namespace orderman
@@ -34,7 +34,6 @@
#include "lib/orderman/events/TickerUpdateEvent.h"
#include "lib/orderman/events/TradeSignalEvent.h"
#include "lib/orderman/events/UpdateOrderEvent.h"
#include "lib/orderman/gateways/ExecutionGatewayFactory.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
#include "lib/orderman/limiter/Limiter.h"
#include "lib/orderman/order_factory/OrderFactory.h"
@@ -10,7 +10,6 @@
#include "lib/orderman/events/ExchangePositionEvent.h"
#include "lib/orderman/events/GatewayStatusChangeEvent.h"
#include "lib/orderman/events/NewExecutionReportEvent.h"
#include "lib/orderman/gateways/ExecutionGatewayFactory.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
#include "lib/utils/PBUtils.h"
@@ -20,6 +20,7 @@
#include "lib/utils/bts_assert.h"
#include <bits/stdint-uintn.h>
#include <boost/lockfree/spsc_queue.hpp>
#include <ccapi_cpp/bts_session_options.h>
#include <ccapi_cpp/ccapi_element.h>
#include <ccapi_cpp/ccapi_event.h>
#include <ccapi_cpp/ccapi_macro.h>
@@ -255,7 +256,8 @@ struct CCAPIDeribitOrderMux::PImpl {
{CCAPI_DERIBIT_CLIENT_ID, d_conn_config.client_id},
{CCAPI_DERIBIT_CLIENT_SECRET, d_conn_config.client_secret},
});
session = new ccapi::Session({}, session_configs, &event_handler);
session = new ccapi::Session(
{}, {ccapi::BtsVenues::DERIBIT}, session_configs, &event_handler);
}
};
@@ -953,4 +955,4 @@ void CCAPIDeribitOrderMux::_send_buffered_orders(uint32_t sec_id) {
"n_buffered_orders: {}",
me->buffered_creates.size());
}
} // namespace bts::orderman
} // namespace bts::orderman
@@ -20,12 +20,13 @@ class NewExecutionReportEvent : public mercury::Event<NewExecutionReportEvent> {
public:
using Tag = GatewayName;
using SynopsisPolicy = mercury::CacheAll<NewExecutionReportEvent>;
ExecutionReport const *data;
std::shared_ptr<const ExecutionReport> data;
trace::CorID trace;
// used by ContentSubscription
OrderKey key() const { return data->order_key; }
NewExecutionReportEvent(const ExecutionReport *dt, trace::CorID const &trace)
: data(dt), trace(trace){};
NewExecutionReportEvent(
std::shared_ptr<const ExecutionReport> dt, trace::CorID const &trace)
: data(std::move(dt)), trace(trace){};
};
} // namespace orderman
@@ -40,7 +40,7 @@
#include <bits/stdint-uintn.h>
//#include <boost/container/small_vector.hpp>
#include "lib/orderman/ccapi/CCAPIConnectionConfig.h"
#include <boost/pool/object_pool.hpp>
#include <cmath>
#include <cstdint>
#include <optional>
@@ -138,7 +138,6 @@ struct CCAPIExecutionGateway::PImpl {
OnExchangeOpenOrdersState on_exchange_open_orders;
// orders are sent out and callbacks received via IEMOrderMuxer
size_t msg_seq_num = 0;
boost::object_pool<ExecutionReport> exe_report_pool;
std::unordered_map<OrderKey, CreateOrderMsg> order_map = {};
// this is updated as we get exchange positions
ExchangePosition exchange_position;
@@ -339,8 +338,7 @@ void CCAPIExecutionGateway::_on_order_cb(const CCAPIOrderState &s) {
const auto &cr_msg = it->second;
// cant create one on the stack
ExecutionReport *er_p = me->exe_report_pool.construct();
ExecutionReport &er = *er_p;
ExecutionReport er;
// length of these have to be <= 12
to_array(cr_msg.routing_record->account(), &er.account_name.value());
to_array(cr_msg.routing_record->operator_id(), &er.operator_id.value());
@@ -390,7 +388,7 @@ void CCAPIExecutionGateway::_on_order_cb(const CCAPIOrderState &s) {
LOG(OrderMan, Info,
"CCAPIExecutionGateway::_process_cb_and_fire_er firing er: {}",
er.to_string());
me->on_exe_report(er, {});
me->on_exe_report(std::make_shared<ExecutionReport>(std::move(er)), {});
// _fire_leg_er(em_cb, er);
}
@@ -1,61 +0,0 @@
#include "ExecutionGatewayFactory.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/SecurityOracle.h"
#include "lib/orderman/gateways/CCAPIExecutionGateway.h"
// #include "lib/orderman/gateways/EMExecutionGateway.h"
#include "lib/orderman/gateways/FakeExecutionGateway.h"
#include "lib/orderman/gateways/GeneralExecutionGateway.h"
#include "lib/orderman/gateways/GLBXExecutionGateway.h"
#include "lib/orderman/gateways/GLBXiLink3ExecutionGateway.h"
#include "lib/orderman/gateways/ICEExecutionGateway.h"
#include "lib/orderman/gateways/IExecutionGenericAdapter.h"
#include "lib/orderman/gateways/OkxExecutionAdapter.h"
#include "lib/orderman/gateways/OnixFIXAdapter.h"
#include "lib/orderman/gateways/sim/SimExecutionGatewayFactory.h"
#include "lib/orderman/gateways/sim/SimExecutionGatewayUniqueWrapper.h"
#include "lib/tracing/PerfLog.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/ValueWrapper.h"
#include <memory>
#include <variant>
namespace bts::orderman {
std::unique_ptr<IExecutionGateway> ExecutionGatewayFactory::create_gateway(
const config::JetFIX::Connection &conn,
trace::PerfLog &pl,
md::SecurityOracle o) {
LOG(OrderMan, Info, "ExecutionGatewayFactory::create_gateway conn: {}",
utils::pb::to_string(conn));
if (SimExecutionGatewayFactory::is_sim_execution_on(conn.venue())) {
return std::make_unique<SimExecutionGatewayUniqueWrapper>(conn.venue());
}
LOG(OrderMan, Info, "ExecutionGatewayFactory::create_gateway NOT SIM conn: {}",
utils::pb::to_string(conn));
switch(conn.venue()){
case bts::api::VENUE_ICE:
case bts::api::VENUE_SIMICE:
return std::make_unique<ICEExecutionGateway>(conn, pl, o);
case bts::api::VENUE_GLBX:{
if (conn.use_ilink3())
return std::make_unique<ilink3::GLBXiLink3ExecutionGateway>( conn, pl, o);
return std::make_unique<GLBXExecutionGateway>(conn, pl, o);
}
case bts::api::VENUE_DERIBIT:
return std::make_unique<CCAPIExecutionGateway>(conn.venue(), o);
case bts::api::VENUE_CBOE:
{
std::unique_ptr<IExecutionGenericAdapter> adapter = std::make_unique<OnixFIXAdapter>(o);
return std::make_unique<GeneralExecutionGateway>(conn.venue(),std::move(adapter),o);
}
case bts::api::VENUE_OKX:
{
std::unique_ptr<IExecutionGenericAdapter> adapter = std::make_unique<okxexe::OkxExecutionAdapter>(o);
return std::make_unique<GeneralExecutionGateway>(conn.venue(),std::move(adapter),o);
}
default:
return std::make_unique<FakeExecutionGateway>(o);
}
}
} // namespace bts::orderman
@@ -1,25 +0,0 @@
#pragma once
#include "lib/config/protos/jetfix.pb.h"
#include <memory>
namespace bts::trace {
class PerfLog;
}
namespace bts::md {
class SecurityOracle;
}
namespace bts::orderman {
class IExecutionGateway;
class ExecutionGatewayFactory {
public:
static std::unique_ptr<IExecutionGateway> create_gateway(
const config::JetFIX::Connection &,
trace::PerfLog &,
md::SecurityOracle);
};
} // namespace bts::orderman
@@ -25,7 +25,6 @@
#include "server/sparkd/md/platforms/emumba/IEMOrderMuxer.h"
#include <atomic>
#include <boost/pool/object_pool.hpp>
#include <cstdint>
#include <set>
#include <string>
@@ -54,7 +53,7 @@ struct FakeExecutionGateway::PImpl {
md::SecurityOracle sec_oracle;
std::vector<OrderMsg> buffered_msgs = {};
unsigned long exchange_oid = 0;
std::vector<ExecutionReport> exe_reports = {};
std::vector<std::shared_ptr<ExecutionReport>> exe_reports = {};
IEMOrderMuxer *_order_mux = nullptr;
};
@@ -84,7 +83,9 @@ void FakeExecutionGateway::dispatch_incoming_fix_messages() {
"FakeExecutionGateway::dispatch_incoming_fix_message key: {} sec: "
"{} p: {} q: {}",
cr_msg.key, cr_msg.security->id(), cr_msg.price, cr_msg.quantity);
ExecutionReport er = {};
me->exe_reports.push_back(std::make_shared<ExecutionReport>());
std::shared_ptr<ExecutionReport> &er_p = me->exe_reports.back();
ExecutionReport &er = *er_p;
std::string account_name = "ACCA1";
std::array<char, 12> an_array = {};
size_t i = 0;
@@ -115,19 +116,18 @@ void FakeExecutionGateway::dispatch_incoming_fix_messages() {
er.msg = "Message Placed";
er.exchange = api::VENUE_SIM;
trace::CorID cid = {};
me->exe_reports.push_back(er);
const auto &er_pheap = me->exe_reports.back();
LOG(OrderMan, Info,
"FakeExecutionGateway::dispatch_incoming_fix_messages er: {}",
er_pheap.to_string());
me->on_exe_report(er_pheap, cid);
er.to_string());
me->on_exe_report(er_p, cid);
} else if (std::holds_alternative<CancelOrderMsg>(m)) {
CancelOrderMsg cxl_m = std::get<CancelOrderMsg>(m);
for (auto &er : me->exe_reports) {
for (std::shared_ptr<ExecutionReport> &er_p : me->exe_reports) {
auto &er = *er_p;
if (er.order_key == cxl_m.key) {
er.order_status = api::orderman::NATIVE_ORDER_STATUS_CANCELED;
er.msg = "Cancelled";
me->on_exe_report(er, {});
me->on_exe_report(er_p, {});
LOG(OrderMan, Info,
"FakeExecutionGateway::dispatch_incoming_fix_messages er: "
"{}",
@@ -138,13 +138,14 @@ void FakeExecutionGateway::dispatch_incoming_fix_messages() {
} else if (std::holds_alternative<CancelReplaceOrderMsg>(m)) {
CancelReplaceOrderMsg cxl_r_msg = std::get<CancelReplaceOrderMsg>(m);
CreateOrderMsg cr_msg = cxl_r_msg.create_fields;
for (auto &er : me->exe_reports) {
for (std::shared_ptr<ExecutionReport> &er_p : me->exe_reports) {
auto &er = *er_p;
if (er.order_key == cr_msg.key) {
er.order_status = api::orderman::NATIVE_ORDER_STATUS_REPLACED;
er.msg = "Replaced";
er.price = cr_msg.price;
er.quantity = cr_msg.quantity;
me->on_exe_report(er, {});
me->on_exe_report(er_p, {});
return;
}
}
@@ -196,4 +197,4 @@ OnExchangeOpenOrdersState &FakeExecutionGateway::on_exchange_open_orders() {
}
} // namespace orderman
} // namespace bts
} // namespace bts
@@ -13,8 +13,6 @@
#include "lib/utils/TimeStamp.h"
#include "lib/utils/bts_chrono.h"
#include <boost/pool/object_pool.hpp>
namespace bts {
namespace orderman {
@@ -31,12 +29,11 @@ struct GLBXExecutionGateway::PImpl {
config::JetFIX::Connection const &cfg,
trace::PerfLog &l,
md::SecurityOracle o)
: gw(cfg, l), exe_report_pool(), state(State::WAITING_FOR_SECURITIES),
: gw(cfg, l), state(State::WAITING_FOR_SECURITIES),
securities(), securities_by_descriptor(), gw_name(cfg.name()),
cfg(cfg), perf_log(l), sec_oracle(o) {}
JFGw gw;
boost::object_pool<ExecutionReport> exe_report_pool;
State state;
std::unordered_map<SecurityKey::Value, api::Security const *> securities;
std::unordered_map<std::string, api::Security const *>
@@ -209,8 +206,8 @@ void GLBXExecutionGateway::on_exe_report(
if (msg.has<fix::Text>())
text_msg = msg.get<fix::Text>();
// allocate from our pool
auto data = me->exe_report_pool.construct();
ExecutionReport er;
ExecutionReport *data = &er;
data->exec_type = api::orderman::ExecType(msg.get<fix::ExecType>());
data->msg_seq_num = msg.get<fix::MsgSeqNum>();
@@ -290,7 +287,8 @@ void GLBXExecutionGateway::on_exe_report(
data->cfg = &me->cfg;
data->msg = text_msg;
data->exchange = api::Venue::VENUE_GLBX;
me->on_exe_report(*data, msg.trace());
me->on_exe_report(
std::make_shared<ExecutionReport>(std::move(er)), msg.trace());
}
}
@@ -12,6 +12,7 @@
#include "lib/orderman/ilink3/IL3Reactor.h"
#include "lib/orderman/ilink3/IL3SecDB.h"
#include "lib/orderman/ilink3/SessionStore.h"
#include "lib/orderman/orderman_utils/ExecutionReport.h"
#include "lib/orderman/orderman_utils/fix_order_codec.h"
#include "lib/utils/PBUtils.h"
#include <cstddef>
@@ -149,8 +150,11 @@ void GLBXiLink3ExecutionGateway::connect(jetfix::SessionName const &sn) {
if (!me->has_replayed) {
me->has_replayed = true;
me->replay_messages = me->session_store.value().replay();
for (auto const &er : me->replay_messages)
me->on_exe_report(er, {});
for (ExecutionReport &er : me->replay_messages) {
me->on_exe_report(
std::make_shared<ExecutionReport>(std::move(er)), {});
}
me->replay_messages.clear();
}
c.raw_message_logger =
@@ -169,7 +173,7 @@ void GLBXiLink3ExecutionGateway::connect(jetfix::SessionName const &sn) {
me->gw->subscribe_execution_reports([this](auto er) {
bts_assert(me->state == State::READY || me->state == State::CONNECTING);
me->on_exe_report(*er, {});
me->on_exe_report(std::move(er), {});
});
me->gw->subscribe_session(
@@ -64,7 +64,9 @@ void GeneralExecutionGateway::send_msgs(
msg);
}
}
void GeneralExecutionGateway::dispatch_incoming_fix_messages() {}
void GeneralExecutionGateway::dispatch_incoming_fix_messages() {
me->_adapter->dispatch_incoming_fix_messages();
}
void GeneralExecutionGateway::on_securities(
const mercury::Update<NewSecuritiesEvent> &) {}
void GeneralExecutionGateway::on_admin_data(
@@ -18,7 +18,6 @@
#include "lib/utils/helper_macros.h"
#include <atomic>
#include <boost/pool/object_pool.hpp>
#include <set>
namespace bts {
@@ -39,13 +38,12 @@ struct ICEExecutionGateway::PImpl {
const config::JetFIX::Connection &cfg,
trace::PerfLog &l,
md::SecurityOracle o)
: gw(cfg, l), exe_report_pool(), state(State::WAITING_FOR_SECURITIES),
securities(), securities_by_exchange_id(), gw_name(cfg.name()),
cfg(cfg), perf_log(l), admin_data(), nusers(-1), connection_time(),
: gw(cfg, l), state(State::WAITING_FOR_SECURITIES), securities(),
securities_by_exchange_id(), gw_name(cfg.name()), cfg(cfg),
perf_log(l), admin_data(), nusers(-1), connection_time(),
exec_id_to_memo(), sec_oracle(o) {}
JFGw gw;
boost::object_pool<ExecutionReport> exe_report_pool;
State state;
std::unordered_map<SecurityKey::Value, api::Security const *> securities;
std::unordered_map<int64_t, api::Security const *> securities_by_exchange_id;
@@ -373,7 +371,8 @@ void ICEExecutionGateway::on_exe_report(
std::to_string(msg.get<fix::DeleteReason>());
// allocate from our pool
auto data = me->exe_report_pool.construct();
ExecutionReport er;
ExecutionReport *data = &er;
if (msg.has<fix::Memo>())
data->memo = msg.get<fix::Memo>();
@@ -467,7 +466,8 @@ void ICEExecutionGateway::on_exe_report(
data->cfg = &me->cfg;
data->msg = text_msg;
data->exchange = api::Venue::VENUE_ICE;
me->on_exe_report(*data, msg.trace());
me->on_exe_report(
std::make_shared<ExecutionReport>(std::move(er)), msg.trace());
}
}
@@ -28,8 +28,8 @@ class ExecutionReport;
class CancelReject;
using OnGwStatusChange = std::function<void(bool)>;
using OnExeReport =
std::function<void(ExecutionReport const &, trace::CorID const &)>;
using OnExeReport = std::function<void(
std::shared_ptr<const ExecutionReport>, trace::CorID const &)>;
using OnCancelReject = std::function<void(CancelReject const &)>;
using OnExchangePositionState = std::function<void(ExchangePosition const &)>;
using OnExchangeOpenOrdersState =
@@ -20,7 +20,8 @@ class NewAdminDataEvent;
class ExecutionReport;
class CancelReject;
using OnExecutionReport = std::function<void(ExecutionReport const &, const trace::CorID &)>;
using OnExecutionReport = std::function<void(
std::shared_ptr<const ExecutionReport>, const trace::CorID &)>;
// execution gateway for a single session
class IExecutionGenericAdapter {
@@ -34,6 +35,7 @@ class IExecutionGenericAdapter {
virtual void launch_new_order(const CreateOrderMsg &) =0;
virtual void modify_order(const CancelReplaceOrderMsg &) = 0;
virtual void cancel_order(const CancelOrderMsg &) =0;
virtual void dispatch_incoming_fix_messages() = 0;
// as new securities are added
// virtual void update_securities(
// const mercury::Update<NewSecuritiesEvent> &) = 0;
@@ -1,21 +0,0 @@
#pragma once
#include "IExecutionGenericAdapter.h"
#include "lib/md/SecurityOracle.h"
namespace bts::orderman::okxexe {
class OkxExecutionAdapter : public IExecutionGenericAdapter {
public:
OkxExecutionAdapter(md::SecurityOracle sec_oracle);
~OkxExecutionAdapter() override;
void register_callback(const OnExecutionReport &report) override;
void launch_new_order(const CreateOrderMsg &msg) override;
void cancel_order(const CancelOrderMsg &msg) override;
void modify_order(const CancelReplaceOrderMsg &) override;
void start() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::orderman
@@ -1,4 +1,4 @@
#include "OkxExecutionAdapter.h"
#include "OkxExecutionGateway.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/config/protos/okx_exe_cfg.pb.h"
#include "lib/domain_model/Security.pb.h"
@@ -6,7 +6,6 @@
#include "lib/io/http/http_client.h"
#include "lib/io/http/http_response.h"
#include "lib/io/websocket/websocket_client.h"
#include "lib/io/websocket/websocket_client_config.h"
#include "lib/logging/logging.h"
#include "lib/md/SecurityOracle.h"
#include "lib/orderman/CancelOrderMsg.h"
@@ -18,13 +17,13 @@
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/Uuid.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/thread_manager.h"
#include <OnixS/FIXEngine/FIX/Exception.h>
#include <OnixS/FIXEngine/FIX/FIX42.h>
#include <OnixS/FIXEngine/FIX/Message.h>
#include <OnixS/FIXEngine/FIX/Numeric.h>
#include <OnixS/FIXEngine/FIX/ProtocolVersion.h>
#include <atomic>
#include <boost/pool/object_pool.hpp>
#include <cassert>
#include <chrono>
#include <cstddef>
@@ -44,30 +43,61 @@
// Example namespace alias for nlohmann::json
using json = nlohmann::json;
namespace bts::orderman::okxexe {
namespace bts::orderman {
OkxExecutionAdapter::OkxExecutionAdapter(md::SecurityOracle o)
OkxExecutionGateway::OkxExecutionGateway(md::SecurityOracle o)
: me_(std::make_unique<PImpl>(o)) {
LOG(OrderMan, Info, "OkxExecutionAdapter::constructor ");
} //listener() {}
OkxExecutionAdapter::~OkxExecutionAdapter() {}
OkxExecutionGateway::~OkxExecutionGateway() {}
enum class OkxExecutionState { OFF, STARTED, LOGGED_IN, SUBSCIRBED };
struct OkxExecutionAdapter::PImpl {
void OkxExecutionGateway::send_msgs(
std::vector<OrderMsg> const &msgs, trace::CorID) {
LOG(OrderMan,Info,"ExecutionGateway::send_msgs" );
for (const auto &msg : msgs) {
std::visit(
[this](auto &&order) {
using T = std::decay_t<decltype(order)>;
if constexpr (std::is_same_v<T, CreateOrderMsg>) {
LOG(OrderMan,Warn,"ExecutionGateway::CreateOrderMsg" );
launch_new_order(order);
} else if constexpr (std::is_same_v<T, CancelOrderMsg>) {
LOG(OrderMan,Warn,"ExecutionGateway::CancelOrderMsg" );
cancel_order(order);
} else if constexpr (std::is_same_v<T, CancelReplaceOrderMsg>) {
LOG(OrderMan,Warn,"ExecutionGateway::CancelReplaceOrderMsg" );
modify_order(order);
} else if constexpr (std::is_same_v<T, RFQMsg>) {
// Handle RFQMsg if necessary
// adapter->handle_rfq(order); // Example placeholder
}
},
msg);
}
}
struct OkxExecutionGateway::PImpl {
const std::string api_key = config::get<config::OkxExeCfg>().api_key();
const std::string secret_key = config::get<config::OkxExeCfg>().secret_key();
const std::string passphrase = config::get<config::OkxExeCfg>().passphrase();
const std::string url =
config::get<config::OkxExeCfg>().ws_url() + "/ws/v5/private";
const bool simulated_trading = config::get<config::OkxExeCfg>().simulated_trading();
const std::string tdmode = config::get<config::OkxExeCfg>().tdmode();
const core::TimeSpan connect_timeout = core::TimeSpan::from_seconds(0);
const core::TimeSpan pong_timeout = core::TimeSpan::from_seconds(
config::get<config::OkxExeCfg>().ws_ping_interval_seconds());
websocket::WebsocketClient ws_client;
boost::object_pool<ExecutionReport> exe_report_pool;
tbb::concurrent_queue<std::shared_ptr<ExecutionReport>> buffered_exec_reps;
std::optional<std::thread> ws_thread;
OnExeReport cb;
OnGwStatusChange on_gw_status_change;
OnCancelReject on_cancel_reject;
OnExchangePositionState on_exchange_position_state;
OnExchangeOpenOrdersState on_exchange_open_orders;
md::SecurityOracle o;
std::atomic<OkxExecutionState> state = OkxExecutionState::OFF;
PImpl(md::SecurityOracle o)
: ws_client(
websocket::WebsocketClient({url, connect_timeout, pong_timeout})),
@@ -75,7 +105,15 @@ struct OkxExecutionAdapter::PImpl {
ws_client.message_handler().bind<&PImpl::on_message>(*this);
ws_client.close_handler().bind<&PImpl::on_close>(*this);
};
static void dummy(const ExecutionReport &, const trace::CorID &) {
~PImpl() {
state = OkxExecutionState::OFF;
if (ws_run_thread) {
ws_run_thread->join();
}
}
static void dummy(std::shared_ptr<const ExecutionReport>, const trace::CorID &) {
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
}
void setCallback(const OnExeReport &callback) {
@@ -179,6 +217,14 @@ struct OkxExecutionAdapter::PImpl {
bts_assert_msgf(
false, "Websocket stream client disconnected. {}", ec.message());
}
// fills execution report form basic_json and writes it to
// buffered_exec_reps, which is picked up from the other thread
// and passed over to the order infrastructure
void _push_execution_report(const nlohmann::basic_json<> &order) {
ExecutionReport er;
get_exe_report(order, er);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
}
void on_message(const std::string &msg_str) {
LOG(OrderMan, Info, "okxlistener::on_message. {}", msg_str);
nlohmann::json msg;
@@ -224,21 +270,38 @@ struct OkxExecutionAdapter::PImpl {
const auto &data = msg.at("data");
for (const nlohmann::basic_json<> &order : data) {
ExecutionReport *er_p = exe_report_pool.construct();
ExecutionReport &report = *er_p;
get_exe_report(order, report);
cb(report, {});
_push_execution_report(order);
}
}
void start() {
size_t attempt = 0;
bts_assert(!ws_thread);
state = OkxExecutionState::STARTED;
while (state != OkxExecutionState::OFF) {
ws_thread.emplace([this]() {
bts::threading::configure_current_thread("okx_execution");
start_impl();
});
}
void start_impl() {
size_t attempt = 0;
for (size_t version = 0; state != OkxExecutionState::OFF; version++) {
try {
LOG(OrderMan, Warn,
"OkxExecutionAdapter::start Attempting login (Attempt: {})",
attempt + 1);
ws_client.connect({}, true);
if (ws_run_thread) {
if (ws_run_thread->joinable()) {
ws_run_thread->join();
}
ws_run_thread.reset();
}
ws_client.connect();
ws_run_thread = threading::make_thread(
"okx_execution_ws", [&, orig_version = version]() {
while (version == orig_version) {
ws_client.run_one();
}
});
sleep(2);
if (ws_ping_thread.has_value()) {
if (ws_ping_thread->joinable()) {
@@ -249,7 +312,7 @@ struct OkxExecutionAdapter::PImpl {
.reset(); // Reset the optional before creating a new thread
}
ws_ping_thread.emplace([&]() {
ws_ping_thread.emplace([&, orig_version = version]() {
LOG(OrderMan, Warn,
"OkxExecutionAdapter::ws_ping_thread thread starting. url: "
"{}",
@@ -260,7 +323,7 @@ struct OkxExecutionAdapter::PImpl {
std::this_thread::sleep_for(
std::chrono::milliseconds(ping_interval.millis()));
while (ws_client.is_connected()) {
while (ws_client.is_connected() && version == orig_version) {
const auto now = bts::core::TimeStamp::now();
if ((now - last_ping_time) > ping_interval) {
LOG(OrderMan, Warn,
@@ -327,6 +390,7 @@ struct OkxExecutionAdapter::PImpl {
LOG(OrderMan, Warn,
"OkxExecutionAdapter::subscribing to orders successful{}",
auth_res2.message());
on_gw_status_change(true);
break; // Exit loop on success
} catch (const std::exception &ex) {
const std::string err = ex.what();
@@ -354,9 +418,11 @@ struct OkxExecutionAdapter::PImpl {
}
}
};
void OkxExecutionAdapter::start() { me_->start(); }
void OkxExecutionAdapter::modify_order(
void OkxExecutionGateway::start() { me_->start(); }
OnGwStatusChange &OkxExecutionGateway::on_gw_status_change() {
return me_->on_gw_status_change;
}
void OkxExecutionGateway::modify_order(
const CancelReplaceOrderMsg &msg) { //confirm will have everything
try {
LOG(OrderMan, Warn, "OkxExecutionAdapter::modify_order modifying order{}",
@@ -372,7 +438,7 @@ void OkxExecutionAdapter::modify_order(
msg.create_fields.quantity.full_value());
auto headers = OkxUtils::createHeaders(
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
body);
me_->simulated_trading,body);
// Generate and log the curl command
std::string curlCommand = OkxUtils::generateCurlCommand(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
@@ -383,39 +449,49 @@ void OkxExecutionAdapter::modify_order(
headers, &res);
const auto &[suc, top_msg] = OkxUtils::orderAccepted(res.body);
// ExecutionReport *er_p = me_->exe_report_pool.construct();
// ExecutionReport &er = *er_p;
// OkxUtils::populateCreateOrderFields(er, msg.create_fields);
// er.price = msg.create_fields.price;
// er.quantity = msg.create_fields.quantity;
// er.order_key = msg.create_fields.key;
// const auto &[id, ts, ex_msg] = OkxUtils::extractOrderInfo(res.body);
if (suc) {
LOG(md, Info, "Http get succeeded {}", res.body);
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_PENDING_REPLACE;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_PENDING_REPLACE;
} else {
LOG(md, Info, "Http get failed {},,,{}", res.errors, res.body);
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_REJECTED;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
// er.msg = ex_msg + " " + top_msg;
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
}
// me_->cb(er, {});
} catch (const std::exception &ex) {
const std::string err = ex.what();
OkxUtils::handleOkException("Amend order", err);
}
}
void OkxExecutionAdapter::launch_new_order(const CreateOrderMsg &msg) {
void OkxExecutionGateway::dispatch_incoming_fix_messages() {
if (me_->buffered_exec_reps.empty()) {
return;
}
std::shared_ptr<ExecutionReport> er;
while (me_->buffered_exec_reps.try_pop(er)) {
trace::CorID cid = {};
LOG(OrderMan, Info,
"OkxExecutionGateway::dispatch_incoming_fix_messages er: {} ",
er->to_string());
me_->cb(std::move(er), cid);
}
}
OnExeReport &OkxExecutionGateway::on_exe_report() { return me_->cb; }
OnCancelReject &OkxExecutionGateway::on_cancel_reject() {
static OnCancelReject default_cancel_reject;
return default_cancel_reject;
}
OnExchangePositionState &OkxExecutionGateway::on_exchange_position_state() {
static OnExchangePositionState default_exchange_position_state;
return default_exchange_position_state;
}
OnExchangeOpenOrdersState &OkxExecutionGateway::on_exchange_open_orders() {
static OnExchangeOpenOrdersState default_exchange_open_orders;
return default_exchange_open_orders;
}
void OkxExecutionGateway::launch_new_order(const CreateOrderMsg &msg) {
LOG(OrderMan, Warn, "launch order {}", msg);
try {
LOG(OrderMan, Warn,
"OkxExecutionAdapter::launch_new_order creating order");
static constexpr const char *body_template =
R"({{"instId": "{}", "tdMode": "cash", "side": "{}", "ordType": "{}", "px": "{}", "sz": "{}", "clordId": "{}"}})";
R"({{"instId": "{}", "tdMode": "{}", "side": "{}", "ordType": "{}", "px": "{}", "sz": "{}", "clordId": "{}"}})";
const std::string REQUEST_PATH = "/api/v5/trade/order";
const std::string side = msg.side == api::Side::SIDE_BUY ? "buy" : "sell";
const std::string quantity = std::to_string(msg.quantity.full_value());
@@ -426,22 +502,21 @@ void OkxExecutionAdapter::launch_new_order(const CreateOrderMsg &msg) {
const std::string ord_type =
FIX::string_toLower(ord_full_name.substr(pos + suffix.size()));
const std::string body = fmt::format(
body_template, msg.security->okx().security_desc(), side, ord_type,
body_template, msg.security->okx().security_desc(),me_->tdmode, side, ord_type,
price, quantity, msg.key);
http::HttpResponse res;
auto headers = OkxUtils::createHeaders(
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
body);
me_->simulated_trading,body);
// Generate and log the curl command
std::string curlCommand = OkxUtils::generateCurlCommand(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
body);
LOG(OrderMan, Warn, "Curl Command: \n{}", curlCommand);
ExecutionReport er;
http::HttpClient::post(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, body,
headers, &res);
ExecutionReport *er_p = me_->exe_report_pool.construct();
ExecutionReport &er = *er_p;
const auto &[suc, top_msg] = OkxUtils::orderAccepted(res.body);
OkxUtils::populateCreateOrderFields(er, msg);
@@ -462,12 +537,13 @@ void OkxExecutionAdapter::launch_new_order(const CreateOrderMsg &msg) {
er.exec_type = api::orderman::ExecType::EXEC_TYPE_PENDING_NEW;
er.order_status = api::orderman::NATIVE_ORDER_STATUS_PENDING_NEW;
} else {
LOG(md, Info, "Http get failed {},,,{}", res.errors, res.body);
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
er.exec_type = api::orderman::ExecType::EXEC_TYPE_REJECTED;
er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
er.msg = ex_msg + " " + top_msg;
}
me_->cb(er, {});
}
me_->buffered_exec_reps.push(
std::make_shared<ExecutionReport>(std::move(er)));
}
// Shut down all reactors if they exist.
catch (const std::exception &ex) {
@@ -475,13 +551,14 @@ void OkxExecutionAdapter::launch_new_order(const CreateOrderMsg &msg) {
OkxUtils::handleOkException("Launch order", err);
}
}
void OkxExecutionAdapter::register_callback(
[[maybe_unused]] const OnExecutionReport &cb) {
void OkxExecutionGateway::register_callback(
[[maybe_unused]] const OnExeReport &cb) {
LOG(OrderMan, Warn,
"OkxExecutionAdapter::register_callback callback registered");
me_->setCallback(cb);
}
void OkxExecutionAdapter::cancel_order(const CancelOrderMsg &msg) {
void OkxExecutionGateway::cancel_order(const CancelOrderMsg &msg) {
LOG(OrderMan, Warn, "cancel order {}", msg);
try {
http::HttpResponse res;
@@ -493,7 +570,7 @@ void OkxExecutionAdapter::cancel_order(const CancelOrderMsg &msg) {
body_template, msg.security->okx().security_desc(), msg.key);
auto headers = OkxUtils::createHeaders(
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
body);
me_->simulated_trading,body);
// Generate and log the curl command
std::string curlCommand = OkxUtils::generateCurlCommand(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
@@ -518,7 +595,7 @@ void OkxExecutionAdapter::cancel_order(const CancelOrderMsg &msg) {
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_PENDING_CANCEL;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_PENDING_CANCEL;
} else {
LOG(md, Info, "Http get failed {},,,{}", res.errors, res.body);
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_REJECTED;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
// er.msg = ex_msg + " " + top_msg;
@@ -531,4 +608,124 @@ void OkxExecutionAdapter::cancel_order(const CancelOrderMsg &msg) {
OkxUtils::handleOkException("Cacnel order", err);
}
}
} // namespace bts::orderman::okxexe
} // namespace bts::orderman::okxexe
// ExecutionReport OkxExecutionAdapter::fire_dummy_modify_order_report(CancelReplaceOrderMsg msg) {
// ExecutionReport *er_p = me_->create_exe_ptr();
// ExecutionReport &er = *er_p;
// std::string account_name = config::get<config::OkxExeCfg>().account();
// std::string ops_id = config::get<config::OkxExeCfg>().ops_id();
// std::array<char, 12> account_array = {};
// std::copy_n(
// account_name.begin(),
// std::min(account_name.size(), account_array.size()),
// account_array.begin());
// er.account_name = AccountName(account_array);
// to_array(ops_id, &er.operator_id.value());
// er.order_key = msg.create_fields.key;
// er.msg_seq_num = 0;
// er.security = msg.create_fields.security;
// er.leg_security = nullptr;
// er.side = msg.create_fields.side;
// er.time_in_force = msg.create_fields.time_in_force;
// er.order_type = msg.create_fields.order_type;
// er.exec_type = api::orderman::EXEC_TYPE_REPLACED;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_REPLACED;
// er.manual_order_indicator = api::orderman::MANUAL_ORDER_INDICATOR_NO;
// auto s = std::to_string(msg.create_fields.key.value());
// std::array<char, 17> idAsArray{};
// std::copy_n(s.begin(), std::min(s.size()-1, idAsArray.size() - 1), idAsArray.begin());
// er.exchange_order_id = ExchangeOrderID{idAsArray};
// er.price = msg.create_fields.price;
// er.quantity = msg.create_fields.quantity;
// er.filled_quantity = Quantity(0);
// er.last_shares = Quantity(0);
// er.msg = "";
// er.exchange = msg.create_fields.security->exchange();
// // Set transaction time
// ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
// er.transact_time = now;
// me_->cb(er, {});
// return er;
// }
// ExecutionReport OkxExecutionAdapter::fire_dummy_cancel_order_report(CancelOrderMsg msg) {
// ExecutionReport *er_p = me_->create_exe_ptr();
// ExecutionReport &er = *er_p;
// std::string account_name = config::get<config::OkxExeCfg>().account();
// std::string ops_id = config::get<config::OkxExeCfg>().ops_id();
// std::array<char, 12> account_array = {};
// std::copy_n(
// account_name.begin(),
// std::min(account_name.size(), account_array.size()),
// account_array.begin());
// er.account_name = AccountName(account_array);
// to_array(ops_id, &er.operator_id.value());
// er.order_key = msg.key;
// er.msg_seq_num = 0;
// er.security = msg.security;
// er.leg_security = nullptr;
// er.side = msg.side;
// er.time_in_force = api::orderman::TIME_IN_FORCE_GTC;
// er.order_type = api::orderman::NATIVE_ORDER_TYPE_LIMIT;
// er.exec_type = api::orderman::EXEC_TYPE_TRADE_CANCEL;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_CANCELED;
// er.manual_order_indicator = api::orderman::MANUAL_ORDER_INDICATOR_NO;
// auto s = std::to_string(msg.key.value());
// std::array<char, 17> idAsArray{};
// std::copy_n(s.begin(), std::min(s.size()-1, idAsArray.size() - 1), idAsArray.begin());
// er.exchange_order_id = ExchangeOrderID{idAsArray};
// er.filled_quantity = Quantity(0);
// er.last_shares = Quantity(0);
// er.msg = "";
// er.exchange = msg.security->exchange();
// // Set transaction time
// ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
// er.transact_time = now;
// me_->cb(er, {});
// return er;
// }
// ExecutionReport OkxExecutionAdapter::fire_dummy_new_order_report(CreateOrderMsg msg) {
// ExecutionReport *er_p = me_->create_exe_ptr();
// ExecutionReport &er = *er_p;
// std::string account_name = config::get<config::OkxExeCfg>().account();
// std::string ops_id = config::get<config::OkxExeCfg>().ops_id();
// std::array<char, 12> account_array = {};
// std::copy_n(
// account_name.begin(),
// std::min(account_name.size(), account_array.size()),
// account_array.begin());
// er.account_name = AccountName(account_array);
// to_array(ops_id, &er.operator_id.value());
// er.order_key = msg.key;
// er.msg_seq_num = 0;
// er.security = msg.security;
// er.leg_security = nullptr;
// er.side = msg.side;
// er.time_in_force = api::orderman::TIME_IN_FORCE_GTC;
// er.order_type = msg.order_type;
// er.exec_type = api::orderman::EXEC_TYPE_NEW;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_NEW;
// er.manual_order_indicator = api::orderman::MANUAL_ORDER_INDICATOR_NO;
// auto s = std::to_string(msg.key.value());
// std::array<char, 17> idAsArray{};
// std::copy_n(s.begin(), std::min(s.size()-1, idAsArray.size() - 1), idAsArray.begin());
// er.exchange_order_id = ExchangeOrderID{idAsArray};
// er.price = msg.price;
// er.quantity = msg.quantity;
// er.filled_quantity = Quantity(0);
// er.last_shares = Quantity(0);
// er.msg = "";
// er.exchange = msg.security->exchange();
// // Set transaction time
// ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
// er.transact_time = now;
// me_->cb(er, {});
// return er;
// }
@@ -0,0 +1,39 @@
#pragma once
#include "lib/logging/logging.h"
#include "lib/md/SecurityOracle.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
namespace bts::orderman {
class OkxExecutionGateway : public IExecutionGateway {
public:
OkxExecutionGateway(md::SecurityOracle sec_oracle);
~OkxExecutionGateway() override;
void register_callback(const OnExeReport &report);
void start() override;
void dispatch_incoming_fix_messages() override;
void on_securities(const mercury::Update<NewSecuritiesEvent> &) override {
LOG(md, Info,"Not implemented");
};
void on_admin_data(const mercury::Update<NewAdminDataEvent> &) override{
LOG(md, Info,"Not implemented");
};
void send_fix(const jetfix::OutFIXPtr &, trace::CorID) override{
LOG(md, Info,"Not implemented");
}
void send_msgs(std::vector<OrderMsg> const &, trace::CorID) override;
OnExeReport &on_exe_report() override;
OnGwStatusChange &on_gw_status_change() override;
OnCancelReject &on_cancel_reject() override;
OnExchangePositionState &on_exchange_position_state() override;
OnExchangeOpenOrdersState &on_exchange_open_orders() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
void launch_new_order(const CreateOrderMsg &msg);
void cancel_order(const CancelOrderMsg &msg);
void modify_order(const CancelReplaceOrderMsg &);
};
} // namespace bts::orderman
@@ -238,6 +238,9 @@ void populate_common_order_fields(Message &order, const CreateOrderMsg &msg) {
.set(OnixS::FIX::FIX42::Tags::Currency, msg.security->currency());
}
}
void OnixFIXAdapter::dispatch_incoming_fix_messages() {
listener.dispatch_incoming_fix_messages();
}
void OnixFIXAdapter::modify_order(
const CancelReplaceOrderMsg &msg) { //confirm will have everything
try {
@@ -12,6 +12,7 @@ class OnixFIXAdapter : public IExecutionGenericAdapter {
public:
OnixFIXAdapter(md::SecurityOracle sec_oracle);
~OnixFIXAdapter() override;
void dispatch_incoming_fix_messages() override;
void register_callback(const OnExecutionReport &report) override;
void launch_new_order(const CreateOrderMsg &msg) override;
void cancel_order(const CancelOrderMsg &msg) override;
+35 -21
View File
@@ -15,9 +15,8 @@
#include <string>
namespace bts::orderman {
ExecutionReport OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
ExecutionReport *er_p = exe_report_pool.construct();
ExecutionReport &er = *er_p;
void OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
ExecutionReport er;
std::string account_name = "BTSIM";
std::array<char, 12> an_array = {};
size_t i = 0;
@@ -51,13 +50,17 @@ ExecutionReport OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
// Set transaction time
ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
er.transact_time = now;
cb(er,{});
return er;
if(er.security==nullptr){
LOG(OrderMan, Error,"DID not have sec");
return;
}
LOG(OrderMan, Info,"msg {}",er.msg);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
return;
}
namespace {
ExecutionReport createDefaultExecutionReport() {
ExecutionReport er;
void createDefaultExecutionReport(ExecutionReport &er) {
er.exchange = api::VENUE_CBOE;
std::string account_name = "BTSIM";
std::string ops_id = "op_id_bts";
@@ -67,7 +70,6 @@ namespace {
er.account_name = AccountName(account_array);
to_array(ops_id, &er.operator_id.value());
return er;
}
void populateExecTypeAndOrderStatus(ExecutionReport &er, int exec_type_value) {
@@ -120,9 +122,7 @@ namespace {
return idAsArray;
}
}
ExecutionReport OnixFixListener::get_exe_report(Message msg) {
ExecutionReport *er_p = exe_report_pool.construct();
ExecutionReport &er = *er_p;
void OnixFixListener::get_exe_report(Message msg, ExecutionReport &er) {
auto msg_str = msg.toString();
LOG(OrderMan,Info, "OnixFixListener::get_exe_report {}", msg_str);
@@ -135,7 +135,7 @@ ExecutionReport OnixFixListener::get_exe_report(Message msg) {
populateCreateOrderFields(er, m.value());
}else{ //handle this later
LOG(OrderMan,Error, "OnixFixListener::get_exe_report {} could not find create order message", msg_str);
er = createDefaultExecutionReport();
createDefaultExecutionReport(er);
}
// Populate fields from the message
@@ -186,8 +186,6 @@ ExecutionReport OnixFixListener::get_exe_report(Message msg) {
// Set transaction time
ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
er.transact_time = now;
return er;
}
void OnixFixListener::onInboundApplicationMsg(Message &msg, Session * /*sn*/) {
@@ -210,13 +208,16 @@ void OnixFixListener::onInboundApplicationMsg(Message &msg, Session * /*sn*/) {
case '8':
{
//exection report
auto er = get_exe_report(msg);
ExecutionReport *er_p = exe_report_pool.construct();
ExecutionReport &er = *er_p;
get_exe_report(msg,er);
if(er.security==nullptr){
LOG(OrderMan, Error,"DID not have sec");
return;
LOG(OrderMan, Error,"DID not have sec");
exe_report_pool.destroy(&er);
return;
}
LOG(OrderMan, Error,"msg {}",er.msg);
cb(er,{});
LOG(OrderMan, Info,"msg {}",er.msg);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
}
}
@@ -226,7 +227,20 @@ void OnixFixListener::onInboundApplicationMsg(Message &msg, Session * /*sn*/) {
// cb(*er, {});
}
void OnixFixListener::dispatch_incoming_fix_messages() {
if (buffered_exec_reps.empty()) {
return;
}
std::shared_ptr<const ExecutionReport> er;
while (buffered_exec_reps.try_pop(er)) {
trace::CorID cid = {};
LOG(OrderMan, Info,
"OnixFixListener::dispatch_incoming_fix_messages er: {} ",
er->to_string());
cb(std::move(er), cid);
}
}
void OnixFixListener::onInboundSessionMsg(Message & msg, Session * /*sn*/)
{
@@ -261,4 +275,4 @@ void OnixFixListener::onWarning(WarningReason::Enum /*reason*/, const std::strin
}
} // namespace bts::orderman
} // namespace bts::orderman
+8 -4
View File
@@ -26,6 +26,7 @@ class OnixFixListener : public OnixS::FIX::ISessionListener {
LOG(OrderMan, Info, "OnixFixListener::setCallback callback registered");
cb = callback;
}
void dispatch_incoming_fix_messages();
/// Is called when the session-level message is received from the counterparty.
void onInboundSessionMsg(Message &msg, Session *sn) ONIXS_FIXENGINE_FINAL;
void onStateChange(
@@ -33,9 +34,12 @@ class OnixFixListener : public OnixS::FIX::ISessionListener {
SessionState::Enum prevState,
Session *sn) ONIXS_FIXENGINE_FINAL;
OnExeReport cb;
ExecutionReport fire_dummy_report(CreateOrderMsg msg);
ExecutionReport get_exe_report(Message msg);
static void dummy(const ExecutionReport &, const trace::CorID &) {
tbb::concurrent_queue<std::shared_ptr<const ExecutionReport>>
buffered_exec_reps;
void fire_dummy_report(CreateOrderMsg msg);
void get_exe_report(Message msg, ExecutionReport &er);
static void dummy(
std::shared_ptr<const ExecutionReport>, const trace::CorID &) {
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
}
void onError(
@@ -49,4 +53,4 @@ class OnixFixListener : public OnixS::FIX::ISessionListener {
boost::object_pool<ExecutionReport> exe_report_pool;
};
} // namespace bts::orderman
} // namespace bts::orderman
@@ -1,31 +0,0 @@
#include "execution_gateway_provider.h"
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
#include "lib/orderman/gateways/ExecutionGatewayFactory.h"
namespace bts::orderman {
struct ExecutionGatewayProvider::PImpl {
PImpl(trace::PerfLog *l, md::SecurityOracle o) : l(l), o(std::move(o)) {}
trace::PerfLog *l;
md::SecurityOracle o;
};
ExecutionGatewayProvider::ExecutionGatewayProvider(
trace::PerfLog *l, md::SecurityOracle o)
: me_(std::make_unique<PImpl>(l, std::move(o))) {}
ExecutionGatewayProvider::~ExecutionGatewayProvider() = default;
std::vector<OrderGatewayDefinition> ExecutionGatewayProvider::create() {
auto conns = config::get<config::JetFIX>().connection();
std::vector<OrderGatewayDefinition> gateways;
for (auto c : conns) {
auto &gw = gateways.emplace_back();
gw.connection = std::move(c);
gw.gateway = ExecutionGatewayFactory::create_gateway(gw.connection, *me_->l, me_->o);
}
return gateways;
}
} // namespace bts::orderman
@@ -33,7 +33,6 @@
#include "lib/utils/thread_manager.h"
#include <bits/stdint-uintn.h>
#include <boost/pool/object_pool.hpp>
#include <cstdint>
#include <optional>
#include <queue>
@@ -280,10 +279,9 @@ struct SimExecutionGateway::PImpl {
// send_msgs is sent on the order_man thread, its dequeued in the market data
// thread, and execution reports are queued to buffered_exec_reps
tbb::concurrent_queue<OrderMsg> buffered_order_ops;
tbb::concurrent_queue<ExecutionReport *> buffered_exec_reps;
tbb::concurrent_queue<std::shared_ptr<ExecutionReport>> buffered_exec_reps;
std::unordered_map<bts::SecurityKey, SEGSecurityData> books = {};
tbb::concurrent_hash_map<uint32_t, api::Security const *> sec_map = {};
boost::object_pool<ExecutionReport> exe_report_pool;
size_t msg_seq_num = 0;
std::set<SecurityKey> touched_secs;
api::Security const *find_sec(uint32_t key) const {
@@ -321,13 +319,13 @@ void SimExecutionGateway::dispatch_incoming_fix_messages() {
if (me->buffered_exec_reps.empty()) {
return;
}
ExecutionReport *er;
std::shared_ptr<ExecutionReport> er;
while (me->buffered_exec_reps.try_pop(er)) {
trace::CorID cid = {};
LOG(OrderMan, Info,
"ESimExecutionGateway::dispatch_incoming_fix_messages er: {} ",
er->to_string());
me->on_exe_report(*er, cid);
me->on_exe_report(std::move(er), cid);
}
}
@@ -651,10 +649,9 @@ SEGSecurityData *SimExecutionGateway::_find_add_security_entry(
// buffered_exec_reps, which is picked up from the other thread
// and passed over to the order infrastructure
void SimExecutionGateway::_push_execution_report(const SEGOrderState &seg_os) {
ExecutionReport *er_p = me->exe_report_pool.construct();
ExecutionReport &er = *er_p;
ExecutionReport er;
fill_execution_report(seg_os, er);
me->buffered_exec_reps.push(er_p);
me->buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
}
void SimExecutionGateway::_write_book(SecurityKey key) {
+1 -1
View File
@@ -45,7 +45,7 @@ IL3GW::IL3GW(
const Config &conf,
config::JetFIX::Connection const *cfg)
: _config(conf), _x_state(State::INIT), _buffer(buffer),
_on_state_change([](auto) {}), _on_exe_report([](auto *) {}),
_on_state_change([](auto) {}), _on_exe_report([](auto) {}),
_on_session_change([](auto) {}), _connection(con), _session(session),
_msg_count(0), _time(), _pool(), _exe_parser(cfg, db), _retransmit() {
LOG(OrderMan, Info,
+9 -5
View File
@@ -10,6 +10,9 @@
#include "lib/orderman/ilink3/RawMsg.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
#include "lib/orderman/orderman_utils/CancelReject.h"
#include "lib/utils/auto_init_shared_ptr.h"
#include "lib/utils/object_pool.h"
#include <cstdint>
#include <functional>
#include <queue>
@@ -46,7 +49,8 @@ struct Config {
using State = IL3State;
typedef std::function<void(State)> OnStateChange;
typedef std::function<void(const ExecutionReport *)> OnExecutionReport;
typedef std::function<void(std::shared_ptr<const ExecutionReport>)>
OnExecutionReport;
typedef std::function<void(const Session &s)> OnSessionChange;
typedef std::function<void(CancelReject &)> OnCancelReject;
@@ -101,7 +105,7 @@ class IL3GW : IL3ConnectionHandler {
// A pool of pre-allocated ExecutionReport objcets, used to avoid
// allocation when processing an execution report.
boost::object_pool<ExecutionReport> _pool;
bts::utils::object_pool<utils::auto_init_shared_ptr<ExecutionReport>> _pool;
// A helper used to turn MDP3 messages into normalized execution
// report objects
@@ -242,10 +246,10 @@ class IL3GW : IL3ConnectionHandler {
}
template <class T> void handle_exe_report(const T &msg) {
auto *report = _pool.construct();
bool result = _exe_parser.parse(msg, report);
std::shared_ptr<ExecutionReport> report = _pool.construct()->value();
bool result = _exe_parser.parse(msg, report.get());
if (result) {
_on_exe_report(report);
_on_exe_report(std::move(report));
} else {
LOG(iLink3, Error, "Failed to parse error report.");
}
@@ -377,7 +377,7 @@ DISABLED_TEST_CASE("OutrightVanillaOrder", "[ilink3][execution]") {
size_t exe_reports = 0;
gw.subscribe_execution_reports(
[&](const ExecutionReport *) { exe_reports += 1; });
[&](std::shared_ptr<const ExecutionReport>) { exe_reports += 1; });
gw.send_order(order, {});
gw.flush();
@@ -12,10 +12,10 @@ using namespace bts::api::orderman;
std::string ExecutionReport::to_string() const {
std::ostringstream rv;
rv << "account_name: " << to_trim_str(account_name)
<< " operator_id: " << to_trim_str(operator_id)
rv << "account_name: " << to_trim_strn(account_name)
<< " operator_id: " << to_trim_strn(operator_id)
<< " order_key: " << order_key.value() << " msg_seq_num: " << msg_seq_num
<< " security: " << security->id()
<< " security: " << (security == nullptr ? 0 : security->id())
<< " leg_security: " << (leg_security == nullptr ? 0 : leg_security->id())
<< " side: " << bts::api::Side_Name(side) << " time_in_force: "
<< bts::api::orderman::TimeInForce_Name(time_in_force)
@@ -32,4 +32,4 @@ std::string ExecutionReport::to_string() const {
}
} // namespace orderman
} // namespace bts
} // namespace bts
+5 -3
View File
@@ -30,7 +30,7 @@
#include <tuple>
#include <unistd.h>
#include <unordered_map>
namespace bts::orderman::okxexe {
namespace bts::orderman {
using json = nlohmann::json;
class OkxUtils {
public:
@@ -168,22 +168,24 @@ class OkxUtils {
const std::string &secretKey,
const std::string &method,
const std::string &requestPath,
const bool simulated_trading,
const std::string &body = "") {
auto ts = bts::core::TimeStamp::now();
std::string timestamp = getUTCTimestamp(ts);
std::string prehash = timestamp + method + requestPath + body;
auto signature = getSign(prehash, secretKey);
LOG(OrderMan, bts::Info, "prehash '{}' , key '{}'", prehash, signature);
// Prepare headers
std::unordered_map<std::string, std::string> headers = {
{"accept", "application/json"},
{"Content-Type", "application/json"},
{"x-simulated-trading", "1"},
{"OK-ACCESS-KEY", apiKey},
{"OK-ACCESS-SIGN", signature},
{"OK-ACCESS-TIMESTAMP", timestamp},
{"OK-ACCESS-PASSPHRASE", passphrase}};
if (simulated_trading){
headers.insert({"x-simulated-trading", "1"});
}
return headers;
}
@@ -48,6 +48,15 @@ void to_str(std::array<char, N> const &in, std::string *out) {
result = std::string(&in[0], N);
}
template <size_t N>
void to_strn(std::array<char, N> const &in, std::string *out) {
std::size_t n = 0;
while (n < N && in[n] != '\0') {
n++;
}
*out = std::string(&in[0], n);
}
template <class T, class S>
T to(S const &str) {
T result;
@@ -67,6 +76,13 @@ inline std::string to_str(T const &t) {
return result;
}
template <class T>
inline std::string to_strn(T const &t) {
std::string result;
to_strn(t.value(), &result);
return result;
}
template <size_t N>
std::pair<const char *, size_t> find_trim_str(std::array<char, N> const &in) {
auto i = N;
@@ -79,12 +95,36 @@ std::pair<const char *, size_t> find_trim_str(std::array<char, N> const &in) {
return std::make_pair(&in[0], i);
}
template <size_t N>
std::pair<const char *, size_t> find_trim_strn(std::array<char, N> const &in) {
size_t i = 0;
while (i < N && in[i] != '\0') {
i++;
}
while (i > 0 && (in[i - 1] == ' ' || in[i - 1] == 0)) {
i--;
}
if (i == 0) {
return {nullptr, 0};
}
return {&in[0], i};
}
template <class T>
std::string to_trim_str(T const &t) {
auto ts = find_trim_str(t.value());
return std::string(ts.first, ts.second);
}
template <class T>
std::string to_trim_strn(T const &t) {
auto ts = find_trim_strn(t.value());
if (ts.second == 0) {
return {};
}
return std::string{ts.first, ts.second};
}
inline bts::api::orderman::OrderStatus get_external_status(
bts::api::orderman::NativeOrderStatus status) {
using namespace bts::api::orderman;
@@ -166,7 +206,7 @@ inline void set_exchange_order_id(
overloaded{
[out](ExchangeOrderID const &str) {
out->set_exchange_order_id_u64(0);
*(out->mutable_exchange_order_id()) = to_trim_str(str);
*(out->mutable_exchange_order_id()) = to_trim_strn(str);
},
[out](ExchangeOrderID64 oid) {
out->set_exchange_order_id_u64(oid.value());
@@ -292,6 +332,29 @@ inline void set_update_quantity(
} // namespace bts
FMT_IMPL(bts::orderman::AccountID, id, "{}", *id);
FMT_IMPL(bts::orderman::ExchangeOrderID, id, "{}", bts::orderman::to_strn(id));
FMT_IMPL(bts::orderman::ExchangeOrderID64, id, "{}", *id);
template <>
struct fmt::formatter<bts::orderman::ExchangeOrderIDVar> {
constexpr auto parse(fmt::format_parse_context &ctx) -> decltype(ctx.end()) {
return ctx.end();
}
template <typename FormatContext>
auto format(const bts::orderman::ExchangeOrderIDVar &id, FormatContext &ctx)
const -> decltype(ctx.out()) {
if (std::holds_alternative<bts::orderman::ExchangeOrderID64>(id)) {
return fmt::format_to(
ctx.out(), "{}",
std::get<bts::orderman::ExchangeOrderID64>(id).value());
}
if (std::holds_alternative<bts::orderman::ExchangeOrderID>(id)) {
return fmt::format_to(
ctx.out(), "{}", std::get<bts::orderman::ExchangeOrderID>(id));
}
return ctx.out();
}
};
FMT_IMPL(bts::orderman::GatewayName, n, "{}", *n);
FMT_IMPL(bts::orderman::OrderKey, k, "{}", *k);
@@ -8,6 +8,7 @@
#include "lib/utils/bts_chrono.h"
#include "lib/utils/helper_macros.h"
#include <boost/move/detail/placement_new.hpp>
#include <ccapi_cpp/bts_session_options.h>
#include <ccapi_cpp/ccapi_message.h>
#include <ccapi_cpp/ccapi_util_private.h>
#include <chrono>
@@ -65,7 +66,9 @@ class Bootstrapper {
ccapi::SessionOptions sessionOptions;
ccapi::SessionConfigs sessionConfigs;
MyEventHandler eventHandler;
ccapi::Session session(sessionOptions, sessionConfigs, &eventHandler);
ccapi::Session session(
sessionOptions, {ccapi::BtsVenues::BINANCE_USDS_FUTURES},
sessionConfigs, &eventHandler);
ccapi::Request request(
ccapi::Request::Operation::CREATE_ORDER,
CCAPI_EXCHANGE_NAME_BINANCE_USDS_FUTURES, "BTCUSD");
@@ -85,7 +88,9 @@ class Bootstrapper {
"_FY2PcXYNc8VdlfkZO5c-pkFJQFhn8P9z4_p8YA2fS0"},
});
MyEventHandler eventHandler;
ccapi::Session session(sessionOptions, sessionConfigs, &eventHandler);
ccapi::Session session(
sessionOptions, {ccapi::BtsVenues::DERIBIT}, sessionConfigs,
&eventHandler);
ccapi::Request request(
ccapi::Request::Operation::CREATE_ORDER, CCAPI_EXCHANGE_NAME_DERIBIT,
"BTC-29SEP23");
@@ -135,4 +140,4 @@ TEST_CASE("Simple", "[ccapi]") {
}
}
} // namespace bts::orderman
} // namespace bts::orderman
+1 -1
View File
@@ -26,7 +26,7 @@ boost::filesystem::path SparkDataDirectory::config_json_dir() const {
if (p.has_parent_path()) {
p = p.parent_path();
}
return p / "conf_json/";
return p / "conf/";
}
boost::filesystem::path SparkDataDirectory::config_json_file(
+42 -5
View File
@@ -1,8 +1,10 @@
#pragma once
#include <fstream>
#include <google/protobuf/repeated_ptr_field.h>
#include <google/protobuf/text_format.h>
#include <google/protobuf/timestamp.pb.h>
#include <fstream>
#include <nlohmann/json.hpp>
#include <string>
#ifndef _WIN32
@@ -25,14 +27,16 @@ namespace utils {
namespace pb {
BTS_EXPORT std::string to_string(google::protobuf::Message const &msg);
BTS_EXPORT google::protobuf::Timestamp
to_timestamp(datetime::PTime const &ptime);
BTS_EXPORT google::protobuf::Timestamp to_timestamp(
datetime::PTime const &ptime);
template <class T> bool from_string(const std::string &str, T *out) {
template <class T>
bool from_string(const std::string &str, T *out) {
return google::protobuf::TextFormat::ParseFromString(str, out);
}
template <class T> T from_string(const std::string &str) {
template <class T>
T from_string(const std::string &str) {
T result;
bts_assert(google::protobuf::TextFormat::ParseFromString(str, &result));
return result;
@@ -105,3 +109,36 @@ inline double to_double_time(google::protobuf::Timestamp const &t1) {
} // namespace pb
} // namespace utils
} // namespace bts
namespace google::protobuf {
template <
class T,
typename std::enable_if_t<
nlohmann::detail::has_to_json<nlohmann::json, T>::value,
int> = 0>
void to_json(
nlohmann::json &j, const google::protobuf::RepeatedPtrField<T> &vals) {
j = nlohmann::json::array();
for (const T &v : vals) {
to_json(j.emplace_back(), v);
}
}
template <
class T,
typename std::enable_if_t<
nlohmann::detail::has_to_json<nlohmann::json, T>::value,
int> = 0>
void from_json(
const nlohmann::json &j, google::protobuf::RepeatedPtrField<T> &vals) {
vals.Clear();
if (!j.is_array()) {
return;
}
for (auto it = j.begin(); it != j.end(); it++) {
it->get_to(*vals.Add());
}
}
} // namespace google::protobuf
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <memory>
namespace bts::utils {
/**
* Simple wrapper for shared_ptr that initializes the underlying data in the
* default constructor.
*/
template <typename T>
class auto_init_shared_ptr {
public:
auto_init_shared_ptr() : value_(std::make_shared<T>()) {}
std::shared_ptr<T> &value() { return value_; }
const std::shared_ptr<T> &value() const { return value_; }
T &operator*() { return *value_; }
const T &operator*() const { return *value_; }
private:
std::shared_ptr<T> value_;
};
} // namespace bts::utils
@@ -0,0 +1,17 @@
#pragma once
#include <fmt/format.h>
#include <system_error>
template <>
struct fmt::formatter<std::error_code> {
constexpr auto parse(fmt::format_parse_context &ctx) -> decltype(ctx.end()) {
return ctx.end();
}
template <typename FormatContext>
auto format(const std::error_code &e, FormatContext &ctx) const
-> decltype(ctx.out()) {
return fmt::format_to(ctx.out(), "{}", e.message());
}
};
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include "lib/utils/bts_assert.h"
#include "lib/utils/type_name.h"
#include <atomic>
#include <boost/pool/object_pool.hpp>
namespace bts::utils {
static inline std::atomic<std::uint16_t> instance_id_counter = 0;
template <typename T>
class object_pool {
std::atomic<std::size_t> count = 0;
std::size_t max_count = 128;
std::uint16_t instance_id = ++instance_id_counter;
std::size_t log_freq = 0;
std::string name;
boost::object_pool<T> pool;
public:
object_pool() : name(bts::utils::type_name<T>()) {}
object_pool(std::string name, std::size_t max_count, std::size_t log_freq)
: name(std::move(name)), max_count(max_count), log_freq(log_freq) {}
void set_max_count(std::size_t value) { max_count = value; }
void set_log_freq(std::uint16_t value) { log_freq = value; }
T *construct() {
std::size_t n = ++count;
bts_assert_msgf(
n < max_count, "n < max_count. {} < {}. name: {}; id: {}", n,
max_count, name, instance_id);
if (log_freq == 0 || n % log_freq == 0) {
LOG(Gen, Info, "object_pool::construct. name: {}; id: {}; count: {}",
name, instance_id, n);
}
return pool.construct();
}
void destroy(T *const p) {
std::size_t n = --count;
bts_assert_msgf(
n < max_count, "n < max_count. {} < {}. name: {}; id: {}", n,
max_count, name, instance_id);
if (log_freq == 0 || n % log_freq == 0) {
LOG(Gen, Info, "object_pool::destroy. name: {}; id: {}; count: {}",
name, instance_id, n);
}
pool.destroy(p);
}
};
} // namespace bts::utils
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#define PB_JSON_READ_STRING(j, dest, name, it) \
it = j.find(#name); \
if (it != j.end()) { \
dest.clear_##name(); \
it->get_to(*dest.mutable_##name()); \
}
#define PB_JSON_READ_VALUE(j, dest, name, it) \
it = j.find(#name); \
if (it != j.end()) { \
dest.set_##name(it->get<decltype(dest.name())>()); \
}
#define PB_JSON_READ_STRUCT(j, dest, name, it) \
it = j.find(#name); \
if (it != j.end()) { \
it->get_to(*dest.mutable_##name()); \
}
#define PB_JSON_READ_ARRAY(j, dest, name, it) \
it = j.find(#name); \
if (it != j.end() && it->is_array()) { \
dest.clear_##name(); \
for (auto &pb_json_array_val : *it) { \
pb_json_array_val.get_to(*dest.add_##name()); \
} \
}
#define PB_JSON_READ_ONEOF(j, dest, name, it) \
if (!parsed_oneof) { \
it = j.find(#name); \
if (it != j.end()) { \
it->get_to(*dest.mutable_##name()); \
parsed_oneof = true; \
} \
}
#define PB_JSON_READ_ONEOFS(fields) \
{ \
bool parsed_oneof = false; \
fields(PB_JSON_READ_ONEOF) \
}
#define PB_JSON_WRITE_IF_TRUE(j, src, name) \
if (src.name()) { \
j[#name] = src.name(); \
}
#define PB_JSON_WRITE_IF_NOT_ZERO(j, src, name) \
if (src.name() != 0) { \
j[#name] = src.name(); \
}
#define PB_JSON_WRITE_IF_NOT_EMPTY(j, src, name) \
if (!src.name().empty()) { \
j[#name] = src.name(); \
}
#define PB_JSON_WRITE_VALUE(j, src, name) j[#name] = src.name()
#define PB_JSON_WRITE_STRUCT(j, src, name) \
if (src.has_##name()) { \
j[#name] = src.name(); \
}
#define _PB_JSON_WRITE_ONEOF_CASE(j, src, enum_name, field_name) \
case std::remove_const< \
std::remove_reference<decltype(src)>::type>::type::k##enum_name: \
PB_JSON_WRITE_VALUE(j, src, field_name); \
break;
#define PB_JSON_WRITE_ONEOF(j, src, oneof_name, cases) \
switch (src.oneof_name##_case()) { \
cases(_PB_JSON_WRITE_ONEOF_CASE) default : break; \
}
+12 -35
View File
@@ -11,6 +11,9 @@ bool copy_to_array(
std::array<char, N> &dest,
bool null_terminate = true);
template <class Iter, class UIntType>
bool parse_uint(Iter first, Iter last, UIntType &i);
template <class UIntType>
bool parse_uint(const std::string &s, UIntType &i);
@@ -44,44 +47,18 @@ bool copy_to_array(
template <class UIntType>
inline bool parse_uint(const std::string &s, UIntType &i) {
return parse_uint(s.begin(), s.end(), i);
}
template <class Iter, class UIntType>
inline bool parse_uint(Iter first, Iter last, UIntType &i) {
i = 0;
const auto sz = s.size();
int pow10 = 1;
for (std::size_t n = 0; n < sz; n++) {
switch (s[sz - n - 1]) {
case '0':
break;
case '1':
i += pow10;
break;
case '2':
i += 2 * pow10;
break;
case '3':
i += 3 * pow10;
break;
case '4':
i += 4 * pow10;
break;
case '5':
i += 5 * pow10;
break;
case '6':
i += 6 * pow10;
break;
case '7':
i += 7 * pow10;
break;
case '8':
i += 8 * pow10;
break;
case '9':
i += 9 * pow10;
break;
default:
for (; first != last; first++) {
int digit = (*first - '0');
if (digit < 0 || digit > 9) {
return false;
}
pow10 *= 10;
i = i * 10 + digit;
}
return true;
}
+5
View File
@@ -24,5 +24,10 @@ inline const char *type_name(std::type_info const &ti) {
#endif
}
template <class T> const char *cached_type_name() {
static const char *tn = type_name<T>();
return tn;
}
} // namespace utils
} // namespace bts
@@ -0,0 +1,479 @@
#include "cross_margin_gateway.h"
#include "binance_execution/cross_margin/messages/cancel_order_response.h"
#include "binance_execution/cross_margin/messages/create_order_request.h"
#include "binance_execution/cross_margin/rest_api_client.h"
#include "binance_execution/cross_margin/side_effect_type.h"
#include "binance_execution/cross_margin/trade_data_stream/trade_data_subscription.h"
#include "binance_execution/execution_report_store.h"
#include "binance_execution/gateway_config.h"
#include "binance_execution/listen_key_requestor.h"
#include "binance_execution/order_key_map.h"
#include "binance_execution/rest_api_result.h"
#include "binance_execution/type_validators.h"
#include "lib/api/service_helpers/AdminServiceUtils.h"
#include "lib/api/services/AdminService.pb.h"
#include "lib/api/services/OrderService.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/json_formatter.h"
#include "lib/logging/logging.h"
#include "lib/md/SecurityOracle.h"
#include "lib/mercury/events/Update.h"
#include "lib/orderman/CancelOrderMsg.h"
#include "lib/orderman/CancelReplaceOrderMsg.h"
#include "lib/orderman/CreateOrderMsg.h"
#include "lib/orderman/RFQMsg.h"
#include "lib/orderman/events/NewAdminDataEvent.h"
#include "lib/orderman/events/NewSecuritiesEvent.h"
#include "lib/orderman/orderman_utils/ExchangePosition.h"
#include "lib/orderman/orderman_utils/ExecutionReport.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/overloaded.h"
#include <boost/lockfree/spsc_queue.hpp>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <string>
#include <variant>
#include <vector>
using nlohmann::json;
namespace binance_execution::cross_margin {
#define ASSERT_FN(test, fn, ...) \
bts_assert_msgf(test, "BinanceCrossMargin::" fn ". " __VA_ARGS__)
#define LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "BinanceCrossMargin::" fn ". " __VA_ARGS__)
#define DIAG_FN(fn, ...) LOG_FN(bts::Diag, fn, __VA_ARGS__)
#define INFO_FN(fn, ...) LOG_FN(bts::Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) LOG_FN(bts::Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) LOG_FN(bts::Error, fn, __VA_ARGS__)
#define VENUE bts::api::VENUE_BINANCE
using boost::lockfree::spsc_queue;
using bts::core::Delegate;
using bts::io::json_formatter;
using bts::orderman::CancelOrderMsg;
using bts::orderman::CancelReplaceOrderMsg;
using bts::orderman::CreateOrderMsg;
using bts::orderman::ExecutionReport;
using bts::orderman::NewAdminDataEvent;
using bts::orderman::NewSecuritiesEvent;
using bts::orderman::OrderMsg;
using bts::orderman::RFQMsg;
using std::int64_t;
using std::optional;
using std::shared_ptr;
using std::size_t;
using std::string;
using std::uint32_t;
using std::vector;
struct ExecutionGatewayCallbacks {
bts::orderman::OnExeReport exe_report;
bts::orderman::OnGwStatusChange gw_status_change;
bts::orderman::OnCancelReject cancel_reject;
bts::orderman::OnExchangePositionState exchange_position_state;
bts::orderman::OnExchangeOpenOrdersState exchange_open_orders;
};
struct CrossMarginGateway::PImpl {
explicit PImpl(
bts::md::SecurityOracle sec_oracle,
GatewayConfig config,
bts::config::JetFIX_Connection jetfix_connection)
: sec_oracle(std::move(sec_oracle)), config(std::move(config)),
jetfix_connection(std::make_shared<bts::config::JetFIX_Connection>(
std::move(jetfix_connection))),
rest_api_client(
this->config.rest_endpoint,
this->config.api_key,
this->config.secret_key),
listen_key_requestor(
[this](const auto &req) {
return rest_api_client.get_listen_key(req);
},
[this](const auto &req) {
return rest_api_client.refresh_listen_key(req);
}),
key_map(std::make_shared<OrderKeyMap>()),
orders(
[this](const std::string &exch_key) {
return *key_map->get_bts_key(exch_key);
},
this->config.account,
this->jetfix_connection) {
bts_assert(this->jetfix_connection->venue() == VENUE);
bts_assert(!this->jetfix_connection->name().empty());
pos.exchange = VENUE;
}
static constexpr const char *CANCEL_REPLACE_CANCEL_SUFFIX = "crc";
bts::md::SecurityOracle sec_oracle;
GatewayConfig config;
shared_ptr<bts::config::JetFIX_Connection> jetfix_connection;
RestApiClient rest_api_client;
optional<trade_data_stream::TradeDataSubscription> trade_data_sub;
Delegate<void(trade_data_stream::AccountUpdate)> account_update_callback;
Delegate<void(trade_data_stream::OrderUpdate)> order_update_callback;
ListenKeyRequestor listen_key_requestor;
spsc_queue<trade_data_stream::AccountUpdate> incoming_account_updates{1024};
spsc_queue<trade_data_stream::OrderUpdate> incoming_order_updates{1024};
spsc_queue<shared_ptr<const ExecutionReport>> internal_order_rejects{256};
vector<trade_data_stream::AccountUpdate> account_updates_vec;
ExecutionGatewayCallbacks callbacks;
bts::orderman::ExchangePosition pos;
bts::api::admin::BTSAccount account;
shared_ptr<OrderKeyMap> key_map;
ExecutionReportStore orders;
void start() {
INFO_FN("start");
listen_key_requestor.callback().bind<&PImpl::on_listen_key_>(*this);
listen_key_requestor.start();
}
void stop() {
INFO_FN("stop");
trade_data_sub.reset();
}
void on_account_update(trade_data_stream::AccountUpdate u) {
INFO_FN("on_account_update", "\n{:2}", json_formatter(u));
incoming_account_updates.push(std::move(u));
}
void on_order_update(trade_data_stream::OrderUpdate u) {
INFO_FN("on_order_update", "\n{:2}", json_formatter(u));
if (u.orig_client_order_id.ends_with(CANCEL_REPLACE_CANCEL_SUFFIX)) {
// This is the "canceled" order part of a cancel/replace op. This op
// generates another update without such a suffix. That's the new
// order; that's the one we want.
DIAG_FN(
"on_order_update",
"Ignoring cancel/replace cancel order update. id: {}",
u.client_order_id);
return;
}
incoming_order_updates.push(std::move(u));
}
void on_listen_key_(const string &listen_key) {
INFO_FN("on_listen_key", "value: {}", listen_key);
trade_data_sub.reset();
if (listen_key.empty()) {
LOG(OrderMan, bts::Error,
"Unable to subscribe to user data because 'listen_key' "
"is empty.");
return;
}
auto &uds = trade_data_sub.emplace(
config.ws_stream_endpoint + "/ws/" + listen_key);
uds.account_update_callback().bind<&PImpl::on_account_update>(*this);
uds.order_update_callback().bind<&PImpl::on_order_update>(*this);
uds.start();
}
void create_order(const CreateOrderMsg &m, bool is_replace = false) {
CreateOrderRequest req;
req.autoRepayAtCancel = true;
req.sideEffectType = SideEffectType::AUTO_BORROW_REPAY;
std::string err;
if (!req.init(m, err)) {
ERROR_FN(
"create_order",
"{} {} {} @ {} (id={}, type={}, tif={}). Rejected "
"internally (request not sent). {}",
m.side, m.quantity, req.symbol, m.price, m.key, m.order_type,
m.time_in_force, err);
internal_order_rejects.push(orders.reject(m, err));
return;
}
if (is_replace) {
key_map->increment_order_version(m.key, req.newClientOrderId);
}
INFO_FN("create_order", "Sending request", json_formatter(req));
const auto res = rest_api_client.create_order(req);
if (!res.ok()) {
ERROR_FN(
"create_order", "id={}. {}", req.newClientOrderId,
json_formatter(res));
internal_order_rejects.push(
orders.reject({}, json_formatter(*res.error).to_string()));
}
}
void update_order(const CancelReplaceOrderMsg &m) {
CancelOrderMsg cxl;
cxl.exchange_order_id = m.exchange_order_id;
cxl.manual_order_indicator = m.create_fields.manual_order_indicator;
cxl.key = m.create_fields.key;
cxl.routing_record = m.create_fields.routing_record;
cxl.security = m.create_fields.security;
cxl.side = m.create_fields.side;
INFO_FN("update_order", "Cancelling order");
const auto *res = cancel_order(cxl);
if (res != nullptr && res->ok()) {
INFO_FN("update_order", "Sending replacement order");
create_order(m.create_fields, true);
}
}
const RestApiResult<CancelOrderResponse> *cancel_order(
const CancelOrderMsg &m) {
CancelOrderRequest req;
std::string err;
if (!req.init(m, *key_map, err)) {
ERROR_FN(
"cancel_order", "{} {} (id={}, exch_id={}). {}", m.side,
req.symbol, m.key, m.exchange_order_id, err);
internal_order_rejects.push(orders.set_error(
m.key, "Rejected internally (request not sent). " + err));
return nullptr;
}
INFO_FN(
"cancel_order", "{} {} (id={}, exch_id={})", m.side, req.symbol,
m.key, m.exchange_order_id);
const auto &res = rest_api_client.cancel_order(req);
if (!res.ok()) {
ERROR_FN(
"cancel_order", "{} {} (id={}, exch_id={}). {}", m.side,
req.symbol, m.key, m.exchange_order_id, json_formatter(res));
internal_order_rejects.push(orders.set_error(
m.key,
fmt::format(
"Cancel request failed. {}", json_formatter(*res.error))));
}
return &res;
}
void dispatch_internal_rejects() {
shared_ptr<const ExecutionReport> curr;
while (internal_order_rejects.pop(curr)) {
callbacks.exe_report(curr, {});
}
}
void dispatch_order_updates() {
if (incoming_order_updates.empty()) {
return;
}
trade_data_stream::OrderUpdate curr;
auto &km = *key_map;
while (incoming_order_updates.pop(curr)) {
auto bts_key = km.get_bts_key(curr.client_order_id);
if (!bts_key) {
ERROR_FN(
"dispatch_order_updates",
"Failed to generate exe report. Did not resolve bts key from "
"exch clientOrderId '{}'",
curr.client_order_id);
continue;
}
auto exch_key = km.get_exch_key(*bts_key);
if (exch_key != curr.client_order_id) {
INFO_FN(
"dispatch_order_updates",
"Ignoring update w/ old clientOrderId '{}' (expected '{}', "
"exch_id={}). This is expected for CancelReplace ops.",
curr.client_order_id, curr.order_id, exch_key);
continue;
}
// orders
shared_ptr<const ExecutionReport> er_p =
orders.update(curr.client_order_id, [&](auto &er_p, bool) {
auto &er = *er_p;
std::string error;
if (!curr.to_execution_report(er, error)) {
WARN_FN(
"update", "Failed to initialize exe report. {}. {}",
error, json_formatter(curr));
return;
}
er.security = sec_oracle.get_security(
VENUE, bts::api::SECURITYTYPE_SPOT, curr.symbol);
});
const ExecutionReport &er = *er_p;
string error;
if (!validate(er, error)) {
ERROR_FN(
"dispatch_order_updates",
"Generated invalid ExecutionReport (id={}, exch_id={}). {}",
curr.client_order_id, curr.order_id, error);
continue;
}
switch (curr.order_status.value) {
case OrderStatus::FILLED:
case OrderStatus::CANCELED:
case OrderStatus::REJECTED: {
const size_t n_active = orders.erase(er.order_key);
INFO_FN(
"dispatch_order_updates",
"Order reached terminal state {} (id={}, exch_id={}). {} "
"active orders.",
curr.order_status, curr.client_order_id, curr.order_id,
n_active);
break;
}
default:
break;
}
INFO_FN(
"dispatch_order_updates",
"Firing exe report callback. {} {} {} @ {} {} (filled {}/{}, "
"id={}). {} active orders",
er.side, er.security->binance().security_desc(), er.quantity,
er.price, er.order_status, er.filled_quantity, er.quantity,
er.order_key, orders.size());
callbacks.exe_report(std::move(er_p), {});
}
}
void dispatch_account_updates() {
trade_data_stream::AccountUpdate u;
auto &acct_upds = account_updates_vec;
acct_upds.clear();
while (incoming_account_updates.pop(acct_upds.emplace_back())) {
}
acct_upds.pop_back();
if (acct_upds.empty()) {
return;
}
const auto pos_ticker_comp =
[](const bts::orderman::ExchangePosition::UnitPosition &unit_pos,
const string &ticker) {
return unit_pos.exchange_sec_desc < ticker;
};
auto &dest = pos.total_exchange_positions;
for (const auto &acct_upd : acct_upds) {
for (const auto &pos : acct_upd.balances) {
bts_assert(pos.free >= 0);
bts_assert(pos.locked >= 0);
auto it = std::lower_bound(
dest.begin(), dest.end(), pos.asset, pos_ticker_comp);
if (it == dest.end() || it->exchange_sec_desc != pos.asset) {
const auto *sec = sec_oracle.get_security(
VENUE, bts::api::SECURITYTYPE_SPOT, pos.asset);
if (sec == nullptr) {
continue;
}
bts::orderman::ExchangePosition::UnitPosition new_pos;
new_pos.exchange_sec_desc = pos.asset;
new_pos.security = sec;
new_pos.account_id = account.id();
new_pos.account_name = account.name();
it = dest.insert(it, std::move(new_pos));
}
it->position = pos.locked + pos.free;
it->avg_filled_price.value() =
std::numeric_limits<double>::quiet_NaN();
}
}
callbacks.exchange_position_state(pos);
}
};
CrossMarginGateway::CrossMarginGateway(
bts::md::SecurityOracle sec_oracle,
GatewayConfig session_cfg,
bts::config::JetFIX::Connection jetfix_connection)
: me_(std::make_unique<PImpl>(
std::move(sec_oracle),
std::move(session_cfg),
std::move(jetfix_connection))) {
bts_assert(
me_->config.account.size() <=
bts::orderman::ExecutionReport{}.account_name.value().size());
}
CrossMarginGateway::~CrossMarginGateway() = default;
void CrossMarginGateway::start() { me_->start(); }
void CrossMarginGateway::on_securities(
const bts::mercury::Update<NewSecuritiesEvent> & /*update*/) {
INFO_FN(
"on_securities", "t_id: {} t_name: {}", bts::os::get_thread_id(),
bts::os::get_thread_name());
}
void CrossMarginGateway::on_admin_data(
const bts::mercury::Update<NewAdminDataEvent> &update) {
INFO_FN(
"on_admin_data", "t_id: {} t_name: {}", bts::os::get_thread_id(),
bts::os::get_thread_name());
if (update.events == nullptr || update.events->empty()) {
return;
}
const auto *account = bts::admin::AdminServiceUtils::find_account(
*update.events->back().admin_data, me_->config.account);
if (account == nullptr) {
WARN_FN(
"on_admin_data",
"Gateway account not found. Cannot execute orders. name: '{}'",
me_->config.account);
return;
}
if (me_->account.name() == account->name() &&
me_->account.id() == account->id()) {
return;
}
me_->account.CopyFrom(*account);
INFO_FN("on_admin_data", "Resolved gateway account '{}'", account->name());
}
void CrossMarginGateway::send_msgs(
vector<OrderMsg> const &msg_vec, bts::trace::CorID) {
INFO_FN("send_msgs", "count: {}", msg_vec.size());
for (const auto &msg : msg_vec) {
std::visit(
bts::overloaded{
[&](const CreateOrderMsg &m) { me_->create_order(m); },
[&](const CancelReplaceOrderMsg &m) { me_->update_order(m); },
[&](const CancelOrderMsg &m) { me_->cancel_order(m); },
[&](const RFQMsg &) {
bts_assert_msg(false, "Binance RFQ not implemented");
},
},
msg);
}
}
void CrossMarginGateway::send_fix(
const bts::jetfix::OutFIXPtr &, bts::trace::CorID) {
bts_assert_msg(false, "Binance CrossMarginGateway does not support fix");
}
void CrossMarginGateway::dispatch_incoming_fix_messages() {
me_->dispatch_internal_rejects();
me_->dispatch_order_updates();
me_->dispatch_account_updates();
}
// Callbacks
bts::orderman::OnExeReport &CrossMarginGateway::on_exe_report() {
return me_->callbacks.exe_report;
}
bts::orderman::OnGwStatusChange &CrossMarginGateway::on_gw_status_change() {
return me_->callbacks.gw_status_change;
}
bts::orderman::OnCancelReject &CrossMarginGateway::on_cancel_reject() {
return me_->callbacks.cancel_reject;
}
bts::orderman::OnExchangePositionState &
CrossMarginGateway::on_exchange_position_state() {
return me_->callbacks.exchange_position_state;
}
bts::orderman::OnExchangeOpenOrdersState &
CrossMarginGateway::on_exchange_open_orders() {
return me_->callbacks.exchange_open_orders;
}
} // namespace binance_execution::cross_margin
@@ -0,0 +1,53 @@
#pragma once
#include "lib/orderman/gateways/IExecutionGateway.h"
#include <memory>
namespace bts::config {
class JetFIX_Connection;
}
namespace bts::md {
class SecurityOracle;
}
namespace binance_execution {
struct GatewayConfig;
}
namespace binance_execution::cross_margin {
class CrossMarginGateway : public bts::orderman::IExecutionGateway {
public:
explicit CrossMarginGateway(
bts::md::SecurityOracle sec_oracle,
GatewayConfig session_cfg,
bts::config::JetFIX_Connection jetfix_connection);
~CrossMarginGateway() override;
void start() override;
void dispatch_incoming_fix_messages() override;
void on_securities(
const bts::mercury::Update<bts::orderman::NewSecuritiesEvent> &)
override;
void on_admin_data(
const bts::mercury::Update<bts::orderman::NewAdminDataEvent> &) override;
void send_fix(const bts::jetfix::OutFIXPtr &, bts::trace::CorID) override;
void send_msgs(
std::vector<bts::orderman::OrderMsg> const &,
bts::trace::CorID) override;
bts::orderman::OnExeReport &on_exe_report() override;
bts::orderman::OnGwStatusChange &on_gw_status_change() override;
bts::orderman::OnCancelReject &on_cancel_reject() override;
bts::orderman::OnExchangePositionState &
on_exchange_position_state() override;
bts::orderman::OnExchangeOpenOrdersState &on_exchange_open_orders() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace binance_execution::cross_margin
@@ -3,9 +3,9 @@
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::usds_futures {
namespace binance_execution::cross_margin {
struct ExecutionGatewayConfig {
struct CrossMarginGatewayConfig {
std::string api_key;
std::string secret_key;
std::string account;
@@ -15,7 +15,7 @@ struct ExecutionGatewayConfig {
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
ExecutionGatewayConfig,
CrossMarginGatewayConfig,
api_key,
secret_key,
account,
@@ -23,4 +23,4 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
ws_endpoint,
ws_stream_endpoint);
} // namespace binance_execution::usds_futures
} // namespace binance_execution::cross_margin
@@ -0,0 +1,96 @@
#include "cancel_order_request.h"
#include "binance_execution/error_list.h"
#include "binance_execution/order_key_map.h"
#include "binance_execution/type_converters.h"
#include "binance_execution/type_validators.h"
#include "lib/io/JsonReader.h"
#include "lib/orderman/CancelOrderMsg.h"
#include "lib/utils/checked_arithmetic.h"
namespace binance_execution::cross_margin {
bool CancelOrderRequest::init(
const bts::orderman::CancelOrderMsg &m,
OrderKeyMap &key_map,
std::string &error) {
const std::size_t orig_error_sz = error.size();
if (validate(m.security, bts::api::VENUE_BINANCE, error)) {
symbol = m.security->binance().security_desc();
}
orderId = bts::system::math::checked_ultol(m.exchange_order_id.value());
origClientOrderId = key_map.get_exch_key(m.key);
// The exchange accepts either orderId, or origClientOrderId (only one
// need be populated).
if (orderId == 0 && origClientOrderId.empty()) {
ErrorList::append(
error, "Either orderId or origClientOrderId must be defined");
} else {
// Can't reuse the same order id (only increment order version
// when init is successful, as this means the order will be sent).
key_map.increment_order_version(m.key);
newClientOrderId = key_map.get_exch_key(m.key);
}
timestamp = convert_time_stamp(bts::core::TimeStamp::now());
return error.size() == orig_error_sz;
}
void to_json(nlohmann::json &j, const CancelOrderRequest &req) {
j["symbol"] = req.symbol;
if (req.isIsolated) {
j["isIsolated"] = true;
}
if (req.orderId != 0) {
j["orderId"] = req.orderId;
}
if (!req.origClientOrderId.empty()) {
j["origClientOrderId"] = req.origClientOrderId;
}
if (!req.newClientOrderId.empty()) {
j["newClientOrderId"] = req.newClientOrderId;
}
if (req.recvWindow > 0) {
j["recvWindow"] = req.newClientOrderId;
}
if (req.timestamp > 0) {
j["timestamp"] = req.timestamp;
}
}
void from_json(const nlohmann::json &j, CancelOrderRequest &req) {
const bts::io::JsonReader jr{&j};
j.at("symbol").get_to(req.symbol);
jr.read_or("isIsolated", req.isIsolated);
jr.read_or("orderId", req.orderId);
jr.read_or("origClientOrderId", req.origClientOrderId);
jr.read_or("newClientOrderId", req.newClientOrderId);
jr.read_or("recvWindow", req.recvWindow);
j.at("timestamp").get_to(req.timestamp);
}
void to_query_params(std::string &qps, const CancelOrderRequest &req) {
qps.append("symbol=");
qps.append(req.symbol);
if (req.isIsolated) {
qps.append("&isIsolated=true");
}
if (req.orderId != 0) {
qps.append("&orderId=");
qps.append(std::to_string(req.orderId));
}
if (!req.origClientOrderId.empty()) {
qps.append("&origClientOrderId=");
qps.append(req.origClientOrderId);
}
if (!req.newClientOrderId.empty()) {
qps.append("&newClientOrderId=");
qps.append(req.newClientOrderId);
}
if (req.recvWindow > 0) {
qps.append("&recvWindow=");
qps.append(std::to_string(req.recvWindow));
}
qps.append("&timestamp=");
qps.append(std::to_string(req.timestamp));
}
} // namespace binance_execution::cross_margin
@@ -0,0 +1,39 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace bts::orderman {
struct CancelOrderMsg;
}
namespace binance_execution {
struct OrderKeyMap;
}
namespace binance_execution::cross_margin {
/**
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order
*/
struct CancelOrderRequest {
std::string symbol;
bool isIsolated = false;
std::int64_t orderId;
std::string origClientOrderId;
std::string newClientOrderId;
std::int64_t timestamp = 0;
std::int64_t recvWindow = 0;
bool init(
const bts::orderman::CancelOrderMsg &m,
OrderKeyMap &key_map,
std::string &error);
};
void to_json(nlohmann::json &j, const CancelOrderRequest &req);
void from_json(const nlohmann::json &j, CancelOrderRequest &req);
void to_query_params(std::string &query_params, const CancelOrderRequest &req);
} // namespace binance_execution::cross_margin
@@ -0,0 +1,67 @@
#include "cancel_order_response.h"
#include "binance_execution/enums/order_status.h"
#include "binance_execution/enums/order_type.h"
#include "binance_execution/enums/time_in_force.h"
#include "lib/io/JsonReader.h"
namespace binance_execution::cross_margin {
void to_json(nlohmann::json &j, const CancelOrderResponse &res) {
j["symbol"] = res.symbol;
if (res.isIsolated) {
j["isIsolated"] = true;
}
j["orderId"] = res.orderId;
if (!res.origClientOrderId.empty()) {
j["origClientOrderId"] = res.origClientOrderId;
}
if (!res.clientOrderId.empty()) {
j["clientOrderId"] = res.clientOrderId;
}
if (!std::isnan(res.price)) {
j["price"] = res.price;
}
if (!std::isnan(res.origQty)) {
j["origQty"] = res.origQty;
}
if (!std::isnan(res.executedQty)) {
j["executedQty"] = res.executedQty;
}
if (!std::isnan(res.cummulativeQuoteQty)) {
j["cummulativeQuoteQty"] = res.cummulativeQuoteQty;
}
if (res.status != OrderStatus::NONE) {
j["status"] = res.status;
}
if (res.timeInForce != TimeInForce::NONE) {
j["timeInForce"] = res.timeInForce;
}
if (res.type != OrderType::NONE) {
j["type"] = res.type;
}
if (res.side != Side::NONE) {
j["side"] = res.side;
}
}
void from_json(const nlohmann::json &j, CancelOrderResponse &res) {
j.at("symbol").get_to(res.symbol);
auto it = j.find("isIsolated");
if (it != j.end()) {
it->get_to(res.isIsolated);
}
const bts::io::JsonReader jr{&j};
jr.read("orderId", res.orderId);
jr.read("origClientOrderId", res.origClientOrderId);
jr.read("clientOrderId", res.clientOrderId);
jr.read("price", res.price);
jr.read("origQty", res.origQty);
jr.read("executedQty", res.executedQty);
jr.read("cummulativeQuoteQty", res.cummulativeQuoteQty);
jr.read("status", res.status);
jr.read("timeInForce", res.timeInForce);
jr.read("type", res.type);
jr.read("side", res.side);
}
} // namespace binance_execution::cross_margin
@@ -0,0 +1,38 @@
#pragma once
#include "binance_execution/enums/order_status.h"
#include "binance_execution/enums/order_type.h"
#include "binance_execution/enums/side.h"
#include "binance_execution/enums/time_in_force.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::cross_margin {
/**
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order#response-example
*/
struct CancelOrderResponse {
std::string symbol;
bool isIsolated = false;
std::int64_t orderId = 0;
std::string origClientOrderId;
std::string clientOrderId;
double price = std::numeric_limits<double>::quiet_NaN();
double origQty = std::numeric_limits<double>::quiet_NaN();
double executedQty = std::numeric_limits<double>::quiet_NaN();
// Note: 'cummulative' is deliberately mispelled to match the json payload.
double cummulativeQuoteQty = std::numeric_limits<double>::quiet_NaN();
OrderStatus status = OrderStatus::NONE;
TimeInForce timeInForce = TimeInForce::NONE;
OrderType type = OrderType::NONE;
Side side = Side::NONE;
};
void to_json(nlohmann::json &j, const CancelOrderResponse &res);
void from_json(const nlohmann::json &j, CancelOrderResponse &res);
} // namespace binance_execution::cross_margin
@@ -0,0 +1,49 @@
// #pragma once
// #include "binance_execution/enums/new_order_response_type.h"
// #include "binance_execution/enums/order_type.h"
// #include "binance_execution/enums/side.h"
// #include "binance_execution/enums/time_in_force.h"
// #include <nlohmann/json.hpp>
// #include <string>
// namespace bts::orderman {
// struct CreateOrderMsg;
// }
// namespace binance_execution::cross_margin {
// /**
// * @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade
// */
// struct CreateOcoRequest {
// std::string symbol;
// std::string listClientOrderId;
// Side side = Side::NONE;
// double quantity = 0;
// std::string limitClientOrderId;
// double price = std::numeric_limits<double>::quiet_NaN();
// double limitIcebergQty = 0;
// std::string stopClientOrderId;
// double stopPrice;
// double stopLimitPrice;
// double stopIcebergQty;
// TimeInForce stopLimitTimeInForce;
// NewOrderResponseType newOrderRespType;
// OrderType type = OrderType::NONE;
// std::int64_t timestamp = 0;
// std::string newClientOrderId;
// TimeInForce timeInForce = TimeInForce::NONE;
// std::int64_t recvWindow = 0;
// NewOrderResponseType newOrderRespType = NewOrderResponseType::NONE;
// bool init(const bts::orderman::CreateOrderMsg &m, std::string &error);
// };
// void to_json(nlohmann::json &j, const CreateOcoRequest &req);
// void from_json(const nlohmann::json &j, CreateOcoRequest &req);
// void to_query_params(std::string &query_params, const CreateOcoRequest &req);
// } // namespace binance_execution::spots
@@ -0,0 +1,201 @@
#include "create_order_request.h"
#include "binance_execution/enums/new_order_response_type.h"
#include "binance_execution/price.h"
#include "binance_execution/quantity.h"
#include "binance_execution/type_converters.h"
#include "binance_execution/type_validators.h"
#include "lib/io/JsonReader.h"
#include "lib/orderman/CreateOrderMsg.h"
namespace binance_execution::cross_margin {
bool CreateOrderRequest::init(
const bts::orderman::CreateOrderMsg &m, std::string &error) {
const std::size_t orig_error_sz = error.size();
newClientOrderId = std::to_string(m.key.value());
if (validate(m.security, bts::api::VENUE_BINANCE, error)) {
symbol = m.security->binance().security_desc();
}
convert_side(m.side, side, error);
price = m.price.value();
quantity = m.quantity.full_value();
convert_order_type(m.order_type, type, error);
convert_time_in_force(m.time_in_force, timeInForce, error);
if (error.size() != orig_error_sz) {
return false;
}
timestamp = convert_time_stamp(bts::core::TimeStamp::now());
return true;
}
void to_json(nlohmann::json &j, const CreateOrderRequest &req) {
j["symbol"] = req.symbol;
j["side"] = req.side;
j["type"] = req.type;
j["timestamp"] = req.timestamp;
if (req.isIsolated) {
j["isIsolated"] = req.isIsolated;
}
std::string str;
if (req.quantity > 0) {
quantity_to_string(req.quantity, str);
j["quantity"] = str;
str.clear();
}
if (req.quoteOrderQty > 0) {
quantity_to_string(req.quoteOrderQty, str);
j["quoteOrderQty"] = str;
str.clear();
}
if (!std::isnan(req.price)) {
price_to_string(req.price, str);
j["price"] = str;
str.clear();
}
if (!std::isnan(req.stopPrice)) {
price_to_string(req.stopPrice, str);
j["stopPrice"] = str;
str.clear();
}
if (!req.newClientOrderId.empty()) {
j["newClientOrderId"] = req.newClientOrderId;
}
if (req.icebergQty > 0) {
quantity_to_string(req.icebergQty, str);
j["icebergQty"] = str;
str.clear();
}
if (req.newOrderRespType != NewOrderResponseType::NONE) {
j["newOrderRespType"] = req.newOrderRespType;
}
if (req.sideEffectType != SideEffectType::NO_SIDE_EFFECT) {
j["sideEffectType"] = req.sideEffectType;
}
if (req.timeInForce != TimeInForce::NONE) {
j["timeInForce"] = req.timeInForce;
}
if (req.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
j["selfTradePreventionMode"] = req.selfTradePreventionMode;
}
if (req.autoRepayAtCancel) {
j["autoRepayAtCancel"] = req.autoRepayAtCancel;
}
if (req.recvWindow > 0) {
j["recvWindow"] = std::to_string(req.recvWindow);
}
}
void from_json(const nlohmann::json &j, CreateOrderRequest &req) {
j.at("symbol").get_to(req.symbol);
j.at("side").get_to(req.side);
j.at("type").get_to(req.type);
j.at("timestamp").get_to(req.timestamp);
auto it = j.find("isIsolated");
bts::io::JsonReader jr{&j};
if (it != j.end()) {
it->get_to(req.isIsolated);
}
jr.read("quantity", req.price);
jr.read("quoteOrderQty", req.price);
jr.read("price", req.price);
jr.read("stopPrice", req.price);
it = j.find("newClientOrderId");
if (it != j.end()) {
it->get_to(req.newClientOrderId);
}
jr.read("icebergQty", req.price);
if (it != j.end()) {
it->get_to(req.icebergQty);
}
it = j.find("newOrderRespType");
if (it != j.end()) {
it->get_to(req.newOrderRespType);
}
it = j.find("sideEffectType");
if (it != j.end()) {
it->get_to(req.sideEffectType);
}
it = j.find("timeInForce");
if (it != j.end()) {
it->get_to(req.timeInForce);
}
it = j.find("selfTradePreventionMode");
if (it != j.end()) {
it->get_to(req.selfTradePreventionMode);
}
it = j.find("isIsolated");
if (it != j.end()) {
it->get_to(req.isIsolated);
}
it = j.find("autoRepayAtCancel");
if (it != j.end()) {
it->get_to(req.autoRepayAtCancel);
}
it = j.find("recvWindow");
if (it != j.end()) {
it->get_to(req.recvWindow);
}
}
void to_query_params(std::string &qps, const CreateOrderRequest &req) {
qps.append("symbol=");
qps.append(req.symbol);
qps.append("&side=");
qps.append(req.side.name());
qps.append("&type=");
qps.append(req.type.name());
qps.append("&timestamp=");
qps.append(std::to_string(req.timestamp));
if (req.isIsolated) {
qps.append("&isIsolated=true");
}
if (req.quantity > 0) {
qps.append("&quantity=");
quantity_to_string(req.quantity, qps);
}
if (req.quoteOrderQty > 0) {
qps.append("&quoteOrderQty=");
quantity_to_string(req.quoteOrderQty, qps);
}
if (!std::isnan(req.price)) {
qps.append("&price=");
price_to_string(req.price, qps);
}
if (!std::isnan(req.stopPrice)) {
qps.append("&stopPrice=");
price_to_string(req.stopPrice, qps);
}
if (!req.newClientOrderId.empty()) {
qps.append("&newClientOrderId=");
qps.append(req.newClientOrderId);
}
if (req.icebergQty > 0) {
qps.append("&icebergQty=");
quantity_to_string(req.icebergQty, qps);
}
if (req.newOrderRespType != NewOrderResponseType::NONE) {
qps.append("&newOrderRespType=");
qps.append(req.newOrderRespType.name());
}
if (req.sideEffectType != SideEffectType::NO_SIDE_EFFECT) {
qps.append("&sideEffectType=");
qps.append(req.sideEffectType.name());
}
if (req.timeInForce != TimeInForce::NONE) {
qps.append("&timeInForce=");
qps.append(req.timeInForce.name());
}
if (req.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
qps.append("&selfTradePreventionMode=");
qps.append(req.selfTradePreventionMode.name());
}
if (req.autoRepayAtCancel) {
qps.append("&autoRepayAtCancel=true");
}
if (req.recvWindow > 0) {
qps.append("&recvWindow=");
qps.append(std::to_string(req.recvWindow));
}
}
} // namespace binance_execution::cross_margin
@@ -0,0 +1,51 @@
#pragma once
#include "binance_execution/cross_margin/side_effect_type.h"
#include "binance_execution/enums/new_order_response_type.h"
#include "binance_execution/enums/order_type.h"
#include "binance_execution/enums/self_trade_prevention_mode.h"
#include "binance_execution/enums/side.h"
#include "binance_execution/enums/time_in_force.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
namespace bts::orderman {
struct CreateOrderMsg;
}
namespace binance_execution::cross_margin {
/**
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-Order
*/
struct CreateOrderRequest {
std::string symbol;
bool isIsolated = false;
Side side;
OrderType type;
double quantity = std::numeric_limits<double>::quiet_NaN();
double quoteOrderQty = std::numeric_limits<double>::quiet_NaN();
double price = std::numeric_limits<double>::quiet_NaN();
double stopPrice = std::numeric_limits<double>::quiet_NaN();
std::string newClientOrderId;
double icebergQty = std::numeric_limits<double>::quiet_NaN();
NewOrderResponseType newOrderRespType = NewOrderResponseType::NONE;
SideEffectType sideEffectType = SideEffectType::NO_SIDE_EFFECT;
TimeInForce timeInForce = TimeInForce::NONE;
SelfTradePreventionMode selfTradePreventionMode =
SelfTradePreventionMode::NONE;
bool autoRepayAtCancel = false;
std::int64_t timestamp = 0;
std::int64_t recvWindow = 0;
bool init(const bts::orderman::CreateOrderMsg &m, std::string &error);
};
void to_json(nlohmann::json &j, const CreateOrderRequest &req);
void from_json(const nlohmann::json &j, CreateOrderRequest &req);
void to_query_params(std::string &query_params, const CreateOrderRequest &req);
} // namespace binance_execution::cross_margin
@@ -0,0 +1,73 @@
#include "create_order_response.h"
#include "binance_execution/enums/self_trade_prevention_mode.h"
#include <limits>
namespace binance_execution::cross_margin {
void to_json(nlohmann::json &j, const CreateOrderResponse &res) {
j["symbol"] = res.symbol;
j["orderId"] = res.orderId;
j["clientOrderId"] = res.clientOrderId;
j["transactTime"] = res.transactTime;
if (!std::isnan(res.price)) {
j["price"] = res.price;
}
if (!std::isnan(res.origQty)) {
j["origQty"] = res.origQty;
}
if (!std::isnan(res.executedQty)) {
j["executedQty"] = res.executedQty;
}
if (!std::isnan(res.cummulativeQuoteQty)) {
j["cummulativeQuoteQty"] = res.cummulativeQuoteQty;
}
j["status"] = res.status;
j["timeInForce"] = res.timeInForce;
j["type"] = res.type;
j["side"] = res.side;
if (!std::isnan(res.marginBuyBorrowAmount)) {
j["marginBuyPowerAmount"] = res.marginBuyBorrowAmount;
}
if (!res.marginBuyBorrowAsset.empty()) {
j["marginBuyBorrowAsset"] = res.marginBuyBorrowAsset;
}
if (res.isIsolated) {
j["isIsolated"] = true;
}
if (res.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
j["selfTradePreventionMode"] = res.selfTradePreventionMode;
}
if (!res.fills.empty()) {
j["fills"] = res.fills;
}
}
void from_json(const nlohmann::json &j, CreateOrderResponse &res) {
const bts::io::JsonReader jr{&j};
j.at("symbol").get_to(res.symbol);
j.at("orderId").get_to(res.orderId);
j.at("clientOrderId").get_to(res.clientOrderId);
j.at("transactTime").get_to(res.transactTime);
jr.read_or("price", res.price, std::numeric_limits<double>::quiet_NaN());
jr.read_or("origQty", res.origQty, std::numeric_limits<double>::quiet_NaN());
jr.read_or(
"executedQty", res.executedQty,
std::numeric_limits<double>::quiet_NaN());
jr.read_or(
"cummulativeQuoteQty", res.cummulativeQuoteQty,
std::numeric_limits<double>::quiet_NaN());
j.at("status").get_to(res.status);
j.at("timeInForce").get_to(res.timeInForce);
j.at("type").get_to(res.type);
j.at("side").get_to(res.side);
jr.read_or(
"marginBuyBorrowAmount", res.marginBuyBorrowAmount,
std::numeric_limits<double>::quiet_NaN());
jr.read("marginBuyBorrowAsset", res.marginBuyBorrowAsset);
jr.read("isIsolated", res.isIsolated);
jr.read("selfTradePreventionMode", res.selfTradePreventionMode);
jr.read_or("fills", res.fills);
}
} // namespace binance_execution::cross_margin
@@ -0,0 +1,75 @@
#pragma once
#include "binance_execution/enums/order_status.h"
#include "binance_execution/enums/order_type.h"
#include "binance_execution/enums/self_trade_prevention_mode.h"
#include "binance_execution/enums/side.h"
#include "binance_execution/enums/time_in_force.h"
#include "lib/io/JsonReader.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::cross_margin {
/**
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-Order
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-Order#response-example
*/
struct CreateOrderResponse {
struct Fill {
double price = std::numeric_limits<double>::quiet_NaN();
double qty = std::numeric_limits<double>::quiet_NaN();
double commission = std::numeric_limits<double>::quiet_NaN();
std::string commissionAsset;
std::uint32_t tradeId = 0;
};
std::string symbol;
std::int64_t orderId = 0;
std::string clientOrderId;
std::int64_t transactTime = 0;
double price = std::numeric_limits<double>::quiet_NaN();
double origQty = std::numeric_limits<double>::quiet_NaN();
double executedQty = std::numeric_limits<double>::quiet_NaN();
// Note: 'cummulative' is deliberately mispelled to match the json payload.
double cummulativeQuoteQty = std::numeric_limits<double>::quiet_NaN();
OrderStatus status = OrderStatus::NONE;
TimeInForce timeInForce = TimeInForce::NONE;
OrderType type = OrderType::NONE;
Side side = Side::NONE;
double marginBuyBorrowAmount = std::numeric_limits<double>::quiet_NaN();
std::string marginBuyBorrowAsset;
bool isIsolated = false;
SelfTradePreventionMode selfTradePreventionMode =
SelfTradePreventionMode::NONE;
std::vector<Fill> fills;
};
void to_json(nlohmann::json &j, const CreateOrderResponse &res);
void from_json(const nlohmann::json &j, CreateOrderResponse &res);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const CreateOrderResponse::Fill &fill) {
j["price"] = fill.price;
j["qty"] = fill.qty;
j["commission"] = fill.commission;
j["commissionAsset"] = fill.commissionAsset;
j["tradeId"] = fill.tradeId;
}
inline void from_json(
const nlohmann::json &j, CreateOrderResponse::Fill &fill) {
bts::io::JsonReader jr{&j};
jr.read("price", fill.price);
jr.read("qty", fill.qty);
jr.read("commission", fill.commission);
jr.read("commissionAsset", fill.commissionAsset);
jr.read("tradeId", fill.tradeId);
}
} // namespace binance_execution::cross_margin
@@ -0,0 +1,112 @@
#include "rest_api_client.h"
#include "lib/io/http/http_client.h"
#include "lib/io/http/http_response.h"
#include "lib/utils/crypto/hmac.h"
namespace binance_execution::cross_margin {
using bts::http::HttpClient;
using bts::http::HttpResponse;
using bts::system::crypto::hmac_digest_hex;
using std::string;
#define _LOG_FN(level, fn, ...) \
LOG(OrderMan, level, "Binance::" fn ". " __VA_ARGS__)
#define DIAG_FN(fn, ...) _LOG_FN(bts::Diag, fn, __VA_ARGS__)
#define INFO_FN(fn, ...) _LOG_FN(bts::Info, fn, __VA_ARGS__)
#define WARN_FN(fn, ...) _LOG_FN(bts::Warn, fn, __VA_ARGS__)
#define ERROR_FN(fn, ...) _LOG_FN(bts::Error, fn, __VA_ARGS__)
template <std::size_t MAX_SIZE, std::size_t DEFAULT_SIZE>
static void shrink_string(std::string &str) {
if (str.size() > MAX_SIZE) {
str.resize(DEFAULT_SIZE);
str.shrink_to_fit();
}
}
template <class T, std::size_t MAX_SIZE, std::size_t DEFAULT_SIZE>
static void shrink_vec(std::vector<T> &vec) {
if (vec.size() > MAX_SIZE) {
vec.resize(DEFAULT_SIZE);
vec.shrink_to_fit();
}
}
bool RestApiClient::sign_query_params_(std::string &qp) {
std::string signature;
if (!hmac_digest_hex(secret_key_, qp, signature)) {
LOG(OrderMan, bts::Error, "Failed to sign request. {}", qp);
return false;
}
static constexpr const std::string SIGNATURE_LABEL = "&signature=";
qp.reserve(qp.size() + SIGNATURE_LABEL.size() + signature.size());
qp.append(SIGNATURE_LABEL);
qp.append(signature);
return true;
}
template <class ResponseT, class RequestT>
void RestApiClient::request_(
const RequestT &req,
const std::string &path,
bts::io::http::HttpMethod method,
const std::string &body,
RestApiResult<ResponseT> &res) {
// Use thread_local vars to reduce memory allocations. Shrink strings
// to reduce memory footprint from previous requests.
thread_local bts::http::HttpRequest http_req;
shrink_string<4096, 128>(http_req.body);
http_req.reset();
// Prepare request
http_req.url = url_;
http_req.url.append(path);
http_req.body = body;
http_req.method = method;
http_req.headers["X-MBX-APIKEY"] = api_key_;
thread_local std::string qps;
shrink_string<256, 16>(qps);
qps.clear();
to_query_params(qps, req);
if (!qps.empty()) {
sign_query_params_(qps);
http_req.url.append("?");
http_req.url.append(qps);
}
// Send it
thread_local HttpResponse http_res;
shrink_string<4096, 128>(http_res.body);
http_res.reset();
HttpClient::send(http_req, &http_res);
res.http_code = http_res.code;
res.http_error = http_res.errors;
if (!http_res.ok()) {
ERROR_FN("{} {}", "res: {}", method, path, http_res);
try {
nlohmann::json::parse(http_res.body).get_to(res.error.emplace());
} catch (const std::exception &ex) {
ERROR_FN("request", "Failed to parse error json. {}", http_res);
}
return;
}
// Parse response
INFO_FN("{} {}", "res: {}", method, path, http_res);
try {
nlohmann::json::parse(http_res.body).get_to(res.response.emplace());
} catch (const std::exception &ex) {
ERROR_FN("{} {}", "ex: {}", method, path, string{ex.what()});
bts_assert_msgf(false, "Failed to parse {} response", path);
}
}
#define REQUEST_DEF(ResponseT, RequestT) \
template void RestApiClient::request_<ResponseT, RequestT>( \
const RequestT &req, const std::string &path, \
bts::io::http::HttpMethod method, const std::string &body, \
RestApiResult<ResponseT> &res)
REQUEST_DEF(CreateOrderResponse, CreateOrderRequest);
REQUEST_DEF(CancelOrderResponse, CancelOrderRequest);
REQUEST_DEF(ListenKeyResponse, ListenKeyRequest);
} // namespace binance_execution::cross_margin
@@ -0,0 +1,95 @@
#pragma once
#include "binance_execution/cross_margin/messages/cancel_order_request.h"
#include "binance_execution/cross_margin/messages/cancel_order_response.h"
#include "binance_execution/cross_margin/messages/create_order_request.h"
#include "binance_execution/cross_margin/messages/create_order_response.h"
#include "binance_execution/messages/listen_key_request.h"
#include "binance_execution/messages/listen_key_response.h"
#include "binance_execution/rest_api_result.h"
#include "lib/io/http/HttpMethod.h"
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
namespace binance_execution::cross_margin {
class RestApiClient {
public:
explicit RestApiClient(
std::string url, std::string api_key, std::string secret_key);
const RestApiResult<CreateOrderResponse> &create_order(
const CreateOrderRequest &req);
const RestApiResult<CancelOrderResponse> &cancel_order(
const CancelOrderRequest &req);
const RestApiResult<ListenKeyResponse> &get_listen_key(
const ListenKeyRequest &req = {});
const RestApiResult<ListenKeyResponse> &refresh_listen_key(
const ListenKeyRequest &req = {});
private:
std::string url_;
std::string api_key_;
std::string secret_key_;
template <class ResponseT, class RequestT>
void request_(
const RequestT &req,
const std::string &path,
bts::io::http::HttpMethod method,
const std::string &body,
RestApiResult<ResponseT> &op_res);
bool sign_query_params_(std::string &qp);
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline RestApiClient::RestApiClient(
std::string url, std::string api_key, std::string secret_key)
: url_(std::move(url)), api_key_(std::move(api_key)),
secret_key_(std::move(secret_key)) {}
inline const RestApiResult<CreateOrderResponse> &RestApiClient::create_order(
const CreateOrderRequest &req) {
static const std::string path{"/sapi/v1/margin/order"};
thread_local RestApiResult<CreateOrderResponse> res;
res.reset();
request_<CreateOrderResponse>(
req, path, bts::io::http::HttpMethod::POST, "", res);
return res;
}
inline const RestApiResult<CancelOrderResponse> &RestApiClient::cancel_order(
const CancelOrderRequest &req) {
static const std::string path{"/sapi/v1/margin/order"};
thread_local RestApiResult<CancelOrderResponse> res;
res.reset();
request_<CancelOrderResponse>(
req, path, bts::io::http::HttpMethod::DELETE, "", res);
return res;
}
inline const RestApiResult<ListenKeyResponse> &RestApiClient::get_listen_key(
const ListenKeyRequest &req) {
static const std::string path{"/sapi/v1/userDataStream"};
thread_local RestApiResult<ListenKeyResponse> res;
res.reset();
request_(req, path, bts::io::http::HttpMethod::POST, "", res);
return res;
}
inline const RestApiResult<ListenKeyResponse> &
RestApiClient::refresh_listen_key(const ListenKeyRequest &req) {
static const std::string path{"/sapi/v1/userDataStream"};
thread_local RestApiResult<ListenKeyResponse> res;
res.reset();
request_(req, path, bts::io::http::HttpMethod::PUT, "", res);
return res;
}
} // namespace binance_execution::cross_margin
@@ -0,0 +1,28 @@
#pragma once
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
namespace binance_execution::cross_margin {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
per_value(SideEffectType, NO_SIDE_EFFECT) \
per_value(SideEffectType, MARGIN_BUY) \
per_value(SideEffectType, AUTO_REPAY) \
per_value(SideEffectType, AUTO_BORROW_REPAY)
// clang-format on
/**
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-OCO
*/
ENUM_CLASS_DEF(SideEffectType, FOR_ALL_VALUES)
#undef FOR_ALL_VALUES
} // namespace binance_execution::cross_margin
FMT_IMPL(binance_execution::cross_margin::SideEffectType, t, "{}", t.name());
@@ -0,0 +1,64 @@
#pragma once
#include "binance_execution/cross_margin/trade_data_stream/event_type.h"
#include "lib/io/JsonReader.h"
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::cross_margin::trade_data_stream {
/**
* @a https://developers.binance.com/docs/margin_trading/trade-data-stream/Event-Account-Update
*/
struct AccountUpdate {
struct Balance {
std::string asset; // json: a
double free; // json: f
double locked; // json: l
};
EventType event_type; // json: e
std::int64_t event_time; // json: E
std::int64_t last_update_time; // json: u
std::vector<Balance> balances; // json: B
};
void to_json(nlohmann::json &j, const AccountUpdate::Balance &b);
void from_json(const nlohmann::json &j, AccountUpdate::Balance &b);
void to_json(nlohmann::json &j, const AccountUpdate &u);
void from_json(const nlohmann::json &j, AccountUpdate &u);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const AccountUpdate::Balance &b) {
j["a"] = b.asset;
j["f"] = b.free;
j["l"] = b.locked;
}
inline void from_json(const nlohmann::json &j, AccountUpdate::Balance &b) {
bts::io::JsonReader jr{&j};
j.at("a").get_to(b.asset);
jr.read("f", b.free);
jr.read("l", b.locked);
}
inline void to_json(nlohmann::json &j, const AccountUpdate &u) {
j["e"] = u.event_type;
j["E"] = u.event_time;
j["u"] = u.last_update_time;
j["B"] = u.balances;
}
inline void from_json(const nlohmann::json &j, AccountUpdate &u) {
j.at("e").get_to(u.event_type);
j.at("E").get_to(u.event_time);
j.at("u").get_to(u.last_update_time);
j.at("B").get_to(u.balances);
};
} // namespace binance_execution::cross_margin::trade_data_stream
@@ -0,0 +1,46 @@
#pragma once
#include "binance_execution/cross_margin/trade_data_stream/event_type.h"
#include "lib/io/JsonReader.h"
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::cross_margin::trade_data_stream {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/user-data-stream#account-update
*/
struct BalanceUpdate {
EventType event_type; // json: e
std::int64_t event_time; // json: E
std::string asset; // json: a
double balance_delta; // json: d
std::int64_t clear_time; // json: T
};
void to_json(nlohmann::json &j, const BalanceUpdate &b);
void from_json(const nlohmann::json &j, BalanceUpdate &b);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const BalanceUpdate &b) {
j["e"] = b.event_type;
j["E"] = b.event_time;
j["a"] = b.asset;
j["d"] = b.balance_delta;
j["T"] = b.clear_time;
}
inline void from_json(const nlohmann::json &j, BalanceUpdate &b) {
j.at("e").get_to(b.event_type);
j.at("E").get_to(b.event_time);
j.at("a").get_to(b.asset);
bts::io::JsonReader jr{&j};
jr.read("d", b.balance_delta);
j.at("T").get_to(b.clear_time);
}
} // namespace binance_execution::cross_margin::trade_data_stream
Loaded 100 of 216 files, more files were not shown because too many files have changed in this diff. Show more