Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f12b8c4b1 | ||
|
|
30b6719c2d | ||
|
|
a502c365e5 | ||
|
|
fdacd8b543 | ||
|
|
3d1de9a0c7 | ||
|
|
b2b3fa5949 | ||
|
|
86ecf69820 | ||
|
|
8f537a8fda | ||
|
|
01b9ff6b47 | ||
|
|
2609663d3e | ||
|
|
fd89241611 | ||
|
|
a69118c5d3 | ||
|
|
5accd0e2b4 | ||
|
|
df5ad61cd2 | ||
|
|
4a7674eb29 | ||
|
|
5a458287f4 | ||
|
|
5921f9f6b5 | ||
|
|
4c73a63ae8 | ||
|
|
42b1c921a8 | ||
|
|
fab3334b1e | ||
|
|
7239dd3ade | ||
|
|
dd4c8eb649 | ||
|
|
76a29bdc8a | ||
|
|
06d708f3ea | ||
|
|
e2f70cbc19 |
No files matched your search
@@ -18,6 +18,7 @@ src/client/spark/Properties/launchSettings.json
|
||||
compile_commands.json
|
||||
.cquery_cached_index/
|
||||
src/client/sparkcmd/Properties/launchSettings.json
|
||||
src/sdk/examples/client_sdk/bts.api.csharp/src/Properties/launchSettings.json
|
||||
node_modules
|
||||
paket-files
|
||||
project.lock.json
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
+++
|
||||
+++
|
||||
title = "Release notes"
|
||||
weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.16.2
|
||||
### User Visible Change
|
||||
- Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units.
|
||||
## 2.16.1
|
||||
### Bug Fixes
|
||||
- OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically
|
||||
## 2.16.0
|
||||
### Bug Fixes
|
||||
- Solved an issue that could result in incorrect ladder levels
|
||||
- Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes
|
||||
### API Changes
|
||||
- Add C# client side API samples for order entry, position monitoring and market data observation
|
||||
|
||||
## 2.15.10
|
||||
### Bug Fixes
|
||||
- Solved an issue that could cause the user interface to fail when playing some alert sounds
|
||||
@@ -715,3 +728,4 @@ insert_anchor_links = "right"
|
||||
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
|
||||
- Corrected cash per point values, used in pnl calculations, for a number of products
|
||||
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 0afd5e92de...0df20ec20a.
+1
-1
Submodule externals updated: 38945c5f49...a558597a1a.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "2.1.508",
|
||||
"rollForward": "latestMajor"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@
|
||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(VCTargetsPath)'==''">
|
||||
<VCTargetsPath>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets</VCTargetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -549,24 +549,48 @@
|
||||
</u:BindingUpdateGroupCollection>
|
||||
</u:Behavior.BindingUpdateGroups>
|
||||
</TextBox>
|
||||
<TextBlock
|
||||
<StackPanel
|
||||
x:Name="PART_TickFactors_Label"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks"
|
||||
IsEnabled="True"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<c:TickFactorsPicker
|
||||
>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks ("
|
||||
/>
|
||||
<CheckBox
|
||||
Margin="1,0,0,0"
|
||||
IsChecked="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Padding="0"
|
||||
>
|
||||
fractional
|
||||
</CheckBox>
|
||||
<TextBlock VerticalAlignment="Center" Margin="1,0,0,0">)</TextBlock>
|
||||
</StackPanel>
|
||||
<Grid
|
||||
x:Name="PART_TickFactors_Picker"
|
||||
Grid.Column="3"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="True"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
>
|
||||
<c:TickFactorsPicker
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static cv:BooleanToVisibilityConverter.HiddenInstance}}"
|
||||
/>
|
||||
<c:TickUpDownInt
|
||||
HorizontalAlignment="Stretch"
|
||||
Minimum="1"
|
||||
Value="{Binding IntegerTickSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource FalseVisibilityConverter}}"
|
||||
/>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
x:Name="PART_Pricing_Label"
|
||||
Grid.Column="4"
|
||||
|
||||
@@ -52,6 +52,26 @@ namespace mbts.ui.spark {
|
||||
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region UseFractionalTicks
|
||||
public static readonly DependencyProperty UseFractionalTicksProperty = WPFHelper.create_dp<bool, ExecutionStrategyEditor>(
|
||||
nameof(UseFractionalTicks), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, true);
|
||||
|
||||
public bool UseFractionalTicks {
|
||||
get => (bool)GetValue(UseFractionalTicksProperty);
|
||||
set => SetValue(UseFractionalTicksProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IntegerTickSize
|
||||
public static readonly DependencyProperty IntegerTickSizeProperty = WPFHelper.create_dp<int, ExecutionStrategyEditor>(
|
||||
nameof(IntegerTickSize), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 1);
|
||||
|
||||
public int IntegerTickSize {
|
||||
get => (int)GetValue(IntegerTickSizeProperty);
|
||||
set => SetValue(IntegerTickSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IsNew
|
||||
public static readonly DependencyProperty IsNewProperty =
|
||||
DependencyProperty.Register(
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
UseFractionalTicks="{Binding use_fractional_ticks}"
|
||||
IntegerTickSize="{Binding integer_tick_size}"
|
||||
/>
|
||||
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
|
||||
</Grid>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -30,8 +29,16 @@ namespace mbts.spark {
|
||||
readonly Property<bool> _has_working_orders, _can_add_leg;
|
||||
|
||||
Security _synthetic;
|
||||
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
|
||||
bool
|
||||
_set_name_automatically,
|
||||
_set_ticks_automatically,
|
||||
_set_quantities_automatically,
|
||||
_set_price_mults_automatically,
|
||||
_is_setting_quantities,
|
||||
_is_setting_price_mults,
|
||||
_use_fractional_ticks = true;
|
||||
TickFactors _tick_factors;
|
||||
int _integer_tick_size = 1;
|
||||
ExecutionStrategyImmutable _strategy;
|
||||
HedgePricing _hedge_pricing;
|
||||
|
||||
@@ -44,6 +51,10 @@ namespace mbts.spark {
|
||||
: this(synthetic, strategy, false) {
|
||||
ExceptionAssert.InvalidOperation.That(synthetic.Id != 0, "This constructor may only be used for already-existing synthetic securities (id must be non-zero).");
|
||||
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
|
||||
if (TickUtils.is_integer_tick(synthetic.ContractSpec)) {
|
||||
_use_fractional_ticks = false;
|
||||
_integer_tick_size = DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0;
|
||||
}
|
||||
title = $"Edit '{synthetic.Synthetic.Name}'";
|
||||
icon = Icons.ModifySynthetic;
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
|
||||
@@ -131,14 +142,14 @@ namespace mbts.spark {
|
||||
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (_tick_factors == null || !_tick_factors.is_valid()) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
|
||||
if (use_fractional_ticks ? (_tick_factors == null || !_tick_factors.is_valid()) : integer_tick_size == 0) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, use_fractional_ticks ? nameof(tick_factors) : nameof(integer_tick_size)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
|
||||
_synthetic.Symbol = _deduce_symbol(_synthetic);
|
||||
_synthetic.ContractSpec = _tick_factors.to_spec();
|
||||
_synthetic.ContractSpec = use_fractional_ticks ? _tick_factors.to_spec() : TickUtils.create_integer_tick_spec(integer_tick_size);
|
||||
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
@@ -295,6 +306,20 @@ namespace mbts.spark {
|
||||
public bool has_working_orders => _has_working_orders.Value;
|
||||
public bool is_new { get; }
|
||||
public bool is_new_leg_popup_open { get; set; }
|
||||
public bool use_fractional_ticks {
|
||||
get => _use_fractional_ticks;
|
||||
set {
|
||||
if (Set(ref _use_fractional_ticks, value))
|
||||
_set_ticks_automatically = false;
|
||||
}
|
||||
}
|
||||
public int integer_tick_size {
|
||||
get => _integer_tick_size;
|
||||
set {
|
||||
if (Set(ref _integer_tick_size, value))
|
||||
_set_ticks_automatically = false;
|
||||
}
|
||||
}
|
||||
public bool can_add_leg => _can_add_leg.Value;
|
||||
public SyntheticContract.Types.PriceCalc price_calc { get => _synthetic.Synthetic.PriceCalc; set => _synthetic.Synthetic.PriceCalc = value; }
|
||||
public ObservableCollection<LegSpreaderParamsTuple> leg_tuples { get; } = new ObservableCollection<LegSpreaderParamsTuple>();
|
||||
@@ -323,6 +348,16 @@ namespace mbts.spark {
|
||||
_strategy = strategy;
|
||||
hedge_pricing = strategy.HedgePricing;
|
||||
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
|
||||
if (synthetic.HasContractSpec) {
|
||||
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
|
||||
/// rather than the properties <see cref="use_fractional_ticks"/> and <see cref="integer_tick_size"/>
|
||||
/// because the setters of the former flip <see cref="_set_ticks_automatically"/> to false if the value changes.
|
||||
/// This behavior causes the ticks to be automatically deduced as legs are added/removed,
|
||||
/// until the user explicitly enters a different value (as the properties are bound to tick input controls in the view).
|
||||
Set(ref _use_fractional_ticks, !TickUtils.is_integer_tick(synthetic.ContractSpec), nameof(use_fractional_ticks));
|
||||
if (!use_fractional_ticks)
|
||||
Set(ref _integer_tick_size, DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0, nameof(integer_tick_size));
|
||||
}
|
||||
RaisePropertyChanged(nameof(name), nameof(tick_factors));
|
||||
}
|
||||
|
||||
|
||||
@@ -787,7 +787,7 @@ namespace mbts.spark {
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (var p in dcs)
|
||||
foreach (var p in dcs.empty_if_null().OrderBy(kv => kv.Key))
|
||||
_vm.current_algo_custom_state.Add(new NamedValueVM(p.Key, DynamicOrderHelper.fmt_param(p.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<LanguageTargets Condition="'$(VisualStudioVersion)'=='16.0'">$(MSBuildToolsPath)\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<StartupObject>mbts.ui.shell.App</StartupObject>
|
||||
|
||||
@@ -63,5 +63,20 @@ namespace mbts {
|
||||
|
||||
public static double? nan_nullable(double value) =>
|
||||
double.IsNaN(value) ? (double?)null : value;
|
||||
|
||||
public static int? to_optional_int32(double value) {
|
||||
if (double.IsNaN(value) || double.IsInfinity(value))
|
||||
return null;
|
||||
if (value > 0) {
|
||||
value = Math.Floor(value);
|
||||
if (value <= int.MaxValue)
|
||||
return (int)value;
|
||||
} else if (value < 0) {
|
||||
value = Math.Ceiling(value);
|
||||
if (value >= int.MinValue)
|
||||
return (int)value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1005,6 +1005,8 @@
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
UseFractionalTicks="{Binding use_fractional_ticks}"
|
||||
IntegerTickSize="{Binding integer_tick_size}"
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="-4" VerticalAlignment="Top" RightItems="{Binding skirt_items}"/>
|
||||
</Grid>
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace mbts.ui.converters
|
||||
public Visibility VisibilityForFalse { get { return _visfalse; } set { _visfalse = value; } }
|
||||
|
||||
public static BooleanToVisibilityConverter Instance => new BooleanToVisibilityConverter();
|
||||
public static BooleanToVisibilityConverter HiddenInstance => new BooleanToVisibilityConverter {
|
||||
VisibilityForFalse = Visibility.Hidden
|
||||
};
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
|
||||
bool flag = false;
|
||||
|
||||
@@ -49,6 +49,28 @@ namespace mbts.spark {
|
||||
public static double parse(this ContractSpecificationImmutable? spec, string text, double display_multiplier, string nan_text) =>
|
||||
spec.spec_or_default().parse(text, display_multiplier, nan_text);
|
||||
|
||||
public static bool is_integer_tick(ContractSpecificationImmutable spec) =>
|
||||
spec.MainFraction == 0 &&
|
||||
spec.PriceDisplayFormat == 0 &&
|
||||
spec.LowTick > 0 &&
|
||||
MathUtils.is_int(spec.LowTick) &&
|
||||
spec.HighTick > 0 &&
|
||||
MathUtils.is_int(spec.HighTick);
|
||||
|
||||
public static ContractSpecification create_integer_tick_spec(int low_tick) =>
|
||||
create_integer_tick_spec(low_tick, low_tick);
|
||||
|
||||
public static ContractSpecification create_integer_tick_spec(int low_tick, int high_tick) {
|
||||
var rv = SecurityDataHelper.Defaults.ContractSpecification;
|
||||
rv.MainFraction = 0;
|
||||
rv.SubFraction = 0;
|
||||
rv.PriceDisplayFormat = 0;
|
||||
rv.PriceBreak = 0;
|
||||
rv.LowTick = low_tick;
|
||||
rv.HighTick = high_tick;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static double low_tick(NativePriceSize[] bids, NativePriceSize[] asks) {
|
||||
double low = bids[bids.Length - 1].price;
|
||||
if (double.IsNaN(low))
|
||||
@@ -326,7 +348,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
static bool _check_tick_size(double tick_size) =>
|
||||
(tick_size > 0 && tick_size <= 0.5) || tick_size == 1;
|
||||
tick_size > 0 && (tick_size <= 0.5 || MathUtils.is_int(tick_size));
|
||||
|
||||
struct SpecKey : IEquatable<SpecKey> {
|
||||
public readonly uint denominator, display_digits;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import sys
|
||||
f = open(sys.argv[1], 'rb+')
|
||||
license = '5E50A1DACD94D7C1BECD316EEAE5AB02'
|
||||
license = '1C9210495AA3C9C884B7A37A4C7AD8F9'
|
||||
data = f.read()
|
||||
match = data.find(license)
|
||||
if match > 0:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<CSharpAPIRoot>$(MSBuildThisFileDirectory)</CSharpAPIRoot>
|
||||
<Configuration Condition="'$(configuration)'==''">Debug</Configuration>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Output">
|
||||
<BaseOutputPath>$(CSharpAPIRoot)\build\$(Configuration)</BaseOutputPath>
|
||||
<OutputPath>$(BaseOutputPath)\bin</OutputPath>
|
||||
<BaseIntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<IntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</IntermediateOutputPath>
|
||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30104.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "bts.api.csharp", "src/bts.api.csharp.csproj", "{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FC6CE0CA-B0CB-44E3-8176-1D759D05AAD4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="spark_sdk" Version="2.15.10" />
|
||||
<PackageReference Include="System.Reactive" Version="4.4.1" />
|
||||
<PackageReference Include="System.Reactive.Core" Version="4.4.1" />
|
||||
<PackageReference Include="System.Reactive.Interfaces" Version="4.4.1" />
|
||||
<PackageReference Include="System.Reactive.Linq" Version="4.4.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,68 @@
|
||||
using bts.api.orderman;
|
||||
using CommandLine;
|
||||
|
||||
namespace bts.api.csharp {
|
||||
[Verb("watch", HelpText = "List items in the system.")]
|
||||
class WatchOptions {
|
||||
[Option("accounts", Required = false, SetName = "Accounts", HelpText = "print accounts in the system.")]
|
||||
public bool accounts { get; set; }
|
||||
|
||||
[Option("securities", Required = false, SetName = "Securities", HelpText = "print securities in the system (may be combined with --filter-symbols, or --filter-securities).")]
|
||||
public bool securities { get; set; }
|
||||
|
||||
[Option("symbols", Required = false, SetName = "Symbols", HelpText = "print all symbols in the system (may not be combined with other options).")]
|
||||
public bool symbols { get; set; }
|
||||
|
||||
[Option("bbo", Required = false, SetName = "Books", HelpText = "print bbo for an optionally filtered set of securities/symbols.")]
|
||||
public bool bbo { get; set; }
|
||||
|
||||
[Option("orders", Required = false, SetName = "Orders", HelpText = "print orders in the system.")]
|
||||
public bool orders { get; set; }
|
||||
|
||||
[Option("positions", Required = false, SetName = "Positions", HelpText = "print positions in the system (may be combined with --filter-symbols, --filter-securities, and --filter-accounts).")]
|
||||
public bool positions { get; set; }
|
||||
|
||||
// auxiliary filters
|
||||
[Option("filter-securities", Required = false, HelpText = "filter securities with a regular expression (may be combined with --securities, and --bbo).")]
|
||||
public string filter_securities { get; set; }
|
||||
|
||||
[Option("filter-symbols", Required = false, HelpText = "filter securities for a particular set of symbols (may be combined with --securities, and --bbo). Do not confuse with the --symbols option, which prints all symbols in the system.")]
|
||||
public string filter_symbols { get; set; }
|
||||
|
||||
[Option("filter-accounts", Required = false, HelpText = "filter for a particular set of accounts.")]
|
||||
public string filter_accounts { get; set; }
|
||||
}
|
||||
|
||||
[Verb("send-order", HelpText = "Send orders.")]
|
||||
class SendOrdersOptions {
|
||||
// this option only applies to Iceberg orders
|
||||
[Option("show-quantity", Required = false, SetName = "Iceberg")]
|
||||
public double show_quantity { get; set; } = 5;
|
||||
|
||||
[Option("price", Required = false, HelpText = "specify the order price.")]
|
||||
public double price { get; set; } = 100;
|
||||
|
||||
[Option("quantity", Required = false, HelpText = "specify the order quantity.")]
|
||||
public uint quantity { get; set; } = 1;
|
||||
|
||||
[Option("account", Required = false, HelpText = "specify the account with which the order should be sent.")]
|
||||
public string account { get; set; }
|
||||
|
||||
[Option("order-type", Required = false, HelpText = "specify the order type (limit, market, or iceberg).")]
|
||||
public CustomOrderDefinition.CustomOneofCase order_type { get; private set; } = CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
|
||||
/// Run the program with the argument <see cref="WatchOptions.securities"/> to display securities/security keys:
|
||||
/// - display all securities:
|
||||
/// bts.api.csharp.exe watch securities
|
||||
/// - display all 'ES' securities:
|
||||
/// bts.api.csharp.exe watch securities --symbols "ES"
|
||||
/// - display all 'ES' and 'GC' securities:
|
||||
/// bts.api.csharp.exe watch securities --symbols "ES,GC"
|
||||
/// If no security key is specified, a random security is used.
|
||||
[Option("security", Required = false, HelpText = "specify the order security by name, or id.")]
|
||||
public string security { get; set; }
|
||||
|
||||
[Option("side", Required = false, HelpText = "specify the order side (buy/sell).")]
|
||||
public Side side { get; set; } = Side.Buy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.data;
|
||||
using bts.api.csharp.utils;
|
||||
using bts.api.csharp.watch;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts.api.csharp {
|
||||
class Program {
|
||||
static void Main(string[] args) {
|
||||
new Parser(cfg => cfg.CaseInsensitiveEnumValues = true)
|
||||
.ParseArguments<WatchOptions, SendOrdersOptions>(args)
|
||||
.MapResult(
|
||||
_execution_shell<WatchOptions>(_watch),
|
||||
_execution_shell<SendOrdersOptions>(_send_orders, true),
|
||||
errors => 1);
|
||||
}
|
||||
|
||||
static Func<T, int> _execution_shell<T>(Func<ServiceContext, T, IDisposable> execute, bool auto_quit = false) {
|
||||
return opts => {
|
||||
var exit_event = new ManualResetEvent(auto_quit);
|
||||
Console.CancelKeyPress += on_cancel_key_press;
|
||||
var service_context = new ServiceContext();
|
||||
service_context.login("bts", "").Wait();
|
||||
execute(service_context, opts);
|
||||
exit_event.WaitOne();
|
||||
return 0;
|
||||
//
|
||||
void on_cancel_key_press(object sender, ConsoleCancelEventArgs e) {
|
||||
exit_event.Set();
|
||||
e.Cancel = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static IDisposable _watch(ServiceContext service_context, WatchOptions options) {
|
||||
if (options.accounts) {
|
||||
// watch accounts
|
||||
return WatchAccounts.run(service_context.locate<AdminService>());
|
||||
} else if (options.securities) {
|
||||
// watch securities
|
||||
var security_predicate = CLIUtils.parse_security_predicate(options.filter_symbols, options.filter_securities);
|
||||
return WatchSecurities.run(service_context.locate<MarketDataService>(), security_predicate);
|
||||
} else if (options.bbo) {
|
||||
// watch bbo
|
||||
return WatchBooks.run(new bts.md.MarketDataClient(),
|
||||
CLIUtils.parse_security_predicate(options.filter_symbols, options.filter_securities));
|
||||
} else if (options.symbols) {
|
||||
// watch symbols
|
||||
return WatchSymbols.run(service_context.locate<MarketDataService>());
|
||||
} else if (options.orders) {
|
||||
// watch orders (optionally filtered by account)
|
||||
var account_predicate = CLIUtils.parse_delimited_predicate<OrderRecordImmutable>(
|
||||
options.filter_accounts, order => order.Def.Account);
|
||||
return WatchOrders.run(service_context.locate<OrderService>(), account_predicate);
|
||||
} else if (options.positions) {
|
||||
// watch positions (optionally filtered by account)
|
||||
var account_predicate = CLIUtils.parse_delimited_predicate<PositionUnitImmutable>(
|
||||
options.filter_accounts, pos => pos.Account);
|
||||
return WatchPositions.run(service_context.locate<PositionService>(), account_predicate);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static IDisposable _send_orders(ServiceContext service_context, SendOrdersOptions options) {
|
||||
var custom_order_def = OrderUtils.custom_order_definitioni(options.order_type);
|
||||
if (!custom_order_def.HasValue) {
|
||||
Console.WriteLine($"Unable to create order of type '{options.order_type}.' The following types are available:\n\t{string.Join("\n\t", OrderUtils.known_order_types)}");
|
||||
return null;
|
||||
}
|
||||
var user = new NameIDPair(service_context.user, service_context.user_id);
|
||||
var admin_service = service_context.locate<AdminService>();
|
||||
var account = AdminUtils.get_account_async(user, admin_service, options.account).WaitResult();
|
||||
var md_service = service_context.locate<MarketDataService>();
|
||||
var security = SecurityUtils.get_security_async(md_service.subscribei_securities(), options.security).WaitResult();
|
||||
var model = new OrderModel(service_context.locate<OrderService>());
|
||||
/// Note that the call below (<see cref="OrderModel.send_order(NameIDPair, NameIDPair, uint, Side, uint, double, CustomOrderDefinition)"/>)
|
||||
/// blocks the current thread until an ack is received from the server. If you wish to avoid blocking the thread as such,
|
||||
/// use the following method instead: <see cref="OrderModel.send_order_async(NameIDPair, NameIDPair, uint, Side, uint, double, CustomOrderDefinition)"/>.
|
||||
model.send_order(user, account, security.Id, options.side, options.quantity, options.price, custom_order_def.Value.CloneToMutable());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace bts.api.csharp.data {
|
||||
readonly struct NameIDPair {
|
||||
public NameIDPair(string name, uint id) {
|
||||
this.name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public string name { get; }
|
||||
public uint id { get; }
|
||||
}
|
||||
|
||||
static class Users {
|
||||
public static NameIDPair Default =>
|
||||
new NameIDPair("bts", 1);
|
||||
}
|
||||
|
||||
static class Accounts {
|
||||
public static NameIDPair Default =>
|
||||
new NameIDPair("TEST", 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using bts.api.csharp.utils;
|
||||
using bts.api.orderman;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.data {
|
||||
/// <summary>
|
||||
/// Exemplifies how the <see cref="OrderService"/> api may be used to send orders.
|
||||
/// </summary>
|
||||
class OrderModel {
|
||||
readonly OrderService _service;
|
||||
|
||||
public OrderModel(OrderService service) =>
|
||||
_service = service;
|
||||
|
||||
public static OrderDefinition create_order_definition(NameIDPair user, NameIDPair account, uint security_key, Side side, CustomOrderDefinition custom_def) {
|
||||
return new OrderDefinition {
|
||||
Account = account.name,
|
||||
AccountId = account.id,
|
||||
User = user.name,
|
||||
UserId = user.id,
|
||||
SecurityKey = security_key,
|
||||
Side = side,
|
||||
CustomDef = custom_def
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an order of the type specified by <paramref name="custom_order_definition"/> (asynchronous).
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public async Task<uint?> send_order_async(NameIDPair user, NameIDPair account, uint security_key, Side side, uint quantity, double price, CustomOrderDefinition custom_order_definition) {
|
||||
Console.WriteLine($"Creating order {OrderUtils.fmt_order(side, quantity, price, user.name, account.name, custom_order_definition)}");
|
||||
var response = await _service.create_order_async(new Create {
|
||||
Quantity = quantity,
|
||||
Price = price,
|
||||
NewOrder = create_order_definition(user, account, security_key, side, custom_order_definition)
|
||||
});
|
||||
if (response.success) {
|
||||
Console.WriteLine($"Order submission succeeded. Order id is {response.value.OrderKey}.");
|
||||
return response.value.OrderKey;
|
||||
} else {
|
||||
Console.WriteLine($"Order submission failed: [{response.value.ErrorCode}] {response.value.Msg}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an order with specified parameters (the thread will be blocked until an ack is received from the server).
|
||||
/// </summary>
|
||||
public uint? send_order(NameIDPair user, NameIDPair account, uint security_key, Side side, uint quantity, double price, CustomOrderDefinition custom_order_definition) {
|
||||
/// Note, the thread will be blocked until a response is received from the server.
|
||||
/// For this reason, <see cref="OrderService.create_order_async(Create)"/> is usually preferred.
|
||||
/// An example of how to use that method is provided in <see cref="send_order_async(NameIDPair, NameIDPair, CustomOrderDefinition, uint, double)"/>.
|
||||
Console.WriteLine($"Creating order {OrderUtils.fmt_order(side, quantity, price, user.name, account.name, custom_order_definition)}");
|
||||
var response = _service.create_order(new Create {
|
||||
Quantity = quantity,
|
||||
Price = price,
|
||||
NewOrder = create_order_definition(user, account, security_key, side, custom_order_definition)
|
||||
});
|
||||
switch (response.ErrorCode) {
|
||||
case OrderError.Ok:
|
||||
Console.WriteLine($"Order submission succeeded. Order id is {response.OrderKey}.");
|
||||
return response.OrderKey;
|
||||
default:
|
||||
Console.WriteLine($"Order submission failed: [{response.ErrorCode}] {response.Msg}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using bts.api.admin;
|
||||
using bts.api.csharp.data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class AdminUtils {
|
||||
public static IReadOnlyDictionary<uint, NameIDPair> map_accounts(AdminDataImmutable admin_datai) {
|
||||
return admin_datai.Accounts.ToDictionary(acc => acc.Id, acc => new NameIDPair(acc.Name, acc.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Links users to the set of accounts on which they have trading permissions.
|
||||
/// </summary>
|
||||
public static IEnumerable<(NameIDPair user, IEnumerable<uint> accounts)> user_account_permissions(AdminDataImmutable admin_datai) {
|
||||
return admin_datai.Users
|
||||
.Select(user => (new NameIDPair(user.Name, user.Id), _user_tradable_accounts(user)));
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<NameIDPair, ImmutableHashSet<NameIDPair>> map_user_account_permissions(AdminDataImmutable admin_datai) {
|
||||
var account_lookup = admin_datai.Accounts.ToLookup(acc => acc.Id);
|
||||
return admin_datai.Users.ToDictionary(
|
||||
user => new NameIDPair(user.Name, user.Id),
|
||||
user => ImmutableHashSet.CreateRange(_user_tradable_accounts(user)
|
||||
.Select(acc_id => new NameIDPair(account_lookup[acc_id].Single().Name, acc_id))));
|
||||
}
|
||||
|
||||
static IEnumerable<uint> _user_tradable_accounts(BTSUserImmutable user) {
|
||||
return user.UserData.AccountPermissions.AccountPermissions
|
||||
.Where(kv => kv.Value.TradingPermission.HasPermissions)
|
||||
.Select(kv => kv.Key);
|
||||
}
|
||||
|
||||
public static NameIDPair get_account(NameIDPair user, AdminService admin_service, string account_name = null) {
|
||||
var task = get_account_async(user, admin_service, account_name);
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Either searches for the account with the specified name,
|
||||
/// or picks a random account for which the specified user has trading permissions.
|
||||
/// </summary>
|
||||
public static async Task<NameIDPair> get_account_async(NameIDPair user, AdminService admin_service, string account_name = null) {
|
||||
return await (account_name == null ? auto_account(user, admin_service) : find_account(admin_service, account_name).FirstAsync());
|
||||
}
|
||||
|
||||
public static IObservable<NameIDPair> auto_account(NameIDPair user, AdminService admin_service) {
|
||||
return admin_service.subscribei_admin_data()
|
||||
.Where(update => update.all.Any())
|
||||
.Select(update => map_user_account_permissions(update.all.First()))
|
||||
.Select(account_perms => account_perms.TryGetValue(user, out var accs) ? accs : ImmutableHashSet<NameIDPair>.Empty)
|
||||
.Where(accs => accs.Count > 0)
|
||||
.Select(accs => accs.First())
|
||||
.Take(1);
|
||||
}
|
||||
|
||||
public static IObservable<NameIDPair> find_account(AdminService admin_service, string account) {
|
||||
return admin_service.subscribei_admin_data()
|
||||
.Select(update => {
|
||||
foreach (var admin_data in update.all) {
|
||||
foreach (var acc in admin_data.Accounts) {
|
||||
if (acc.Name == account)
|
||||
return new NameIDPair(acc.Name, acc.Id);
|
||||
}
|
||||
}
|
||||
return (NameIDPair?)null;
|
||||
})
|
||||
.Where(acc => acc.HasValue)
|
||||
.Select(acc => acc.Value)
|
||||
.Take(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class BookUtils {
|
||||
/// <summary>
|
||||
/// Get a display string of the bbo.
|
||||
/// </summary>
|
||||
public static string fmt_bbo(BookEntryImmutable book) {
|
||||
return fmt_bbo(get_bbo(book));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a display string of the bbo.
|
||||
/// </summary>
|
||||
public static string fmt_bbo((PriceEntryImmutable? bid, PriceEntryImmutable? ask) bbo) {
|
||||
return $"{fmt_price_entry(bbo.bid)} <--> {fmt_price_entry(bbo.ask)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a display string of a <see cref="PriceEntryImmutable"/>.
|
||||
/// </summary>
|
||||
public static string fmt_price_entry(PriceEntryImmutable? price_entry) {
|
||||
return price_entry.HasValue ? fmt_price_entry(price_entry.Value) : "N/A";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a display string of a <see cref="PriceEntryImmutable"/>.
|
||||
/// </summary>
|
||||
public static string fmt_price_entry(PriceEntryImmutable price_entry) {
|
||||
return $"{price_entry}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a display string of the bbo.
|
||||
/// </summary>
|
||||
public static (PriceEntryImmutable? best_bid, PriceEntryImmutable? best_ask) get_bbo(BookEntryImmutable book) {
|
||||
return (get_top_of_book(book.Bid), get_top_of_book(book.Ask));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first element in <paramref name="depth"/>,
|
||||
/// as this represents the top price level
|
||||
/// of a particular side of the market (bid or ask).
|
||||
/// </summary>
|
||||
public static PriceEntryImmutable? get_top_of_book(IEnumerable<PriceEntryImmutable> depth) {
|
||||
foreach (var d in depth)
|
||||
return d;
|
||||
// there are no elements (the book is empty for this side), so return null.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class CLIUtils {
|
||||
public static IEnumerable<string> parse_delimited(string cli_arg, string file_arg, string delimiter) {
|
||||
var cli_items = _parse_delimited(cli_arg, delimiter);
|
||||
if (file_arg != null) {
|
||||
try {
|
||||
var items = _parse_delimited(File.ReadAllText(file_arg), delimiter);
|
||||
return cli_items is null ? items : cli_items.Concat(items);
|
||||
} catch { }
|
||||
}
|
||||
return cli_items;
|
||||
}
|
||||
|
||||
public static Func<SecurityImmutable, bool> parse_security_predicate(string cli_symbols = null, string cli_security_regex = null) {
|
||||
return _merge_predicates(
|
||||
parse_delimited_predicate<SecurityImmutable>(cli_symbols, sec => sec.Symbol),
|
||||
RegexUtils.default_regex(cli_security_regex)?.to_predicate<SecurityImmutable>());
|
||||
}
|
||||
|
||||
public static Func<T, bool> parse_delimited_predicate<T>(string cli_arg, Func<T, string> get) =>
|
||||
parse_delimited_predicate(cli_arg, null, ",", get);
|
||||
|
||||
public static Func<T, bool> parse_delimited_predicate<T>(string cli_arg, string file_arg, string delimiter, Func<T, string> get) {
|
||||
var items = new HashSet<string>(parse_delimited(cli_arg, file_arg, delimiter));
|
||||
if (items.Count == 0)
|
||||
return null;
|
||||
return sec => items.Contains(get(sec));
|
||||
}
|
||||
|
||||
static IEnumerable<string> _parse_delimited(string delimited_string, string delimiter) {
|
||||
return delimited_string
|
||||
?.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries)
|
||||
?.Select(sym => sym.Trim())
|
||||
?? Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
static Func<T, bool> _merge_predicates<T>(params Func<T, bool>[] predicates) {
|
||||
Func<T, bool> result = null;
|
||||
for (int i = 0; i < predicates.Length; i++) {
|
||||
if (predicates[i] != null) {
|
||||
if (result == null) {
|
||||
result = predicates[i];
|
||||
} else {
|
||||
result += predicates[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class CollectionUtils {
|
||||
public static bool insert_sorted<T>(List<T> list, T value) =>
|
||||
insert_sorted(list, value, Comparer<T>.Default);
|
||||
|
||||
public static bool insert_sorted<T>(List<T> list, T value, IComparer<T> comparer) {
|
||||
if (list is null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
if (comparer is null)
|
||||
throw new ArgumentNullException(nameof(comparer));
|
||||
var sorted_index = list.BinarySearch(value, comparer);
|
||||
if (sorted_index < 0) {
|
||||
// this is a new symbol
|
||||
list.Insert(~sorted_index, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class DelegateUtils {
|
||||
public static Func<TOuter, TResult> wrap<TInner, TOuter, TResult>(this Func<TInner, TResult> func, Func<TOuter, TInner> get_inner) =>
|
||||
outer => func(get_inner(outer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using bts.api.orderman;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class OrderUtils {
|
||||
// add new templates as needed
|
||||
static readonly IReadOnlyDictionary<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinitionImmutable>
|
||||
_custom_def_templates = new Dictionary<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinitionImmutable> {
|
||||
{ CustomOrderDefinition.CustomOneofCase.Limit, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Limit = new LimitOrderDefinition() }) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Market, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Market = new MarketOrderDefinition() }) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Iceberg, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Iceberg = new IcebergOrderDefinition() }) }
|
||||
};
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition.CustomOneofCase> known_order_types => _custom_def_templates.Keys;
|
||||
|
||||
public static CustomOrderDefinitionImmutable? custom_order_definitioni(CustomOrderDefinition.CustomOneofCase order_type) {
|
||||
if (_custom_def_templates.TryGetValue(order_type, out var templatei)) {
|
||||
return templatei.CloneToMutable();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string fmt_order(uint quantity, double price, CustomOrderDefinition custom_order_definition) {
|
||||
return $"{quantity} @ {price}: {custom_order_definition}";
|
||||
}
|
||||
|
||||
public static string fmt_order(Side side, uint quantity, double price, string user, string account, CustomOrderDefinition custom_order_definition) {
|
||||
return $"[{user}][{account}] {side} {fmt_order(quantity, price, custom_order_definition)}.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class RegexUtils {
|
||||
public static Regex default_regex(string pattern) {
|
||||
// these patterns typically come from the user,
|
||||
// so the 'should_ignore' delegate below allows them
|
||||
// to include all results by specifying "all" or "*".
|
||||
return default_regex(pattern, p => string.IsNullOrEmpty(p) || p == "*" || p.ToLower() == "all");
|
||||
}
|
||||
|
||||
public static Regex default_regex(string pattern, Func<string, bool> should_ignore) {
|
||||
if (pattern == null)
|
||||
return null;
|
||||
pattern = pattern.Trim();
|
||||
if (pattern == string.Empty)
|
||||
return null;
|
||||
return new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
|
||||
public static Func<T, bool> to_predicate<T>(this Regex regex) =>
|
||||
regex == null ? null : new Func<T, bool>(val => regex.IsMatch(val.ToString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SymbolTableGroup = bts.api.core.TableUpdateGrouping<bts.api.SecurityImmutable, uint, string>;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class SecurityUtils {
|
||||
public static IObservable<TableUpdate<SymbolTableGroup, string>> GroupBySymbols(this IObservable<TableUpdate<SecurityImmutable, uint>> securities_source) =>
|
||||
securities_source.GroupTable(sec => sec.Id, sec => sec.Symbol);
|
||||
|
||||
public static async Task<SecurityImmutable> get_security_async(IObservable<Update<SecurityImmutable, uint>> source, string security_str = null) {
|
||||
if (!string.IsNullOrEmpty(security_str)) {
|
||||
Func<SecurityImmutable, bool> is_match;
|
||||
// the user may specify the security string, or security key
|
||||
if (uint.TryParse(security_str, out var sec_key)) {
|
||||
is_match = sec => sec.Id == sec_key;
|
||||
} else {
|
||||
// ignore case and white space
|
||||
security_str = security_str.Replace(" ", "").ToLower();
|
||||
is_match = sec => sec.ToString().Replace(" ", "").ToLower() == security_str;
|
||||
}
|
||||
return await source.Select(update => {
|
||||
foreach (var sec in update.all) {
|
||||
if (is_match(sec))
|
||||
return sec;
|
||||
}
|
||||
return (SecurityImmutable?)null;
|
||||
})
|
||||
.Where(sec => sec.HasValue)
|
||||
.Select(sec => sec.Value)
|
||||
.FirstAsync();
|
||||
} else {
|
||||
return await source.Where(update => update.all.Any())
|
||||
.Select(update => update.all.First())
|
||||
.FirstAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Predicates {
|
||||
public static Func<SecurityImmutable, bool> symbols(IEnumerable<string> symbols) {
|
||||
if (symbols is null)
|
||||
return sec => true;
|
||||
// use hashset for quicker Contains check
|
||||
var symbols_set = new HashSet<string>(symbols);
|
||||
return sec => symbols_set.Contains(sec.Symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class StringUtils {
|
||||
public static IEnumerable<string> format_columns(IEnumerable<IEnumerable<string>> columns, int min_width = 8, int padding = 3) =>
|
||||
format_columns(columns.Select(lines => (Math.Max(min_width, lines.Max(l => l.Length) + padding), lines)));
|
||||
|
||||
public static IEnumerable<string> format_columns(IEnumerable<(int width, IEnumerable<string> lines)> columns) {
|
||||
var column_layout = columns.Select((column, index) => (column.lines.GetEnumerator(), column.width, index)).ToList();
|
||||
int num_active = column_layout.Count;
|
||||
if (num_active == 0)
|
||||
yield break;
|
||||
while (true) {
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < column_layout.Count; i++) {
|
||||
var (en, width, pos) = column_layout[i];
|
||||
if (en != null && en.MoveNext()) {
|
||||
sb.Append(string.Format($"{{{0},{-width}}}", en.Current));
|
||||
sb.Append(string.Format($"{{{0,1}}}", "| "));
|
||||
} else {
|
||||
sb.Append(string.Format($"{{{0},{-width}}}", ""));
|
||||
sb.Append(string.Format($"{{{0,1}}}", "| "));
|
||||
if (en != null) {
|
||||
// set the enumerable to null to inidicate this column is empty for the remaining rows
|
||||
en.Dispose();
|
||||
column_layout[i] = (null, width, pos);
|
||||
if (--num_active == 0)
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class TaskExtensions {
|
||||
public static T WaitResult<T>(this Task<T> task) {
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using bts.api.core;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class UpdateUtils {
|
||||
/// <summary>
|
||||
/// Uses a value-subscription delegate to create subscriptions (incrementally) for each value in the update sequence.
|
||||
/// When a value is removed or changed, its corresponding subscription is disposed; if it changed, a new subscription is created with the new value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the updates.</typeparam>
|
||||
/// <typeparam name="TKey">The type of keys in the updates.</typeparam>
|
||||
/// <param name="update_stream">The update sequence.</param>
|
||||
/// <param name="subscribe_value">The delegate to create a subscription for a particular value.</param>
|
||||
/// <returns>An IDisposable that, upon being disposed, disposes the subscription to the update sequence, and all active value subscriptions.</returns>
|
||||
public static IDisposable subscribe_incremental_values<T, TKey>(IObservable<Update<T, TKey>> update_stream, Func<T, IDisposable> subscribe_value)
|
||||
where TKey : IEquatable<TKey> {
|
||||
var locker = new object();
|
||||
var value_subs = new Dictionary<TKey, IDisposable>();
|
||||
var source_sub = update_stream.Subscribe(on_update);
|
||||
return Disposable.Create(dispose);
|
||||
|
||||
// local methods
|
||||
void on_update(Update<T, TKey> update) {
|
||||
lock (locker) {
|
||||
foreach (var value in update.removed) {
|
||||
var k = update.key(value);
|
||||
value_subs[k].Dispose();
|
||||
value_subs.Remove(k);
|
||||
}
|
||||
foreach (var ch in update.changes) {
|
||||
var k = update.key(ch.New);
|
||||
value_subs[k].Dispose();
|
||||
value_subs[k] = subscribe_value(ch.New);
|
||||
}
|
||||
foreach (var value in update.added) {
|
||||
value_subs.Add(update.key(value), subscribe_value(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
lock (locker) {
|
||||
source_sub.Dispose();
|
||||
foreach (var loop in value_subs.Values) {
|
||||
loop.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> SafeFilter<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, Func<T, bool> filter) where TKey : IEquatable<TKey> =>
|
||||
filter == null ? source : source.Filter(filter);
|
||||
|
||||
public static bool is_empty<T, TKey>(this TableUpdate<T, TKey> table) where TKey : IEquatable<TKey> =>
|
||||
!table.added.Any() && !table.changes.Any() && !table.removed.Any();
|
||||
|
||||
public static void print_incremental_changes<T, TKey>(Update<T, TKey> update, Func<T, TKey> key_fn, Func<T, string> fmt_value, string title) where TKey :IEquatable<TKey> {
|
||||
print_incremental_changes<T, TKey>(update.added, update.changes, update.removed, v => $"{key_fn(v)}: {fmt_value(v)}", title);
|
||||
}
|
||||
|
||||
public static void print_incremental_changes<T, TKey>(TableUpdate<T, TKey> table, Func<T, TKey> key_fn, Func<T, string> fmt_value, string title) where TKey :IEquatable<TKey> {
|
||||
if (fmt_value is null)
|
||||
fmt_value = v => v.ToString();
|
||||
print_incremental_changes<T, TKey>(table.added, table.changes, table.removed, v => $"{key_fn(v)}: {fmt_value(v)}", title);
|
||||
}
|
||||
|
||||
public static void print_incremental_changes<T, TKey>(IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, string> fmt, string title) {
|
||||
if (fmt is null)
|
||||
fmt = v => v?.ToString() ?? "(null)";
|
||||
var console_out = new StringBuilder();
|
||||
Console.WriteLine($"Received {title} update");
|
||||
var lines = StringUtils.format_columns(new[] {
|
||||
added.Select(fmt).Prepend(" Added "),
|
||||
changes.Select(ch => ch.New).Select(fmt).Prepend(" Changed "),
|
||||
removed.Select(fmt).Prepend(" Removed ")
|
||||
});
|
||||
foreach (var l in lines)
|
||||
Console.WriteLine("\t" + l);
|
||||
}
|
||||
|
||||
public static void _print_incremental_changes<T, TKey>(IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, string> fmt, string title) {
|
||||
if (fmt is null)
|
||||
fmt = v => v?.ToString() ?? "(null)";
|
||||
var console_out = new StringBuilder();
|
||||
console_out.AppendLine($"Received {title} update");
|
||||
using (var added_en = added.GetEnumerator()) {
|
||||
if (added_en.MoveNext()) {
|
||||
console_out.AppendLine(" Added:");
|
||||
do console_out.AppendLine(" " + fmt(added_en.Current));
|
||||
while (added_en.MoveNext());
|
||||
}
|
||||
}
|
||||
using (var changes_en = changes.GetEnumerator()) {
|
||||
if (changes_en.MoveNext()) {
|
||||
console_out.AppendLine(" Changes");
|
||||
do console_out.AppendLine($" {fmt(changes_en.Current.Old)} --> {fmt(changes_en.Current.New)}");
|
||||
while (changes_en.MoveNext());
|
||||
}
|
||||
}
|
||||
using (var removed_en = removed.GetEnumerator()) {
|
||||
if (removed_en.MoveNext()) {
|
||||
console_out.AppendLine(" Removed");
|
||||
do console_out.AppendLine(" " + fmt(removed_en.Current));
|
||||
while (removed_en.MoveNext());
|
||||
}
|
||||
}
|
||||
Console.Write(console_out.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.data;
|
||||
using bts.api.csharp.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchAccounts {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="AdminService"/> to subscribe to administrative data (users, accounts, permissions, etc.),
|
||||
/// and displays account updates in the console window.
|
||||
/// </summary>
|
||||
public static IDisposable run(AdminService admin_service) {
|
||||
// extract accounts from the admin data into an IObservable<IReadOnlyDictionary<uint, NameIDPair>>.
|
||||
IObservable<IReadOnlyDictionary<uint, NameIDPair>> accounts_source =
|
||||
admin_service.subscribei_admin_data()
|
||||
.Select(update => {
|
||||
// Note that update.all is an IEnumerable<AdminDataImmutable>,
|
||||
// however, it should only have one element. Pass the first element
|
||||
// to the _map_accounts method, and return the resultant IReadOnlyDictionary.
|
||||
return _map_accounts(update.all.FirstOrDefault());
|
||||
});
|
||||
|
||||
// Use the 'Table' extension method to transform the accounts source from an IObservable<IReadOnlyDictionary<TKey, TValue>>
|
||||
// into an IObservable<TableUpdate<TKey, TValue>> (the latter includes incremental update properties).
|
||||
/// <see cref="TableUpdate{T, TKey}.added"/>
|
||||
/// <see cref="TableUpdate{T, TKey}.changes"/>
|
||||
/// <see cref="TableUpdate{T, TKey}.removed"/>
|
||||
IObservable<TableUpdate<NameIDPair, uint>> incremental_accounts_source =
|
||||
accounts_source.Table(account => account.id);
|
||||
|
||||
// subscribe to the incremental account updates,
|
||||
// and print the changes in the console window.
|
||||
return incremental_accounts_source
|
||||
.ObserveOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(upd => UpdateUtils.print_incremental_changes(upd, acc => acc.id, acc => acc.name, "Accounts"));
|
||||
}
|
||||
|
||||
static IReadOnlyDictionary<uint, NameIDPair> _map_accounts(AdminDataImmutable admin_data) =>
|
||||
admin_data.Accounts.ToDictionary(acc => acc.Id, acc => new NameIDPair(acc.Name, acc.Id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using bts.md;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using SecurityKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchBooks {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="MarketDataService"/> to observe book updates
|
||||
/// for an optionally filtered list of securities in the system,
|
||||
/// and organize them into a dictionary.
|
||||
/// </summary>
|
||||
public static IDisposable run(MarketDataClient md_client, Func<SecurityImmutable, bool> security_predicate = null) {
|
||||
var books = new Dictionary<SecurityKey, IMDBook>();
|
||||
object locker = new object();
|
||||
if (md_client is null)
|
||||
throw new ArgumentNullException(nameof(md_client));
|
||||
if (security_predicate is null)
|
||||
security_predicate = new Func<SecurityImmutable, bool>(sec => true);
|
||||
/// unlike the other api subscriptions, which publish <see cref="core.Update{T, TKey}"/>
|
||||
/// this subscription publishes an <see cref="IEnumerable{T}"/>, so the updates are not incremental
|
||||
/// (i.e., don't have added/removed/changed items).
|
||||
return md_client.subscribe_book(security_predicate)
|
||||
.ObserveOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(on_update);
|
||||
|
||||
void on_update(IEnumerable<IMDBook> new_books) {
|
||||
lock (locker) {
|
||||
foreach (var book in new_books) {
|
||||
Console.WriteLine($"{book.security} book updated: {_fmt_bbo(book)}.");
|
||||
books[book.security.Id] = book;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static string _fmt_bbo(IMDBook book) {
|
||||
return $"{_fmt_price_size(book.bids.FirstOrDefault())} <--> {_fmt_price_size(book.asks.FirstOrDefault())}";
|
||||
}
|
||||
|
||||
static string _fmt_price_size(PriceSize price_size) {
|
||||
return price_size == null ? "N/A" : $"{price_size.size} @ {price_size.price}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using AccountKey = System.UInt32;
|
||||
using OrderKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchOrders {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="OrderService"/> to observe order updates, and group them by account.
|
||||
/// Fills and status changes are displayed in the console window.
|
||||
/// </summary>
|
||||
public static IDisposable run(OrderService order_service, Func<OrderRecordImmutable, bool> predicate) {
|
||||
/// 'OrderService' has 2 methods which can be used to subscribe to order updates.
|
||||
/// -------------------------------------------------------------------------------------
|
||||
/// 1) <see cref="OrderService.subscribe_orders(string)"/>.
|
||||
/// 2) <see cref="OrderService.subscribei_orders(string)"/>.
|
||||
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
|
||||
/// --------------------------------------------------------------------------------------
|
||||
var orders_source = order_service.subscribei_orders();
|
||||
if (predicate != null)
|
||||
orders_source = orders_source.Filter(predicate);
|
||||
return run(orders_source.ObserveOn(ThreadPoolScheduler.Instance));
|
||||
}
|
||||
|
||||
public static IDisposable run(IObservable<Update<OrderRecordImmutable, uint>> orders_source) {
|
||||
object locker = new object();
|
||||
var account_order_map = new Dictionary<AccountKey, Dictionary<OrderKey, OrderRecordImmutable>>();
|
||||
return orders_source.Subscribe(on_update);
|
||||
|
||||
void on_order_added(OrderRecordImmutable new_order) {
|
||||
if (!account_order_map.TryGetValue(new_order.Def.AccountId, out var account_orders)) {
|
||||
// this is the first order for this account.
|
||||
// initialize the account orders dictionary, and add it to our data.
|
||||
account_orders = new Dictionary<AccountKey, OrderRecordImmutable>();
|
||||
account_order_map.Add(new_order.Def.AccountId, account_orders);
|
||||
}
|
||||
_print_order_info(new_order, $"observed for the first time.");
|
||||
// now add the order to account_orders
|
||||
account_orders.Add(new_order.Key, new_order);
|
||||
}
|
||||
|
||||
void on_order_changed(Change<OrderRecordImmutable> change) {
|
||||
// ch.New and ch.Old have the same key.
|
||||
Debug.Assert(change.New.Key == change.Old.Key);
|
||||
var key = change.New.Key;
|
||||
// detect fills
|
||||
if (change.New.State.FilledQuantity != change.Old.State.FilledQuantity) {
|
||||
if (change.New.State.FilledQuantity > change.New.State.Quantity) {
|
||||
// order has been overfilled
|
||||
var num_over_fills = change.New.State.FilledQuantity - change.New.State.Quantity;
|
||||
_print_order_info(change.New, $"overfilled by {num_over_fills}.");
|
||||
} else if (change.New.State.FilledQuantity == change.New.State.Quantity) {
|
||||
// order has been filled to completion
|
||||
_print_order_info(change.New, "filled to completion");
|
||||
} else if (change.New.State.Status == OrderStatus.Working) {
|
||||
// order is still working.
|
||||
var working_qty = change.New.State.Quantity - change.New.State.FilledQuantity;
|
||||
_print_order_info(change.New, $"still working {working_qty}");
|
||||
} else {
|
||||
// order has stopped working prematurely (perhaps it was canceled)
|
||||
_print_order_info(change.New, $"is inactive");
|
||||
}
|
||||
}
|
||||
// Account will not change, so our dictionary will definitely have a key for this account.
|
||||
Debug.Assert(account_order_map.ContainsKey(change.New.Def.AccountId));
|
||||
var account_orders = account_order_map[change.New.Def.AccountId];
|
||||
// The value for this key in account_orders should equal ch.Old.
|
||||
Debug.Assert(account_orders[key].Equals(change.Old));
|
||||
// update account orders with the changed order.
|
||||
account_orders[key] = change.New;
|
||||
}
|
||||
|
||||
void on_order_removed(OrderRecordImmutable old_val) {
|
||||
// An entry for this order should be present in our data,
|
||||
// as it is in the 'removed' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(account_order_map.ContainsKey(old_val.Def.AccountId));
|
||||
var account_orders = account_order_map[old_val.Def.AccountId];
|
||||
// the removed value should match the one currently in our data.
|
||||
// Assert this is the case, then remove the order.
|
||||
Debug.Assert(account_orders[old_val.Key].Equals(old_val));
|
||||
account_orders.Remove(old_val.Key);
|
||||
}
|
||||
|
||||
void on_update(Update<OrderRecordImmutable, OrderKey> update) {
|
||||
lock (locker) {
|
||||
foreach (OrderRecordImmutable new_order in update.added) {
|
||||
on_order_added(new_order);
|
||||
}
|
||||
foreach (Change<OrderRecordImmutable> change in update.changes) {
|
||||
on_order_changed(change);
|
||||
}
|
||||
foreach (OrderRecordImmutable old_order in update.removed) {
|
||||
on_order_removed(old_order);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _print_order_info(OrderRecordImmutable order, string message) {
|
||||
Console.WriteLine($"Order {order.Key} ({order.State.Status}, filled {order.State.FilledQuantity}/{order.State.Quantity}): {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
/// <summary>
|
||||
/// A pair of security, and account id,
|
||||
/// which uniquely identifies each PositionUnit.
|
||||
/// </summary>
|
||||
readonly struct PositionKey {
|
||||
public PositionKey(uint security_id, uint account_id) {
|
||||
this.security_id = security_id;
|
||||
this.account_id = account_id;
|
||||
}
|
||||
|
||||
public uint security_id { get; }
|
||||
public uint account_id { get; }
|
||||
|
||||
public static PositionKey from_position(PositionUnitImmutable p) =>
|
||||
new PositionKey(p.SecurityId, p.AccountId);
|
||||
|
||||
public override string ToString() =>
|
||||
$"Sec {security_id}, Acc {account_id}";
|
||||
}
|
||||
|
||||
static class WatchPositions {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="PositionService"/> api to monitor positions
|
||||
/// for every account/security pair, and organize them into a Dictionary.
|
||||
/// </summary>
|
||||
public static IDisposable run(PositionService position_service, Func<PositionUnitImmutable, bool> predicate) {
|
||||
/// 'PositionService' has 2 methods which can be used to subscribe to position updates.
|
||||
/// -------------------------------------------------------------------------------------
|
||||
/// 1) <see cref="PositionService.subscribe_position_data(string)"/>.
|
||||
/// 2) <see cref="PositionService.subscribei_position_data(string)"/>.
|
||||
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
|
||||
/// --------------------------------------------------------------------------------------
|
||||
return run(position_service.subscribei_position_data().Filter(predicate).ObserveOn(ThreadPoolScheduler.Instance));
|
||||
}
|
||||
|
||||
public static IDisposable run(IObservable<Update<PositionUnitImmutable, int>> position_source) {
|
||||
var data = new Dictionary<PositionKey, PositionUnitImmutable>();
|
||||
return position_source.Subscribe(on_update);
|
||||
|
||||
void on_position_added(PositionUnitImmutable new_val) {
|
||||
var k = PositionKey.from_position(new_val);
|
||||
// this entry should NOT be present in our _data, as it is in the 'added' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(!data.ContainsKey(k));
|
||||
print_position_info(k, $"initial position received. Day: Long {new_val.DayLongPos}, Short {new_val.DayShortPos}, Net {new_val.DayLongPos - new_val.DayShortPos}.");
|
||||
// add the new position unit to _data
|
||||
data.Add(k, new_val);
|
||||
}
|
||||
|
||||
void on_position_changed(Change<PositionUnitImmutable> change) {
|
||||
var k = PositionKey.from_position(change.New);
|
||||
// ch.New and ch.Old have the same key (i.e., the Account/Security pair),
|
||||
Debug.Assert(change.New.AccountId == change.Old.AccountId);
|
||||
Debug.Assert(change.New.SecurityId == change.Old.SecurityId);
|
||||
// of course, the value for this key in our dictionary should equal ch.Old.
|
||||
Debug.Assert(data[k].Equals(change.Old));
|
||||
// Display changes to the day position in the console window.
|
||||
// 1) check if the day long changed
|
||||
if (change.New.DayLongPos != change.Old.DayLongPos) {
|
||||
print_position_info(k, $"day long changed from {change.Old.DayLongPos} to {change.New.DayLongPos}.");
|
||||
}
|
||||
// 2) check if day short changed
|
||||
if (change.New.DayShortPos != change.Old.DayShortPos) {
|
||||
print_position_info(k, $"day short changed from {change.Old.DayShortPos} to {change.New.DayShortPos}.");
|
||||
}
|
||||
// 3) check if net day position changed
|
||||
var new_net_day_pos = change.New.DayLongPos - change.New.DayShortPos;
|
||||
var old_net_day_pos = change.Old.DayLongPos - change.Old.DayShortPos;
|
||||
if (new_net_day_pos != old_net_day_pos) {
|
||||
print_position_info(k, $"day net changed from {old_net_day_pos} to {new_net_day_pos}.");
|
||||
}
|
||||
// update _data with the changed position.
|
||||
data[k] = change.New;
|
||||
}
|
||||
|
||||
void on_position_removed(PositionUnitImmutable old_val) {
|
||||
var k = PositionKey.from_position(old_val);
|
||||
// this entry should be present in our _data, as it is in the 'removed' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(data.ContainsKey(k));
|
||||
// this should match our data. Assert this is the case.
|
||||
Debug.Assert(data[k].Equals(old_val));
|
||||
data.Remove(k);
|
||||
}
|
||||
|
||||
void on_update(Update<PositionUnitImmutable, int> update) {
|
||||
foreach (PositionUnitImmutable p in update.added) {
|
||||
on_position_added(p);
|
||||
}
|
||||
foreach (Change<PositionUnitImmutable> ch in update.changes) {
|
||||
on_position_changed(ch);
|
||||
}
|
||||
foreach (PositionUnitImmutable p in update.removed) {
|
||||
on_position_removed(p);
|
||||
}
|
||||
}
|
||||
|
||||
void print_position_info(PositionKey position_key, string message) {
|
||||
Console.WriteLine($"Position {position_key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.utils;
|
||||
using System;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchSecurities {
|
||||
public static IDisposable run(MarketDataService market_data_service, Func<SecurityImmutable, bool> predicate) {
|
||||
// watch securities
|
||||
var securities = market_data_service.subscribei_securities().Table();
|
||||
if (predicate != null)
|
||||
securities = securities.Filter(predicate);
|
||||
return securities
|
||||
.ObserveOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(upd => {
|
||||
if (!upd.is_empty())
|
||||
UpdateUtils.print_incremental_changes(upd, sec => sec.Id, null, "Securities");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using SymbolTableGroup = bts.api.core.TableUpdateGrouping<bts.api.SecurityImmutable, uint, string>;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchSymbols {
|
||||
/// <summary>
|
||||
/// Observes securities via <see cref="MarketDataService.subscribei_securities(string)"/>, and prints newly received symbols to the console.
|
||||
/// </summary>
|
||||
/// <returns>An IDisposable to dispose the subscription to the securities source.</returns>
|
||||
public static IDisposable run(MarketDataService market_data_service) {
|
||||
return run(market_data_service.subscribei_securities().Table());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Observes a security update sequence, and prints newly received symbols to the console.
|
||||
/// </summary>
|
||||
/// <returns>An IDisposable to dispose the subscription to the securities source.</returns>
|
||||
public static IDisposable run(IObservable<TableUpdate<SecurityImmutable, uint>> securities_source) {
|
||||
ushort update_count = 0;
|
||||
object locker = new object();
|
||||
return securities_source.GroupTable(sec => sec.Id, sec => sec.Symbol)
|
||||
.ObserveOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(on_update);
|
||||
|
||||
void on_update(TableUpdate<SymbolTableGroup, string> group_update) {
|
||||
lock (locker) {
|
||||
var num_new_securities = group_update.added.Concat(group_update.changed).Sum(grp => grp.table.added.Count());
|
||||
|
||||
Console.WriteLine(
|
||||
$"Security update number {++update_count} provided {num_new_securities} new securities " +
|
||||
$"on {group_update.added.Count()} new symbols.");
|
||||
|
||||
var new_symbols = group_update.added
|
||||
.Select(table_group => table_group.group_key)
|
||||
.OrderBy(sym => sym);
|
||||
|
||||
foreach (var new_symbol in new_symbols) {
|
||||
Console.WriteLine("\t" + new_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
|
||||
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "TestCeloxica.h"
|
||||
|
||||
#include "server/sparkd/md/platforms/celoxica/CXMain.h"
|
||||
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace cmd
|
||||
{
|
||||
|
||||
int TestCeloxica::execute(const std::vector<std::string>&)
|
||||
{
|
||||
bts::md::CXMain main;
|
||||
main.initialize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string TestCeloxica::help()
|
||||
{
|
||||
return "\t test BTS <> Celoxica integration \n";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ICmd.h"
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace cmd
|
||||
{
|
||||
|
||||
class TestCeloxica : public ICmd
|
||||
{
|
||||
public:
|
||||
int execute(std::vector<std::string> const &args);
|
||||
std::string help();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "server/sparkd/cmds/RunSim.h"
|
||||
#include "server/sparkd/cmds/Span.h"
|
||||
#include "server/sparkd/cmds/Test.h"
|
||||
#include "server/sparkd/cmds/TestCeloxica.h"
|
||||
#include "server/sparkd/cmds/Web.h"
|
||||
|
||||
using namespace bts;
|
||||
@@ -32,7 +31,6 @@ int main(int argc, char **argv) {
|
||||
REGISTER(Help);
|
||||
REGISTER(Run);
|
||||
REGISTER(Md);
|
||||
REGISTER(TestCeloxica);
|
||||
REGISTER(ConfigCheck);
|
||||
REGISTER(KeyCode);
|
||||
REGISTER(RunSim);
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "CXListener.h"
|
||||
|
||||
#include "lib/utils/defer.h"
|
||||
#include "lib/utils/CallTimer.h"
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include <tuple>
|
||||
|
||||
namespace celoxica { namespace gmac {} }
|
||||
#include "celoxica.h"
|
||||
using namespace celoxica::gmac;
|
||||
using namespace std;
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
|
||||
CXListener::CXListener(CXSecurityIndex& index, MDProducer& producer, SecurityIDRecorder& recorder,
|
||||
co2::SettlementRecorder& settles, std::vector<MDProducerChannel> channels,
|
||||
utils::Dispatcher& disp) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_packet_arrived(),
|
||||
_latest_sequence(0),
|
||||
_security_recorder(recorder),
|
||||
_settlement_recorder(settles),
|
||||
_symbol_filter_storage(),
|
||||
_channels(channels),
|
||||
_background(disp),
|
||||
_security_recorder_input_timer(disp, 900, [&]() { dequeue_securities(); }),
|
||||
_security_recorder_input(),
|
||||
_concurrency_monitor(0),
|
||||
_book_updates()
|
||||
{
|
||||
_book_updates.reserve(32);
|
||||
_security_recorder_input_timer.start();
|
||||
}
|
||||
|
||||
CXListener::~CXListener()
|
||||
{
|
||||
_security_recorder_input_timer.stop();
|
||||
}
|
||||
|
||||
void CXListener::add_software_filter(const std::string& symbol)
|
||||
{
|
||||
_symbol_filter_storage.push_back(symbol);
|
||||
//_symbol_filter.clear();
|
||||
//for (const auto& str : _symbol_filter_storage) {
|
||||
// _symbol_filter.insert(onixs::StringRef(str));
|
||||
//}
|
||||
}
|
||||
|
||||
void CXListener::on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CXListener::dequeue_securities()
|
||||
{
|
||||
}
|
||||
|
||||
void CXListener::flush_books()
|
||||
{
|
||||
// This is called at the end of the packet
|
||||
// Write all book updates to shared memory and reset for the next packet
|
||||
//for (auto& entry : _book_updates) {
|
||||
// // bbo first
|
||||
// uint16_t sequence = (_latest_sequence & 0xFFFF);
|
||||
// entry.handle.update_bbo(_packet_arrived, entry.bids[0], entry.asks[0], sequence);
|
||||
|
||||
// // Perf Note: the remainder of the book update could be done (carefully) with a memcpy
|
||||
// for (size_t x = 1; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
// entry.handle.update_bid(_packet_arrived, entry.bids[x], x);
|
||||
// entry.handle.update_ask(_packet_arrived, entry.asks[x], x);
|
||||
// }
|
||||
//}
|
||||
//// Reset pending updates
|
||||
//_book_updates.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
md::CXListener::BookData::BookData(MDProducerHandle & handle) : handle(handle), bids(), asks()
|
||||
{
|
||||
}
|
||||
|
||||
}//namespace
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
|
||||
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
struct GMACMessageOrderDepthV3;
|
||||
|
||||
class CXListener
|
||||
{
|
||||
public:
|
||||
CXListener(CXSecurityIndex&, MDProducer&, SecurityIDRecorder&,
|
||||
co2::SettlementRecorder&, std::vector<MDProducerChannel>, utils::Dispatcher&);
|
||||
~CXListener();
|
||||
|
||||
void add_software_filter(const std::string& symbol);
|
||||
|
||||
void on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*);
|
||||
|
||||
private:
|
||||
void flush_books();
|
||||
void dequeue_securities();
|
||||
|
||||
private:
|
||||
BTS_NO_COPY(CXListener);
|
||||
|
||||
CXSecurityIndex& _index;
|
||||
MDProducer& _producer;
|
||||
utils::TimeStamp _packet_arrived;
|
||||
uint64_t _latest_sequence;
|
||||
SecurityIDRecorder& _security_recorder;
|
||||
co2::SettlementRecorder& _settlement_recorder;
|
||||
std::vector<std::string> _symbol_filter_storage;
|
||||
std::vector<MDProducerChannel> _channels;
|
||||
utils::Dispatcher& _background;
|
||||
utils::DispatcherTimer _security_recorder_input_timer;
|
||||
tbb::concurrent_queue<CXSecurityData*> _security_recorder_input;
|
||||
std::atomic<int> _concurrency_monitor;
|
||||
|
||||
struct BookData {
|
||||
BookData(MDProducerHandle& handle);
|
||||
MDProducerHandle handle;
|
||||
PriceSize bids[10];
|
||||
PriceSize asks[10];
|
||||
};
|
||||
|
||||
std::vector<BookData> _book_updates;
|
||||
};
|
||||
|
||||
|
||||
}}//namespace
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
#include "CXMain.h"
|
||||
|
||||
#include "lib/md/cme/MDP3Utils.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
#include "lib/utils/proto_helper.h"
|
||||
#include "lib/utils/helper_macros.h"
|
||||
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/config/protos/system.pb.h"
|
||||
|
||||
namespace celoxica { namespace gmac {} }
|
||||
#include "celoxica.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#define __retcode_start__
|
||||
#define __retcode_end__
|
||||
}
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
using namespace std;
|
||||
using namespace celoxica::gmac;
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
|
||||
void run_cx_server(UserSymbol* psyms, size_t symcount)
|
||||
{
|
||||
CXMain main;
|
||||
vector<UserSymbol> symbols;
|
||||
for(size_t x = 0; x < symcount; ++x) {
|
||||
symbols.push_back(psyms[x]);
|
||||
}
|
||||
main.run(symbols);
|
||||
}
|
||||
|
||||
CXMain::CXMain() :
|
||||
_producer(co2::PLATFORM_CELOXICA),
|
||||
_security_recorder({}),
|
||||
_settlement_recorder(co2::PLATFORM_CELOXICA),
|
||||
_listeners()
|
||||
{
|
||||
}
|
||||
|
||||
CXMain::~CXMain() {}
|
||||
|
||||
void CXMain::initialize()
|
||||
{
|
||||
//celoxica::gmac::GMACIdentify(stderr);
|
||||
//const auto& options = config::get<CMEOptions>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void CXMain::run(std::vector<UserSymbol>& symbols)
|
||||
{
|
||||
bts::md::await_cme_config();
|
||||
initialize();
|
||||
|
||||
parse_channels(symbols);
|
||||
// const auto& channels = parse_channels(symbols);
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::unique_ptr<CXListener> CXMain::prepare_listener(const std::string& channel, const CMEChannel&)
|
||||
{
|
||||
UNUSED(channel);
|
||||
return std::unique_ptr<CXListener>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
|
||||
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
void run_cx_server(UserSymbol* sym, size_t symcount);
|
||||
|
||||
struct CMEChannel;
|
||||
class CXListener;
|
||||
|
||||
/*
|
||||
* The CX provider is very simple. 1 CXMain maps to 1 Celoxica Queue, which maps to 1 thread and one bound CPU.
|
||||
* Control starts in CXMain which starts the thread, binds to a cpu, provides a (currently unused) list of all the symbols we need
|
||||
* prepare_handler configures an exchange channel to be owned by this queue, and prepare_listener sets up thing that sees
|
||||
* all callbacks which maps the exchange id to the MDProducerHandle and writes to shared mem
|
||||
*/
|
||||
class CXMain
|
||||
{
|
||||
public:
|
||||
CXMain();
|
||||
~CXMain();
|
||||
|
||||
void run(std::vector<UserSymbol>&); //call initialize, start the pump
|
||||
void initialize(); //set up channels, etc
|
||||
|
||||
std::unique_ptr<CXListener> prepare_listener(const std::string& channel, const CMEChannel&);
|
||||
|
||||
private:
|
||||
BTS_NO_COPY(CXMain);
|
||||
|
||||
CXSecurityIndex _index;
|
||||
MDProducer _producer;
|
||||
SecurityIDRecorder _security_recorder;
|
||||
co2::SettlementRecorder _settlement_recorder;
|
||||
std::vector<std::unique_ptr<CXListener>> _listeners;
|
||||
};
|
||||
|
||||
}}//namespace
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/md/SecurityIndex.h"
|
||||
#include <string>
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
|
||||
struct CXSecurityData
|
||||
{
|
||||
CXSecurityData(uint32_t id, MDProducerHandle& h) : id(id), handle(h) {}
|
||||
uint32_t id; // exchange id
|
||||
MDProducerHandle handle;
|
||||
};
|
||||
|
||||
using CXSecurityIndex = ConcurrentSecurityIndex<uint32_t, CXSecurityData>;
|
||||
|
||||
}}//namespace
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace celoxica
|
||||
{
|
||||
namespace gmac
|
||||
{
|
||||
|
||||
#include "celoxica/gmac.h"
|
||||
#include "celoxica/gmac_template_v3.h"
|
||||
#include "celoxica/gmac_info.h"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
// #include "catch/catch.hpp"
|
||||
|
||||
// #include "lib/config/config.h"
|
||||
// #include "lib/config/protos/MarketData.pb.h"
|
||||
|
||||
// #include <vector>
|
||||
// #include <string>
|
||||
// #include <functional>
|
||||
// #include <stdarg.h>
|
||||
|
||||
// #include "../MDP3Utils.h"
|
||||
// #include "../onixs.h"
|
||||
|
||||
// using namespace bts::md;
|
||||
// using namespace std;
|
||||
|
||||
|
||||
// namespace bts {
|
||||
// namespace md {
|
||||
|
||||
// map<string, onixs::FeedEngineSettings> create_feed_engine_settings(const std::vector<CMEChannel>& channels);
|
||||
// const CMEOptions::CMEChannel channel_settings_for(const string& id);
|
||||
|
||||
// namespace test {
|
||||
|
||||
// vector<CMEChannel> test_channels(int count, ...) {
|
||||
// // Doing this the C way because I still haven't updated VS
|
||||
// vector<CMEChannel> result;
|
||||
// va_list ap;
|
||||
// va_start(ap, count);
|
||||
// for (int x = 0; x < count; ++x) {
|
||||
// const char* val = va_arg(ap, char*);
|
||||
// CMEChannel temp;
|
||||
// temp.id = val;
|
||||
// result.push_back(temp);
|
||||
// }
|
||||
// va_end(ap);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
// TEST_CASE("mdp3_tests","[md]") {
|
||||
// SECTION("test_defaults") {
|
||||
// config::internal::initialize_for_test("CMEOptions {}");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(1, "310");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// channels = test_channels(2, "310", "311");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// channels = test_channels(4, "310", "311", "400", "412");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// REQUIRE(!channel_settings_for("310").has_feed_engine());
|
||||
// REQUIRE(!channel_settings_for("311").has_feed_engine());
|
||||
// REQUIRE(!channel_settings_for("315").has_feed_engine());
|
||||
// }
|
||||
|
||||
// SECTION("test_segregate_futures") {
|
||||
// config::internal::initialize_for_test(
|
||||
// "CMEOptions {\n"
|
||||
// " value {\n"
|
||||
// " feed_engine {\n"
|
||||
// " name: 'default'\n"
|
||||
// " }\n"
|
||||
// " feed_engine {\n"
|
||||
// " name: 'futures'\n"
|
||||
// " num_threads: 4\n"
|
||||
// " }\n"
|
||||
// " # Futures\n"
|
||||
// " channel {\n"
|
||||
// " id: '310'\n"
|
||||
// " id: '312'\n"
|
||||
// " feed_engine: 'futures'\n"
|
||||
// " }\n"
|
||||
// " # Options\n"
|
||||
// " channel {\n"
|
||||
// " id: '311'\n"
|
||||
// " id: '313'\n"
|
||||
// " id: '315'\n"
|
||||
// " feed_engine: 'default'\n"
|
||||
// " }\n"
|
||||
// " }\n"
|
||||
// "}");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(1, "310");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// REQUIRE(result.begin()->first == "futures");
|
||||
// REQUIRE(result.begin()->second.threadsCount() == 4);
|
||||
|
||||
// channels = test_channels(1, "311");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// REQUIRE(result.begin()->first == "default");
|
||||
// REQUIRE(result.begin()->second.threadsCount() == 1);
|
||||
|
||||
// channels = test_channels(2, "311", "310");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 2);
|
||||
// REQUIRE(result["default"].threadsCount() == 1);
|
||||
// REQUIRE(result["futures"].threadsCount() == 4);
|
||||
|
||||
// // Test channels not listed in config
|
||||
// channels = test_channels(2, "317", "318");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
// }
|
||||
|
||||
// SECTION("test_no_default") {
|
||||
// config::internal::initialize_for_test(
|
||||
// "CMEOptions {\n"
|
||||
// " value {\n"
|
||||
// " feed_engine {\n"
|
||||
// " name: 'futures'\n"
|
||||
// " num_threads: 4\n"
|
||||
// " }\n"
|
||||
// " # any channel not listed will use the default feed engine\n"
|
||||
// " # Futures\n"
|
||||
// " channel {\n"
|
||||
// " id: '310'\n"
|
||||
// " id: '312'\n"
|
||||
// " feed_engine: 'futures'\n"
|
||||
// " }\n"
|
||||
// " # Options\n"
|
||||
// " channel {\n"
|
||||
// " id: '311'\n"
|
||||
// " id: '313'\n"
|
||||
// " id: '315'\n"
|
||||
// " use_tcp_replay: false\n"
|
||||
// " }\n"
|
||||
// " }\n"
|
||||
// "}");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(1, "310");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// REQUIRE(result.begin()->first == "futures");
|
||||
// REQUIRE(result.begin()->second.threadsCount() == 4);
|
||||
|
||||
// channels = test_channels(1, "311");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// channels = test_channels(2, "311", "310");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// REQUIRE(result["futures"].threadsCount() == 4);
|
||||
|
||||
|
||||
// REQUIRE(channel_settings_for("310").use_tcp_replay() == true);
|
||||
// REQUIRE(channel_settings_for("311").use_tcp_replay() == false);
|
||||
// REQUIRE(channel_settings_for("315").use_tcp_replay() == false);
|
||||
// }
|
||||
|
||||
// SECTION("test_one_thread_each") {
|
||||
// config::internal::initialize_for_test(
|
||||
// "CMEOptions {\n"
|
||||
// " value {\n"
|
||||
// " channel {\n"
|
||||
// " id: '*'\n"
|
||||
// " }\n"
|
||||
// " }\n"
|
||||
// "}\n");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(1, "310", "311");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// const auto& settings = channel_settings_for("311");
|
||||
// REQUIRE(!settings.has_feed_engine());
|
||||
// }
|
||||
|
||||
// SECTION("test_common_futures_thread") {
|
||||
// config::internal::initialize_for_test(
|
||||
// "CMEOptions {\n"
|
||||
// " value {\n"
|
||||
// " feed_engine {\n"
|
||||
// " name: 'futures'\n"
|
||||
// " num_threads: 4\n"
|
||||
// " }\n"
|
||||
// " channel {\n"
|
||||
// " id: '310'\n"
|
||||
// " feed_engine: 'futures'\n"
|
||||
// " udp_socket_buffer_size: 1234\n"
|
||||
// " }\n"
|
||||
// " channel {\n"
|
||||
// " id: '*'\n"
|
||||
// " }\n"
|
||||
// " }\n"
|
||||
// "}\n");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(6, "310", "311", "315", "100", "10", "42");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// result = create_feed_engine_settings(test_channels(1, "315"));
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// REQUIRE(!channel_settings_for("311").has_feed_engine());
|
||||
// REQUIRE(channel_settings_for("310").has_feed_engine());
|
||||
// REQUIRE(channel_settings_for("310").udp_socket_buffer_size() == 1234);
|
||||
// REQUIRE(channel_settings_for("311").has_udp_socket_buffer_size() == false);
|
||||
// }
|
||||
// }
|
||||
|
||||
// }}}//namespace
|
||||
+1
-10
@@ -2,11 +2,6 @@
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
||||
export PATH="$PATH:/root/.dotnet/tools"
|
||||
|
||||
# install npx
|
||||
if ! [ -x "$(command -v npx)" ]; then
|
||||
npm install -g npx
|
||||
fi
|
||||
|
||||
export OUT=${BUILD_DIR:-../../../build}/web
|
||||
MODE=manual
|
||||
case "$OUT" in
|
||||
@@ -39,9 +34,6 @@ PROTOC=../../../externals/linux/bin/protoc
|
||||
ORANGE='\033[0;33m'
|
||||
NOCOLOR='\033[0m'
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
# Run pbjs to produce protobuf module
|
||||
echo -e \* ${ORANGE} pbjs ${NOCOLOR}
|
||||
npx --no-install pbjs -t json-module -w commonjs -o $OUT/api/proto.js $PROTO_LIST
|
||||
@@ -79,11 +71,10 @@ if [ "$FULL" == "1" ]; then
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
||||
echo -e \* ${ORANGE} fable ${NOCOLOR}
|
||||
cd ../ui/
|
||||
npm install
|
||||
cd src
|
||||
mkdir -p $OUT/ui/
|
||||
cp -r ../public/* $OUT/ui/
|
||||
dotnet restore
|
||||
dotnet restore -s ../../../../externals/nuget/packages
|
||||
if [ "$MODE" == "release" ]; then
|
||||
dotnet fable webpack -- --env.BUILD_PATH=$OUT -p
|
||||
else
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libbase.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libutils.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libfix_lib.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxlog.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade_version.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclx.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac_version.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libcputimestamp.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxconfig.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libconfigfile.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libac.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/librc.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/liblldt.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libregister.so")
|
||||
|
||||
file(COPY ${CELOXICA_LIB} DESTINATION ${CMAKE_BINARY_DIR}/src/server/sparkd)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ if not exist build\src\lib\libspark.vcxproj (
|
||||
)
|
||||
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools\VsDevCmd.bat"
|
||||
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln /p:Configuration=release
|
||||
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln /p:Configuration=release
|
||||
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR_AND_END
|
||||
|
||||
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary spark_client.sln /p:Configuration=release
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#!/bin/bash
|
||||
BUILD=clang_debug
|
||||
BUILD_LOC=/home/ppinto/temp/spark_build
|
||||
|
||||
if [ -f /.dockerenv ]; then
|
||||
cd $BUILD_LOC/$BUILD && ninja
|
||||
(cd `readlink -f .` && ninja)
|
||||
else
|
||||
cid=`docker ps | grep -P "emacs_ide|bluetradingsystems/linux_docker" | awk '{print $1}'`
|
||||
docker exec $cid /bin/bash -c "cd $BUILD_LOC/$BUILD && ninja"
|
||||
docker exec $cid /bin/bash -c "(cd `readlink -f .` && ninja)"
|
||||
fi
|
||||
@@ -2,6 +2,6 @@
|
||||
setlocal
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\VsDevCmd.bat" > nul
|
||||
cd /d %~dp0/../
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore spark_client.sln
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
|
||||
|
||||
@@ -4,6 +4,11 @@ SRC_ROOT=$DIR/../
|
||||
mkdir -p $SRC_ROOT/build/
|
||||
BUILD_ROOT=`readlink -f $SRC_ROOT/build`
|
||||
|
||||
rm -f $SRC_ROOT/src/web/ui/node_modules
|
||||
rm -f $SRC_ROOT/src/web/api/node_modules
|
||||
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/ui/node_modules
|
||||
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/api/node_modules
|
||||
|
||||
rm -rf $BUILD_ROOT/clang_debug
|
||||
mkdir $BUILD_ROOT/clang_debug
|
||||
cd $BUILD_ROOT/clang_debug
|
||||
|
||||
Reference in new issue
Block a user