Compare commits
79
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0282ad6df2 | ||
|
|
e3c1ea70f2 | ||
|
|
93b93969f5 | ||
|
|
0a10111846 | ||
|
|
eee00cc407 | ||
|
|
0eb58eaba8 | ||
|
|
12f6733d1e | ||
|
|
93a0984ed1 | ||
|
|
b7e9c393a0 | ||
|
|
aa602b3fd6 | ||
|
|
b4b8cabdae | ||
|
|
cfe06869b3 | ||
|
|
b177ed1cc0 | ||
|
|
ff17a64933 | ||
|
|
81a1fe11f6 | ||
|
|
ad5bf88fcf | ||
|
|
e15f155ac9 | ||
|
|
6d6822cd65 | ||
|
|
ace0c70516 | ||
|
|
e9e5059bc2 | ||
|
|
cde72f51a0 | ||
|
|
e1fd49ae32 | ||
|
|
274f3f98e4 | ||
|
|
a4b7d84318 | ||
|
|
be17205094 | ||
|
|
6ef9bee8e4 | ||
|
|
2c8b3563f1 | ||
|
|
71f7e64ccb | ||
|
|
c31b44ecfb | ||
|
|
19efb77e07 | ||
|
|
76de640421 | ||
|
|
fb030742d3 | ||
|
|
73608f1b45 | ||
|
|
58aebe7002 | ||
|
|
299cb0b29e | ||
|
|
06663f711e | ||
|
|
51479f4885 | ||
|
|
5b520ba241 | ||
|
|
f0651a5341 | ||
|
|
897e0990a2 | ||
|
|
30e1ae5c3c | ||
|
|
efe83f81ec | ||
|
|
5fb21ada97 | ||
|
|
58edd07f6b | ||
|
|
9d81354c3a | ||
|
|
0f37be89a4 | ||
|
|
f6586fe636 | ||
|
|
7827ccc0e7 | ||
|
|
af6a2acc0a | ||
|
|
84abf59a55 | ||
|
|
2fc1d30bf1 | ||
|
|
4dcd5d63a1 | ||
|
|
63e5697b20 | ||
|
|
8e5cb100a7 | ||
|
|
938bee1817 | ||
|
|
d48af15480 | ||
|
|
e3b2e98cd9 | ||
|
|
ee53fb854c | ||
|
|
d36e2d61f6 | ||
|
|
bfff3ea5fe | ||
|
|
059c03d289 | ||
|
|
8c561f7886 | ||
|
|
288567d1eb | ||
|
|
ecfc5405fe | ||
|
|
8db34a2a11 | ||
|
|
91094c4d84 | ||
|
|
dae8c85d73 | ||
|
|
abdd98082f | ||
|
|
2f6aa785ab | ||
|
|
7acf1f7043 | ||
|
|
1c3c7a078a | ||
|
|
93ace52bfa | ||
|
|
b5d97c0b1b | ||
|
|
284aa53ad4 | ||
|
|
8a9952f52d | ||
|
|
748aafaab3 | ||
|
|
3ba30c70de | ||
|
|
e72f465999 | ||
|
|
f3d93f7749 |
No files matched your search
-1
Submodule docker/linux deleted from 56c61231f3.
Submodule docker/windows deleted from de7d40c09d.
+1
-1
Submodule externals updated: 8069cc893f...959b63f2f6.
@@ -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}" />
|
||||
|
||||
@@ -15,4 +15,7 @@ message DatabentoMarketDataConfig {
|
||||
string dataset = 70;
|
||||
uint32 filter_securities_upto_n_months= 80;
|
||||
bool load_reference_data = 90;
|
||||
uint32 debug_sec = 100;
|
||||
bool only_securities = 110;
|
||||
string bbo_start_time = 120;
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/api/services/native_order_type.h"
|
||||
#include "lib/api/services/order_status.h"
|
||||
#include "lib/api/services/time_in_force.h"
|
||||
#include "lib/api/services/trigger_price_type.h"
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
@@ -10,22 +14,70 @@
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
PB_ENUM_FROM_JSON(OrderStatus)
|
||||
PB_ENUM_TO_JSON(OrderStatus)
|
||||
PB_ENUM_FROM_JSON(Fill_FillKind)
|
||||
PB_ENUM_TO_JSON(Fill_FillKind)
|
||||
|
||||
PB_ENUM_FROM_JSON(TriggerPriceType)
|
||||
PB_ENUM_TO_JSON(TriggerPriceType)
|
||||
PB_ENUM_FROM_JSON(Fill_FillStatus)
|
||||
PB_ENUM_TO_JSON(Fill_FillStatus)
|
||||
|
||||
void to_json(nlohmann::json &j, const Fill &f);
|
||||
void from_json(const nlohmann::json &j, Fill &f);
|
||||
|
||||
PB_ENUM_FROM_JSON(OrderSubType)
|
||||
PB_ENUM_TO_JSON(OrderSubType)
|
||||
|
||||
PB_ENUM_FROM_JSON(SelfMatchPreventionInstruction)
|
||||
PB_ENUM_TO_JSON(SelfMatchPreventionInstruction)
|
||||
|
||||
PB_ENUM_FROM_JSON(CustomerOrFirm)
|
||||
PB_ENUM_TO_JSON(CustomerOrFirm)
|
||||
|
||||
void to_json(nlohmann::json &j, const RoutingRecord &r);
|
||||
void from_json(const nlohmann::json &j, RoutingRecord &r);
|
||||
|
||||
PB_ENUM_FROM_JSON(NativeOrderStatus)
|
||||
PB_ENUM_TO_JSON(NativeOrderStatus)
|
||||
|
||||
PB_ENUM_FROM_JSON(NativeOrderState_SubError)
|
||||
PB_ENUM_TO_JSON(NativeOrderState_SubError)
|
||||
|
||||
void to_json(nlohmann::json &j, const NativeOrderState &n);
|
||||
void from_json(const nlohmann::json &j, NativeOrderState &n);
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::OrderStatus,
|
||||
s,
|
||||
bts::api::orderman::Fill_FillKind,
|
||||
k,
|
||||
"{}",
|
||||
bts::api::orderman::OrderStatus_Name(s));
|
||||
bts::api::orderman::Fill_FillKind_Name(k));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::TriggerPriceType,
|
||||
bts::api::orderman::Fill_FillStatus,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::Fill_FillStatus(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::OrderSubType,
|
||||
t,
|
||||
"{}",
|
||||
bts::api::orderman::TriggerPriceType_Name(t));
|
||||
bts::api::orderman::OrderSubType_Name(t));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::SelfMatchPreventionInstruction,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::SelfMatchPreventionInstruction_Name(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::NativeOrderStatus,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::NativeOrderStatus_Name(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::NativeOrderState_SubError,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::NativeOrderState_SubError_Name(s));
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/utils/LockBox.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/type_name.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace bts::collections {
|
||||
|
||||
template <class KeyT, class ValueT>
|
||||
class ConcurrentFactory {
|
||||
utils::LockBox<std::unordered_map<KeyT, std::shared_ptr<ValueT>>> store;
|
||||
|
||||
public:
|
||||
std::shared_ptr<ValueT> add(
|
||||
const KeyT &key,
|
||||
std::function<void(std::shared_ptr<ValueT> &)> callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key, std::make_shared<ValueT>());
|
||||
bts_assert_msgf(
|
||||
is_new, "{}::add. Duplicate key '{}'",
|
||||
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
|
||||
callback(it->second);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t erase(const KeyT &key) {
|
||||
return store.lock([&](auto &map) {
|
||||
map.erase(key);
|
||||
return map.size();
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
return store.lock([&](auto &map) { return map.size(); });
|
||||
}
|
||||
|
||||
template <
|
||||
typename CallbackT = std::function<void(std::shared_ptr<ValueT> &)>>
|
||||
std::shared_ptr<const ValueT> get(const KeyT &key, CallbackT callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key, std::make_shared<ValueT>());
|
||||
bts_assert_msgf(
|
||||
!is_new, "{}::get. Key not found '{}'",
|
||||
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
|
||||
callback(it->second);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
|
||||
template <
|
||||
typename CallbackT =
|
||||
std::function<void(std::shared_ptr<ValueT> &, bool)>>
|
||||
std::shared_ptr<const ValueT> get_or_add(
|
||||
const KeyT &key, CallbackT callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key);
|
||||
if (is_new) {
|
||||
it->second = std::make_shared<ValueT>();
|
||||
}
|
||||
callback(it->second, is_new);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
|
||||
template <
|
||||
typename CallbackT =
|
||||
std::function<void(std::shared_ptr<ValueT> &, bool)>>
|
||||
std::shared_ptr<const ValueT> replace(const KeyT &key, CallbackT callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key);
|
||||
bts_assert_msgf(
|
||||
!is_new, "{}::replace. Key not found '{}'",
|
||||
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
|
||||
it->second = std::make_shared<ValueT>();
|
||||
callback(it->second, is_new);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
|
||||
template <
|
||||
typename CallbackT =
|
||||
std::function<void(std::shared_ptr<ValueT> &, bool)>>
|
||||
std::shared_ptr<const ValueT> replace_or_add(
|
||||
const KeyT &key, CallbackT callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key);
|
||||
it->second = std::make_shared<ValueT>();
|
||||
callback(it->second, is_new);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bts::collections
|
||||
@@ -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: "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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); \
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,10 @@ void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
|
||||
// Prepare req
|
||||
//to debug curl requests
|
||||
// curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
|
||||
// Disable signals to avoid strange stack corruption related to set/longjmp
|
||||
// https://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers.data());
|
||||
if (response != nullptr) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "lib/core/TimeSpan.h"
|
||||
#include "lib/core/TimeStamp.h"
|
||||
#include "lib/io/websocket/websocket_client_config.h"
|
||||
#include "lib/io/websocket/websocket_client_logger.h"
|
||||
#include "lib/io/websocket/websocket_client_state.h"
|
||||
#include "lib/io/websocket/websocket_close_status.h"
|
||||
#include "lib/io/websocket/websocket_error_code.h"
|
||||
@@ -11,7 +12,10 @@
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/formatters/exception_formatter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <fmt/format.h>
|
||||
#include <mutex>
|
||||
@@ -19,6 +23,7 @@
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <websocketpp/close.hpp>
|
||||
#include <websocketpp/common/connection_hdl.hpp>
|
||||
@@ -29,11 +34,51 @@
|
||||
#include <websocketpp/config/core_client.hpp>
|
||||
#include <websocketpp/roles/client_endpoint.hpp>
|
||||
|
||||
namespace websocketpp::config {
|
||||
|
||||
/// Client config with asio transport and TLS enabled
|
||||
struct bts_asio_tls_client : public core_client {
|
||||
typedef bts_asio_tls_client type;
|
||||
typedef core_client base;
|
||||
|
||||
typedef base::concurrency_type concurrency_type;
|
||||
|
||||
typedef base::request_type request_type;
|
||||
typedef base::response_type response_type;
|
||||
|
||||
typedef base::message_type message_type;
|
||||
typedef base::con_msg_manager_type con_msg_manager_type;
|
||||
typedef base::endpoint_msg_manager_type endpoint_msg_manager_type;
|
||||
|
||||
typedef websocketpp::log::WebsocketClientLogger<
|
||||
websocketpp::log::ElevelAdapter>
|
||||
elog_type;
|
||||
typedef websocketpp::log::WebsocketClientLogger<
|
||||
websocketpp::log::AlevelAdapter>
|
||||
alog_type;
|
||||
|
||||
typedef base::rng_type rng_type;
|
||||
|
||||
struct transport_config : public base::transport_config {
|
||||
typedef type::concurrency_type concurrency_type;
|
||||
typedef type::alog_type alog_type;
|
||||
typedef type::elog_type elog_type;
|
||||
typedef type::request_type request_type;
|
||||
typedef type::response_type response_type;
|
||||
typedef transport::asio::tls_socket::endpoint socket_type;
|
||||
};
|
||||
|
||||
typedef transport::asio::endpoint<transport_config> transport_type;
|
||||
};
|
||||
|
||||
} // namespace websocketpp::config
|
||||
|
||||
namespace bts::websocket {
|
||||
|
||||
struct WebsocketClientSnapshot {
|
||||
int client_id;
|
||||
WebsocketClientState state;
|
||||
int n_reconnects;
|
||||
int version;
|
||||
};
|
||||
|
||||
@@ -42,9 +87,10 @@ struct WebsocketClientSnapshot {
|
||||
FMT_IMPL(
|
||||
bts::websocket::WebsocketClientSnapshot,
|
||||
t,
|
||||
"id: {}; state: {}; version: {}",
|
||||
"id: {}; state: {}; n_reconnects: {}; version: {}",
|
||||
t.client_id,
|
||||
t.state,
|
||||
t.n_reconnects,
|
||||
t.version);
|
||||
|
||||
namespace bts::websocket {
|
||||
@@ -62,10 +108,11 @@ using std::size_t;
|
||||
using std::string;
|
||||
using std::thread;
|
||||
using std::uint32_t;
|
||||
using websocketpp::config::asio_tls_client;
|
||||
using std::chrono::nanoseconds;
|
||||
using std::this_thread::sleep_for;
|
||||
|
||||
using WebsocketppClient =
|
||||
websocketpp::client<websocketpp::config::asio_tls_client>;
|
||||
websocketpp::client<websocketpp::config::bts_asio_tls_client>;
|
||||
using ConnectionHdl = websocketpp::connection_hdl;
|
||||
using ConnectionPtr = WebsocketppClient::connection_ptr;
|
||||
using MessageTypePtr = websocketpp::config::core_client::message_type::ptr;
|
||||
@@ -86,9 +133,16 @@ using State = WebsocketClientState;
|
||||
#define ERROR(...) LOG(Http, Error, __VA_ARGS__)
|
||||
#define ERROR_FN(...) LOG(Http, Error, "WebsocketClient::" __VA_ARGS__)
|
||||
|
||||
static bts::core::TimeSpan reconnect_interval(std::uint32_t n_attempts) {
|
||||
std::int64_t secs = 5 * n_attempts;
|
||||
secs = std::clamp(secs, 5L, 300L);
|
||||
return bts::core::TimeSpan::from_seconds(secs);
|
||||
}
|
||||
|
||||
struct WebsocketClient::PImpl {
|
||||
WebsocketClientConfig config;
|
||||
WebsocketppClient client{};
|
||||
boost::asio::io_context *io_context = nullptr;
|
||||
WebsocketppClient::connection_ptr con;
|
||||
optional<OpenHandler> open_handler;
|
||||
optional<MessageHandler> message_handler;
|
||||
@@ -98,8 +152,48 @@ struct WebsocketClient::PImpl {
|
||||
optional<thread> io_thread;
|
||||
int client_id{client_id_counter++};
|
||||
mutex instance_mx;
|
||||
State state;
|
||||
State state = State::IDLE;
|
||||
int version = 0;
|
||||
int n_reconnects = 0;
|
||||
|
||||
PImpl(WebsocketClientConfig config, boost::asio::io_context *io_context)
|
||||
: config(std::move(config)), io_context(io_context) {
|
||||
client.get_alog().set_name(this->config.name);
|
||||
client.get_elog().set_name(this->config.name);
|
||||
client.set_access_channels(websocketpp::log::alevel::all);
|
||||
// client.clear_access_channels(websocketpp::log::alevel::frame_payload);
|
||||
client.set_error_channels(websocketpp::log::elevel::all);
|
||||
if (io_context == nullptr) {
|
||||
client.init_asio();
|
||||
} else {
|
||||
client.init_asio(io_context);
|
||||
}
|
||||
client.set_message_handler(
|
||||
[this](auto hdl, auto msg) { on_message(hdl, msg); });
|
||||
client.set_open_handler([this](auto hdl) { on_open(hdl); });
|
||||
client.set_ping_handler([](auto /*hdl*/, auto /*msg*/) {
|
||||
LOG(Http, Info, "Received ping");
|
||||
return true;
|
||||
});
|
||||
client.set_pong_timeout(0);
|
||||
client.set_pong_handler([](auto /*hdl*/, auto /*msg*/) {
|
||||
LOG(Http, Info, "Received pong");
|
||||
return true;
|
||||
});
|
||||
client.set_tls_init_handler([&](auto hdl) { return on_tls_init(hdl); });
|
||||
client.set_close_handler([=, this](auto hdl) {
|
||||
on_close(hdl);
|
||||
if (config.reconnect_on_close) {
|
||||
sleep_for(nanoseconds{reconnect_interval(n_reconnects).nanos()});
|
||||
connect();
|
||||
}
|
||||
});
|
||||
client.set_fail_handler([this](auto hdl) {
|
||||
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
|
||||
ERROR_FN("fail_handler. {}; err: {}", snapshot(), ec.message());
|
||||
on_fail(hdl);
|
||||
});
|
||||
}
|
||||
|
||||
~PImpl() {
|
||||
if (io_thread) {
|
||||
@@ -120,7 +214,7 @@ struct WebsocketClient::PImpl {
|
||||
}
|
||||
|
||||
constexpr WebsocketClientSnapshot snapshot() const {
|
||||
return {client_id, state, version};
|
||||
return {client_id, state, n_reconnects, version};
|
||||
}
|
||||
|
||||
void close(websocketpp::close::status::value code, std::string reason) {
|
||||
@@ -164,8 +258,7 @@ struct WebsocketClient::PImpl {
|
||||
/**
|
||||
* Connects to the websocket server
|
||||
*/
|
||||
void connect(
|
||||
const map<string, string> &headers, bool run_in_background = true) {
|
||||
void connect() {
|
||||
lock_guard<mutex> lock{instance_mx};
|
||||
INFO_FN("connect. {}", snapshot());
|
||||
switch (state.value) {
|
||||
@@ -177,9 +270,6 @@ struct WebsocketClient::PImpl {
|
||||
}
|
||||
set_state(State::OPENING);
|
||||
try {
|
||||
init_logging();
|
||||
init_handlers();
|
||||
// Get connection
|
||||
websocketpp::lib::error_code ec;
|
||||
con = client.get_connection(config.url, ec);
|
||||
if (ec) {
|
||||
@@ -187,20 +277,11 @@ struct WebsocketClient::PImpl {
|
||||
return;
|
||||
}
|
||||
// Add headers
|
||||
for (const auto &[k, v] : headers) {
|
||||
for (const auto &[k, v] : config.headers) {
|
||||
con->append_header(k, v);
|
||||
}
|
||||
// Connect
|
||||
client.connect(con);
|
||||
if (run_in_background) {
|
||||
bts_assert_msgf(
|
||||
!io_thread, "Websocket io_thread already instantiated. {}",
|
||||
snapshot());
|
||||
io_thread = std::thread{[this]() { run(); }};
|
||||
if (config.connect_timeout > core::TimeSpan::zero()) {
|
||||
wait_for_connect(config.connect_timeout);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::string str = ex.what();
|
||||
ERROR_FN(
|
||||
@@ -212,22 +293,11 @@ struct WebsocketClient::PImpl {
|
||||
}
|
||||
}
|
||||
|
||||
void wait_for_connect(const TimeSpan timeout) {
|
||||
INFO_FN("wait_for_connect. {}; timeout: {}", snapshot(), timeout);
|
||||
const auto start = TimeStamp::now();
|
||||
while (state == State::OPENING) {
|
||||
const auto elapsed = TimeStamp::now() - start;
|
||||
const auto rem = timeout - elapsed;
|
||||
if (rem < TimeSpan::zero()) {
|
||||
throw std::runtime_error{fmt::format(
|
||||
"Timed out waiting for connection after {} secs. {}",
|
||||
elapsed.total_seconds(), snapshot())};
|
||||
}
|
||||
WARN_FN(
|
||||
"wait_for_connect. Still waiting. {}; timeout: {}; rem: {}",
|
||||
snapshot(), timeout, rem);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
void reconnect() {
|
||||
WARN_FN("reconnect. {}", snapshot());
|
||||
sleep_for(nanoseconds{reconnect_interval(n_reconnects).nanos()});
|
||||
++n_reconnects;
|
||||
connect();
|
||||
}
|
||||
|
||||
void run() {
|
||||
@@ -258,10 +328,33 @@ struct WebsocketClient::PImpl {
|
||||
INFO("Websocket 'run' thread end. {}", snapshot());
|
||||
}
|
||||
|
||||
void run_one() {
|
||||
try {
|
||||
client.run_one();
|
||||
} catch (const websocketpp::exception &ex) {
|
||||
ERROR_FN(
|
||||
"io_thread (run_one). Websocket io threw websocketpp::exception. "
|
||||
"{}; err: {}",
|
||||
snapshot(), ex);
|
||||
} catch (const std::exception &ex) {
|
||||
ERROR_FN(
|
||||
"io_thread (run_one). Websocket io threw std::exception. {}; err: "
|
||||
"{}",
|
||||
snapshot(), ex);
|
||||
} catch (...) {
|
||||
bts_assert_msgf(
|
||||
false,
|
||||
"io_thread (run_one). Websocket io threw unknown exception. Could "
|
||||
"this have been thrown by a callback delegate? {}",
|
||||
snapshot());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static std::atomic<int> client_id_counter;
|
||||
|
||||
auto on_tls_init(ConnectionHdl /*hdl*/) {
|
||||
std::shared_ptr<boost::asio::ssl::context> on_tls_init(
|
||||
ConnectionHdl /*hdl*/) {
|
||||
INFO_FN("on_tls_init. state: {}; version: {}", state, version);
|
||||
auto result =
|
||||
websocketpp::lib::make_shared<SslContext>(SslContext::sslv23_client);
|
||||
@@ -269,38 +362,6 @@ struct WebsocketClient::PImpl {
|
||||
return result;
|
||||
}
|
||||
|
||||
void init_handlers() {
|
||||
INFO_FN("init_handlers. state: {}; version: {}", state, version);
|
||||
bts_assert(!io_thread);
|
||||
client.init_asio();
|
||||
client.set_message_handler(
|
||||
[this](auto hdl, auto msg) { on_message(hdl, msg); });
|
||||
client.set_open_handler([this](auto hdl) { on_open(hdl); });
|
||||
client.set_ping_handler([](auto /*hdl*/, auto /*msg*/) {
|
||||
LOG(Http, Info, "Received ping");
|
||||
return true;
|
||||
});
|
||||
client.set_pong_timeout(0);
|
||||
client.set_pong_handler([](auto /*hdl*/, auto /*msg*/) {
|
||||
LOG(Http, Info, "Received pong");
|
||||
return true;
|
||||
});
|
||||
client.set_tls_init_handler(
|
||||
[this](auto hdl) { return on_tls_init(hdl); });
|
||||
client.set_close_handler([this](auto hdl) { on_close(hdl); });
|
||||
client.set_fail_handler([this](auto hdl) {
|
||||
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
|
||||
ERROR_FN("fail_handler. {}; err: {}", snapshot(), ec.message());
|
||||
});
|
||||
}
|
||||
|
||||
void init_logging() {
|
||||
INFO_FN("init_logging. {}", snapshot());
|
||||
client.set_access_channels(websocketpp::log::alevel::all);
|
||||
// client.clear_access_channels(websocketpp::log::alevel::frame_payload);
|
||||
client.set_error_channels(websocketpp::log::elevel::all);
|
||||
}
|
||||
|
||||
void on_open(ConnectionHdl hdl) {
|
||||
lock_guard<mutex> lock{instance_mx};
|
||||
INFO_FN("on_open. {}", snapshot());
|
||||
@@ -358,11 +419,34 @@ struct WebsocketClient::PImpl {
|
||||
if (close_handler) {
|
||||
(*close_handler)(ec);
|
||||
}
|
||||
if (config.reconnect_on_close) {
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void on_fail(ConnectionHdl hdl) {
|
||||
lock_guard<mutex> lock{instance_mx};
|
||||
ERROR_FN("on_fail. {}", snapshot());
|
||||
set_state(State::IDLE);
|
||||
const auto con = client.get_con_from_hdl(hdl);
|
||||
const auto ec = con->get_ec();
|
||||
if (ec) {
|
||||
ERROR_FN(
|
||||
"on_fail. {}; local_code: {}; "
|
||||
"local_reason: {}; remote_code: {}; remote_reason: {}; err: {}",
|
||||
snapshot(), con->get_local_close_code(),
|
||||
con->get_local_close_reason(), con->get_remote_close_code(),
|
||||
con->get_remote_close_reason(), ec.message());
|
||||
}
|
||||
if (config.reconnect_on_fail) {
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebsocketClient::WebsocketClient(WebsocketClientConfig config)
|
||||
: me_(std::make_unique<PImpl>(std::move(config))) {}
|
||||
WebsocketClient::WebsocketClient(
|
||||
WebsocketClientConfig config, boost::asio::io_context *io_context)
|
||||
: me_(std::make_unique<PImpl>(std::move(config), io_context)) {}
|
||||
|
||||
WebsocketClient::~WebsocketClient() {
|
||||
me_->close(websocketpp::close::status::going_away, "");
|
||||
@@ -391,13 +475,16 @@ WebsocketClient::CloseHandler &WebsocketClient::close_handler() {
|
||||
return *me_->close_handler;
|
||||
}
|
||||
|
||||
void WebsocketClient::connect(
|
||||
const std::map<std::string, std::string> &headers, bool run_in_background) {
|
||||
me_->connect(headers, run_in_background);
|
||||
}
|
||||
void WebsocketClient::connect() { me_->connect(); }
|
||||
|
||||
void WebsocketClient::run() { me_->run(); }
|
||||
|
||||
void WebsocketClient::run_one() { me_->run_one(); }
|
||||
|
||||
boost::asio::io_context *WebsocketClient::io_context() {
|
||||
return me_->io_context;
|
||||
}
|
||||
|
||||
websocketpp::lib::error_code WebsocketClient::send_text(const string &text) {
|
||||
return me_->send_text(text);
|
||||
}
|
||||
@@ -415,4 +502,6 @@ bool WebsocketClient::is_connected() const {
|
||||
|
||||
void WebsocketClient::ping(const string &payload) { me_->con->ping(payload); }
|
||||
|
||||
const string &WebsocketClient::url() const { return me_->config.url; }
|
||||
|
||||
} // namespace bts::websocket
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "lib/io/websocket/websocket_client_config.h"
|
||||
#include "lib/utils/Delegate.h"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <websocketpp/config/core_client.hpp>
|
||||
@@ -12,28 +13,33 @@ namespace bts::websocket {
|
||||
|
||||
class WebsocketClient {
|
||||
public:
|
||||
explicit WebsocketClient(WebsocketClientConfig config);
|
||||
WebsocketClient(
|
||||
WebsocketClientConfig config,
|
||||
boost::asio::io_context *io_context = nullptr);
|
||||
~WebsocketClient();
|
||||
|
||||
using OpenHandler = bts::core::Delegate<void(const std::error_code &)>;
|
||||
using MessageHandler = bts::core::Delegate<void(const std::string &)>;
|
||||
using CloseHandler = bts::core::Delegate<void(const std::error_code &)>;
|
||||
|
||||
void connect(
|
||||
const std::map<std::string, std::string> &headers = {},
|
||||
bool run_in_background = true);
|
||||
void connect();
|
||||
void disconnect();
|
||||
bool is_connected() const;
|
||||
boost::asio::io_context *io_context();
|
||||
void run();
|
||||
void run_one();
|
||||
CloseHandler &open_handler();
|
||||
MessageHandler &message_handler();
|
||||
CloseHandler &close_handler();
|
||||
websocketpp::lib::error_code send_text(const std::string &text);
|
||||
void ping(const std::string &payload = "");
|
||||
|
||||
const std::string &url() const;
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me_;
|
||||
};
|
||||
|
||||
} // namespace bts::websocket
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
namespace bts::websocket {
|
||||
|
||||
struct WebsocketClientConfig {
|
||||
std::string url;
|
||||
core::TimeSpan connect_timeout;
|
||||
core::TimeSpan pong_timeout;
|
||||
std::string url{};
|
||||
core::TimeSpan connect_timeout = core::TimeSpan::zero();
|
||||
core::TimeSpan pong_timeout = core::TimeSpan::zero();
|
||||
bool reconnect_on_close = false;
|
||||
bool reconnect_on_fail = false;
|
||||
std::map<std::string, std::string> headers{};
|
||||
// name to use in logs
|
||||
std::string name{};
|
||||
};
|
||||
|
||||
} // namespace bts::websocket
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/logging/Common.h"
|
||||
#include "lib/logging/logging.h"
|
||||
|
||||
#include <websocketpp/concurrency/basic.hpp>
|
||||
#include <websocketpp/logger/basic.hpp>
|
||||
#include <websocketpp/logger/levels.hpp>
|
||||
|
||||
namespace websocketpp::log {
|
||||
|
||||
struct ElevelAdapter {
|
||||
typedef elevel LevelT;
|
||||
|
||||
constexpr bts::log::Level operator()(level channel) {
|
||||
switch (channel) {
|
||||
case elevel::devel:
|
||||
return bts::Diag;
|
||||
case elevel::library:
|
||||
case elevel::warn:
|
||||
return bts::Warn;
|
||||
case elevel::rerror:
|
||||
return bts::Error;
|
||||
case elevel::fatal:
|
||||
return bts::Fatal;
|
||||
default:
|
||||
return bts::Info;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct AlevelAdapter {
|
||||
typedef alevel LevelT;
|
||||
|
||||
constexpr bts::log::Level operator()(level channel) {
|
||||
switch (channel) {
|
||||
case alevel::control:
|
||||
case alevel::frame_header:
|
||||
case alevel::frame_payload:
|
||||
case alevel::message_header:
|
||||
case alevel::message_payload:
|
||||
case alevel::debug_handshake:
|
||||
case alevel::debug_close:
|
||||
case alevel::devel:
|
||||
return bts::Diag;
|
||||
case alevel::disconnect:
|
||||
return bts::Warn;
|
||||
case alevel::fail:
|
||||
return bts::Error;
|
||||
default:
|
||||
return bts::Info;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename LevelAdapter>
|
||||
class WebsocketClientLogger
|
||||
: public basic<concurrency::basic, typename LevelAdapter::LevelT> {
|
||||
typedef basic<concurrency::basic, typename LevelAdapter::LevelT> base;
|
||||
|
||||
std::string name;
|
||||
LevelAdapter level_adapter;
|
||||
|
||||
public:
|
||||
WebsocketClientLogger(
|
||||
level c, channel_type_hint::value h = channel_type_hint::access)
|
||||
: base(c, h) {}
|
||||
|
||||
void set_name(std::string name) {
|
||||
concurrency::basic::scoped_lock_type lock{this->m_lock};
|
||||
this->name = std::move(name);
|
||||
}
|
||||
|
||||
void write(level channel, const std::string &msg) {
|
||||
concurrency::basic::scoped_lock_type lock{this->m_lock};
|
||||
if (!this->dynamic_test(channel)) {
|
||||
return;
|
||||
}
|
||||
LOG(Http, level_adapter(channel), "WebsocketClient-{} {}. {}", name,
|
||||
std::string{LevelAdapter::LevelT::channel_name(channel)}, msg);
|
||||
}
|
||||
|
||||
void write(level channel, const char *msg) {
|
||||
write(channel, std::string{msg});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace websocketpp::log
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <cstdint>
|
||||
#include <fmt/ostream.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -94,7 +95,7 @@ std::ifstream::pos_type filesize(std::string const &filename) {
|
||||
Sink file_sink(LoggingConfig const &cfg) {
|
||||
static std::ofstream stream;
|
||||
static uint64_t bytes_written = 0;
|
||||
const auto max_sz = 120 * 1024 * 1024;
|
||||
const uintmax_t max_sz = 1024 * 1024 * 1024 ;
|
||||
|
||||
auto f = formatter(cfg);
|
||||
auto path = log_path();
|
||||
|
||||
@@ -290,7 +290,7 @@ struct GeneralMarketDataListener::PImpl {
|
||||
// if this is set to true, then the security key passed on in the
|
||||
// on_securities function is used. If this is not set to true, then a new key
|
||||
// is generated
|
||||
const bool use_given_sec_key = false;
|
||||
const bool use_given_sec_key = true;
|
||||
const std::string cc_name_prefix = "cc_security";
|
||||
size_t n_bk_updates = 0;
|
||||
bts::persistence::IDGenerator &id_gen =
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
#define LOG_FN(level, fn, ...) \
|
||||
LOG(OrderMan, level, "FillStore::" fn ". " __VA_ARGS__)
|
||||
#define INFO_FN(fn, ...) LOG_FN(Info, fn, __VA_ARGS__)
|
||||
#define WARN_FN(fn, ...) LOG_FN(Warn, fn, __VA_ARGS__)
|
||||
#define ERROR_FN(fn, ...) LOG_FN(Error, fn, __VA_ARGS__)
|
||||
|
||||
auto const fills_ns = "fills";
|
||||
|
||||
struct FillStore::UnresolvedFills {
|
||||
@@ -160,7 +166,7 @@ void FillStore::on_execution_report(
|
||||
ev.data->to_string());
|
||||
auto &er = *ev.data;
|
||||
auto fk = fmt::format(
|
||||
"{}:{}", er.order_key.value(), to_str(er.exchange_fill_id));
|
||||
"{}:{}", er.order_key.value(), to_strn(er.exchange_fill_id));
|
||||
auto is_known = me->known_fills.count(fk);
|
||||
if (er.exec_type == api::orderman::EXEC_TYPE_PARTIAL_FILL ||
|
||||
er.exec_type == api::orderman::EXEC_TYPE_FILL) {
|
||||
@@ -176,7 +182,7 @@ void FillStore::on_execution_report(
|
||||
fill.set_fill_kind(api::orderman::Fill::FILL_KIND_STANDARD);
|
||||
fill.set_security_key(er.security->id());
|
||||
}
|
||||
fill.set_account(to_str(er.account_name));
|
||||
fill.set_account(to_strn(er.account_name));
|
||||
fill.set_last_price(er.last_price.value());
|
||||
fill.set_avg_price(er.avg_price.value());
|
||||
fill.set_side(er.side);
|
||||
@@ -207,7 +213,15 @@ void FillStore::on_execution_report(
|
||||
auto it = me->known_orders.find(er.order_key);
|
||||
if (it != me->known_orders.end()) {
|
||||
auto &order_def = it->second;
|
||||
fill.set_account(order_def.account());
|
||||
if (fill.account() != order_def.account()) {
|
||||
WARN_FN(
|
||||
"on_execution_report",
|
||||
"ExecutionReport account '{}' does not match "
|
||||
"OrderDefinition account '{}' (order_key: {}). "
|
||||
"Setting fill account to the latter",
|
||||
fill.account(), order_def.account(), er.order_key);
|
||||
fill.set_account(order_def.account());
|
||||
}
|
||||
fill.set_internal_account_id(order_def.account_id());
|
||||
} else
|
||||
me->fills_with_unresolved_accounts[er.order_key]
|
||||
@@ -215,6 +229,7 @@ void FillStore::on_execution_report(
|
||||
|
||||
auto fv = utils::pb::to_string(fill);
|
||||
me->store->insert(fills_ns, fk, fv);
|
||||
LOG(OrderMan, Info, "Publishing fill. {}", fv);
|
||||
publish(me->fill_stream, fill, *er.security);
|
||||
} else
|
||||
LOG(OrderMan, Info,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "lib/io/http/http_client.h"
|
||||
#include "lib/io/http/http_response.h"
|
||||
#include "lib/io/websocket/websocket_client.h"
|
||||
#include "lib/io/websocket/websocket_client_config.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/SecurityOracle.h"
|
||||
#include "lib/orderman/CancelOrderMsg.h"
|
||||
@@ -77,6 +78,16 @@ void OkxExecutionGateway::send_msgs(
|
||||
msg);
|
||||
}
|
||||
}
|
||||
|
||||
static websocket::WebsocketClientConfig get_ws_client_config(
|
||||
const std::string &url,
|
||||
core::TimeSpan connect_timeout,
|
||||
core::TimeSpan pong_timeout) {
|
||||
websocket::WebsocketClientConfig rv{url, connect_timeout, pong_timeout};
|
||||
rv.name = "OkxExecutionGateway";
|
||||
return rv;
|
||||
}
|
||||
|
||||
struct OkxExecutionGateway::PImpl {
|
||||
const std::string api_key = config::get<config::OkxExeCfg>().api_key();
|
||||
const std::string secret_key = config::get<config::OkxExeCfg>().secret_key();
|
||||
@@ -89,9 +100,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;
|
||||
@@ -101,8 +110,8 @@ struct OkxExecutionGateway::PImpl {
|
||||
md::SecurityOracle o;
|
||||
std::atomic<OkxExecutionState> state = OkxExecutionState::OFF;
|
||||
PImpl(md::SecurityOracle o)
|
||||
: ws_client(
|
||||
websocket::WebsocketClient({url, connect_timeout, pong_timeout})),
|
||||
: ws_client(websocket::WebsocketClient(
|
||||
get_ws_client_config(url, connect_timeout, pong_timeout))),
|
||||
cb(dummy), o(o) {
|
||||
ws_client.message_handler().bind<&PImpl::on_message>(*this);
|
||||
ws_client.close_handler().bind<&PImpl::on_close>(*this);
|
||||
@@ -115,7 +124,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 +232,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);
|
||||
@@ -292,8 +300,33 @@ struct OkxExecutionGateway::PImpl {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start Attempting login (Attempt: {})",
|
||||
attempt + 1);
|
||||
ws_client.connect({}, true);
|
||||
sleep(2);
|
||||
if (ws_run_thread) {
|
||||
// Join this thread to block until the websocket disconnects (then reconnect)
|
||||
if (ws_run_thread->joinable()) {
|
||||
ws_run_thread->join();
|
||||
}
|
||||
ws_run_thread.reset();
|
||||
}
|
||||
// Connect
|
||||
ws_client.connect();
|
||||
ws_run_thread = threading::make_thread(
|
||||
"okx_execution_ws", [&]() { ws_client.run(); });
|
||||
// Block until the websocket connects (or times out)
|
||||
{
|
||||
const auto start_time = bts::core::TimeStamp::now();
|
||||
for (auto elapsed = core::TimeSpan::zero();
|
||||
!ws_client.is_connected() &&
|
||||
elapsed < core::TimeSpan::from_seconds(20);
|
||||
elapsed = core::TimeStamp::now() - start_time) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
if (!ws_client.is_connected()) {
|
||||
LOG(OrderMan, Warn,
|
||||
"OkxExecutionAdapter::start. Timed out waiting for "
|
||||
"websocket to connect");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (ws_ping_thread.has_value()) {
|
||||
if (ws_ping_thread->joinable()) {
|
||||
ws_ping_thread
|
||||
@@ -454,13 +487,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 +537,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 +566,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 +752,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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
@@ -68,6 +68,8 @@ OrderOpResult SpreaderOrder::on_activate(
|
||||
return OrderOpResult{api::orderman::ORDER_ERROR_OP_NOT_SUPPORTED};
|
||||
quantity =
|
||||
_bookkeeping_data().fqty_helper().in_transform_spreader_qty(quantity);
|
||||
LOG(OrderMan, Info, "SpreaderOrder::on_activate price: {} quantity: {}",
|
||||
price, quantity);
|
||||
SpreaderOrderServices spreader_order_services(&services);
|
||||
_on_activate(price, quantity, spreader_order_services);
|
||||
// the reason why this is immediatly reported (vs in the timer
|
||||
@@ -82,8 +84,9 @@ OrderOpResult SpreaderOrder::on_update(Price price, Quantity quantity) {
|
||||
return OrderOpResult{api::orderman::ORDER_ERROR_OP_NOT_SUPPORTED};
|
||||
quantity =
|
||||
_bookkeeping_data().fqty_helper().in_transform_spreader_qty(quantity);
|
||||
LOG(OrderMan, Info, "SpreaderOrder.on_update key:{} price:{} quantity:{}",
|
||||
key().value(), price.value(), quantity.value());
|
||||
|
||||
LOG(OrderMan, Info, "SpreaderOrder::on_update price: {} quantity: {}", price,
|
||||
quantity);
|
||||
_state_worker->on_update(price, quantity);
|
||||
_report_state();
|
||||
return OrderOpResult{};
|
||||
@@ -329,8 +332,11 @@ void SpreaderOrder::_on_activate(
|
||||
Price price, Quantity quantity, SpreaderOrderServices &order_services) {
|
||||
bts_assert(_inited());
|
||||
bts_assert(!_activated());
|
||||
quantity =
|
||||
_bookkeeping_data().fqty_helper().in_transform_spreader_qty(quantity);
|
||||
LOG(OrderMan, Info, "SpreaderOrder::_on_activate price: {} quantity: {}",
|
||||
price, quantity);
|
||||
// quantity has already been fractional_normalized
|
||||
// quantity =
|
||||
// _bookkeeping_data().fqty_helper().in_transform_spreader_qty(quantity);
|
||||
LOG(OrderMan, Info, "SpreaderOrder.on_activate key:{} price:{} quantity:{}",
|
||||
key().value(), price.value(), quantity.value());
|
||||
_order_services = new SpreaderOrderServices(order_services);
|
||||
|
||||
@@ -285,6 +285,9 @@ class SpreaderOrder : public Order, public ISpreaderManager {
|
||||
}
|
||||
// just sets the price and qty in the bookkeeping data
|
||||
void _set_price_quantity(Price input_price, Quantity quantity) {
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderOrder::_set_price_quantity input_price: {} quantity: {}",
|
||||
input_price, quantity);
|
||||
Price effective_price = _compute_effective_price(input_price);
|
||||
_bookkeeping_data().update_order(input_price, effective_price, quantity);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <memory>
|
||||
@@ -76,7 +77,11 @@ class SpreaderBookkeepingData {
|
||||
}
|
||||
Price input_price() const { return _input_price; }
|
||||
Price effective_price() const { return _effective_price; }
|
||||
Quantity quantity() const { return _quantity; }
|
||||
Quantity quantity() const {
|
||||
LOG(OrderMan, Info, "SpreaderBookkeepingData::quantity _quantity: {}",
|
||||
_quantity);
|
||||
return _quantity;
|
||||
}
|
||||
NLegsHedgeRecompData &hedge_recomp_data() { return _hedge_recomp_data; }
|
||||
// gets leg data from leg's sec key
|
||||
SpreaderLegData &get_leg_data(SecurityKey security_key) {
|
||||
@@ -146,6 +151,10 @@ class SpreaderBookkeepingData {
|
||||
}
|
||||
void update_order(
|
||||
Price input_price, Price effective_price, Quantity quantity) {
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderBookkeepingData::update_order input_price: {} "
|
||||
"effective_price: {} quantity: {}",
|
||||
input_price, effective_price, quantity.full_value());
|
||||
_input_price = input_price;
|
||||
_effective_price = effective_price;
|
||||
_quantity = quantity;
|
||||
@@ -157,9 +166,6 @@ class SpreaderBookkeepingData {
|
||||
}
|
||||
size_t n_quoting_legs() const { return _n_quoting_legs; }
|
||||
SpreaderFractionalQtyHelper &fqty_helper() { return _fqty_helper; }
|
||||
// SpreaderFractionalQtyHelper const *spreader_qty_helper() {
|
||||
// return &_fqty_helper;
|
||||
// }
|
||||
};
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "lib/orderman/events/BookUpdateEvent.h"
|
||||
#include "lib/orderman/events/OrderStateUpdateEvent.h"
|
||||
//#include "lib/orderman/orders/spreader_orders/SpreaderOrder.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "lib/os/os.h"
|
||||
@@ -63,7 +64,9 @@ struct SpreaderFractionalQtyHelper ::PImpl {
|
||||
Book op_book = {};
|
||||
OrderStateUpdateEvent op_ord = {};
|
||||
const bool passthrough = false;
|
||||
//const double in_qty_mult;
|
||||
// we can keep the mult per leg, based on their tick info, but its easier
|
||||
// (and correct) if we do it
|
||||
const double in_qty_mult = 100000;
|
||||
const bool is_notional;
|
||||
const QtyTickInfo def_qty_tick_info = QtyTickInfo(10000.0, 1.0);
|
||||
std::map<bts::SecurityKey, QtyTickInfo> qty_info_map = {};
|
||||
@@ -92,25 +95,43 @@ struct SpreaderFractionalQtyHelper ::PImpl {
|
||||
}
|
||||
};
|
||||
|
||||
double SpreaderFractionalQtyHelper::_get_mult(size_t) const {
|
||||
return me->in_qty_mult;
|
||||
}
|
||||
|
||||
Quantity SpreaderFractionalQtyHelper::in_transform_spreader_qty(
|
||||
Quantity qty) const {
|
||||
// nothing needs to be done with spreader qty
|
||||
if (me->passthrough)
|
||||
return qty;
|
||||
double qm = _get_mult(0);
|
||||
double rv = qty.full_value() * qm;
|
||||
auto rv_qty = Quantity::create_new(rv);
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderFractionalQtyHelper::in_transform_spreader_qty {} => {}", qty,
|
||||
rv_qty);
|
||||
return rv_qty;
|
||||
}
|
||||
|
||||
std::vector<LegPropertyData> SpreaderFractionalQtyHelper::get_transformed_legs(
|
||||
const std::vector<LegPropertyData> &legs) const {
|
||||
std::vector<LegPropertyData> rv = {};
|
||||
for (const auto &l : legs) {
|
||||
bts::api::ContractLeg leg_adj = l.leg_contract();
|
||||
const auto &q_tinfo = get_qty_tick_info(
|
||||
me->qty_info_map, bts::SecurityKey(l.leg_security_key()),
|
||||
me->def_qty_tick_info);
|
||||
double qm = q_tinfo.tick_mult;
|
||||
leg_adj.set_quantity(l.size_multiplier() * qm);
|
||||
double qty_tick_size = q_tinfo.tick_sz;
|
||||
LegPropertyData lpd_adj(
|
||||
l.leg_index(), leg_adj, l.leg_params(), l.trading_side(),
|
||||
bts::api::SyntheticContract::PriceCalc::
|
||||
SyntheticContract_PriceCalc_PRICECALC_STANDARD,
|
||||
qty_tick_size);
|
||||
rv.push_back(lpd_adj);
|
||||
}
|
||||
return rv;
|
||||
// std::vector<LegPropertyData> rv = {};
|
||||
// for (const auto &l : legs) {
|
||||
// bts::api::ContractLeg leg_adj = l.leg_contract();
|
||||
// const auto &q_tinfo = get_qty_tick_info(
|
||||
// me->qty_info_map, bts::SecurityKey(l.leg_security_key()),
|
||||
// double qm = q_tinfo.tick_mult;
|
||||
// leg_adj.set_quantity(l.size_multiplier() * qm);
|
||||
// double qty_tick_size = q_tinfo.tick_sz;
|
||||
// LegPropertyData lpd_adj(
|
||||
// l.leg_index(), leg_adj, l.leg_params(), l.trading_side(),
|
||||
// bts::api::SyntheticContract::PriceCalc::
|
||||
// SyntheticContract_PriceCalc_PRICECALC_STANDARD,
|
||||
// qty_tick_size);
|
||||
// rv.push_back(lpd_adj);
|
||||
// }
|
||||
// return rv;
|
||||
return legs;
|
||||
}
|
||||
|
||||
SpreaderFractionalQtyHelper::SpreaderFractionalQtyHelper(
|
||||
@@ -140,9 +161,7 @@ const Book &SpreaderFractionalQtyHelper::in_transform_book(
|
||||
} else {
|
||||
me->op_book = bk;
|
||||
}
|
||||
const auto &q_tinfo = get_qty_tick_info(
|
||||
me->qty_info_map, bk.security_key(), me->def_qty_tick_info);
|
||||
double qm = q_tinfo.tick_mult;
|
||||
double qm = _get_mult(0);
|
||||
for (auto &ps : me->op_book.bid()) {
|
||||
ps = bts::md::PriceSize(ps.int_price(), ps.size() * qm);
|
||||
}
|
||||
@@ -183,9 +202,7 @@ SpreaderFractionalQtyHelper::in_transform_order_event(
|
||||
me->op_ord.filled_at_attach_time_quantity = os::nan();
|
||||
}
|
||||
// factor mult
|
||||
const auto &q_tinfo =
|
||||
get_qty_tick_info(me->qty_info_map, sec_key, me->def_qty_tick_info);
|
||||
double qmult = q_tinfo.tick_mult;
|
||||
double qmult = _get_mult(sec_key.value());
|
||||
me->op_ord.quantity *= qmult;
|
||||
me->op_ord.filled_quantity *= qmult;
|
||||
me->op_ord.filled_at_attach_time_quantity *= qmult;
|
||||
@@ -193,14 +210,6 @@ SpreaderFractionalQtyHelper::in_transform_order_event(
|
||||
return me->op_ord;
|
||||
}
|
||||
|
||||
Quantity SpreaderFractionalQtyHelper::in_transform_spreader_qty(
|
||||
Quantity qty) const {
|
||||
// nothing needs to be done with spreader qty
|
||||
if (me->passthrough)
|
||||
return qty;
|
||||
return qty;
|
||||
}
|
||||
|
||||
Quantity SpreaderFractionalQtyHelper::out_transform_leg_order_qty(
|
||||
SecurityKey skey, Price price, Quantity qty) const {
|
||||
auto qty_init = qty;
|
||||
@@ -214,13 +223,10 @@ Quantity SpreaderFractionalQtyHelper::out_transform_leg_order_qty(
|
||||
"notional -> physical {} -> {}",
|
||||
qty_init, qty);
|
||||
}
|
||||
const auto &q_tinfo =
|
||||
get_qty_tick_info(me->qty_info_map, skey, me->def_qty_tick_info);
|
||||
double tick_sz = q_tinfo.tick_sz;
|
||||
double qm = q_tinfo.tick_mult;
|
||||
double qm = _get_mult(skey.value());
|
||||
double qty_out = qty.full_value() / qm;
|
||||
// rouning it to tick size
|
||||
qty_out = std::floor(qty_out / tick_sz) * tick_sz;
|
||||
//qty_out = std::floor(qty_out / tick_sz) * tick_sz;
|
||||
return Quantity::create_new(qty_out);
|
||||
};
|
||||
auto rv_qty = tr();
|
||||
@@ -241,9 +247,15 @@ void SpreaderFractionalQtyHelper::out_transform_order_state(
|
||||
s_txt, s.filled_quantity(), s.avg_price());
|
||||
// todo:
|
||||
// this needs to divide the filled qty of the spreader
|
||||
// if (me->passthrough)
|
||||
// return ord_st;
|
||||
// return ord_st;
|
||||
if (me->passthrough)
|
||||
return;
|
||||
double qm = _get_mult(0);
|
||||
set_quantity(
|
||||
s, StateQuantityType::OrderQuantity,
|
||||
Quantity::create_new(static_cast<double>(s.quantity()) / qm));
|
||||
set_quantity(
|
||||
s, StateQuantityType::FilledQuantity,
|
||||
static_cast<double>(s.filled_quantity()) / qm);
|
||||
}
|
||||
|
||||
} // namespace bts::orderman
|
||||
@@ -15,31 +15,52 @@ class OrderState;
|
||||
namespace bts::orderman {
|
||||
class Book;
|
||||
class OrderStateUpdateEvent;
|
||||
// forinstruments with fractional qtys this sits on the outside layer and does conversions,
|
||||
//both for market data and orders
|
||||
// this resdies inside SpeaderBookkeepingData
|
||||
// for instruments with fractional qtys this sits on the outside layer and does conversions,
|
||||
// both for market data and orders
|
||||
// it resides inside SpeaderBookkeepingData
|
||||
// these are the trasnfo
|
||||
// spreader_qty in *mult
|
||||
// books coming in *mult
|
||||
// order out /mult
|
||||
// order cb in * mult
|
||||
// report out /mult
|
||||
class SpreaderFractionalQtyHelper {
|
||||
public:
|
||||
SpreaderFractionalQtyHelper(
|
||||
OrderKey spreader_ord_key,
|
||||
const api::orderman::SpreaderOrderDefinition &spreader_order_def,
|
||||
const std::vector<LegPropertyData> &legs);
|
||||
// the leg qtys need to be translated
|
||||
double _get_mult(size_t leg_idx) const;
|
||||
////////////////////////////////////
|
||||
// 1. qty (in)
|
||||
////////////////////////////////////
|
||||
Quantity in_transform_spreader_qty(Quantity qty) const;
|
||||
////////////////////////////////////
|
||||
// 2. leg sizes (in)
|
||||
////////////////////////////////////
|
||||
std::vector<LegPropertyData> get_transformed_legs(
|
||||
const std::vector<LegPropertyData> &legs) const;
|
||||
// in_ and out_ signify market data / orders coming in or out.
|
||||
////////////////////////////////////
|
||||
// 3. books (in)
|
||||
////////////////////////////////////
|
||||
const Book &in_transform_book(const Book &book) const;
|
||||
// this is called when the order feedback fomr the outside in to the spreader
|
||||
////////////////////////////////////
|
||||
// 4. orders event (in)
|
||||
////////////////////////////////////
|
||||
const OrderStateUpdateEvent &in_transform_order_event(
|
||||
SecurityKey sec_key, const OrderStateUpdateEvent &) const;
|
||||
Quantity in_transform_spreader_qty(Quantity qty) const;
|
||||
// TODO add price
|
||||
////////////////////////////////////
|
||||
// 5. orders (out)
|
||||
////////////////////////////////////
|
||||
Quantity out_transform_leg_order_qty(
|
||||
SecurityKey key, Price price, Quantity qty) const;
|
||||
// the state reported ot he rest of the system also has to be converted
|
||||
////////////////////////////////////
|
||||
// 6. order report (out)
|
||||
////////////////////////////////////
|
||||
void out_transform_order_state(bts::api::orderman::OrderState &) const;
|
||||
// struct QtyTickInfo;
|
||||
// const QtyTickInfo &_get_tickinfo(SecurityKey skey);
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "SpreaderStartStateWorker.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/orderman/OrderBuilder.h"
|
||||
#include "lib/orderman/orders/PreActivationOrderServices.h"
|
||||
#include "lib/orderman/orders/spreader_orders/SpreaderOrder.h"
|
||||
@@ -47,6 +48,8 @@ void SpreaderStartStateWorker::on_init_new(
|
||||
std::string msg = "Could not resolve securities";
|
||||
_spreader->_transform_to_END(&msg);
|
||||
}
|
||||
LOG(OrderMan, Info, "SpreaderStartStateWorker::on_init_new qty: {}",
|
||||
_spreader->_bookkeeping_data().quantity().full_value());
|
||||
}
|
||||
void SpreaderStartStateWorker::on_init_recovered(
|
||||
const bts::api::orderman::OrderRecord &order_record,
|
||||
|
||||
@@ -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
|
||||
@@ -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(
|
||||
|
||||
@@ -73,7 +73,7 @@ std::optional<uint64_t> IDGenerator::get_id(
|
||||
return std::optional<uint64_t>(rowid);
|
||||
} else {
|
||||
if (add_if_new) {
|
||||
auto all = me->kv_store->retrieve_all();
|
||||
// auto all = me->kv_store->retrieve_all();
|
||||
auto new_id = me->kv_store->insert(ns, key, "");
|
||||
LOG(Gen, Diag, "IDGenerator::get_id ns: {} key: id: {}", ns, key,
|
||||
new_id);
|
||||
|
||||
+42
-5
@@ -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
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
namespace bts::utils {
|
||||
|
||||
/**
|
||||
* Simple wrapper for shared_ptr that initializes the underlying data in the
|
||||
* default constructor.
|
||||
*/
|
||||
template <typename T>
|
||||
class auto_init_shared_ptr {
|
||||
public:
|
||||
auto_init_shared_ptr() : value_(std::make_shared<T>()) {}
|
||||
|
||||
std::shared_ptr<T> &value() { return value_; }
|
||||
const std::shared_ptr<T> &value() const { return value_; }
|
||||
|
||||
T &operator*() { return *value_; }
|
||||
const T &operator*() const { return *value_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<T> value_;
|
||||
};
|
||||
|
||||
} // namespace bts::utils
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <system_error>
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<std::error_code> {
|
||||
constexpr auto parse(fmt::format_parse_context &ctx) -> decltype(ctx.end()) {
|
||||
return ctx.end();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::error_code &e, FormatContext &ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return fmt::format_to(ctx.out(), "{}", e.message());
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
@@ -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; \
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,467 @@
|
||||
#include "cross_margin_gateway.h"
|
||||
#include "binance_execution/cross_margin/messages/cancel_order_response.h"
|
||||
#include "binance_execution/cross_margin/messages/create_order_request.h"
|
||||
#include "binance_execution/cross_margin/rest_api_client.h"
|
||||
#include "binance_execution/cross_margin/side_effect_type.h"
|
||||
#include "binance_execution/cross_margin/trade_data_stream/trade_data_subscription.h"
|
||||
#include "binance_execution/execution_report_store.h"
|
||||
#include "binance_execution/gateway_config.h"
|
||||
#include "binance_execution/listen_key_requestor.h"
|
||||
#include "binance_execution/order_key_map.h"
|
||||
#include "binance_execution/rest_api_result.h"
|
||||
#include "binance_execution/type_validators.h"
|
||||
#include "lib/api/service_helpers/AdminServiceUtils.h"
|
||||
#include "lib/api/services/AdminService.pb.h"
|
||||
#include "lib/api/services/OrderService.h"
|
||||
#include "lib/config/protos/jetfix.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/io/json_formatter.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/SecurityOracle.h"
|
||||
#include "lib/mercury/events/Update.h"
|
||||
#include "lib/orderman/CancelOrderMsg.h"
|
||||
#include "lib/orderman/CancelReplaceOrderMsg.h"
|
||||
#include "lib/orderman/CreateOrderMsg.h"
|
||||
#include "lib/orderman/RFQMsg.h"
|
||||
#include "lib/orderman/events/NewAdminDataEvent.h"
|
||||
#include "lib/orderman/events/NewSecuritiesEvent.h"
|
||||
#include "lib/orderman/orderman_utils/ExchangePosition.h"
|
||||
#include "lib/orderman/orderman_utils/ExecutionReport.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/utils/overloaded.h"
|
||||
|
||||
#include <boost/lockfree/spsc_queue.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
#define ASSERT_FN(test, fn, ...) \
|
||||
bts_assert_msgf(test, "BinanceCrossMargin::" fn ". " __VA_ARGS__)
|
||||
#define LOG_FN(level, fn, ...) \
|
||||
LOG(OrderMan, level, "BinanceCrossMargin::" fn ". " __VA_ARGS__)
|
||||
#define DIAG_FN(fn, ...) LOG_FN(bts::Diag, fn, __VA_ARGS__)
|
||||
#define INFO_FN(fn, ...) LOG_FN(bts::Info, fn, __VA_ARGS__)
|
||||
#define WARN_FN(fn, ...) LOG_FN(bts::Warn, fn, __VA_ARGS__)
|
||||
#define ERROR_FN(fn, ...) LOG_FN(bts::Error, fn, __VA_ARGS__)
|
||||
|
||||
#define VENUE bts::api::VENUE_BINANCE
|
||||
|
||||
using boost::lockfree::spsc_queue;
|
||||
using bts::core::Delegate;
|
||||
using bts::io::json_formatter;
|
||||
using bts::orderman::CancelOrderMsg;
|
||||
using bts::orderman::CancelReplaceOrderMsg;
|
||||
using bts::orderman::CreateOrderMsg;
|
||||
using bts::orderman::ExecutionReport;
|
||||
using bts::orderman::NewAdminDataEvent;
|
||||
using bts::orderman::NewSecuritiesEvent;
|
||||
using bts::orderman::OrderMsg;
|
||||
using bts::orderman::RFQMsg;
|
||||
using std::int64_t;
|
||||
using std::optional;
|
||||
using std::shared_ptr;
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
using std::uint32_t;
|
||||
using std::vector;
|
||||
|
||||
struct ExecutionGatewayCallbacks {
|
||||
bts::orderman::OnExeReport exe_report;
|
||||
bts::orderman::OnGwStatusChange gw_status_change;
|
||||
bts::orderman::OnCancelReject cancel_reject;
|
||||
bts::orderman::OnExchangePositionState exchange_position_state;
|
||||
bts::orderman::OnExchangeOpenOrdersState exchange_open_orders;
|
||||
};
|
||||
|
||||
struct CrossMarginGateway::PImpl {
|
||||
explicit PImpl(
|
||||
bts::md::SecurityOracle sec_oracle,
|
||||
GatewayConfig config,
|
||||
bts::config::JetFIX_Connection jetfix_connection)
|
||||
: sec_oracle(std::move(sec_oracle)), config(std::move(config)),
|
||||
jetfix_connection(std::make_shared<bts::config::JetFIX_Connection>(
|
||||
std::move(jetfix_connection))),
|
||||
rest_api_client(
|
||||
this->config.rest_endpoint,
|
||||
this->config.api_key,
|
||||
this->config.secret_key),
|
||||
listen_key_requestor(
|
||||
[this](const auto &req) {
|
||||
return rest_api_client.get_listen_key(req);
|
||||
},
|
||||
[this](const auto &req) {
|
||||
return rest_api_client.refresh_listen_key(req);
|
||||
}),
|
||||
key_map(std::make_shared<OrderKeyMap>()),
|
||||
orders(
|
||||
[this](const std::string &exch_key) {
|
||||
return *key_map->get_bts_key(exch_key);
|
||||
},
|
||||
this->config.account,
|
||||
this->jetfix_connection) {
|
||||
bts_assert(this->jetfix_connection->venue() == VENUE);
|
||||
bts_assert(!this->jetfix_connection->name().empty());
|
||||
pos.exchange = VENUE;
|
||||
}
|
||||
|
||||
bts::md::SecurityOracle sec_oracle;
|
||||
GatewayConfig config;
|
||||
shared_ptr<bts::config::JetFIX_Connection> jetfix_connection;
|
||||
|
||||
RestApiClient rest_api_client;
|
||||
optional<trade_data_stream::TradeDataSubscription> trade_data_sub;
|
||||
Delegate<void(trade_data_stream::AccountUpdate)> account_update_callback;
|
||||
Delegate<void(trade_data_stream::OrderUpdate)> order_update_callback;
|
||||
ListenKeyRequestor listen_key_requestor;
|
||||
|
||||
spsc_queue<trade_data_stream::AccountUpdate> incoming_account_updates{1024};
|
||||
spsc_queue<trade_data_stream::OrderUpdate> incoming_order_updates{1024};
|
||||
spsc_queue<shared_ptr<const ExecutionReport>> internal_order_rejects{256};
|
||||
vector<trade_data_stream::AccountUpdate> account_updates_vec;
|
||||
|
||||
ExecutionGatewayCallbacks callbacks;
|
||||
bts::orderman::ExchangePosition pos;
|
||||
bts::api::admin::BTSAccount account;
|
||||
shared_ptr<OrderKeyMap> key_map;
|
||||
ExecutionReportStore orders;
|
||||
|
||||
void start() {
|
||||
INFO_FN("start");
|
||||
listen_key_requestor.callback().bind<&PImpl::on_listen_key_>(*this);
|
||||
listen_key_requestor.start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
INFO_FN("stop");
|
||||
trade_data_sub.reset();
|
||||
}
|
||||
|
||||
void on_account_update(trade_data_stream::AccountUpdate u) {
|
||||
INFO_FN("on_account_update", "\n{:2}", json_formatter(u));
|
||||
incoming_account_updates.push(std::move(u));
|
||||
}
|
||||
|
||||
void on_order_update(trade_data_stream::OrderUpdate u) {
|
||||
INFO_FN("on_order_update", "\n{:2}", json_formatter(u));
|
||||
incoming_order_updates.push(std::move(u));
|
||||
}
|
||||
|
||||
void on_listen_key_(const string &listen_key) {
|
||||
INFO_FN("on_listen_key", "value: {}", listen_key);
|
||||
trade_data_sub.reset();
|
||||
if (listen_key.empty()) {
|
||||
LOG(OrderMan, bts::Error,
|
||||
"Unable to subscribe to user data because 'listen_key' "
|
||||
"is empty.");
|
||||
return;
|
||||
}
|
||||
auto &uds = trade_data_sub.emplace(
|
||||
config.ws_stream_endpoint + "/ws/" + listen_key);
|
||||
uds.account_update_callback().bind<&PImpl::on_account_update>(*this);
|
||||
uds.order_update_callback().bind<&PImpl::on_order_update>(*this);
|
||||
uds.start();
|
||||
}
|
||||
|
||||
void create_order(const CreateOrderMsg &m, bool is_replace = false) {
|
||||
CreateOrderRequest req;
|
||||
req.autoRepayAtCancel = true;
|
||||
req.sideEffectType = SideEffectType::AUTO_BORROW_REPAY;
|
||||
std::string err;
|
||||
if (!req.init(m, err)) {
|
||||
ERROR_FN(
|
||||
"create_order",
|
||||
"{} {} {} @ {} (id={}, type={}, tif={}). Rejected "
|
||||
"internally (request not sent). {}",
|
||||
m.side, m.quantity, req.symbol, m.price, m.key, m.order_type,
|
||||
m.time_in_force, err);
|
||||
internal_order_rejects.push(orders.reject(m, err));
|
||||
return;
|
||||
}
|
||||
if (is_replace) {
|
||||
key_map->increment_order_version(m.key, req.newClientOrderId);
|
||||
}
|
||||
INFO_FN("create_order", "Sending request", json_formatter(req));
|
||||
const auto res = rest_api_client.create_order(req);
|
||||
if (!res.ok()) {
|
||||
ERROR_FN(
|
||||
"create_order", "id={}. {}", req.newClientOrderId,
|
||||
json_formatter(res));
|
||||
internal_order_rejects.push(
|
||||
orders.reject({}, json_formatter(*res.error).to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
void update_order(const CancelReplaceOrderMsg &m) {
|
||||
CancelOrderMsg cxl;
|
||||
cxl.exchange_order_id = m.exchange_order_id;
|
||||
cxl.manual_order_indicator = m.create_fields.manual_order_indicator;
|
||||
cxl.key = m.create_fields.key;
|
||||
cxl.routing_record = m.create_fields.routing_record;
|
||||
cxl.security = m.create_fields.security;
|
||||
cxl.side = m.create_fields.side;
|
||||
INFO_FN("update_order", "Cancelling order");
|
||||
const auto *res = cancel_order(cxl);
|
||||
if (res != nullptr && res->ok()) {
|
||||
INFO_FN("update_order", "Sending replacement order");
|
||||
create_order(m.create_fields, true);
|
||||
}
|
||||
}
|
||||
|
||||
const RestApiResult<CancelOrderResponse> *cancel_order(
|
||||
const CancelOrderMsg &m) {
|
||||
CancelOrderRequest req;
|
||||
std::string err;
|
||||
if (!req.init(m, *key_map, err)) {
|
||||
ERROR_FN(
|
||||
"cancel_order", "{} {} (id={}, exch_id={}). {}", m.side,
|
||||
req.symbol, m.key, m.exchange_order_id, err);
|
||||
internal_order_rejects.push(orders.set_error(
|
||||
m.key, "Rejected internally (request not sent). " + err));
|
||||
return nullptr;
|
||||
}
|
||||
INFO_FN(
|
||||
"cancel_order", "{} {} (id={}, exch_id={})", m.side, req.symbol,
|
||||
m.key, m.exchange_order_id);
|
||||
const auto &res = rest_api_client.cancel_order(req);
|
||||
if (!res.ok()) {
|
||||
ERROR_FN(
|
||||
"cancel_order", "{} {} (id={}, exch_id={}). {}", m.side,
|
||||
req.symbol, m.key, m.exchange_order_id, json_formatter(res));
|
||||
internal_order_rejects.push(orders.set_error(
|
||||
m.key,
|
||||
fmt::format(
|
||||
"Cancel request failed. {}", json_formatter(*res.error))));
|
||||
}
|
||||
return &res;
|
||||
}
|
||||
|
||||
void dispatch_internal_rejects() {
|
||||
shared_ptr<const ExecutionReport> curr;
|
||||
while (internal_order_rejects.pop(curr)) {
|
||||
callbacks.exe_report(curr, {});
|
||||
}
|
||||
}
|
||||
|
||||
void dispatch_order_updates() {
|
||||
if (incoming_order_updates.empty()) {
|
||||
return;
|
||||
}
|
||||
trade_data_stream::OrderUpdate curr;
|
||||
auto &km = *key_map;
|
||||
while (incoming_order_updates.pop(curr)) {
|
||||
const auto bts_key = km.get_bts_key(curr.client_order_id);
|
||||
if (!bts_key) {
|
||||
ERROR_FN(
|
||||
"dispatch_order_updates",
|
||||
"Failed to generate exe report. Did not resolve bts key from "
|
||||
"exch clientOrderId '{}'",
|
||||
curr.client_order_id);
|
||||
continue;
|
||||
}
|
||||
const auto exch_key = km.get_exch_key(*bts_key);
|
||||
if (exch_key != curr.client_order_id) {
|
||||
INFO_FN(
|
||||
"dispatch_order_updates",
|
||||
"Ignoring update w/ old clientOrderId '{}' (expected '{}', "
|
||||
"exch_id={}). This is expected for CancelReplace ops.",
|
||||
curr.client_order_id, curr.order_id, exch_key);
|
||||
continue;
|
||||
}
|
||||
// orders
|
||||
shared_ptr<const ExecutionReport> er_p =
|
||||
orders.update(curr.client_order_id, [&](auto &er_p, bool) {
|
||||
auto &er = *er_p;
|
||||
std::string error;
|
||||
if (!curr.to_execution_report(er, error)) {
|
||||
WARN_FN(
|
||||
"update", "Failed to initialize exe report. {}. {}",
|
||||
error, json_formatter(curr));
|
||||
return;
|
||||
}
|
||||
er.security = sec_oracle.get_security(
|
||||
VENUE, bts::api::SECURITYTYPE_SPOT, curr.symbol);
|
||||
});
|
||||
const ExecutionReport &er = *er_p;
|
||||
string error;
|
||||
if (!validate(er, error)) {
|
||||
ERROR_FN(
|
||||
"dispatch_order_updates",
|
||||
"Generated invalid ExecutionReport (id={}, exch_id={}). {}",
|
||||
curr.client_order_id, curr.order_id, error);
|
||||
continue;
|
||||
}
|
||||
switch (curr.order_status.value) {
|
||||
case OrderStatus::FILLED:
|
||||
case OrderStatus::CANCELED:
|
||||
case OrderStatus::REJECTED: {
|
||||
const size_t n_active = orders.erase(er.order_key);
|
||||
INFO_FN(
|
||||
"dispatch_order_updates",
|
||||
"Order reached terminal state {} (id={}, exch_id={}). {} "
|
||||
"active orders.",
|
||||
curr.order_status, curr.client_order_id, curr.order_id,
|
||||
n_active);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
INFO_FN(
|
||||
"dispatch_order_updates",
|
||||
"Firing exe report callback. {} {} {} @ {} {} (filled {}/{}, "
|
||||
"id={}). {} active orders",
|
||||
er.side, er.security->binance().security_desc(), er.quantity,
|
||||
er.price, er.order_status, er.filled_quantity, er.quantity,
|
||||
er.order_key, orders.size());
|
||||
callbacks.exe_report(std::move(er_p), {});
|
||||
}
|
||||
}
|
||||
|
||||
void dispatch_account_updates() {
|
||||
trade_data_stream::AccountUpdate u;
|
||||
auto &acct_upds = account_updates_vec;
|
||||
acct_upds.clear();
|
||||
while (incoming_account_updates.pop(acct_upds.emplace_back())) {
|
||||
}
|
||||
acct_upds.pop_back();
|
||||
if (acct_upds.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto pos_ticker_comp =
|
||||
[](const bts::orderman::ExchangePosition::UnitPosition &unit_pos,
|
||||
const string &ticker) {
|
||||
return unit_pos.exchange_sec_desc < ticker;
|
||||
};
|
||||
auto &dest = pos.total_exchange_positions;
|
||||
for (const auto &acct_upd : acct_upds) {
|
||||
for (const auto &pos : acct_upd.balances) {
|
||||
bts_assert(pos.free >= 0);
|
||||
bts_assert(pos.locked >= 0);
|
||||
auto it = std::lower_bound(
|
||||
dest.begin(), dest.end(), pos.asset, pos_ticker_comp);
|
||||
if (it == dest.end() || it->exchange_sec_desc != pos.asset) {
|
||||
const auto *sec = sec_oracle.get_security(
|
||||
VENUE, bts::api::SECURITYTYPE_SPOT, pos.asset);
|
||||
if (sec == nullptr) {
|
||||
continue;
|
||||
}
|
||||
bts::orderman::ExchangePosition::UnitPosition new_pos;
|
||||
new_pos.exchange_sec_desc = pos.asset;
|
||||
new_pos.security = sec;
|
||||
new_pos.account_id = account.id();
|
||||
new_pos.account_name = account.name();
|
||||
it = dest.insert(it, std::move(new_pos));
|
||||
}
|
||||
it->position = pos.locked + pos.free;
|
||||
it->avg_filled_price.value() =
|
||||
std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
callbacks.exchange_position_state(pos);
|
||||
}
|
||||
};
|
||||
|
||||
CrossMarginGateway::CrossMarginGateway(
|
||||
bts::md::SecurityOracle sec_oracle,
|
||||
GatewayConfig session_cfg,
|
||||
bts::config::JetFIX::Connection jetfix_connection)
|
||||
: me_(std::make_unique<PImpl>(
|
||||
std::move(sec_oracle),
|
||||
std::move(session_cfg),
|
||||
std::move(jetfix_connection))) {
|
||||
bts_assert(
|
||||
me_->config.account.size() <=
|
||||
bts::orderman::ExecutionReport{}.account_name.value().size());
|
||||
}
|
||||
|
||||
CrossMarginGateway::~CrossMarginGateway() = default;
|
||||
|
||||
void CrossMarginGateway::start() { me_->start(); }
|
||||
|
||||
void CrossMarginGateway::on_securities(
|
||||
const bts::mercury::Update<NewSecuritiesEvent> & /*update*/) {
|
||||
INFO_FN(
|
||||
"on_securities", "t_id: {} t_name: {}", bts::os::get_thread_id(),
|
||||
bts::os::get_thread_name());
|
||||
}
|
||||
|
||||
void CrossMarginGateway::on_admin_data(
|
||||
const bts::mercury::Update<NewAdminDataEvent> &update) {
|
||||
INFO_FN(
|
||||
"on_admin_data", "t_id: {} t_name: {}", bts::os::get_thread_id(),
|
||||
bts::os::get_thread_name());
|
||||
if (update.events == nullptr || update.events->empty()) {
|
||||
return;
|
||||
}
|
||||
const auto *account = bts::admin::AdminServiceUtils::find_account(
|
||||
*update.events->back().admin_data, me_->config.account);
|
||||
if (account == nullptr) {
|
||||
WARN_FN(
|
||||
"on_admin_data",
|
||||
"Gateway account not found. Cannot execute orders. name: '{}'",
|
||||
me_->config.account);
|
||||
return;
|
||||
}
|
||||
if (me_->account.name() == account->name() &&
|
||||
me_->account.id() == account->id()) {
|
||||
return;
|
||||
}
|
||||
me_->account.CopyFrom(*account);
|
||||
INFO_FN("on_admin_data", "Resolved gateway account '{}'", account->name());
|
||||
}
|
||||
|
||||
void CrossMarginGateway::send_msgs(
|
||||
vector<OrderMsg> const &msg_vec, bts::trace::CorID) {
|
||||
INFO_FN("send_msgs", "count: {}", msg_vec.size());
|
||||
for (const auto &msg : msg_vec) {
|
||||
std::visit(
|
||||
bts::overloaded{
|
||||
[&](const CreateOrderMsg &m) { me_->create_order(m); },
|
||||
[&](const CancelReplaceOrderMsg &m) { me_->update_order(m); },
|
||||
[&](const CancelOrderMsg &m) { me_->cancel_order(m); },
|
||||
[&](const RFQMsg &) {
|
||||
bts_assert_msg(false, "Binance RFQ not implemented");
|
||||
},
|
||||
},
|
||||
msg);
|
||||
}
|
||||
}
|
||||
|
||||
void CrossMarginGateway::send_fix(
|
||||
const bts::jetfix::OutFIXPtr &, bts::trace::CorID) {
|
||||
bts_assert_msg(false, "Binance CrossMarginGateway does not support fix");
|
||||
}
|
||||
|
||||
void CrossMarginGateway::dispatch_incoming_fix_messages() {
|
||||
me_->dispatch_internal_rejects();
|
||||
me_->dispatch_order_updates();
|
||||
me_->dispatch_account_updates();
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
bts::orderman::OnExeReport &CrossMarginGateway::on_exe_report() {
|
||||
return me_->callbacks.exe_report;
|
||||
}
|
||||
bts::orderman::OnGwStatusChange &CrossMarginGateway::on_gw_status_change() {
|
||||
return me_->callbacks.gw_status_change;
|
||||
}
|
||||
bts::orderman::OnCancelReject &CrossMarginGateway::on_cancel_reject() {
|
||||
return me_->callbacks.cancel_reject;
|
||||
}
|
||||
bts::orderman::OnExchangePositionState &
|
||||
CrossMarginGateway::on_exchange_position_state() {
|
||||
return me_->callbacks.exchange_position_state;
|
||||
}
|
||||
bts::orderman::OnExchangeOpenOrdersState &
|
||||
CrossMarginGateway::on_exchange_open_orders() {
|
||||
return me_->callbacks.exchange_open_orders;
|
||||
}
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/gateways/IExecutionGateway.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace bts::config {
|
||||
class JetFIX_Connection;
|
||||
}
|
||||
|
||||
namespace bts::md {
|
||||
class SecurityOracle;
|
||||
}
|
||||
|
||||
namespace binance_execution {
|
||||
struct GatewayConfig;
|
||||
}
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
class CrossMarginGateway : public bts::orderman::IExecutionGateway {
|
||||
public:
|
||||
explicit CrossMarginGateway(
|
||||
bts::md::SecurityOracle sec_oracle,
|
||||
GatewayConfig session_cfg,
|
||||
bts::config::JetFIX_Connection jetfix_connection);
|
||||
~CrossMarginGateway() override;
|
||||
|
||||
void start() override;
|
||||
void dispatch_incoming_fix_messages() override;
|
||||
void on_securities(
|
||||
const bts::mercury::Update<bts::orderman::NewSecuritiesEvent> &)
|
||||
override;
|
||||
void on_admin_data(
|
||||
const bts::mercury::Update<bts::orderman::NewAdminDataEvent> &) override;
|
||||
void send_fix(const bts::jetfix::OutFIXPtr &, bts::trace::CorID) override;
|
||||
void send_msgs(
|
||||
std::vector<bts::orderman::OrderMsg> const &,
|
||||
bts::trace::CorID) override;
|
||||
|
||||
bts::orderman::OnExeReport &on_exe_report() override;
|
||||
bts::orderman::OnGwStatusChange &on_gw_status_change() override;
|
||||
bts::orderman::OnCancelReject &on_cancel_reject() override;
|
||||
bts::orderman::OnExchangePositionState &
|
||||
on_exchange_position_state() override;
|
||||
bts::orderman::OnExchangeOpenOrdersState &on_exchange_open_orders() override;
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me_;
|
||||
};
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
#include "cancel_order_request.h"
|
||||
#include "binance_execution/error_list.h"
|
||||
#include "binance_execution/order_key_map.h"
|
||||
#include "binance_execution/type_converters.h"
|
||||
#include "binance_execution/type_validators.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/orderman/CancelOrderMsg.h"
|
||||
#include "lib/utils/checked_arithmetic.h"
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
bool CancelOrderRequest::init(
|
||||
const bts::orderman::CancelOrderMsg &m,
|
||||
OrderKeyMap &key_map,
|
||||
std::string &error) {
|
||||
const std::size_t orig_error_sz = error.size();
|
||||
if (validate(m.security, bts::api::VENUE_BINANCE, error)) {
|
||||
symbol = m.security->binance().security_desc();
|
||||
}
|
||||
orderId = bts::system::math::checked_ultol(m.exchange_order_id.value());
|
||||
origClientOrderId = key_map.get_exch_key(m.key);
|
||||
// The exchange accepts either orderId, or origClientOrderId (only one
|
||||
// need be populated).
|
||||
if (orderId == 0 && origClientOrderId.empty()) {
|
||||
ErrorList::append(
|
||||
error, "Either orderId or origClientOrderId must be defined");
|
||||
} else {
|
||||
// Can't reuse the same order id (only increment order version
|
||||
// when init is successful, as this means the order will be sent).
|
||||
key_map.increment_order_version(m.key);
|
||||
newClientOrderId = key_map.get_exch_key(m.key);
|
||||
}
|
||||
timestamp = convert_time_stamp(bts::core::TimeStamp::now());
|
||||
return error.size() == orig_error_sz;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const CancelOrderRequest &req) {
|
||||
j["symbol"] = req.symbol;
|
||||
if (req.isIsolated) {
|
||||
j["isIsolated"] = true;
|
||||
}
|
||||
if (req.orderId != 0) {
|
||||
j["orderId"] = req.orderId;
|
||||
}
|
||||
if (!req.origClientOrderId.empty()) {
|
||||
j["origClientOrderId"] = req.origClientOrderId;
|
||||
}
|
||||
if (!req.newClientOrderId.empty()) {
|
||||
j["newClientOrderId"] = req.newClientOrderId;
|
||||
}
|
||||
if (req.recvWindow > 0) {
|
||||
j["recvWindow"] = req.newClientOrderId;
|
||||
}
|
||||
if (req.timestamp > 0) {
|
||||
j["timestamp"] = req.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, CancelOrderRequest &req) {
|
||||
const bts::io::JsonReader jr{&j};
|
||||
j.at("symbol").get_to(req.symbol);
|
||||
jr.read_or("isIsolated", req.isIsolated);
|
||||
jr.read_or("orderId", req.orderId);
|
||||
jr.read_or("origClientOrderId", req.origClientOrderId);
|
||||
jr.read_or("newClientOrderId", req.newClientOrderId);
|
||||
jr.read_or("recvWindow", req.recvWindow);
|
||||
j.at("timestamp").get_to(req.timestamp);
|
||||
}
|
||||
|
||||
void to_query_params(std::string &qps, const CancelOrderRequest &req) {
|
||||
qps.append("symbol=");
|
||||
qps.append(req.symbol);
|
||||
if (req.isIsolated) {
|
||||
qps.append("&isIsolated=true");
|
||||
}
|
||||
if (req.orderId != 0) {
|
||||
qps.append("&orderId=");
|
||||
qps.append(std::to_string(req.orderId));
|
||||
}
|
||||
if (!req.origClientOrderId.empty()) {
|
||||
qps.append("&origClientOrderId=");
|
||||
qps.append(req.origClientOrderId);
|
||||
}
|
||||
if (!req.newClientOrderId.empty()) {
|
||||
qps.append("&newClientOrderId=");
|
||||
qps.append(req.newClientOrderId);
|
||||
}
|
||||
if (req.recvWindow > 0) {
|
||||
qps.append("&recvWindow=");
|
||||
qps.append(std::to_string(req.recvWindow));
|
||||
}
|
||||
qps.append("×tamp=");
|
||||
qps.append(std::to_string(req.timestamp));
|
||||
}
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace bts::orderman {
|
||||
struct CancelOrderMsg;
|
||||
}
|
||||
|
||||
namespace binance_execution {
|
||||
struct OrderKeyMap;
|
||||
}
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
/**
|
||||
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order
|
||||
*/
|
||||
struct CancelOrderRequest {
|
||||
std::string symbol;
|
||||
bool isIsolated = false;
|
||||
std::int64_t orderId;
|
||||
std::string origClientOrderId;
|
||||
std::string newClientOrderId;
|
||||
std::int64_t timestamp = 0;
|
||||
std::int64_t recvWindow = 0;
|
||||
|
||||
bool init(
|
||||
const bts::orderman::CancelOrderMsg &m,
|
||||
OrderKeyMap &key_map,
|
||||
std::string &error);
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, const CancelOrderRequest &req);
|
||||
void from_json(const nlohmann::json &j, CancelOrderRequest &req);
|
||||
|
||||
void to_query_params(std::string &query_params, const CancelOrderRequest &req);
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#include "cancel_order_response.h"
|
||||
#include "binance_execution/enums/order_status.h"
|
||||
#include "binance_execution/enums/order_type.h"
|
||||
#include "binance_execution/enums/time_in_force.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
void to_json(nlohmann::json &j, const CancelOrderResponse &res) {
|
||||
j["symbol"] = res.symbol;
|
||||
if (res.isIsolated) {
|
||||
j["isIsolated"] = true;
|
||||
}
|
||||
j["orderId"] = res.orderId;
|
||||
if (!res.origClientOrderId.empty()) {
|
||||
j["origClientOrderId"] = res.origClientOrderId;
|
||||
}
|
||||
if (!res.clientOrderId.empty()) {
|
||||
j["clientOrderId"] = res.clientOrderId;
|
||||
}
|
||||
if (!std::isnan(res.price)) {
|
||||
j["price"] = res.price;
|
||||
}
|
||||
if (!std::isnan(res.origQty)) {
|
||||
j["origQty"] = res.origQty;
|
||||
}
|
||||
if (!std::isnan(res.executedQty)) {
|
||||
j["executedQty"] = res.executedQty;
|
||||
}
|
||||
if (!std::isnan(res.cummulativeQuoteQty)) {
|
||||
j["cummulativeQuoteQty"] = res.cummulativeQuoteQty;
|
||||
}
|
||||
if (res.status != OrderStatus::NONE) {
|
||||
j["status"] = res.status;
|
||||
}
|
||||
if (res.timeInForce != TimeInForce::NONE) {
|
||||
j["timeInForce"] = res.timeInForce;
|
||||
}
|
||||
if (res.type != OrderType::NONE) {
|
||||
j["type"] = res.type;
|
||||
}
|
||||
if (res.side != Side::NONE) {
|
||||
j["side"] = res.side;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, CancelOrderResponse &res) {
|
||||
j.at("symbol").get_to(res.symbol);
|
||||
auto it = j.find("isIsolated");
|
||||
if (it != j.end()) {
|
||||
it->get_to(res.isIsolated);
|
||||
}
|
||||
const bts::io::JsonReader jr{&j};
|
||||
jr.read("orderId", res.orderId);
|
||||
jr.read("origClientOrderId", res.origClientOrderId);
|
||||
jr.read("clientOrderId", res.clientOrderId);
|
||||
jr.read("price", res.price);
|
||||
jr.read("origQty", res.origQty);
|
||||
jr.read("executedQty", res.executedQty);
|
||||
jr.read("cummulativeQuoteQty", res.cummulativeQuoteQty);
|
||||
jr.read("status", res.status);
|
||||
jr.read("timeInForce", res.timeInForce);
|
||||
jr.read("type", res.type);
|
||||
jr.read("side", res.side);
|
||||
}
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "binance_execution/enums/order_status.h"
|
||||
#include "binance_execution/enums/order_type.h"
|
||||
#include "binance_execution/enums/side.h"
|
||||
#include "binance_execution/enums/time_in_force.h"
|
||||
|
||||
#include <limits>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
/**
|
||||
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order
|
||||
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order#response-example
|
||||
*/
|
||||
struct CancelOrderResponse {
|
||||
std::string symbol;
|
||||
bool isIsolated = false;
|
||||
std::int64_t orderId = 0;
|
||||
std::string origClientOrderId;
|
||||
std::string clientOrderId;
|
||||
double price = std::numeric_limits<double>::quiet_NaN();
|
||||
double origQty = std::numeric_limits<double>::quiet_NaN();
|
||||
double executedQty = std::numeric_limits<double>::quiet_NaN();
|
||||
// Note: 'cummulative' is deliberately mispelled to match the json payload.
|
||||
double cummulativeQuoteQty = std::numeric_limits<double>::quiet_NaN();
|
||||
OrderStatus status = OrderStatus::NONE;
|
||||
TimeInForce timeInForce = TimeInForce::NONE;
|
||||
OrderType type = OrderType::NONE;
|
||||
Side side = Side::NONE;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, const CancelOrderResponse &res);
|
||||
void from_json(const nlohmann::json &j, CancelOrderResponse &res);
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
#include "create_order_request.h"
|
||||
#include "binance_execution/enums/new_order_response_type.h"
|
||||
#include "binance_execution/price.h"
|
||||
#include "binance_execution/quantity.h"
|
||||
#include "binance_execution/type_converters.h"
|
||||
#include "binance_execution/type_validators.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/orderman/CreateOrderMsg.h"
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
bool CreateOrderRequest::init(
|
||||
const bts::orderman::CreateOrderMsg &m, std::string &error) {
|
||||
const std::size_t orig_error_sz = error.size();
|
||||
newClientOrderId = std::to_string(m.key.value());
|
||||
if (validate(m.security, bts::api::VENUE_BINANCE, error)) {
|
||||
symbol = m.security->binance().security_desc();
|
||||
}
|
||||
convert_side(m.side, side, error);
|
||||
price = m.price.value();
|
||||
quantity = m.quantity.full_value();
|
||||
convert_order_type(m.order_type, type, error);
|
||||
convert_time_in_force(m.time_in_force, timeInForce, error);
|
||||
if (error.size() != orig_error_sz) {
|
||||
return false;
|
||||
}
|
||||
timestamp = convert_time_stamp(bts::core::TimeStamp::now());
|
||||
return true;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const CreateOrderRequest &req) {
|
||||
j["symbol"] = req.symbol;
|
||||
j["side"] = req.side;
|
||||
j["type"] = req.type;
|
||||
j["timestamp"] = req.timestamp;
|
||||
if (req.isIsolated) {
|
||||
j["isIsolated"] = req.isIsolated;
|
||||
}
|
||||
std::string str;
|
||||
if (req.quantity > 0) {
|
||||
quantity_to_string(req.quantity, str);
|
||||
j["quantity"] = str;
|
||||
str.clear();
|
||||
}
|
||||
if (req.quoteOrderQty > 0) {
|
||||
quantity_to_string(req.quoteOrderQty, str);
|
||||
j["quoteOrderQty"] = str;
|
||||
str.clear();
|
||||
}
|
||||
if (!std::isnan(req.price)) {
|
||||
price_to_string(req.price, str);
|
||||
j["price"] = str;
|
||||
str.clear();
|
||||
}
|
||||
if (!std::isnan(req.stopPrice)) {
|
||||
price_to_string(req.stopPrice, str);
|
||||
j["stopPrice"] = str;
|
||||
str.clear();
|
||||
}
|
||||
if (!req.newClientOrderId.empty()) {
|
||||
j["newClientOrderId"] = req.newClientOrderId;
|
||||
}
|
||||
if (req.icebergQty > 0) {
|
||||
quantity_to_string(req.icebergQty, str);
|
||||
j["icebergQty"] = str;
|
||||
str.clear();
|
||||
}
|
||||
if (req.newOrderRespType != NewOrderResponseType::NONE) {
|
||||
j["newOrderRespType"] = req.newOrderRespType;
|
||||
}
|
||||
if (req.sideEffectType != SideEffectType::NO_SIDE_EFFECT) {
|
||||
j["sideEffectType"] = req.sideEffectType;
|
||||
}
|
||||
if (req.timeInForce != TimeInForce::NONE) {
|
||||
j["timeInForce"] = req.timeInForce;
|
||||
}
|
||||
if (req.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
|
||||
j["selfTradePreventionMode"] = req.selfTradePreventionMode;
|
||||
}
|
||||
if (req.autoRepayAtCancel) {
|
||||
j["autoRepayAtCancel"] = req.autoRepayAtCancel;
|
||||
}
|
||||
if (req.recvWindow > 0) {
|
||||
j["recvWindow"] = std::to_string(req.recvWindow);
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, CreateOrderRequest &req) {
|
||||
j.at("symbol").get_to(req.symbol);
|
||||
j.at("side").get_to(req.side);
|
||||
j.at("type").get_to(req.type);
|
||||
j.at("timestamp").get_to(req.timestamp);
|
||||
auto it = j.find("isIsolated");
|
||||
bts::io::JsonReader jr{&j};
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.isIsolated);
|
||||
}
|
||||
jr.read("quantity", req.price);
|
||||
jr.read("quoteOrderQty", req.price);
|
||||
jr.read("price", req.price);
|
||||
jr.read("stopPrice", req.price);
|
||||
it = j.find("newClientOrderId");
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.newClientOrderId);
|
||||
}
|
||||
jr.read("icebergQty", req.price);
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.icebergQty);
|
||||
}
|
||||
it = j.find("newOrderRespType");
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.newOrderRespType);
|
||||
}
|
||||
it = j.find("sideEffectType");
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.sideEffectType);
|
||||
}
|
||||
it = j.find("timeInForce");
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.timeInForce);
|
||||
}
|
||||
it = j.find("selfTradePreventionMode");
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.selfTradePreventionMode);
|
||||
}
|
||||
it = j.find("isIsolated");
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.isIsolated);
|
||||
}
|
||||
it = j.find("autoRepayAtCancel");
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.autoRepayAtCancel);
|
||||
}
|
||||
it = j.find("recvWindow");
|
||||
if (it != j.end()) {
|
||||
it->get_to(req.recvWindow);
|
||||
}
|
||||
}
|
||||
|
||||
void to_query_params(std::string &qps, const CreateOrderRequest &req) {
|
||||
qps.append("symbol=");
|
||||
qps.append(req.symbol);
|
||||
qps.append("&side=");
|
||||
qps.append(req.side.name());
|
||||
qps.append("&type=");
|
||||
qps.append(req.type.name());
|
||||
qps.append("×tamp=");
|
||||
qps.append(std::to_string(req.timestamp));
|
||||
if (req.isIsolated) {
|
||||
qps.append("&isIsolated=true");
|
||||
}
|
||||
if (req.quantity > 0) {
|
||||
qps.append("&quantity=");
|
||||
quantity_to_string(req.quantity, qps);
|
||||
}
|
||||
if (req.quoteOrderQty > 0) {
|
||||
qps.append(""eOrderQty=");
|
||||
quantity_to_string(req.quoteOrderQty, qps);
|
||||
}
|
||||
if (!std::isnan(req.price)) {
|
||||
qps.append("&price=");
|
||||
price_to_string(req.price, qps);
|
||||
}
|
||||
if (!std::isnan(req.stopPrice)) {
|
||||
qps.append("&stopPrice=");
|
||||
price_to_string(req.stopPrice, qps);
|
||||
}
|
||||
if (!req.newClientOrderId.empty()) {
|
||||
qps.append("&newClientOrderId=");
|
||||
qps.append(req.newClientOrderId);
|
||||
}
|
||||
if (req.icebergQty > 0) {
|
||||
qps.append("&icebergQty=");
|
||||
quantity_to_string(req.icebergQty, qps);
|
||||
}
|
||||
if (req.newOrderRespType != NewOrderResponseType::NONE) {
|
||||
qps.append("&newOrderRespType=");
|
||||
qps.append(req.newOrderRespType.name());
|
||||
}
|
||||
if (req.sideEffectType != SideEffectType::NO_SIDE_EFFECT) {
|
||||
qps.append("&sideEffectType=");
|
||||
qps.append(req.sideEffectType.name());
|
||||
}
|
||||
if (req.timeInForce != TimeInForce::NONE) {
|
||||
qps.append("&timeInForce=");
|
||||
qps.append(req.timeInForce.name());
|
||||
}
|
||||
if (req.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
|
||||
qps.append("&selfTradePreventionMode=");
|
||||
qps.append(req.selfTradePreventionMode.name());
|
||||
}
|
||||
if (req.autoRepayAtCancel) {
|
||||
qps.append("&autoRepayAtCancel=true");
|
||||
}
|
||||
if (req.recvWindow > 0) {
|
||||
qps.append("&recvWindow=");
|
||||
qps.append(std::to_string(req.recvWindow));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "binance_execution/cross_margin/side_effect_type.h"
|
||||
#include "binance_execution/enums/new_order_response_type.h"
|
||||
#include "binance_execution/enums/order_type.h"
|
||||
#include "binance_execution/enums/self_trade_prevention_mode.h"
|
||||
#include "binance_execution/enums/side.h"
|
||||
#include "binance_execution/enums/time_in_force.h"
|
||||
|
||||
#include <limits>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace bts::orderman {
|
||||
struct CreateOrderMsg;
|
||||
}
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
/**
|
||||
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-Order
|
||||
*/
|
||||
struct CreateOrderRequest {
|
||||
std::string symbol;
|
||||
bool isIsolated = false;
|
||||
Side side;
|
||||
OrderType type;
|
||||
double quantity = std::numeric_limits<double>::quiet_NaN();
|
||||
double quoteOrderQty = std::numeric_limits<double>::quiet_NaN();
|
||||
double price = std::numeric_limits<double>::quiet_NaN();
|
||||
double stopPrice = std::numeric_limits<double>::quiet_NaN();
|
||||
std::string newClientOrderId;
|
||||
double icebergQty = std::numeric_limits<double>::quiet_NaN();
|
||||
NewOrderResponseType newOrderRespType = NewOrderResponseType::NONE;
|
||||
SideEffectType sideEffectType = SideEffectType::NO_SIDE_EFFECT;
|
||||
TimeInForce timeInForce = TimeInForce::NONE;
|
||||
SelfTradePreventionMode selfTradePreventionMode =
|
||||
SelfTradePreventionMode::NONE;
|
||||
bool autoRepayAtCancel = false;
|
||||
std::int64_t timestamp = 0;
|
||||
std::int64_t recvWindow = 0;
|
||||
|
||||
bool init(const bts::orderman::CreateOrderMsg &m, std::string &error);
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, const CreateOrderRequest &req);
|
||||
void from_json(const nlohmann::json &j, CreateOrderRequest &req);
|
||||
|
||||
void to_query_params(std::string &query_params, const CreateOrderRequest &req);
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
#include "create_order_response.h"
|
||||
#include "binance_execution/enums/self_trade_prevention_mode.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
void to_json(nlohmann::json &j, const CreateOrderResponse &res) {
|
||||
j["symbol"] = res.symbol;
|
||||
j["orderId"] = res.orderId;
|
||||
j["clientOrderId"] = res.clientOrderId;
|
||||
j["transactTime"] = res.transactTime;
|
||||
if (!std::isnan(res.price)) {
|
||||
j["price"] = res.price;
|
||||
}
|
||||
if (!std::isnan(res.origQty)) {
|
||||
j["origQty"] = res.origQty;
|
||||
}
|
||||
if (!std::isnan(res.executedQty)) {
|
||||
j["executedQty"] = res.executedQty;
|
||||
}
|
||||
if (!std::isnan(res.cummulativeQuoteQty)) {
|
||||
j["cummulativeQuoteQty"] = res.cummulativeQuoteQty;
|
||||
}
|
||||
j["status"] = res.status;
|
||||
j["timeInForce"] = res.timeInForce;
|
||||
j["type"] = res.type;
|
||||
j["side"] = res.side;
|
||||
if (!std::isnan(res.marginBuyBorrowAmount)) {
|
||||
j["marginBuyPowerAmount"] = res.marginBuyBorrowAmount;
|
||||
}
|
||||
if (!res.marginBuyBorrowAsset.empty()) {
|
||||
j["marginBuyBorrowAsset"] = res.marginBuyBorrowAsset;
|
||||
}
|
||||
if (res.isIsolated) {
|
||||
j["isIsolated"] = true;
|
||||
}
|
||||
if (res.selfTradePreventionMode != SelfTradePreventionMode::NONE) {
|
||||
j["selfTradePreventionMode"] = res.selfTradePreventionMode;
|
||||
}
|
||||
if (!res.fills.empty()) {
|
||||
j["fills"] = res.fills;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, CreateOrderResponse &res) {
|
||||
const bts::io::JsonReader jr{&j};
|
||||
j.at("symbol").get_to(res.symbol);
|
||||
j.at("orderId").get_to(res.orderId);
|
||||
j.at("clientOrderId").get_to(res.clientOrderId);
|
||||
j.at("transactTime").get_to(res.transactTime);
|
||||
jr.read_or("price", res.price, std::numeric_limits<double>::quiet_NaN());
|
||||
jr.read_or("origQty", res.origQty, std::numeric_limits<double>::quiet_NaN());
|
||||
jr.read_or(
|
||||
"executedQty", res.executedQty,
|
||||
std::numeric_limits<double>::quiet_NaN());
|
||||
jr.read_or(
|
||||
"cummulativeQuoteQty", res.cummulativeQuoteQty,
|
||||
std::numeric_limits<double>::quiet_NaN());
|
||||
j.at("status").get_to(res.status);
|
||||
j.at("timeInForce").get_to(res.timeInForce);
|
||||
j.at("type").get_to(res.type);
|
||||
j.at("side").get_to(res.side);
|
||||
jr.read_or(
|
||||
"marginBuyBorrowAmount", res.marginBuyBorrowAmount,
|
||||
std::numeric_limits<double>::quiet_NaN());
|
||||
jr.read("marginBuyBorrowAsset", res.marginBuyBorrowAsset);
|
||||
jr.read("isIsolated", res.isIsolated);
|
||||
jr.read("selfTradePreventionMode", res.selfTradePreventionMode);
|
||||
jr.read_or("fills", res.fills);
|
||||
}
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "binance_execution/enums/order_status.h"
|
||||
#include "binance_execution/enums/order_type.h"
|
||||
#include "binance_execution/enums/self_trade_prevention_mode.h"
|
||||
#include "binance_execution/enums/side.h"
|
||||
#include "binance_execution/enums/time_in_force.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
|
||||
#include <limits>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
/**
|
||||
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-Order
|
||||
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-Order#response-example
|
||||
*/
|
||||
struct CreateOrderResponse {
|
||||
struct Fill {
|
||||
double price = std::numeric_limits<double>::quiet_NaN();
|
||||
double qty = std::numeric_limits<double>::quiet_NaN();
|
||||
double commission = std::numeric_limits<double>::quiet_NaN();
|
||||
std::string commissionAsset;
|
||||
std::uint32_t tradeId = 0;
|
||||
};
|
||||
|
||||
std::string symbol;
|
||||
std::int64_t orderId = 0;
|
||||
std::string clientOrderId;
|
||||
std::int64_t transactTime = 0;
|
||||
double price = std::numeric_limits<double>::quiet_NaN();
|
||||
double origQty = std::numeric_limits<double>::quiet_NaN();
|
||||
double executedQty = std::numeric_limits<double>::quiet_NaN();
|
||||
// Note: 'cummulative' is deliberately mispelled to match the json payload.
|
||||
double cummulativeQuoteQty = std::numeric_limits<double>::quiet_NaN();
|
||||
OrderStatus status = OrderStatus::NONE;
|
||||
TimeInForce timeInForce = TimeInForce::NONE;
|
||||
OrderType type = OrderType::NONE;
|
||||
Side side = Side::NONE;
|
||||
double marginBuyBorrowAmount = std::numeric_limits<double>::quiet_NaN();
|
||||
std::string marginBuyBorrowAsset;
|
||||
bool isIsolated = false;
|
||||
SelfTradePreventionMode selfTradePreventionMode =
|
||||
SelfTradePreventionMode::NONE;
|
||||
std::vector<Fill> fills;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, const CreateOrderResponse &res);
|
||||
void from_json(const nlohmann::json &j, CreateOrderResponse &res);
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline void to_json(nlohmann::json &j, const CreateOrderResponse::Fill &fill) {
|
||||
j["price"] = fill.price;
|
||||
j["qty"] = fill.qty;
|
||||
j["commission"] = fill.commission;
|
||||
j["commissionAsset"] = fill.commissionAsset;
|
||||
j["tradeId"] = fill.tradeId;
|
||||
}
|
||||
|
||||
inline void from_json(
|
||||
const nlohmann::json &j, CreateOrderResponse::Fill &fill) {
|
||||
bts::io::JsonReader jr{&j};
|
||||
jr.read("price", fill.price);
|
||||
jr.read("qty", fill.qty);
|
||||
jr.read("commission", fill.commission);
|
||||
jr.read("commissionAsset", fill.commissionAsset);
|
||||
jr.read("tradeId", fill.tradeId);
|
||||
}
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "rest_api_client.h"
|
||||
#include "lib/io/http/http_client.h"
|
||||
#include "lib/io/http/http_response.h"
|
||||
#include "lib/utils/crypto/hmac.h"
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
using bts::http::HttpClient;
|
||||
using bts::http::HttpResponse;
|
||||
using bts::system::crypto::hmac_digest_hex;
|
||||
using std::string;
|
||||
|
||||
#define _LOG_FN(level, fn, ...) \
|
||||
LOG(OrderMan, level, "BinanceCrossMargin::" fn ". " __VA_ARGS__)
|
||||
#define DIAG_FN(fn, ...) _LOG_FN(bts::Diag, fn, __VA_ARGS__)
|
||||
#define INFO_FN(fn, ...) _LOG_FN(bts::Info, fn, __VA_ARGS__)
|
||||
#define WARN_FN(fn, ...) _LOG_FN(bts::Warn, fn, __VA_ARGS__)
|
||||
#define ERROR_FN(fn, ...) _LOG_FN(bts::Error, fn, __VA_ARGS__)
|
||||
|
||||
template <std::size_t MAX_SIZE, std::size_t DEFAULT_SIZE>
|
||||
static void shrink_string(std::string &str) {
|
||||
if (str.size() > MAX_SIZE) {
|
||||
str.resize(DEFAULT_SIZE);
|
||||
str.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, std::size_t MAX_SIZE, std::size_t DEFAULT_SIZE>
|
||||
static void shrink_vec(std::vector<T> &vec) {
|
||||
if (vec.size() > MAX_SIZE) {
|
||||
vec.resize(DEFAULT_SIZE);
|
||||
vec.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
bool RestApiClient::sign_query_params_(std::string &qp) {
|
||||
std::string signature;
|
||||
if (!hmac_digest_hex(secret_key_, qp, signature)) {
|
||||
LOG(OrderMan, bts::Error, "Failed to sign request. {}", qp);
|
||||
return false;
|
||||
}
|
||||
static constexpr const std::string SIGNATURE_LABEL = "&signature=";
|
||||
qp.reserve(qp.size() + SIGNATURE_LABEL.size() + signature.size());
|
||||
qp.append(SIGNATURE_LABEL);
|
||||
qp.append(signature);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class ResponseT, class RequestT>
|
||||
void RestApiClient::request_(
|
||||
const RequestT &req,
|
||||
const std::string &path,
|
||||
bts::io::http::HttpMethod method,
|
||||
const std::string &body,
|
||||
bool sign,
|
||||
RestApiResult<ResponseT> &res) {
|
||||
// Use thread_local vars to reduce memory allocations. Shrink strings
|
||||
// to reduce memory footprint from previous requests.
|
||||
thread_local bts::http::HttpRequest http_req;
|
||||
shrink_string<4096, 128>(http_req.body);
|
||||
http_req.reset();
|
||||
// Prepare request
|
||||
http_req.url = url_;
|
||||
http_req.url.append(path);
|
||||
http_req.body = body;
|
||||
http_req.method = method;
|
||||
http_req.headers["X-MBX-APIKEY"] = api_key_;
|
||||
thread_local std::string qps;
|
||||
shrink_string<256, 16>(qps);
|
||||
qps.clear();
|
||||
to_query_params(qps, req);
|
||||
if (!qps.empty()) {
|
||||
if (sign) {
|
||||
sign_query_params_(qps);
|
||||
}
|
||||
http_req.url.append("?");
|
||||
http_req.url.append(qps);
|
||||
}
|
||||
// Send it
|
||||
thread_local HttpResponse http_res;
|
||||
shrink_string<4096, 128>(http_res.body);
|
||||
http_res.reset();
|
||||
HttpClient::send(http_req, &http_res);
|
||||
res.http_code = http_res.code;
|
||||
res.http_error = http_res.errors;
|
||||
if (!http_res.ok()) {
|
||||
ERROR_FN("{} {}", "res: {}", method, path, http_res);
|
||||
try {
|
||||
nlohmann::json::parse(http_res.body).get_to(res.error.emplace());
|
||||
} catch (const std::exception &ex) {
|
||||
ERROR_FN("request", "Failed to parse error json. {}", http_res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Parse response
|
||||
INFO_FN("{} {}", "res: {}", method, path, http_res);
|
||||
try {
|
||||
nlohmann::json::parse(http_res.body).get_to(res.response.emplace());
|
||||
} catch (const std::exception &ex) {
|
||||
ERROR_FN("{} {}", "ex: {}", method, path, string{ex.what()});
|
||||
bts_assert_msgf(false, "Failed to parse {} response", path);
|
||||
}
|
||||
}
|
||||
|
||||
#define REQUEST_DEF(ResponseT, RequestT) \
|
||||
template void RestApiClient::request_<ResponseT, RequestT>( \
|
||||
const RequestT &req, const std::string &path, \
|
||||
bts::io::http::HttpMethod method, const std::string &body, bool sign, \
|
||||
RestApiResult<ResponseT> &res)
|
||||
|
||||
REQUEST_DEF(CreateOrderResponse, CreateOrderRequest);
|
||||
REQUEST_DEF(CancelOrderResponse, CancelOrderRequest);
|
||||
REQUEST_DEF(ListenKeyResponse, ListenKeyRequest);
|
||||
REQUEST_DEF(RefreshListenKeyResponse, RefreshListenKeyRequest);
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include "binance_execution/cross_margin/messages/cancel_order_request.h"
|
||||
#include "binance_execution/cross_margin/messages/cancel_order_response.h"
|
||||
#include "binance_execution/cross_margin/messages/create_order_request.h"
|
||||
#include "binance_execution/cross_margin/messages/create_order_response.h"
|
||||
#include "binance_execution/messages/listen_key_request.h"
|
||||
#include "binance_execution/messages/listen_key_response.h"
|
||||
#include "binance_execution/messages/refresh_listen_key_request.h"
|
||||
#include "binance_execution/messages/refresh_listen_key_response.h"
|
||||
#include "binance_execution/rest_api_result.h"
|
||||
#include "lib/io/http/HttpMethod.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
class RestApiClient {
|
||||
public:
|
||||
explicit RestApiClient(
|
||||
std::string url, std::string api_key, std::string secret_key);
|
||||
|
||||
const RestApiResult<CreateOrderResponse> &create_order(
|
||||
const CreateOrderRequest &req);
|
||||
const RestApiResult<CancelOrderResponse> &cancel_order(
|
||||
const CancelOrderRequest &req);
|
||||
const RestApiResult<ListenKeyResponse> &get_listen_key(
|
||||
const ListenKeyRequest &req = {});
|
||||
const RestApiResult<RefreshListenKeyResponse> &refresh_listen_key(
|
||||
const RefreshListenKeyRequest &req = {});
|
||||
|
||||
private:
|
||||
std::string url_;
|
||||
std::string api_key_;
|
||||
std::string secret_key_;
|
||||
|
||||
template <class ResponseT, class RequestT>
|
||||
void request_(
|
||||
const RequestT &req,
|
||||
const std::string &path,
|
||||
bts::io::http::HttpMethod method,
|
||||
const std::string &body,
|
||||
bool sign,
|
||||
RestApiResult<ResponseT> &op_res);
|
||||
|
||||
bool sign_query_params_(std::string &qp);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline RestApiClient::RestApiClient(
|
||||
std::string url, std::string api_key, std::string secret_key)
|
||||
: url_(std::move(url)), api_key_(std::move(api_key)),
|
||||
secret_key_(std::move(secret_key)) {}
|
||||
|
||||
inline const RestApiResult<CreateOrderResponse> &RestApiClient::create_order(
|
||||
const CreateOrderRequest &req) {
|
||||
static const std::string path{"/sapi/v1/margin/order"};
|
||||
thread_local RestApiResult<CreateOrderResponse> res;
|
||||
res.reset();
|
||||
request_<CreateOrderResponse>(
|
||||
req, path, bts::io::http::HttpMethod::POST, "", true, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
inline const RestApiResult<CancelOrderResponse> &RestApiClient::cancel_order(
|
||||
const CancelOrderRequest &req) {
|
||||
static const std::string path{"/sapi/v1/margin/order"};
|
||||
thread_local RestApiResult<CancelOrderResponse> res;
|
||||
res.reset();
|
||||
request_<CancelOrderResponse>(
|
||||
req, path, bts::io::http::HttpMethod::DELETE, "", true, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
inline const RestApiResult<ListenKeyResponse> &RestApiClient::get_listen_key(
|
||||
const ListenKeyRequest &req) {
|
||||
static const std::string path{"/sapi/v1/userDataStream"};
|
||||
thread_local RestApiResult<ListenKeyResponse> res;
|
||||
res.reset();
|
||||
request_(req, path, bts::io::http::HttpMethod::POST, "", false, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
inline const RestApiResult<RefreshListenKeyResponse> &
|
||||
RestApiClient::refresh_listen_key(const RefreshListenKeyRequest &req) {
|
||||
static const std::string path{"/sapi/v1/userDataStream"};
|
||||
thread_local RestApiResult<RefreshListenKeyResponse> res;
|
||||
res.reset();
|
||||
request_(req, path, bts::io::http::HttpMethod::PUT, "", false, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace binance_execution::cross_margin {
|
||||
|
||||
// clang-format off
|
||||
#define FOR_ALL_VALUES(per_value) \
|
||||
per_value(SideEffectType, NO_SIDE_EFFECT) \
|
||||
per_value(SideEffectType, MARGIN_BUY) \
|
||||
per_value(SideEffectType, AUTO_REPAY) \
|
||||
per_value(SideEffectType, AUTO_BORROW_REPAY)
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @a https://developers.binance.com/docs/margin_trading/trade/Margin-Account-New-OCO
|
||||
*/
|
||||
ENUM_CLASS_DEF(SideEffectType, FOR_ALL_VALUES)
|
||||
|
||||
#undef FOR_ALL_VALUES
|
||||
|
||||
} // namespace binance_execution::cross_margin
|
||||
|
||||
FMT_IMPL(binance_execution::cross_margin::SideEffectType, t, "{}", t.name());
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "binance_execution/cross_margin/trade_data_stream/event_type.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace binance_execution::cross_margin::trade_data_stream {
|
||||
|
||||
/**
|
||||
* @a https://developers.binance.com/docs/margin_trading/trade-data-stream/Event-Account-Update
|
||||
*/
|
||||
struct AccountUpdate {
|
||||
struct Balance {
|
||||
std::string asset; // json: a
|
||||
double free; // json: f
|
||||
double locked; // json: l
|
||||
};
|
||||
|
||||
EventType event_type; // json: e
|
||||
std::int64_t event_time; // json: E
|
||||
std::int64_t last_update_time; // json: u
|
||||
std::vector<Balance> balances; // json: B
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, const AccountUpdate::Balance &b);
|
||||
void from_json(const nlohmann::json &j, AccountUpdate::Balance &b);
|
||||
|
||||
void to_json(nlohmann::json &j, const AccountUpdate &u);
|
||||
void from_json(const nlohmann::json &j, AccountUpdate &u);
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline void to_json(nlohmann::json &j, const AccountUpdate::Balance &b) {
|
||||
j["a"] = b.asset;
|
||||
j["f"] = b.free;
|
||||
j["l"] = b.locked;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json &j, AccountUpdate::Balance &b) {
|
||||
bts::io::JsonReader jr{&j};
|
||||
j.at("a").get_to(b.asset);
|
||||
jr.read("f", b.free);
|
||||
jr.read("l", b.locked);
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json &j, const AccountUpdate &u) {
|
||||
j["e"] = u.event_type;
|
||||
j["E"] = u.event_time;
|
||||
j["u"] = u.last_update_time;
|
||||
j["B"] = u.balances;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json &j, AccountUpdate &u) {
|
||||
j.at("e").get_to(u.event_type);
|
||||
j.at("E").get_to(u.event_time);
|
||||
j.at("u").get_to(u.last_update_time);
|
||||
j.at("B").get_to(u.balances);
|
||||
};
|
||||
|
||||
} // namespace binance_execution::cross_margin::trade_data_stream
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "binance_execution/cross_margin/trade_data_stream/event_type.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace binance_execution::cross_margin::trade_data_stream {
|
||||
|
||||
/**
|
||||
* @a https://developers.binance.com/docs/binance-spot-api-docs/user-data-stream#account-update
|
||||
*/
|
||||
struct BalanceUpdate {
|
||||
EventType event_type; // json: e
|
||||
std::int64_t event_time; // json: E
|
||||
std::string asset; // json: a
|
||||
double balance_delta; // json: d
|
||||
std::int64_t clear_time; // json: T
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, const BalanceUpdate &b);
|
||||
void from_json(const nlohmann::json &j, BalanceUpdate &b);
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline void to_json(nlohmann::json &j, const BalanceUpdate &b) {
|
||||
j["e"] = b.event_type;
|
||||
j["E"] = b.event_time;
|
||||
j["a"] = b.asset;
|
||||
j["d"] = b.balance_delta;
|
||||
j["T"] = b.clear_time;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json &j, BalanceUpdate &b) {
|
||||
j.at("e").get_to(b.event_type);
|
||||
j.at("E").get_to(b.event_time);
|
||||
j.at("a").get_to(b.asset);
|
||||
bts::io::JsonReader jr{&j};
|
||||
jr.read("d", b.balance_delta);
|
||||
j.at("T").get_to(b.clear_time);
|
||||
}
|
||||
|
||||
} // namespace binance_execution::cross_margin::trade_data_stream
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace binance_execution::cross_margin::trade_data_stream {
|
||||
|
||||
// clang-format off
|
||||
#define FOR_ALL_VALUES(per_value) \
|
||||
per_value(EventType, NONE) \
|
||||
per_value(EventType, OUTBOUND_ACCOUNT_POSITION) \
|
||||
per_value(EventType, BALANCE_UPDATE) \
|
||||
per_value(EventType, ORDER_UPDATE) \
|
||||
per_value(EventType, LIST_STATUS) \
|
||||
per_value(EventType, LISTEN_KEY_EXPIRED)
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @brief Represents the different Event Types for user data streams at Binance
|
||||
*
|
||||
* @a https://developers.binance.com/docs/binance-spot-api-docs/user-data-stream#web-socket-payloads
|
||||
*/
|
||||
ENUM_CLASS_DEF_NO_JSON(EventType, FOR_ALL_VALUES)
|
||||
|
||||
inline void from_json(const nlohmann ::json &j, EventType &value) {
|
||||
std::string value_str;
|
||||
j.get_to(value_str);
|
||||
if (value_str == "outboundAccountPosition") {
|
||||
value = EventType::OUTBOUND_ACCOUNT_POSITION;
|
||||
} else if (value_str == "balanceUpdate") {
|
||||
value = EventType::BALANCE_UPDATE;
|
||||
} else if (value_str == "executionReport") {
|
||||
value = EventType::ORDER_UPDATE;
|
||||
} else if (value_str == "listStatus") {
|
||||
value = EventType::LIST_STATUS;
|
||||
} else if (value_str == "listenKeyExpired") {
|
||||
value = EventType::LISTEN_KEY_EXPIRED;
|
||||
} else {
|
||||
EventType::parse(value_str, &value);
|
||||
}
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json &j, const EventType &value) {
|
||||
switch (value.value) {
|
||||
case EventType::OUTBOUND_ACCOUNT_POSITION:
|
||||
j = "outboundAccountPosition";
|
||||
break;
|
||||
case EventType::BALANCE_UPDATE:
|
||||
j = "balanceUpdate";
|
||||
break;
|
||||
case EventType::ORDER_UPDATE:
|
||||
j = "executionReport";
|
||||
break;
|
||||
case EventType::LIST_STATUS:
|
||||
j = "listStatus";
|
||||
break;
|
||||
case EventType::LISTEN_KEY_EXPIRED:
|
||||
j = "listenKeyExpired";
|
||||
break;
|
||||
default:
|
||||
j = value.name();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef FOR_ALL_VALUES
|
||||
|
||||
} // namespace binance_execution::cross_margin::trade_data_stream
|
||||
|
||||
FMT_IMPL(
|
||||
binance_execution::cross_margin::trade_data_stream::EventType,
|
||||
t,
|
||||
"{}",
|
||||
t.name());
|
||||
Loaded 100 of 218 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user