Compare commits

..
Author SHA1 Message Date
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
159 changed files with 7255 additions and 1590 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>
@@ -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
+52 -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,61 @@
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(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::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
@@ -0,0 +1,8 @@
{
"api_key": "QGwn9OVcpRTU76gOLV0gmQyVAJUmXW6w7H6y9tH8ctanzVnpZN1TP27liObE1Wsd",
"secret_key": "7ohKg6X3DcsZMGaJmngC84EglsCFse9flMl8aKisa5FUY18LjGCEEdm0KEZljk9T",
"account": "binance_dev",
"rest_endpoint": "https://testnet.binance.vision",
"ws_endpoint": "wss://testnet.binance.vision/ws-api/v3",
"ws_stream_endpoint": "wss://stream.testnet.binance.vision:9443"
}
@@ -0,0 +1,8 @@
{
"api_key": "b838c108cd2648e34a4f088a7fc635a9cbccf3c2faa6bcaabcb532ffacc087b0",
"secret_key": "a68f0ab27403c42fa0306b6341b2ddd51d99ac42916f849404155449457d0492",
"account": "binance_dev",
"rest_endpoint": "https://testnet.binancefuture.com",
"ws_endpoint": "wss://testnet.binancefuture.com/ws-fapi/v1",
"ws_stream_endpoint": "wss://stream.binancefuture.com"
}
@@ -0,0 +1,173 @@
ExchangeSubAccounts {
value {
accounts {
exchange_subaccount: "binance_dev"
bts_account_name: "BINANCE_DEV"
}
accounts{
exchange_subaccount: "ZmIKL6fb"
bts_account_name: "MAIN"
}
accounts{
exchange_subaccount: "v1iSzZrY"
bts_account_name: "SMALL"
}
}
}
JetFIX {
value {
connection {
name: "binance_1"
max_order_msgs_per_window: 300
venue: VENUE_BINANCE
}
connection {
name: "binance_usds_futures_1"
max_order_msgs_per_window: 300
venue: VENUE_BINANCE_USDS_FUTURES
}
}
}
PublisherOptions {
value {
network_interface: "0.0.0.0"
heartbeat_ms: 5000
heartbeat_boot_seconds: 60
skip_options: true
hot_initial: 1024
hot_growth: 1024
cold_initial: 8192
cold_growth: 2048
channel_prefix: ""
disable_heartbeat: true
}
}
Logging {
value {
initial_queue_size: 120001
max_queue_growth: 4
console {
default_level: "Warn"
subsystem {
name: "OrderMan"
level: "Diag"
}
subsystem {
name: "Http"
level: "Diag"
}
}
file {
default_level: "Info"
subsystem {
name: "md"
level: "Diag"
}
subsystem {
name: "OrderMan"
level: "Diag"
}
subsystem {
name: "Http"
level: "Diag"
}
}
}
}
MarketDataConfig {
value {
binance {
num_depth_levels: 5
# url_rest_base: "wss://ws-api.binance.com:9443"
# According to Binance, the "apiN" urls offer performance at the cost of stability.
# https://developers.binance.com/docs/binance-spot-api-docs/rest-api/general-api-information
# url_rest_base: "https://api1.binance.com:9443"
# url_rest_base: "https://api.binance.com"
url_rest_base: "https://testnet.binance.vision"
# url_ws_base: "wss://ws-api.binance.com"
url_ws_base: "wss://testnet.binance.vision"
security_criteria {
clauses {
security_types: SECURITY_TYPE_SPOT
symbols: "AAVE"
symbols: "AVAX"
symbols: "BNB"
symbols: "BTC"
symbols: "DOGE"
symbols: "EIGEN"
symbols: "ETH"
}
}
}
binance_usds_futures {
num_depth_levels: 5
# url_rest_base: "https://fapi.binance.com"
# url_ws_base: "wss://fstream.binance.com"
url_rest_base: "https://testnet.binancefuture.com"
url_ws_base: "wss://stream.binancefuture.com"
security_criteria {
clauses {
security_types: SECURITY_TYPE_FUTURE
symbols: "AAVE"
symbols: "AVAX"
symbols: "BNB"
symbols: "BTC"
symbols: "DOGE"
symbols: "EIGEN"
symbols: "ETH"
}
}
}
}
}
Symbols {
value {
venues {
name: "VENUE_BINANCE"
spot: "AAVE"
spot: "AVAX"
spot: "BNB"
spot: "BTC"
spot: "DOGE"
spot: "EIGEN"
spot: "ETH"
}
venues {
name: "VENUE_BINANCE_USDS_FUTURES"
future: "AAVE"
future: "AVAX"
future: "BNB"
future: "BTC"
future: "DOGE"
future: "EIGEN"
future: "ETH"
}
}
}
NotSimExecution {
value {
entries {
venue: VENUE_BINANCE
}
entries {
venue: VENUE_BINANCE_USDS_FUTURES
}
entries {
venue: VENUE_GLBX
}
entries {
venue: VENUE_OKX
}
entries {
venue: VENUE_TAIFEX
}
}
}
@@ -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: "*"
}
}
}
+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();
}
}
+3
View File
@@ -117,6 +117,9 @@ void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
// Prepare req
//to debug curl requests
// curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_callback);
// 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, true);
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers.data());
+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));
}
};
+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);
+17 -2
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) {
@@ -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,59 +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/OkxExecutionGateway.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:
{ return std::make_unique<OkxExecutionGateway>(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(
@@ -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 {
@@ -89,9 +89,7 @@ struct OkxExecutionGateway::PImpl {
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> ws_exe_report_pool;
boost::object_pool<ExecutionReport> rest_exe_report_pool;
tbb::concurrent_queue<ExecutionReport *> buffered_exec_reps;
tbb::concurrent_queue<std::shared_ptr<ExecutionReport>> buffered_exec_reps;
std::optional<std::thread> ws_thread;
OnExeReport cb;
OnGwStatusChange on_gw_status_change;
@@ -115,7 +113,7 @@ struct OkxExecutionGateway::PImpl {
}
}
static void dummy(const ExecutionReport &, const trace::CorID &) {
static void dummy(std::shared_ptr<const ExecutionReport>, const trace::CorID &) {
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
}
void setCallback(const OnExeReport &callback) {
@@ -223,10 +221,9 @@ struct OkxExecutionGateway::PImpl {
// 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_p = ws_exe_report_pool.construct();
ExecutionReport &er = *er_p;
ExecutionReport er;
get_exe_report(order, er);
buffered_exec_reps.push(er_p);
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);
@@ -454,13 +451,13 @@ void OkxExecutionGateway::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,
"OkxExecutionGateway::dispatch_incoming_fix_messages er: {} ",
er->to_string());
me_->cb(*er, cid);
me_->cb(std::move(er), cid);
}
}
OnExeReport &OkxExecutionGateway::on_exe_report() { return me_->cb; }
@@ -504,8 +501,7 @@ void OkxExecutionGateway::launch_new_order(const CreateOrderMsg &msg) {
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
body);
LOG(OrderMan, Warn, "Curl Command: \n{}", curlCommand);
ExecutionReport *er_p = me_->rest_exe_report_pool.construct();
ExecutionReport &er = *er_p;
ExecutionReport er;
http::HttpClient::post(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, body,
headers, &res);
@@ -534,7 +530,8 @@ void OkxExecutionGateway::launch_new_order(const CreateOrderMsg &msg) {
er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
er.msg = ex_msg + " " + top_msg;
}
me_->buffered_exec_reps.push(er_p);
me_->buffered_exec_reps.push(
std::make_shared<ExecutionReport>(std::move(er)));
}
// Shut down all reactors if they exist.
catch (const std::exception &ex) {
@@ -719,4 +716,4 @@ void OkxExecutionGateway::cancel_order(const CancelOrderMsg &msg) {
// er.transact_time = now;
// me_->cb(er, {});
// return er;
// }
// }
@@ -16,8 +16,7 @@
namespace bts::orderman {
void OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
ExecutionReport *er_p = exe_report_pool.construct();
ExecutionReport &er = *er_p;
ExecutionReport er;
std::string account_name = "BTSIM";
std::array<char, 12> an_array = {};
size_t i = 0;
@@ -53,11 +52,10 @@ void OnixFixListener::fire_dummy_report(CreateOrderMsg msg) {
er.transact_time = now;
if(er.security==nullptr){
LOG(OrderMan, Error,"DID not have sec");
exe_report_pool.destroy(&er);
return;
}
LOG(OrderMan, Info,"msg {}",er.msg);
buffered_exec_reps.push(er_p);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
return;
}
@@ -219,7 +217,7 @@ void OnixFixListener::onInboundApplicationMsg(Message &msg, Session * /*sn*/) {
return;
}
LOG(OrderMan, Info,"msg {}",er.msg);
buffered_exec_reps.push(er_p);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
}
}
@@ -233,13 +231,13 @@ void OnixFixListener::dispatch_incoming_fix_messages() {
if (buffered_exec_reps.empty()) {
return;
}
ExecutionReport *er;
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(*er, cid);
cb(std::move(er), cid);
}
}
@@ -277,4 +275,4 @@ void OnixFixListener::onWarning(WarningReason::Enum /*reason*/, const std::strin
}
} // namespace bts::orderman
} // namespace bts::orderman
+5 -3
View File
@@ -34,10 +34,12 @@ class OnixFixListener : public OnixS::FIX::ISessionListener {
SessionState::Enum prevState,
Session *sn) ONIXS_FIXENGINE_FINAL;
OnExeReport cb;
tbb::concurrent_queue<ExecutionReport *> buffered_exec_reps;
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(const ExecutionReport &, const trace::CorID &) {
static void dummy(
std::shared_ptr<const ExecutionReport>, const trace::CorID &) {
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
}
void onError(
@@ -51,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
@@ -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
+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,27 @@
#pragma once
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
namespace binance_execution {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
per_value(CancelReplaceMode, NONE) \
per_value(CancelReplaceMode, STOP_ON_FAILURE) \
per_value(CancelReplaceMode, ALLOW_FAILURE)
// clang-format on
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-an-existing-order-and-send-a-new-order-trade
*/
ENUM_CLASS_DEF(CancelReplaceMode, FOR_ALL_VALUES)
#undef FOR_ALL_VALUES
} // namespace binance_execution
FMT_IMPL(binance_execution::CancelReplaceMode, m, "{}", m.name());
@@ -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 {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
per_value(NewOrderResponseType, NONE) \
per_value(NewOrderResponseType, ACK) \
per_value(NewOrderResponseType, RESULT) \
per_value(NewOrderResponseType, FULL)
// clang-format on
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/enums#order-response-type-neworderresptype
*/
ENUM_CLASS_DEF(NewOrderResponseType, FOR_ALL_VALUES)
#undef FOR_ALL_VALUES
} // namespace binance_execution
FMT_IMPL(binance_execution::NewOrderResponseType, t, "{}", t.name());
@@ -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 {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
per_value(OpSuccess, NONE) \
per_value(OpSuccess, SUCCESS) \
per_value(OpSuccess, FAILURE) \
per_value(OpSuccess, NOT_ATTEMPTED)
// clang-format on
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-an-existing-order-and-send-a-new-order-trade
*/
ENUM_CLASS_DEF(OpSuccess, FOR_ALL_VALUES)
#undef FOR_ALL_VALUES
} // namespace binance_execution
FMT_IMPL(binance_execution::OpSuccess, o, "{}", o.name());
@@ -0,0 +1,27 @@
#pragma once
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
namespace binance_execution {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
per_value(OrderCancelRestriction, NONE) \
per_value(OrderCancelRestriction, ONLY_NEW) \
per_value(OrderCancelRestriction, ONLY_PARTIALLY_FILLED)
// clang-format on
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-order-trade
*/
ENUM_CLASS_DEF(OrderCancelRestriction, FOR_ALL_VALUES)
#undef FOR_ALL_VALUES
} // namespace binance_execution
FMT_IMPL(binance_execution::OrderCancelRestriction, r, "{}", r.name());
@@ -0,0 +1,27 @@
#pragma once
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
namespace binance_execution {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
per_value(OrderRateLimitExceededMode, NONE) \
per_value(OrderRateLimitExceededMode, DO_NOTHING) \
per_value(OrderRateLimitExceededMode, CANCEL_ONLY)
// clang-format on
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-an-existing-order-and-send-a-new-order-trade
*/
ENUM_CLASS_DEF(OrderRateLimitExceededMode, FOR_ALL_VALUES)
#undef FOR_ALL_VALUES
} // namespace binance_execution
FMT_IMPL(binance_execution::OrderRateLimitExceededMode, r, "{}", r.name());
@@ -6,7 +6,7 @@
#include <fmt/format.h>
#include <nlohmann/json.hpp>
namespace binance_execution::usds_futures {
namespace binance_execution {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
@@ -20,6 +20,6 @@ ENUM_CLASS_DEF(PositionSide, FOR_ALL_VALUES)
#undef FOR_ALL_VALUES
} // namespace binance_execution::usds_futures
} // namespace binance_execution
FMT_IMPL(binance_execution::usds_futures::PositionSide, s, "{}", s.name());
FMT_IMPL(binance_execution::PositionSide, s, "{}", s.name());
@@ -10,7 +10,8 @@ namespace binance_execution {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
per_value(SelfTradePreventionMode, NONE)
per_value(SelfTradePreventionMode, NONE) \
per_value(SelfTradePreventionMode, EXPIRE_MAKER)
// clang-format on
ENUM_CLASS_DEF(SelfTradePreventionMode, FOR_ALL_VALUES)
@@ -1,52 +0,0 @@
#include "execution_gateway_provider.h"
#include "binance_execution/usds_futures/execution_gateway.h"
#include "binance_execution/usds_futures/execution_gateway_config.h"
#include "lib/domain_model/Security.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"
namespace binance_execution {
using bts::orderman::OrderGatewayDefinition;
using bts::spark::SparkDataDirectory;
using std::vector;
struct ExecutionGatewayProvider::PImpl {
PImpl(bts::md::SecurityOracle sec_oracle)
: sec_oracle(std::move(sec_oracle)) {}
bts::md::SecurityOracle sec_oracle;
bool load_usds_futures(vector<OrderGatewayDefinition> &dest) {
SparkDataDirectory data_dir;
auto cfg =
data_dir.load_config_json<usds_futures::ExecutionGatewayConfig>(
"binance_usds_futures_execution");
if (!cfg) {
return false;
}
auto &gw = dest.emplace_back();
*gw.connection.mutable_name() = "binance_usds_futures_1";
gw.connection.set_venue(bts::api::VENUE_BINANCE_USDS_FUTURES);
gw.gateway = std::make_shared<usds_futures::ExecutionGateway>(
sec_oracle, std::move(*cfg), gw.connection.name());
return true;
}
};
ExecutionGatewayProvider::ExecutionGatewayProvider(
bts::md::SecurityOracle sec_oracle)
: me_(std::make_unique<PImpl>(std::move(sec_oracle))) {}
ExecutionGatewayProvider::~ExecutionGatewayProvider() = default;
std::vector<bts::orderman::OrderGatewayDefinition>
ExecutionGatewayProvider::create() {
std::vector<bts::orderman::OrderGatewayDefinition> gws;
me_->load_usds_futures(gws);
return gws;
}
} // namespace binance_execution
@@ -1,24 +0,0 @@
#pragma once
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
#include "lib/orderman/gateways/i_execution_gateway_provider.h"
#include <memory>
#include <vector>
namespace binance_execution {
class ExecutionGatewayProvider
: public bts::orderman::IExecutionGatewayProvider {
public:
explicit ExecutionGatewayProvider(bts::md::SecurityOracle sec_oracle);
~ExecutionGatewayProvider() override;
std::vector<bts::orderman::OrderGatewayDefinition> create() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace binance_execution
@@ -0,0 +1,76 @@
#pragma once
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
namespace binance_execution {
template <class ResponseT>
struct RestApiResult {
std::string error;
std::optional<ResponseT> response;
RestApiResult() = default;
static RestApiResult from_error(const std::string &error) {
return RestApiResult{error};
}
static RestApiResult from_success(ResponseT response) {
return RestApiResult{std::move(response)};
}
constexpr bool ok() const { return error.empty(); }
void reset() {
error.clear();
response.reset();
}
private:
explicit RestApiResult(ResponseT response) : response(std::move(response)) {}
explicit RestApiResult(std::string error) : error(std::move(error)) {}
};
template <
class ResponseT,
typename std::enable_if_t<
nlohmann::detail::has_to_json<nlohmann::json, ResponseT>::value,
int> = 0>
inline void to_json(nlohmann::json &j, const RestApiResult<ResponseT> &res) {
if (!res.ok()) {
j["error"] = res.error;
} else if (res.response) {
j["response"] = *res.response;
} else {
j["response"] = nlohmann::json{};
}
}
template <
class ResponseT,
typename std::enable_if_t<
nlohmann::detail::has_from_json<nlohmann::json, ResponseT>::value,
int> = 0>
inline void from_json(const nlohmann::json &j, RestApiResult<ResponseT> &res) {
auto it = j.find("error");
if (it != j.end()) {
it->get_to(res.error);
}
it = j.find("response");
if (it != j.end()) {
if (it->is_null()) {
res.response.reset();
} else {
if (!res.response) {
res.response.emplace();
}
it->get_to(*res.response);
}
}
}
} // namespace binance_execution
@@ -0,0 +1,633 @@
#include "execution_gateway.h"
#include "binance_execution/rest_api_result.h"
#include "binance_execution/spots/messages/modify_order_request.h"
#include "binance_execution/spots/messages/order_record.h"
#include "binance_execution/spots/order_key_map.h"
#include "binance_execution/spots/rest_api_client.h"
#include "binance_execution/spots/type_converters.h"
#include "binance_execution/spots/user_data_streams/listen_key_requestor.h"
#include "binance_execution/spots/user_data_streams/user_data_subscription.h"
#include "binance_execution/type_converters.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/config/protos/toggles.pb.h"
#include "lib/core/TimeStamp.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/Side.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/checked_arithmetic.h"
#include "lib/utils/overloaded.h"
#include <boost/lockfree/spsc_queue.hpp>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
using nlohmann::json;
namespace binance_execution::spots {
#define ASSERT_FN(test, fn, ...) \
bts_assert_msgf(test, "Binance::" fn ". " __VA_ARGS__)
#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__)
#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::NewAdminDataEvent;
using bts::orderman::NewSecuritiesEvent;
using bts::orderman::OrderMsg;
using bts::orderman::RFQMsg;
using std::int64_t;
using std::optional;
using std::size_t;
using std::string;
using std::uint32_t;
using std::vector;
static void append_error(string &dest, const char *err) {
if (!dest.empty()) {
dest.append(" ");
}
dest.append(err);
}
static void validate(bts::orderman::OrderKey key, string &error) {
if (key.value() == 0) {
append_error(error, "order key is 0.");
}
}
static void validate(bts::orderman::ExchangeOrderID64 key, string &error) {
if (key.value() == 0) {
append_error(error, "exch order key is 0.");
}
}
static void validate(bts::orderman::ExchangeOrderIDVar key, string &error) {
if (std::holds_alternative<bts::orderman::ExchangeOrderID64>(key)) {
validate(std::get<bts::orderman::ExchangeOrderID64>(key), error);
} else {
append_error(error, "exch order id is undefined or not an integer.");
}
}
static void validate(bts::Quantity qty, string &error) {
if (qty.full_value() == 0) {
append_error(error, "quantity is 0.");
}
}
static void validate(const bts::api::Security *sec, string &error) {
if (sec == nullptr) {
append_error(error, "security is null.");
} else if (!sec->has_binance()) {
append_error(error, "security not of Binance.");
}
}
static void validate(bts::api::Side side, string &error) {
switch (side) {
case bts::api::SIDE_BUY:
case bts::api::SIDE_SELL:
break;
default:
append_error(error, "invalid side.");
break;
}
}
// TODO: Store exch order id in EMNativeOrder (until then, it'll always be 0).
// We can ignore this for now and use "orig client id" in cancel/replace requests.
static constexpr const bool VALIDATE_EXCH_ORDER_ID = false;
static bool validate(const CreateOrderMsg &m, string &error) {
const size_t orig_sz = error.size();
validate(m.security, error);
validate(m.key, error);
validate(m.quantity, error);
validate(m.side, error);
return error.size() == orig_sz;
}
static bool validate(const CancelOrderMsg &m, string &error) {
const size_t orig_sz = error.size();
validate(m.security, error);
validate(m.key, error);
if (VALIDATE_EXCH_ORDER_ID) {
validate(m.exchange_order_id, error);
}
validate(m.side, error);
return error.size() == orig_sz;
}
static bool validate(const CancelReplaceOrderMsg &m, string &error) {
const size_t orig_sz = error.size();
validate(m.create_fields, error);
if (VALIDATE_EXCH_ORDER_ID) {
validate(m.exchange_order_id, error);
}
return error.size() == orig_sz;
}
static bool validate(const bts::orderman::ExecutionReport &er, string &error) {
const size_t orig_sz = error.size();
validate(er.order_key, error);
validate(er.quantity, error);
validate(er.exchange_order_id, error);
if (er.account_name.value().empty()) {
append_error(error, "account is empty.");
}
validate(er.security, error);
if (er.cfg == nullptr) {
append_error(error, "jetfix config is null.");
}
return error.size() == orig_sz;
}
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 ExecutionGateway::PImpl {
explicit PImpl(
bts::md::SecurityOracle sec_oracle,
ExecutionGatewayConfig config,
bts::config::JetFIX::Connection jetfix_connection)
: sec_oracle(std::move(sec_oracle)), config(std::move(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->rest_api_client) {
bts_assert(this->jetfix_connection.venue() == bts::api::VENUE_BINANCE);
bts_assert(!this->jetfix_connection.name().empty());
pos.exchange = bts::api::VENUE_BINANCE;
}
static constexpr const char *CANCEL_REPLACE_CANCEL_SUFFIX = "crc";
bts::md::SecurityOracle sec_oracle;
ExecutionGatewayConfig config;
bts::config::JetFIX_Connection jetfix_connection;
RestApiClient rest_api_client;
optional<user_data_streams::UserDataSubscription> user_data_sub;
Delegate<void(user_data_streams::AccountUpdate)> account_update_callback;
Delegate<void(user_data_streams::OrderUpdate)> order_update_callback;
ListenKeyRequestor listen_key_requestor;
spsc_queue<nlohmann::json> incoming_msgs{1024};
spsc_queue<user_data_streams::AccountUpdate> incoming_account_updates{1024};
spsc_queue<user_data_streams::OrderUpdate> incoming_order_updates{1024};
spsc_queue<RestApiResult<OrderRecord>> incoming_new_orders{1024};
vector<user_data_streams::AccountUpdate> account_updates_vec;
ExecutionGatewayCallbacks callbacks;
bts::orderman::ExchangePosition pos;
bts::admin::AdminServiceUtils::ExchangeSubAccountResolver
subaccount_resolver{};
bts::api::admin::AdminData admin_data = {};
bts::api::admin::BTSAccount account;
// Since modifications are implemented as cancel/replace at Binance, modified
// orders don't keep their original id. The version number correlates the
// original id with the current one.
std::unordered_map<bts::orderman::OrderKey, std::uint32_t> order_versions;
OrderKeyMap key_map;
void start() {
INFO_FN("start");
listen_key_requestor.callback().bind<&PImpl::on_listen_key_>(*this);
listen_key_requestor.start();
}
void stop() {
INFO_FN("stop");
user_data_sub.reset();
}
void on_account_update(user_data_streams::AccountUpdate u) {
INFO_FN("on_account_update", "\n{:2}", json_formatter(u));
incoming_account_updates.push(std::move(u));
}
void on_order_update(user_data_streams::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);
user_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 = user_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) {
string sec_desc = m.security == nullptr
? "(null)"
: bts::securities::descriptor(*m.security);
RestApiResult<OrderRecord> res;
validate(m, res.error);
LOG_FN(
res.ok() ? bts::Info : bts::Error, "create_order",
"{} {} {} @ {} (id={}, type={}, tif={}, err={})", m.side, m.quantity,
sec_desc, m.price, m.key, m.order_type, m.time_in_force, res.error);
if (res.ok()) {
CreateOrderRequest req;
req.newClientOrderId = std::to_string(m.key.value());
req.symbol = bts::securities::descriptor(*m.security);
req.side = m.side == bts::api::SIDE_BUY ? Side::BUY : Side::SELL;
req.price = m.price.value();
req.quantity = m.quantity.full_value();
req.type = convert_order_type(m.order_type);
req.timeInForce = convert_time_in_force(m.time_in_force);
req.timestamp = convert_time_stamp(bts::core::TimeStamp::now());
res = rest_api_client.create_order(req);
LOG_FN(
res.ok() ? bts::Info : bts::Error, "create_order", "id={}; {}",
m.key, json_formatter(res));
}
incoming_new_orders.push(std::move(res));
}
void update_order(const CancelReplaceOrderMsg &m) {
string sec_desc =
m.create_fields.security == nullptr
? "(null)"
: bts::securities::descriptor(*m.create_fields.security);
RestApiResult<ModifyOrderResponse> res;
validate(m, res.error);
LOG_FN(
res.ok() ? bts::Info : bts::Error, "update_order",
"{} {} {} @ {} (id={}, type={}, tif={}, err={})",
m.create_fields.side, m.create_fields.quantity, sec_desc,
m.create_fields.price, m.create_fields.key,
m.create_fields.order_type, m.create_fields.time_in_force, res.error);
if (res.ok()) {
ModifyOrderRequest req;
const auto orig_key = m.create_fields.key;
req.cancelOrderId = 0;
req.cancelOrigClientOrderId = key_map.get_exch_key(orig_key);
// Can't reuse the same order id.
key_map.increment_order_version(orig_key);
req.cancelNewClientOrderId = key_map.get_exch_key(orig_key).append(
CANCEL_REPLACE_CANCEL_SUFFIX);
key_map.increment_order_version(orig_key);
req.newClientOrderId = key_map.get_exch_key(orig_key);
// The exchange accepts either cancelOrderId, or cancelOrigClientOrderId (only one
// need be populated).
bts_assert(
req.cancelOrderId != 0 || !req.cancelOrigClientOrderId.empty());
req.price = m.create_fields.price.value();
req.quantity = m.create_fields.quantity.full_value();
req.symbol = m.create_fields.security->binance().security_desc();
req.side = convert_side(m.create_fields.side);
req.type = convert_order_type(m.create_fields.order_type);
req.cancelReplaceMode = CancelReplaceMode::STOP_ON_FAILURE;
req.timeInForce = convert_time_in_force(m.create_fields.time_in_force);
req.timestamp = convert_time_stamp(bts::core::TimeStamp::now());
res = rest_api_client.modify_order(req);
LOG_FN(
res.ok() ? bts::Info : bts::Error, "update_order", "id={}; {}",
req.cancelOrderId, json_formatter(res));
}
}
void cancel_order(const CancelOrderMsg &m) {
string sec_desc = m.security == nullptr
? "(null)"
: bts::securities::descriptor(*m.security);
RestApiResult<OrderRecord> res;
validate(m, res.error);
CancelOrderRequest req;
LOG_FN(
res.ok() ? bts::Info : bts::Error, "cancel_order",
"{} {} (id={}, exch_id={}, err={})", m.side, sec_desc, m.key,
m.exchange_order_id, res.error);
if (res.ok()) {
req.orderId =
bts::system::math::checked_ultol(m.exchange_order_id.value());
req.origClientOrderId = key_map.get_exch_key(m.key);
// Can't reuse the same order id.
key_map.increment_order_version(m.key);
req.newClientOrderId = key_map.get_exch_key(m.key);
// The exchange accepts either orderId, or origClientOrderId (only one
// need be populated).
bts_assert(req.orderId != 0 || !req.origClientOrderId.empty());
req.symbol = m.security->binance().security_desc();
req.timestamp = convert_time_stamp(bts::core::TimeStamp::now());
res = rest_api_client.cancel_order(req);
LOG_FN(
res.ok() ? bts::Info : bts::Error, "cancel_order",
"id={}, exch_id={}; {}", m.key, m.exchange_order_id,
json_formatter(res));
}
}
};
ExecutionGateway::ExecutionGateway(
bts::md::SecurityOracle sec_oracle,
ExecutionGatewayConfig 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());
}
ExecutionGateway::~ExecutionGateway() = default;
void ExecutionGateway::start() { me_->start(); }
void ExecutionGateway::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 ExecutionGateway::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) {
return;
}
for (const auto &ev : *(update.events)) {
me_->admin_data.CopyFrom(*ev.admin_data);
}
auto *exch_sub_acct =
me_->subaccount_resolver.find_entry(me_->config.account, false);
if (exch_sub_acct == nullptr) {
WARN_FN(
"on_admin_data",
"Failed to map exchange account '{}' to internal account",
me_->config.account);
return;
}
auto *account = bts::admin::AdminServiceUtils::find_account(
me_->admin_data, exch_sub_acct->bts_account_name());
if (account == nullptr) {
WARN_FN(
"on_admin_data",
"Failed to find internal account '{}' (mapped from exchange account "
"'{}')",
exch_sub_acct->bts_account_name(), me_->config.account);
return;
}
if (me_->account.name() == account->name() &&
me_->account.id() == account->id()) {
return;
}
me_->account = *account;
INFO_FN(
"on_admin_data", "bts_account: {}; exch_subaccount: {}", account->name(),
exch_sub_acct->exchange_subaccount());
}
void ExecutionGateway::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 ExecutionGateway::send_fix(
const bts::jetfix::OutFIXPtr &, bts::trace::CorID) {
bts_assert_msg(false, "ExecutionGateway does not support fix");
}
void ExecutionGateway::dispatch_incoming_fix_messages() {
// Note: We send orders through the REST api, and receive order updates over
// websocket (user data stream). The REST responses are added to the
// "incoming_new_orders" queue for logging purposes, but we only generate
// ExecutionReports in response to the websocket updates (which are added to
// the "incoming_order_updates" queue).
static constexpr const bool IGNORE_CREATE_ORDER_RESPONSE = true;
// Order creates
{
RestApiResult<OrderRecord> curr;
while (me_->incoming_new_orders.pop(curr)) {
if (!curr.ok() || !curr.response) {
ERROR_FN(
"dispatch_incoming_fix_messages",
"Failed to generate exe report due to invalid REST response. "
"{}",
curr.error);
continue;
}
const auto bts_key =
me_->key_map.get_bts_key(curr.response->clientOrderId);
if (!bts_key) {
ERROR_FN(
"dispatch_incoming_fix_messages",
"Failed to generate exe report. Did not resolve bts key from "
"exch clientOrderId '{}'",
curr.response->clientOrderId);
continue;
}
const auto exch_key = me_->key_map.get_exch_key(*bts_key);
if (exch_key != curr.response->clientOrderId) {
INFO_FN(
"dispatch_incoming_fix_messages",
"Ignoring update w/ old clientOrderId '{}'. The current "
"clientOrderId is '{}' (this is expected for CancelReplace "
"ops)",
curr.response->clientOrderId, exch_key);
continue;
}
bts::orderman::ExecutionReport er;
if (!convert_execution_report(
*curr.response, VENUE, me_->account.name(),
&me_->jetfix_connection, er)) {
ERROR_FN(
"dispatch_incoming_fix_messages",
"Failed to generate exe report from REST response. {}",
json_formatter(*curr.response));
continue;
}
er.security = me_->sec_oracle.get_security(
VENUE, bts::api::SECURITYTYPE_SPOT, curr.response->symbol);
string error;
if (!validate(er, error)) {
ERROR_FN(
"dispatch_incoming_fix_messages",
"Generated invalid ExecutionReport. {}", error);
continue;
}
if (!IGNORE_CREATE_ORDER_RESPONSE) {
INFO_FN(
"dispatch_incoming_fix_messages",
"Dispatching ExecutionReport. bts_id: {}, exch_id: {}",
er.order_key, er.exchange_order_id);
me_->callbacks.exe_report(
std::make_shared<bts::orderman::ExecutionReport>(std::move(er)),
{});
}
}
}
// Order updates
{
user_data_streams::OrderUpdate curr;
while (me_->incoming_order_updates.pop(curr)) {
auto bts_key = me_->key_map.get_bts_key(curr.client_order_id);
if (!bts_key) {
ERROR_FN(
"dispatch_incoming_fix_messages",
"Failed to generate exe report. Did not resolve bts key from "
"exch clientOrderId '{}'",
curr.client_order_id);
continue;
}
auto exch_key = me_->key_map.get_exch_key(*bts_key);
if (exch_key != curr.client_order_id) {
INFO_FN(
"dispatch_incoming_fix_messages",
"Ignoring update w/ old clientOrderId '{}'. The current "
"clientOrderId is '{}' (this is expected for CancelReplace "
"ops)",
curr.client_order_id);
continue;
}
bts::orderman::ExecutionReport er;
if (!convert_execution_report(
curr, VENUE, me_->account.name(), &me_->jetfix_connection,
er)) {
WARN_FN(
"dispatch_incoming_fix_messages",
"Failed to generate exe report from OrderUpdate: {}",
json_formatter(curr));
continue;
}
er.security = me_->sec_oracle.get_security(VENUE, curr.symbol);
string error;
if (!validate(er, error)) {
ERROR_FN(
"dispatch_incoming_fix_messages",
"Generated invalid ExecutionReport. {}", error);
continue;
}
INFO_FN(
"dispatch_incoming_fix_messages",
"Dispatching ExecutionReport. bts_id: {}, exch_id: {}",
er.order_key, er.exchange_order_id);
me_->callbacks.exe_report(
std::make_shared<bts::orderman::ExecutionReport>(std::move(er)),
{});
}
}
// Account updates
{
user_data_streams::AccountUpdate u;
auto &acct_upds = me_->account_updates_vec;
acct_upds.clear();
while (me_->incoming_account_updates.pop(acct_upds.emplace_back())) {
}
acct_upds.pop_back();
if (!acct_upds.empty()) {
convert_positions(
VENUE, me_->account, acct_upds,
[sec_oracle = &me_->sec_oracle](
bts::api::Venue venue, const std::string &name) {
return sec_oracle->get_security(venue, name);
},
me_->pos.total_exchange_positions);
if (me_->callbacks.exchange_position_state) {
me_->callbacks.exchange_position_state(me_->pos);
}
}
}
}
// Callbacks
bts::orderman::OnExeReport &ExecutionGateway::on_exe_report() {
return me_->callbacks.exe_report;
}
bts::orderman::OnGwStatusChange &ExecutionGateway::on_gw_status_change() {
return me_->callbacks.gw_status_change;
}
bts::orderman::OnCancelReject &ExecutionGateway::on_cancel_reject() {
return me_->callbacks.cancel_reject;
}
bts::orderman::OnExchangePositionState &
ExecutionGateway::on_exchange_position_state() {
return me_->callbacks.exchange_position_state;
}
bts::orderman::OnExchangeOpenOrdersState &
ExecutionGateway::on_exchange_open_orders() {
return me_->callbacks.exchange_open_orders;
}
} // namespace binance_execution::spots
@@ -0,0 +1,44 @@
#pragma once
#include "binance_execution/spots/execution_gateway_config.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/md/SecurityOracle.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
#include <memory>
namespace binance_execution::spots {
class ExecutionGateway : public bts::orderman::IExecutionGateway {
public:
explicit ExecutionGateway(
bts::md::SecurityOracle sec_oracle,
ExecutionGatewayConfig session_cfg,
bts::config::JetFIX::Connection jetfix_connection);
~ExecutionGateway() 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::spots
@@ -0,0 +1,26 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::spots {
struct ExecutionGatewayConfig {
std::string api_key;
std::string secret_key;
std::string account;
std::string rest_endpoint;
std::string ws_endpoint;
std::string ws_stream_endpoint;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
ExecutionGatewayConfig,
api_key,
secret_key,
account,
rest_endpoint,
ws_endpoint,
ws_stream_endpoint);
} // namespace binance_execution::spots
@@ -0,0 +1,109 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#all-orders-user_data
*/
struct AllOrdersRequest {
std::string symbol;
std::int64_t orderId;
std::int64_t startTime;
std::int64_t endTime;
int limit;
std::int64_t recvWindow;
std::int64_t timestamp;
};
void to_json(nlohmann::json &j, const AllOrdersRequest &req);
void from_json(const nlohmann::json &j, AllOrdersRequest &req);
void to_query_params(std::string &qps, const AllOrdersRequest &req);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const AllOrdersRequest &req) {
j["timestamp"] = req.timestamp;
if (!req.symbol.empty()) {
j["symbol"] = req.symbol;
}
if (req.orderId) {
j["orderId"] = req.orderId;
}
if (req.startTime) {
j["startTime"] = req.startTime;
}
if (req.endTime) {
j["endTime"] = req.endTime;
}
if (req.limit) {
j["limit"] = req.limit;
}
if (req.recvWindow) {
j["recvWindow"] = req.recvWindow;
}
}
inline void from_json(const nlohmann::json &j, AllOrdersRequest &req) {
j.at("timestamp").get_to(req.timestamp);
auto it = j.find("symbol");
if (it != j.end()) {
it->get_to(req.symbol);
}
it = j.find("orderId");
if (it != j.end()) {
it->get_to(req.orderId);
}
it = j.find("startTime");
if (it != j.end()) {
it->get_to(req.startTime);
}
it = j.find("endTime");
if (it != j.end()) {
it->get_to(req.endTime);
}
it = j.find("limit");
if (it != j.end()) {
it->get_to(req.limit);
}
it = j.find("recvWindow");
if (it != j.end()) {
it->get_to(req.recvWindow);
}
}
inline void to_query_params(std::string &qps, const AllOrdersRequest &req) {
qps.append("timestamp=");
qps.append(std::to_string(req.timestamp));
if (!req.symbol.empty()) {
qps.append("&symbol=");
qps.append(req.symbol);
}
if (req.orderId) {
qps.append("&orderId=");
qps.append(std::to_string(req.orderId));
}
if (req.startTime) {
qps.append("&startTime=");
qps.append(std::to_string(req.startTime));
}
if (req.endTime) {
qps.append("&endTime=");
qps.append(std::to_string(req.endTime));
}
if (req.limit) {
qps.append("&limit=");
qps.append(std::to_string(req.limit));
}
if (req.recvWindow) {
qps.append("&recvWindow=");
qps.append(std::to_string(req.recvWindow));
}
}
} // namespace binance_execution::spots
@@ -0,0 +1,103 @@
#pragma once
#include "binance_execution/enums/order_cancel_restriction.h"
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-order-trade
*/
struct CancelOrderRequest {
std::string symbol;
std::int64_t orderId = 0;
std::string origClientOrderId;
std::string newClientOrderId;
OrderCancelRestriction cancelRestrictions = OrderCancelRestriction::NONE;
std::int64_t recvWindow = 0;
std::int64_t timestamp = 0;
};
void to_json(nlohmann::json &j, const CancelOrderRequest &req);
void from_json(const nlohmann::json &j, CancelOrderRequest &req);
void to_query_params(std::string &qps, const CancelOrderRequest &req);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const CancelOrderRequest &req) {
j["symbol"] = req.symbol;
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.cancelRestrictions != OrderCancelRestriction::NONE) {
j["cancelRestrictions"] = req.cancelRestrictions;
}
if (req.recvWindow != 0) {
j["recvWindow"] = req.recvWindow;
}
j["timestamp"] = req.timestamp;
}
inline void from_json(const nlohmann::json &j, CancelOrderRequest &req) {
j.at("symbol").get_to(req.symbol);
auto it = j.find("orderId");
if (it != j.end()) {
it->get_to(req.orderId);
}
it = j.find("origClientOrderId");
if (it != j.end()) {
it->get_to(req.origClientOrderId);
}
it = j.find("newClientOrderId");
if (it != j.end()) {
it->get_to(req.newClientOrderId);
}
it = j.find("cancelRestrictions");
if (it != j.end()) {
it->get_to(req.cancelRestrictions);
}
it = j.find("recvWindow");
if (it != j.end()) {
it->get_to(req.recvWindow);
}
j.at("timestamp").get_to(req.timestamp);
}
inline void to_query_params(std::string &qps, const CancelOrderRequest &req) {
qps.append("symbol=");
qps.append(req.symbol);
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.cancelRestrictions != OrderCancelRestriction::NONE) {
qps.append("&cancelRestrictions=");
qps.append(req.cancelRestrictions.name());
}
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::spots
@@ -0,0 +1,82 @@
#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 <cstdint>
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-order-trade
*/
struct CancelOrderResponse {
std::string symbol;
std::string origClientOrderId;
std::int64_t orderId;
std::int64_t orderListId;
std::string clientOrderId;
std::int64_t transactTime;
double price;
double origQty;
double executedQty;
double cummulativeQuoteQty;
OrderStatus status;
TimeInForce timeInForce;
OrderType type;
Side side;
SelfTradePreventionMode selfTradePreventionMode;
// Note: 'cummulative' is deliberately mispelled to match the json payload.
};
void to_json(nlohmann::json &j, const CancelOrderResponse &res);
void from_json(const nlohmann::json &j, CancelOrderResponse &res);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const CancelOrderResponse &res) {
j["symbol"] = res.symbol;
j["origClientOrderId"] = res.origClientOrderId;
j["orderId"] = res.orderId;
j["orderListId"] = res.orderListId;
j["clientOrderId"] = res.clientOrderId;
j["transactTime"] = res.transactTime;
j["price"] = res.price;
j["origQty"] = res.origQty;
j["executedQty"] = res.executedQty;
j["cummulativeQuoteQty"] = res.cummulativeQuoteQty;
j["status"] = res.status;
j["timeInForce"] = res.timeInForce;
j["type"] = res.type;
j["side"] = res.side;
j["selfTradePreventionMode"] = res.selfTradePreventionMode;
}
inline void from_json(const nlohmann::json &j, CancelOrderResponse &res) {
const bts::io::JsonReader jr{&j};
j.at("symbol").get_to(res.symbol);
j.at("origClientOrderId").get_to(res.origClientOrderId);
j.at("orderId").get_to(res.orderId);
j.at("orderListId").get_to(res.orderListId);
j.at("clientOrderId").get_to(res.clientOrderId);
j.at("transactTime").get_to(res.transactTime);
jr.read("price", res.price);
jr.read("origQty", res.origQty);
jr.read("executedQty", res.executedQty);
jr.read("cummulativeQuoteQty", res.cummulativeQuoteQty);
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);
j.at("selfTradePreventionMode").get_to(res.selfTradePreventionMode);
}
} // namespace binance_execution::spots
@@ -0,0 +1,94 @@
#include "create_order_request.h"
#include "binance_execution/enums/new_order_response_type.h"
#include "binance_execution/price.h"
#include "binance_execution/quantity.h"
namespace binance_execution::spots {
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.newClientOrderId.empty()) {
j["newClientOrderId"] = req.newClientOrderId;
}
if (!std::isnan(req.price)) {
std::string price_str;
price_to_string(req.price, price_str);
j["price"] = price_str;
}
if (req.quantity > 0) {
std::string qty_str;
quantity_to_string(req.quantity, qty_str);
j["quantity"] = qty_str;
}
if (req.timeInForce != TimeInForce::NONE) {
j["timeInForce"] = req.timeInForce;
}
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);
j.at("newClientOrderId").get_to(req.newClientOrderId);
auto it = j.find("price");
if (it != j.end()) {
it->get_to(req.price);
}
it = j.find("quantity");
if (it != j.end()) {
it->get_to(req.quantity);
}
it = j.find("timeInForce");
if (it != j.end()) {
it->get_to(req.timeInForce);
}
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.newClientOrderId.empty()) {
qps.append("&newClientOrderId=");
qps.append(req.newClientOrderId);
}
// Format price & qty with 4 decimal places (should be sufficient the contracts we support)
if (!std::isnan(req.price)) {
qps.append("&price=");
price_to_string(req.price, qps);
}
if (req.quantity > 0) {
qps.append("&quantity=");
quantity_to_string(req.quantity, qps);
}
if (req.timeInForce != TimeInForce::NONE) {
qps.append("&timeInForce=");
qps.append(req.timeInForce.name());
}
if (req.recvWindow > 0) {
qps.append("&recvWindow=");
qps.append(std::to_string(req.recvWindow));
}
if (req.newOrderRespType != NewOrderResponseType::NONE) {
qps.append("&newOrderRespType=");
qps.append(req.newOrderRespType.name());
}
}
} // namespace binance_execution::spots
@@ -0,0 +1,34 @@
#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 binance_execution::spots {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade
*/
struct CreateOrderRequest {
std::string symbol;
Side side = Side::NONE;
OrderType type = OrderType::NONE;
std::int64_t timestamp = 0;
std::string newClientOrderId;
double price = std::numeric_limits<double>::quiet_NaN();
double quantity = 0;
TimeInForce timeInForce = TimeInForce::NONE;
std::int64_t recvWindow = 0;
NewOrderResponseType newOrderRespType = NewOrderResponseType::NONE;
};
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::spots
@@ -0,0 +1,15 @@
#pragma once
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams
*/
struct ListenKeyRequest {};
inline void to_query_params(
std::string & /*qps*/, const ListenKeyRequest & /*req*/) {}
} // namespace binance_execution::spots
@@ -0,0 +1,17 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams
*/
struct ListenKeyResponse {
std::string listenKey;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ListenKeyResponse, listenKey);
} // namespace binance_execution::spots
@@ -0,0 +1,180 @@
#include "modify_order_request.h"
#include "binance_execution/enums/new_order_response_type.h"
#include "binance_execution/enums/time_in_force.h"
#include "binance_execution/price.h"
#include "binance_execution/quantity.h"
#include "lib/io/JsonReader.h"
namespace binance_execution::spots {
void to_json(nlohmann::json &j, const ModifyOrderRequest &req) {
j["symbol"] = req.symbol;
j["side"] = req.side;
j["type"] = req.type;
j["cancelReplaceMode"] = req.cancelReplaceMode;
if (req.timeInForce != TimeInForce::NONE) {
j["timeInForce"] = req.timeInForce;
}
if (!std::isnan(req.quantity)) {
j["quantity"] = req.quantity;
}
if (!std::isnan(req.quoteOrderQty)) {
j["quoteOrderQty"] = req.quoteOrderQty;
}
if (!std::isnan(req.price)) {
j["price"] = req.price;
}
if (!req.cancelNewClientOrderId.empty()) {
j["cancelNewClientOrderId"] = req.cancelNewClientOrderId;
}
if (!req.cancelOrigClientOrderId.empty()) {
j["cancelOrigClientOrderId"] = req.cancelOrigClientOrderId;
}
if (req.cancelOrderId != 0) {
j["cancelOrderId"] = req.cancelOrderId;
}
if (!req.newClientOrderId.empty()) {
j["newClientOrderId"] = req.newClientOrderId;
}
if (req.strategyId) {
j["strategyId"] = *req.strategyId;
}
if (req.strategyType) {
j["strategyType"] = *req.strategyType;
}
if (!std::isnan(req.stopPrice)) {
j["stopPrice"] = req.stopPrice;
}
if (req.trailingDelta) {
j["trailingDelta"] = *req.trailingDelta;
}
if (!std::isnan(req.icebergQty)) {
j["icebergQty"] = req.icebergQty;
}
if (req.newOrderRespType != NewOrderResponseType::NONE) {
j["newOrderRespType"] = req.newOrderRespType;
}
if (req.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
j["selfTradePreventionMode"] = req.selfTradePreventionMode;
}
if (req.cancelRestrictions != OrderCancelRestriction::NONE) {
j["cancelRestrictions"] = req.cancelRestrictions;
}
if (req.orderRateLimitExceededMode != OrderRateLimitExceededMode::NONE) {
j["orderRateLimitExceededMode"] = req.orderRateLimitExceededMode;
}
if (req.recvWindow != 0) {
j["recvWindow"] = req.recvWindow;
}
j["timestamp"] = req.timestamp;
}
void from_json(const nlohmann::json &j, ModifyOrderRequest &req) {
j.at("symbol").get_to(req.symbol);
j.at("side").get_to(req.side);
j.at("type").get_to(req.type);
j.at("cancelReplaceMode").get_to(req.cancelReplaceMode);
const bts::io::JsonReader jr{&j};
jr.read("timeInForce", req.timeInForce);
jr.read("quantity", req.quantity);
jr.read("quoteOrderQty", req.quoteOrderQty);
jr.read("price", req.price);
jr.read("cancelNewClientOrderId", req.cancelNewClientOrderId);
jr.read("cancelOrigClientOrderId", req.cancelOrigClientOrderId);
jr.read("cancelOrderId", req.cancelOrderId);
jr.read("newClientOrderId", req.newClientOrderId);
jr.read_optional_or("strategyId", req.strategyId);
jr.read_optional_or("strategyType", req.strategyType);
jr.read_optional_or("trailingDelta", req.trailingDelta);
jr.read("stopPrice", req.stopPrice);
jr.read("icebergQty", req.icebergQty);
jr.read("newOrderRespType", req.newOrderRespType);
jr.read("selfTradePreventionMode", req.selfTradePreventionMode);
jr.read("cancelRestrictions", req.cancelRestrictions);
jr.read("orderRateLimitExceededMode", req.orderRateLimitExceededMode);
jr.read("recvWindow", req.recvWindow);
j.at("timestamp").get_to(req.timestamp);
}
void to_query_params(std::string &qps, const ModifyOrderRequest &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("&cancelReplaceMode=");
qps.append(req.cancelReplaceMode.name());
if (req.timeInForce != TimeInForce::NONE) {
qps.append("&timeInForce=");
qps.append(req.timeInForce.name());
}
if (!std::isnan(req.quantity)) {
qps.append("&quantity=");
quantity_to_string(req.quantity, qps);
}
if (!std::isnan(req.price)) {
qps.append("&price=");
price_to_string(req.price, qps);
}
if (!req.cancelNewClientOrderId.empty()) {
qps.append("&cancelNewClientOrderId=");
qps.append(req.cancelNewClientOrderId);
}
if (!req.cancelOrigClientOrderId.empty()) {
qps.append("&cancelOrigClientOrderId=");
qps.append(req.cancelOrigClientOrderId);
}
if (req.cancelOrderId != 0) {
qps.append("&cancelOrderId=");
qps.append(std::to_string(req.cancelOrderId));
}
if (!req.newClientOrderId.empty()) {
qps.append("&newClientOrderId=");
qps.append(req.newClientOrderId);
}
if (req.strategyId) {
qps.append("&strategyId=");
qps.append(std::to_string(*req.strategyId));
}
if (req.strategyType) {
qps.append("&strategyType=");
qps.append(std::to_string(*req.strategyType));
}
if (!std::isnan(req.stopPrice)) {
qps.append("&stopPrice=");
price_to_string(req.stopPrice, qps);
}
if (req.trailingDelta) {
qps.append("&trailingDelta=");
qps.append(std::to_string(*req.trailingDelta));
}
if (!std::isnan(req.icebergQty)) {
qps.append("&icebergQty=");
quantity_to_string(req.icebergQty, qps);
}
if (req.newOrderRespType != NewOrderResponseType::NONE) {
qps.append("&newOrderRespType=");
qps.append(req.newOrderRespType.name());
}
if (req.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
qps.append("&selfTradePreventionMode=");
qps.append(req.selfTradePreventionMode.name());
}
if (req.cancelRestrictions != OrderCancelRestriction::NONE) {
qps.append("&cancelRestrictions=");
qps.append(req.cancelRestrictions.name());
}
if (req.orderRateLimitExceededMode != OrderRateLimitExceededMode::NONE) {
qps.append("&orderRateLimitExceededMode=");
qps.append(req.orderRateLimitExceededMode.name());
}
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::spots
@@ -0,0 +1,69 @@
#pragma once
#include "binance_execution/enums/cancel_replace_mode.h"
#include "binance_execution/enums/new_order_response_type.h"
#include "binance_execution/enums/order_cancel_restriction.h"
#include "binance_execution/enums/order_rate_limit_exceeded_mode.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 <optional>
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-an-existing-order-and-send-a-new-order-trade
*/
struct ModifyOrderRequest {
ModifyOrderRequest()
: side(Side::NONE), type(OrderType::NONE),
cancelReplaceMode(CancelReplaceMode::NONE),
timeInForce(TimeInForce::NONE),
quantity(std::numeric_limits<double>::quiet_NaN()),
quoteOrderQty(std::numeric_limits<double>::quiet_NaN()),
price(std::numeric_limits<double>::quiet_NaN()), cancelOrderId(0),
stopPrice(std::numeric_limits<double>::quiet_NaN()),
icebergQty(std::numeric_limits<double>::quiet_NaN()),
newOrderRespType(NewOrderResponseType::NONE),
selfTradePreventionMode(SelfTradePreventionMode::NONE),
cancelRestrictions(OrderCancelRestriction::NONE),
orderRateLimitExceededMode(OrderRateLimitExceededMode::NONE),
recvWindow(0), timestamp(0) {}
std::string symbol; // mandatory
Side side; // mandatory
OrderType type; // mandatory
CancelReplaceMode cancelReplaceMode; // mandatory
TimeInForce timeInForce; // optional
double quantity; // optional
double quoteOrderQty; // optional
double price; // optional
std::string cancelNewClientOrderId; // optional
std::string cancelOrigClientOrderId; // *optional
std::int64_t cancelOrderId; // *optional
std::string newClientOrderId; // optional
std::optional<std::int64_t> strategyId; // optional
std::optional<std::int32_t> strategyType; // optional
std::optional<std::int64_t> trailingDelta; // optional
double stopPrice; // optional
double icebergQty; // optional
NewOrderResponseType newOrderRespType; // optional
SelfTradePreventionMode selfTradePreventionMode; // optional
OrderCancelRestriction cancelRestrictions; // optional
OrderRateLimitExceededMode orderRateLimitExceededMode; // optional
std::int64_t recvWindow; // optional
std::int64_t timestamp; // mandatory
// * Either cancelOrderId or cancelOrigClientOrderId must be specified
};
void to_json(nlohmann::json &j, const ModifyOrderRequest &req);
void from_json(const nlohmann::json &j, ModifyOrderRequest &req);
void to_query_params(std::string &query_params, const ModifyOrderRequest &req);
} // namespace binance_execution::spots
@@ -0,0 +1,41 @@
#pragma once
#include "binance_execution/enums/op_success.h"
#include "binance_execution/spots/messages/order_record.h"
#include <nlohmann/json.hpp>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-an-existing-order-and-send-a-new-order-trade
*/
struct ModifyOrderResponse {
OpSuccess cancelResult;
OpSuccess newOrderResult;
OrderRecord cancelResponse;
OrderRecord newOrderResponse;
};
void to_json(nlohmann::json &j, const ModifyOrderResponse &res);
void from_json(const nlohmann::json &j, ModifyOrderResponse &res);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const ModifyOrderResponse &res) {
j["cancelResult"] = res.cancelResult;
j["newOrderResult"] = res.newOrderResult;
j["cancelResponse"] = res.cancelResponse;
j["newOrderResponse"] = res.newOrderResponse;
}
inline void from_json(const nlohmann::json &j, ModifyOrderResponse &res) {
j.at("cancelResult").get_to(res.cancelResult);
j.at("newOrderResult").get_to(res.newOrderResult);
j.at("cancelResponse").get_to(res.cancelResponse);
j.at("newOrderResponse").get_to(res.newOrderResponse);
}
} // namespace binance_execution::spots
@@ -0,0 +1,61 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#current-open-orders-user_data
*/
struct OpenOrdersRequest {
std::string symbol;
std::int64_t recvWindow;
std::int64_t timestamp;
};
void to_json(nlohmann::json &j, const OpenOrdersRequest &req);
void from_json(const nlohmann::json &j, OpenOrdersRequest &req);
void to_query_params(std::string &qps, const OpenOrdersRequest &req);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const OpenOrdersRequest &req) {
j["timestamp"] = req.timestamp;
if (!req.symbol.empty()) {
j["symbol"] = req.symbol;
}
if (req.recvWindow) {
j["recvWindow"] = req.recvWindow;
}
}
inline void from_json(const nlohmann::json &j, OpenOrdersRequest &req) {
j.at("timestamp").get_to(req.timestamp);
auto it = j.find("symbol");
if (it != j.end()) {
it->get_to(req.symbol);
}
it = j.find("recvWindow");
if (it != j.end()) {
it->get_to(req.recvWindow);
}
}
inline void to_query_params(std::string &qps, const OpenOrdersRequest &req) {
qps.append("timestamp=");
qps.append(std::to_string(req.timestamp));
if (!req.symbol.empty()) {
qps.append("&symbol=");
qps.append(req.symbol);
}
if (req.recvWindow) {
qps.append("&recvWindow=");
qps.append(std::to_string(req.recvWindow));
}
}
} // namespace binance_execution::spots
@@ -0,0 +1,98 @@
#include "order_record.h"
#include <limits>
namespace binance_execution::spots {
void to_json(nlohmann::json &j, const OrderRecord &res) {
j["symbol"] = res.symbol;
j["orderId"] = res.orderId;
j["orderListId"] = res.orderListId;
j["clientOrderId"] = res.clientOrderId;
j["transactTime"] = res.transactTime;
j["price"] = res.price;
j["origQty"] = res.origQty;
j["executedQty"] = res.executedQty;
j["origQuoteOrderQty"] = res.origQuoteOrderQty;
j["cummulativeQuoteQty"] = res.cummulativeQuoteQty;
j["status"] = res.status;
j["timeInForce"] = res.timeInForce;
j["type"] = res.type;
j["side"] = res.side;
if (res.workingTime != 0) {
j["workingTime"] = res.workingTime;
}
j["selfTradePreventionMode"] = res.selfTradePreventionMode;
if (!res.fills.empty()) {
j["fills"] = res.fills;
}
// Optional fields
if (!std::isnan(res.icebergQty)) {
j["icebergQty"] = res.icebergQty;
}
if (res.preventedMatchId) {
j["preventedMatchId"] = *res.preventedMatchId;
}
if (!std::isnan(res.preventedQuantity)) {
j["preventedQuantity"] = res.preventedQuantity;
}
if (!std::isnan(res.stopPrice)) {
j["stopPrice"] = res.stopPrice;
}
if (res.strategyId) {
j["strategyId"] = *res.strategyId;
}
if (res.strategyType) {
j["strategyType"] = *res.strategyType;
}
if (res.trailingDelta) {
j["trailingDelta"] = *res.trailingDelta;
}
if (res.trailingTime) {
j["trailingTime"] = *res.trailingTime;
}
if (res.usedSor) {
j["usedSor"] = *res.usedSor;
}
if (!res.workingFloor.empty()) {
j["usedSor"] = res.workingFloor;
}
}
void from_json(const nlohmann::json &j, OrderRecord &res) {
const bts::io::JsonReader jr{&j};
j.at("symbol").get_to(res.symbol);
j.at("orderId").get_to(res.orderId);
j.at("orderListId").get_to(res.orderListId);
j.at("clientOrderId").get_to(res.clientOrderId);
j.at("transactTime").get_to(res.transactTime);
jr.read("price", res.price);
jr.read("origQty", res.origQty);
jr.read("executedQty", res.executedQty);
jr.read("origQuoteOrderQty", res.origQuoteOrderQty);
jr.read("cummulativeQuoteQty", res.cummulativeQuoteQty);
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("workingTime", res.workingTime);
j.at("selfTradePreventionMode").get_to(res.selfTradePreventionMode);
jr.read_or("fills", res.fills);
// Optional fields
jr.read_or(
"icebergQty", res.icebergQty, std::numeric_limits<double>::quiet_NaN());
jr.read_optional_or("preventedMatchId", res.preventedMatchId);
jr.read_or(
"preventedQuantity", res.preventedQuantity,
std::numeric_limits<double>::quiet_NaN());
jr.read_or(
"stopPrice", res.stopPrice, std::numeric_limits<double>::quiet_NaN());
jr.read_optional_or("strategyId", res.strategyId);
jr.read_optional_or("strategyType", res.strategyType);
jr.read_optional_or("trailingDelta", res.trailingDelta);
jr.read_optional_or("trailingTime", res.trailingTime);
jr.read_optional_or("usedSor", res.usedSor);
jr.read_or("workingFloor", res.workingFloor);
}
} // namespace binance_execution::spots
@@ -0,0 +1,88 @@
#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::spots {
/**
* @a https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade
*/
struct OrderRecord {
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::int64_t orderListId = 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();
double origQuoteOrderQty = 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;
std::int64_t workingTime = 0;
SelfTradePreventionMode selfTradePreventionMode =
SelfTradePreventionMode::NONE;
std::vector<Fill> fills;
// Optional fields
double icebergQty = std::numeric_limits<double>::quiet_NaN();
std::optional<std::int64_t> preventedMatchId;
double preventedQuantity = std::numeric_limits<double>::quiet_NaN();
double stopPrice = std::numeric_limits<double>::quiet_NaN();
std::optional<std::int64_t> strategyId;
std::optional<std::int32_t> strategyType;
std::optional<std::int64_t> trailingDelta;
std::optional<std::int64_t> trailingTime;
std::optional<bool> usedSor;
std::string workingFloor;
};
void to_json(nlohmann::json &j, const OrderRecord::Fill &fill);
void from_json(const nlohmann::json &j, OrderRecord::Fill &fill);
void to_json(nlohmann::json &j, const OrderRecord &fill);
void from_json(const nlohmann::json &j, OrderRecord &fill);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const OrderRecord::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, OrderRecord::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::spots
@@ -0,0 +1,92 @@
#pragma once
#include "binance_execution/enums/position_side.h"
#include "lib/io/JsonReader.h"
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams/Event-Balance-and-Position-Update
*/
struct PositionInfo {
std::string symbol;
PositionSide positionSide;
double positionAmt;
double entryPrice;
double breakEvenPrice;
double markPrice;
double unRealizedProfit;
double liquidationPrice;
double isolatedMargin;
double notional;
std::string marginAsset;
double isolatedWallet;
double initialMargin;
double maintMargin;
double positionInitialMargin;
double openOrderInitialMargin;
std::uint32_t adl;
double bidNotional;
double askNotional;
std::int64_t updateTime;
};
void to_json(nlohmann::json &j, const PositionInfo &p);
void from_json(const nlohmann::json &j, PositionInfo &p);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const PositionInfo &p) {
j["symbol"] = p.symbol;
j["positionSide"] = p.positionSide;
j["positionAmt"] = p.positionAmt;
j["entryPrice"] = p.entryPrice;
j["breakEvenPrice"] = p.breakEvenPrice;
j["markPrice"] = p.markPrice;
j["unRealizedProfit"] = p.unRealizedProfit;
j["liquidationPrice"] = p.liquidationPrice;
j["isolatedMargin"] = p.isolatedMargin;
j["notional"] = p.notional;
j["marginAsset"] = p.marginAsset;
j["isolatedWallet"] = p.isolatedWallet;
j["initialMargin"] = p.initialMargin;
j["maintMargin"] = p.maintMargin;
j["positionInitialMargin"] = p.positionInitialMargin;
j["openOrderInitialMargin"] = p.openOrderInitialMargin;
j["adl"] = p.adl;
j["bidNotional"] = p.bidNotional;
j["askNotional"] = p.askNotional;
j["updateTime"] = p.updateTime;
}
inline void from_json(const nlohmann::json &j, PositionInfo &p) {
j.at("symbol").get_to(p.symbol);
j.at("positionSide").get_to(p.positionSide);
bts::io::JsonReader jr{&j};
jr.read("positionAmt", p.positionAmt);
jr.read("entryPrice", p.entryPrice);
jr.read("breakEvenPrice", p.breakEvenPrice);
jr.read("markPrice", p.markPrice);
jr.read("unRealizedProfit", p.unRealizedProfit);
jr.read("liquidationPrice", p.liquidationPrice);
jr.read("isolatedMargin", p.isolatedMargin);
jr.read("notional", p.notional);
j.at("marginAsset").get_to(p.marginAsset);
jr.read("isolatedWallet", p.isolatedWallet);
jr.read("initialMargin", p.initialMargin);
jr.read("maintMargin", p.maintMargin);
jr.read("positionInitialMargin", p.positionInitialMargin);
jr.read("openOrderInitialMargin", p.openOrderInitialMargin);
j.at("adl").get_to(p.adl);
jr.read("bidNotional", p.bidNotional);
jr.read("askNotional", p.askNotional);
j.at("updateTime").get_to(p.updateTime);
}
} // namespace binance_execution::spots
@@ -0,0 +1,62 @@
#pragma once
#include <cstdint>
#include <nlohmann/json.hpp>
#include <string>
namespace binance_execution::spots {
/**
* @a https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams/Event-Balance-and-Position-Update
*/
struct PositionInfoRequest {
std::string symbol;
std::int64_t recvWindow;
std::int64_t timestamp;
};
void to_json(nlohmann::json &j, const PositionInfoRequest &req);
void from_json(const nlohmann::json &j, PositionInfoRequest &req);
void to_query_params(std::string &qps, const PositionInfoRequest &req);
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline void to_json(nlohmann::json &j, const PositionInfoRequest &req) {
j["timestamp"] = req.timestamp;
if (!req.symbol.empty()) {
j["symbol"] = req.symbol;
}
if (req.recvWindow) {
j["recvWindow"] = req.recvWindow;
}
}
inline void from_json(const nlohmann::json &j, PositionInfoRequest &req) {
j.at("timestamp").get_to(req.timestamp);
auto it = j.find("symbol");
if (it != j.end()) {
it->get_to(req.symbol);
}
it = j.find("recvWindow");
if (it != j.end()) {
it->get_to(req.recvWindow);
}
}
inline void to_query_params(std::string &qps, const PositionInfoRequest &req) {
qps.append("timestamp=");
qps.append(std::to_string(req.timestamp));
if (!req.symbol.empty()) {
qps.append("&symbol=");
qps.append(req.symbol);
}
if (req.recvWindow) {
qps.append("&recvWindow=");
qps.append(std::to_string(req.recvWindow));
}
}
} // namespace binance_execution::spots
@@ -0,0 +1,85 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/checked_arithmetic.h"
#include "lib/utils/string_utils.h"
#include <optional>
#include <unordered_map>
namespace binance_execution::spots {
struct OrderKeyMap {
enum KeyType {
CURRENT,
ORIGINAL,
CANCEL_REPLACE_CANCEL,
CANCEL_REPLACE_REPLACE,
CANCEL,
};
// In our system, an order's id is consistent throughout the order's life (it
// doesn't change when the order is modified). At Binance, modifications are
// implemented as cancel/replace, so a modified order is technically a new
// order with a new id. This class generates new order ids for modifications,
// and maps them back to their original ids.
std::unordered_map<bts::orderman::OrderKey, std::uint32_t> order_versions;
std::optional<bts::orderman::OrderKey> get_bts_key(
const std::string &exch_order_key) {
std::size_t n = exch_order_key.find_last_of('_');
bts::orderman::OrderKey k;
if (n == std::string::npos) {
n = exch_order_key.size();
}
if (bts::system::string_utils::parse_uint(
exch_order_key.begin(), exch_order_key.begin() + n, k.value())) {
return k;
}
return {};
}
std::string get_exch_key(bts::orderman::OrderKey key) {
const auto key_l = bts::system::math::checked_ultol(key.value());
std::string rv = std::to_string(key_l);
uint32_t version = order_versions.try_emplace(key, 0).first->second;
if (version > 0) {
rv.append("_");
rv.append(std::to_string(version));
}
return rv;
}
// std::string get_exch_key(
// bts::orderman::OrderKey key, KeyType type = KeyType::CURRENT) {
// const auto key_l = bts::system::math::checked_ultol(key.value());
// std::string rv = std::to_string(key_l);
// if (type == ORIGINAL) {
// return rv;
// }
// uint32_t version = order_versions.try_emplace(key, 0).first->second;
// if (version > 0) {
// rv.append("_");
// rv.append(std::to_string(version));
// }
// switch (type) {
// case CANCEL_REPLACE_CANCEL:
// rv.append("crc");
// break;
// case CANCEL_REPLACE_REPLACE:
// rv.append("crr");
// break;
// case CANCEL:
// rv.append("c");
// break;
// default:
// break;
// }
// return rv;
// }
void increment_order_version(bts::orderman::OrderKey key) {
order_versions.try_emplace(key, 0).first->second++;
}
};
} // namespace binance_execution::spots
Loaded 100 of 159 files, more files were not shown because too many files have changed in this diff. Show more