Compare commits

..
39 Commits
Author SHA1 Message Date
Taha Afzal f199cb4157 Fixed the broken test (needed to update the size attribute on the managed ColdEntry 2020-08-05 10:54:43 -04:00
Taha Afzal 5a3bfd7f81 Merge branch 'DJ_ICE_proprietary_risk' of bitbucket.org:bluetradesys/spark into DJ_ICE_proprietary_risk 2020-08-04 15:40:29 -04:00
Taha Afzal bf6461c126 More logs and TAS HNG can not be the master 2020-08-04 15:40:21 -04:00
John Colvin 8892505d11 RiskManagement price cells show 4 decimal places, use commas 2020-08-04 12:11:55 -05:00
John Colvin 5d0a29432a RiskManagementVM - row sorting must refer to immutable data (else binary search can break) 2020-08-04 12:11:55 -05:00
John Colvin cd01c7ae5a RiskManagementVM - remove unused props/methods 2020-08-04 12:11:55 -05:00
Taha Afzal bf19ec0fda Merge branch 'DJ_ICE_proprietary_risk' of bitbucket.org:bluetradesys/spark into DJ_ICE_proprietary_risk 2020-08-04 13:08:14 -04:00
John Colvin 1a0158817e remove size constraints on RiskManagementControl 2020-08-04 11:28:23 -05:00
John Colvin 53853591fe move RiskManagementControl.xaml/cs to 'View' dir 2020-08-04 11:28:23 -05:00
Taha Afzal 80d4899f2c removing fake data functions (when the UI was not around) 2020-08-04 09:51:32 -04:00
Taha Afzal 75f15c50d8 working long/short pos was not getting copied in copy_positions_from 2020-08-03 18:25:40 -04:00
John Colvin f197f213f3 Risk Management ui - open edit-rows dialog near mouse 2020-08-03 15:52:26 -05:00
John Colvin a7e1b71883 TextCell available_size is updated in ArrangeOverride 2020-08-03 15:28:41 -05:00
John Colvin f9f22e1e36 add risk management window 2020-08-03 15:28:41 -05:00
John Colvin cda50f562d AdminModel - add methods to subscribe, and modify proprietary risk limits (dj) 2020-08-03 15:28:41 -05:00
John Colvin 4010298a46 parameterize SecurityStore.join (don't mess with core logic/impl) 2020-08-03 15:28:41 -05:00
John Colvin 48e701ba00 client side data types that mirror api protos (minimize client/server coupling) 2020-08-03 15:28:41 -05:00
John Colvin cb07abbf42 made new SelectorVM (todo: replace refs to old one) 2020-08-03 15:28:41 -05:00
John Colvin 3c8e2c636f add SimpleGridSettingsManagerVM (uses DIYGridColumnModels instead of the view type ColumnBase) 2020-08-03 15:28:41 -05:00
John Colvin 6a3b4ebe75 add DIYGridColumnModel, so the DIYGrid column paradigm is cleanear mvvm 2020-08-03 15:28:41 -05:00
John Colvin 80256fbe12 add get/load_state methods to non-generic IDataGridColumnFilter 2020-08-03 15:28:41 -05:00
John Colvin a0a5d82139 add PropertyEqualityComparerAttribute (supports simple automatic equality impl) 2020-08-03 15:28:41 -05:00
John Colvin ee6b00b9f2 unify SecurityCriteria initialization with securities (use pretty_name if single sec) 2020-08-03 15:28:41 -05:00
Taha Afzal 9a494ce24f Parent Child Secrity Pos Limit 2020-08-03 16:25:24 -04:00
Taha Afzal ae78ebac9a All sec based risk done (master, slave remain) 2020-07-31 17:27:06 -04:00
Taha Afzal 5c11eecbd1 SecurityLimitNode keeps working qty and positions differently 2020-07-31 00:21:17 -04:00
Taha Afzal 78a47c33c7 Fixing order rejection message 2020-07-30 14:55:11 -04:00
Taha Afzal 9dcd80e3db Security level risk controls are passed to update order 2020-07-30 13:02:22 -04:00
Taha Afzal f7563413bd New security level controls hooked to order entry 2020-07-30 11:14:45 -04:00
Taha Afzal 29c418db42 Routed the SecLimit to SecurityLimitNode 2020-07-29 18:15:18 -04:00
Taha Afzal f177efde70 Added price arg to the Limiter's can_send and can_update functions 2020-07-29 14:28:16 -04:00
Taha Afzal 3b83d105a0 Filled in the prop risk limits persistence code 2020-07-29 12:29:56 -04:00
Taha Afzal 34aafd79cd Added new risk proto and Admin API function to update it 2020-07-29 11:30:36 -04:00
Taha Afzal d3de164c53 Moved the 2500 translation to the end points of the system 2020-07-27 17:05:19 -04:00
Taha Afzal 1d1c0d6b85 Jan exps' are showing 12 for the month, adjustment for risk 2020-07-21 12:09:41 -04:00
Taha Afzal 2f127ecc18 Using day positions for risk enforcement 2020-07-20 23:48:35 -04:00
Taha Afzal 3fc080d02c Better position violation comments 2020-07-20 23:21:20 -04:00
Taha Afzal d86543261c Risk enforcement changes for ICE NG OTC markets 2020-07-20 13:01:56 -04:00
Taha Afzal 1a7fcb2027 Added is_ice_lots to the ICE sec proto 2020-07-12 17:03:37 -04:00
323 changed files with 5405 additions and 152076 deletions

No files matched your search

-1
View File
@@ -37,4 +37,3 @@ trace_data/
*.rej
/build
.clangd/
*CMakeDeps.cmake
+5 -9
View File
@@ -8,15 +8,13 @@
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd",
"args": [
"run",
"test",
"-a",
"OutrightVanillaOrder"
// "test",
// "-a",
// "[ilink3]"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/server/",
"environment": [
{ "name": "TZ", "value": "America/Chicago" }
],
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
@@ -33,9 +31,7 @@
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": [
"test",
"-a",
"[ilink3]"
"run"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
-1
View File
@@ -41,7 +41,6 @@ else ()
-Wno-missing-braces -Wno-ignored-qualifiers \
--std=c++17 -fdiagnostics-color")
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wno-undef")
add_subdirectory(src/server/sparkd)
add_subdirectory(src/lib)
+1 -1
View File
@@ -1 +1 @@
set(sources /home/pfisher/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/eric/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+10
View File
@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
+51
View File
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace RiskUI {
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:RiskUI"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:RiskUI;assembly=RiskUI"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:CustomControl1/>
///
/// </summary>
public class CustomControl1 : Control {
static CustomControl1() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
}
+24
View File
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<RootNamespace>bts</RootNamespace>
<LangVersion>7.2</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<Version>$(major).$(minor).$(patch)</Version>
<Version Condition="'$(version)'=='..'">1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../spark_api/spark_api.csproj" />
<ProjectReference Include="../Spark/Spark.csproj" />
</ItemGroup>
</Project>
+17
View File
@@ -0,0 +1,17 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RiskUI">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
+105 -2
View File
@@ -1,24 +1,33 @@
using bts;
using bts.admin.risk;
using bts.api;
using bts.api.admin;
using bts.api.core;
using bts.spark;
using bts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
[Export]
class AdminModel : IDisposable {
public readonly UserTuple user;
readonly AdminService _service;
readonly ErrorReporter _error_reporter;
IDisposable _dispose;
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
[ImportingConstructor]
AdminModel(AdminService service, UserTuple user) {
AdminModel(AdminService service, UserTuple user, SecurityStore security_store, ErrorReporter error_reporter) {
this.user = user;
_service = service;
_error_reporter = error_reporter;
// accounts
on_accounts = service.subscribei_admin_data()
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
@@ -31,6 +40,34 @@ namespace mbts.spark {
new Dictionary<uint, string>(0))
.Replay(1)
.RefCount();
// account risk limits
account_security_risk_limits = service.subscribei_admin_data().Where(ad => ad.all.Any())
.Select(upd => {
AdminDataImmutable admin_data = upd.all.First();
return admin_data.Accounts.SelectMany(acc =>
(acc.HasProprietaryRiskLimits ? acc.ProprietaryRiskLimits.SecurityLimits : Enumerable.Empty<SecurityRiskLimitUnitImmutable>())
.Select(api_limit => AccountSecurityRiskLimitsDataHelper.to_client_data(acc.Id, api_limit)))
.ToDictionary(AccountSecurityRiskLimitsDataHelper.key_fn);
})
.Table(AccountSecurityRiskLimitsDataHelper.key_fn);
// this isn't the most efficient join, but the SecurityStore join is more complex than a simple
// left join operation, because missing securities are retrieved asynchronously, then merged back into the output sequence.
// Until it is known that this inefficiency is problematic, best to leave the battle-tested SecurityStore code alone.
account_security_risk_limits = security_store.join<
TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>,
AccountSecurityRiskLimits,
AccountSecurityKey,
TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>>(account_security_risk_limits,
cli_lim => cli_lim.security_key,
table => (table.state.Values, table.added, table.changes, table.removed, AccountSecurityRiskLimitsDataHelper.key_fn),
(all, added, changes, removed, key_fn) =>
new TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>(
ImmutableDictionary.CreateRange<AccountSecurityKey, AccountSecurityRiskLimits>(
all.Select(tup => new KeyValuePair<AccountSecurityKey, AccountSecurityRiskLimits>(tup.val.key, risk_lim_with_sec(tup)))),
added.Select(risk_lim_with_sec),
changes.Select(ch => new Change<AccountSecurityRiskLimits>(risk_lim_with_sec(ch.Old), risk_lim_with_sec(ch.New))),
removed.Select(risk_lim_with_sec),
AccountSecurityRiskLimitsDataHelper.key_fn));
// users
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
@@ -45,12 +82,16 @@ namespace mbts.spark {
.Cache() :
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
_dispose = on_accounts.Subscribe(map => _account_map = map);
// local methods
AccountSecurityRiskLimits risk_lim_with_sec((SecurityImmutable? sec, AccountSecurityRiskLimits val) tup) =>
tup.val.with(security: tup.sec);
}
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
public IObservable<TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>> account_security_risk_limits { get; }
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
user.permissions == UserTuple.Permissions.Admin ?
@@ -68,7 +109,69 @@ namespace mbts.spark {
public AccountTuple? get_default_account() =>
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
public Task<(bool success, string error_msg)> add_or_update_risk_limits(uint account_key, IEnumerable<(uint security_key, RiskLimits limits)> new_limits) {
return _modify_risk_limits("Updating Risk Limits", account_key, add_limits);
//
ProprietaryRiskLimits add_limits(ProprietaryRiskLimits prlimits) {
foreach (var (security_key, limits) in new_limits) {
var api_limits = AccountSecurityRiskLimitsDataHelper.from_client_data(security_key, limits);
if (prlimits.SecurityLimits.TryGetFirstIndex(l => l.SecurityKey == security_key, out int index)) {
prlimits.SecurityLimits[index] = api_limits;
} else {
prlimits.SecurityLimits.Add(api_limits);
}
}
return prlimits;
}
}
public Task<(bool success, string error_msg)> delete_risk_limits(uint account_key, IEnumerable<uint> security_keys) {
return _modify_risk_limits("Deleting Risk Limits", account_key, remove_secs);
//
ProprietaryRiskLimits remove_secs(ProprietaryRiskLimits prlimits) {
var sec_key_set = new HashSet<uint>(security_keys);
for (int i = 0; i < prlimits.SecurityLimits.Count; i++) {
if (sec_key_set.Contains(prlimits.SecurityLimits[i].SecurityKey))
prlimits.SecurityLimits.RemoveAt(i);
}
return prlimits;
}
}
public void Dispose() =>
Disposable.dispose(ref _dispose);
async Task<(bool success, string error_msg)> _modify_risk_limits(string op_desc, uint account_key, Func<ProprietaryRiskLimits, ProprietaryRiskLimits> modify) {
var api_limits = new ProprietaryRiskLimits();
var snapshot = await _get_current_admin_data_async();
if (!snapshot.HasValue)
return (false, "Timed out waiting for data from server.");
var mutable_snap = snapshot.Value.CloneToMutable();
if (!snapshot.Value.Accounts.TryGetFirst(acc => acc.Id == account_key, out var target_acci)) {
return (false, $"Account id '{account_key}' not found.");
}
var prlimits = target_acci.HasProprietaryRiskLimits ? target_acci.ProprietaryRiskLimits.CloneToMutable() : new ProprietaryRiskLimits();
prlimits = modify(prlimits);
if (prlimits is null) {
return (false, "Client-side modification function returned null.");
}
var req = new ProprietaryRiskLimitsAPIArgument {
AccountId = account_key,
ProprietaryRiskLimits = prlimits
};
var reply = await _error_reporter.safe_execute<AdminServiceOperationResult>(
() => _service.update_proprietary_risk_limits_async(req),
r => r.IsSuccess,
r => r.ErrorText,
op_desc);
return (reply.success, reply.value.ErrorText);
}
async Task<AdminDataImmutable?> _get_current_admin_data_async() =>
await _service.subscribei_admin_data()
.Where(upd => upd.all.Any())
.Select(upd => (AdminDataImmutable?)upd.all.First())
.FirstOrDefaultAsync(TimeSpan.FromSeconds(5));
//async Task<AdminDataImmutable?>
}
}
}
+180
View File
@@ -0,0 +1,180 @@
using bts.api;
using bts.api.admin;
using bts.utils;
using System;
namespace bts.admin.risk {
readonly struct AccountSecurityKey : IEquatable<AccountSecurityKey> {
public AccountSecurityKey(uint account, uint security) {
this.account = account;
this.security = security;
}
public uint account { get; }
public uint security { get; }
public bool Equals(AccountSecurityKey other) =>
security == other.security &&
account == other.account;
public override bool Equals(object obj) =>
obj is AccountSecurityKey other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase(security.GetHashCode(), account.GetHashCode());
}
readonly struct AccountSecurityRiskLimits : IEquatable<AccountSecurityRiskLimits> {
readonly AccountTuple? _account;
public AccountSecurityRiskLimits(AccountSecurityKey key, RiskLimits limits, SecurityImmutable? security = null, AccountTuple? account = null) {
this.key = key;
this.limits = limits;
this.security = security;
_account = account;
}
public AccountSecurityKey key { get; }
public uint account_key => key.account;
public uint security_key => key.security;
public string account_name => _account?.name;
public SecurityImmutable? security { get; }
public RiskLimits limits { get; }
public bool Equals(AccountSecurityRiskLimits other) =>
key.Equals(other.key) &&
limits.Equals(other.limits);
public override bool Equals(object obj) =>
obj is AccountSecurityRiskLimits other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase(key.GetHashCode(), limits.GetHashCode());
}
readonly struct RiskLimits : IEquatable<RiskLimits> {
public RiskLimits(
bool is_position_limit_enabled,
uint max_long_position,
uint max_short_position,
bool is_clip_limit_enabled,
uint clip_limit,
bool is_price_limit_enabled,
double min_price,
double max_price) {
//
this.is_position_limit_enabled = is_position_limit_enabled;
this.max_long_position = max_long_position;
this.max_short_position = max_short_position;
this.is_clip_limit_enabled = is_clip_limit_enabled;
this.clip_limit = clip_limit;
this.is_price_limit_enabled = is_price_limit_enabled;
this.min_price = min_price;
this.max_price = max_price;
}
// position
public bool is_position_limit_enabled { get; }
public uint max_long_position { get; }
public uint max_short_position { get; }
// clip
public bool is_clip_limit_enabled { get; }
public uint clip_limit { get; }
// price
public bool is_price_limit_enabled { get; }
public double min_price { get; }
public double max_price { get; }
public bool Equals(RiskLimits other) =>
is_position_limit_enabled == other.is_position_limit_enabled &&
max_long_position == other.max_long_position &&
max_short_position == other.max_short_position &&
is_clip_limit_enabled == other.is_clip_limit_enabled &&
clip_limit == other.clip_limit &&
is_price_limit_enabled == other.is_price_limit_enabled &&
min_price == other.min_price &&
max_price == other.max_price;
public override bool Equals(object obj) =>
obj is RiskLimits other && Equals(other);
public override int GetHashCode() {
const int non_bin_bits = 28;
// Evenly split the non-binary props across the first 28 bits.
// The last 3 bits are used to represent each of the 'is_enabled' (binary) props.
var hash = EqualityComparer.auto_phase(new[] {
max_long_position.GetHashCode(),
max_short_position.GetHashCode(),
clip_limit.GetHashCode(),
min_price.GetHashCode(),
max_price.GetHashCode()
}, non_bin_bits);
if (is_position_limit_enabled)
hash |= 1 << (non_bin_bits + 1);
if (is_clip_limit_enabled)
hash |= 1 << (non_bin_bits + 2);
if (is_price_limit_enabled)
hash |= 1 << (non_bin_bits + 3);
return hash;
}
}
static class AccountSecurityRiskLimitsDataHelper {
public static readonly Func<AccountSecurityRiskLimits, AccountSecurityKey> key_fn =
a => a.key;
// Don't make this a constructor. Subscribers to this data are discouraged
// from using the server side data (client/server coupling should be minimal).
public static AccountSecurityRiskLimits to_client_data(uint account_key, SecurityRiskLimitUnitImmutable api_data) =>
new AccountSecurityRiskLimits(
new AccountSecurityKey(account_key, api_data.SecurityKey),
new RiskLimits(
api_data.EnforceNetPosLimits,
api_data.NetLongPositionLimit,
api_data.NetShortPositionLimit,
api_data.EnforceClipLimits,
api_data.ClipSize,
api_data.EnforcePriceBands,
api_data.MinSellPrice,
api_data.MaxBuyPrice));
public static AccountSecurityRiskLimits with(this AccountSecurityRiskLimits cli_data, RiskLimits? limits = null, AccountTuple? account = null, SecurityImmutable? security = null) =>
new AccountSecurityRiskLimits(
cli_data.key,
limits ?? cli_data.limits,
account: account ?? new AccountTuple(cli_data.account_key, cli_data.account_name),
security: security ?? cli_data.security);
public static RiskLimits with(this RiskLimits limits,
bool? is_position_limit_enabled = null,
uint? max_long_position = null,
uint? max_short_position = null,
bool? is_clip_limit_enabled = null,
uint? clip_limit = null,
bool? is_price_limit_enabled = null,
double? min_price = null,
double? max_price = null) =>
new RiskLimits(
is_position_limit_enabled ?? limits.is_position_limit_enabled,
max_long_position ?? limits.max_long_position,
max_short_position ?? limits.max_short_position,
is_clip_limit_enabled ?? limits.is_clip_limit_enabled,
clip_limit ?? limits.clip_limit,
is_price_limit_enabled ?? limits.is_price_limit_enabled,
min_price ?? limits.min_price,
max_price ?? limits.max_price);
public static SecurityRiskLimitUnit from_client_data(uint security_key, RiskLimits limits) =>
new SecurityRiskLimitUnit {
SecurityKey = security_key,
NetLongPositionLimit = limits.max_long_position,
NetShortPositionLimit = limits.max_short_position,
MaxBuyPrice = limits.max_price,
MinSellPrice = limits.min_price,
ClipSize = limits.clip_limit,
EnforceNetPosLimits = limits.is_position_limit_enabled,
EnforceClipLimits = limits.is_clip_limit_enabled,
EnforcePriceBands = limits.is_price_limit_enabled
};
}
}
@@ -276,7 +276,7 @@ namespace mbts.spark {
ob_settings.filters = new bts.shell.orderbook.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
security_criteria = SecurityCriteria.from_securities(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
@@ -66,7 +66,7 @@ namespace mbts.spark {
if (_sorted_selected_security_criteria_backing != null) {
_selected_security_criteria_backing = _sorted_selected_security_criteria_backing;
} else if (_vm != null)
_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Select(r => r.security));
_selected_security_criteria_backing = SecurityCriteria.from_securities(_vm.selected_rows.Select(r => r.security));
}
return _selected_security_criteria_backing;
}
@@ -92,7 +92,7 @@ namespace mbts.spark {
}
}
var comp = comp_builder.to_comparer();
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
_sorted_selected_security_criteria_backing = SecurityCriteria.from_securities(_vm.selected_rows.Ordered(comp).Select(r => r.security));
}
}
return _sorted_selected_security_criteria_backing;
@@ -135,7 +135,7 @@ namespace bts.shell.time_and_sales {
.complete(selected_rows_src, always_true, get_fills_startup_data),
// mview
SparkCommands.ShowMarketView
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria),
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? SecurityCriteria.from_securities(rows.Select(r => r.security)) : _security_criteria),
// pause
toggle_is_paused_cmd,
// change securities
@@ -180,7 +180,7 @@ namespace bts.shell.time_and_sales {
FillsSettings get_fills_startup_data(ImmutableHashSet<TimeAndSalesRowVM> rows) =>
new FillsSettings(new FillsFilterStates {
security = rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria
security = rows.Count > 0 ? SecurityCriteria.from_securities(rows.Select(r => r.security)) : _security_criteria
});
string get_is_paused_cmd_title() =>
@@ -79,7 +79,7 @@ namespace mbts.spark {
), SparkCommands.ShowOrders.complete(() => {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
security_criteria = SecurityCriteria.from_securities(_vm.value.row_vms.Select(r => r.security))
};
return ob_settings;
}), SparkCommands.window_settings_command(new DialogCommand(() => {
@@ -169,7 +169,7 @@ namespace mbts.spark {
void _show_order_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
security_criteria = SecurityCriteria.from_securities(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
@@ -302,7 +302,7 @@ namespace mbts.spark {
var state = new bts.shell.orderbook.OrderBookStartupData(
new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_owner.security),
security_criteria = SecurityCriteria.from_securities(_owner.security),
price = price_filter,
statuses = new[] { OrderStatus.Working },
order_types = AlgoID.exchange_algo_keys
@@ -583,7 +583,7 @@ namespace mbts.spark {
void _show_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
security_criteria = SecurityCriteria.from_securities(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
@@ -633,7 +633,7 @@ namespace bts.shell.pnl {
case ColumnKeys.Exchange: filter.exchange = new[] { (Venue)group.Name }; break;
case ColumnKeys.Security:
if (group.Name is SecurityImmutable sec)
filter.security = new SecurityCriteria(sec);
filter.security = SecurityCriteria.from_securities(sec);
break;
default: throw new NotImplementedException();
}
@@ -696,7 +696,7 @@ namespace bts.shell.pnl {
case ColumnKeys.Exchange: filter.exchanges = new[] { (Venue)g.Name }; break;
case ColumnKeys.Security:
if (g.Name is SecurityImmutable sec)
filter.security_criteria = new SecurityCriteria(sec);
filter.security_criteria = SecurityCriteria.from_securities(sec);
break;
default: throw new NotImplementedException();
}
@@ -0,0 +1,188 @@
using bts.admin.risk;
using bts.api;
using bts.spark;
using mbts;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace bts.shell.risk_management {
class RiskManagementWindow : SparkWindow<RiskManagementSettings>, ISharedWindow {
const string
_window_type_id = "RiskManagement",
_window_type_display_name = "Risk Management";
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly AdminModel _model;
FrameworkElement _view;
IReadOnlyList<SparkCommand> _commands;
IDisposable _settings_sub, _securities_sub;
RiskManagementSettings _settings;
RiskManagementVM _vm;
SimpleGridSettingsManagerVM<RiskManagementControl.Columns> _grid_settings_manager => _vm.grid_settings_manager;
SparkMenuDisplayVM _menu_display_vm;
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
public RiskManagementWindow(
IBus bus,
AdminModel model,
IWindowSettingsManager<GridSettings> settings_manager,
ISaveWindow<RiskManagementSettings> isw,
RiskManagementSettings settings)
: base(_window_type_display_name, bus, isw, Icons.Risk) {
//
_settings_manager = settings_manager;
_settings = settings;
_model = model;
thread_id = mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().risk_management ? "OrderBook" : null;
_securities_sub = bus.subscribe(SparkTags.SecuritiesTag).Subscribe(sec_map => _security_map = sec_map);
}
public override IEnumerable<SparkCommand> Commands => _commands;
public override string window_type => _window_type_id;
public override RiskManagementSettings SaveState =>
(_vm?.create() ?? RiskManagementSettings.Default).with(menu_display: _menu_display_vm.safe_create());
public override UIElement Content => _view;
public override double MinWidth =>
RiskManagementSettings.DefaultGrid.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public override void Dispose() {
_commands.empty_if_null().OfType<IDisposable>().DisposeAll();
Disposable.dispose(ref _securities_sub);
Disposable.dispose(ref _settings_sub);
Disposable.dispose(ref _vm);
}
public void thread_init() {
// assert nothing has been initialized yet
Log.assert(_menu_display_vm == null, "MenuDisplay viewmodel has already been initialized.");
Log.assert(_vm == null, "Viewmodel has already been initialized.");
Log.assert(_settings != null, "Settings is null.");
Log.assert(_settings_sub == null, "Grid settings subscription has already been initialized.");
// initialize everything: menu_display_vm, vm, settings subscription, and view
_menu_display_vm = _init_menu_display_vm(_settings.menu_display);
_vm = _init_vm(_model, _settings);
_settings_sub = _settings_manager.subscribe_current()
.OnDispatcher()
.Subscribe(gs => _vm.load_from(gs));
_commands = new List<SparkCommand>(_mk_commands());
_view = new RiskManagementControl { DataContext = _vm };
}
static SparkMenuDisplayVM _init_menu_display_vm(SparkMenuDisplaySettings settings) {
var vm = new SparkMenuDisplayVM();
vm.load_from(settings);
return vm;
}
static RiskManagementVM _init_vm(AdminModel model, RiskManagementSettings settings) {
var vm = new RiskManagementVM(model.account_security_risk_limits, model.on_account_tuples);
vm.request_update_limit += update_limit;
vm.load_from(settings);
return vm;
void update_limit(AccountSecurityRiskLimits lim) =>
model.add_or_update_risk_limits(lim.account_key, new[] { (lim.security_key, lim.limits) }).ignore();
}
IEnumerable<SparkCommand> _mk_commands() {
yield return new SparkCommandCommand("Add New Limits", Icons.AddBoxes, new DialogCommand(() => {
if (_vm is null)
return null;
var account_id = _vm.selected_account?.id ?? 0;
var init_state = new RiskLimitsModification(
is_position_limit_enabled: true,
is_clip_limit_enabled: true,
is_price_limit_enabled: true);
return mk_row_editor_dialog(init_state, commit, true);
void commit(RiskLimitsModification result) {
impl();
async void impl() {
var target_secs = await result.security_criteria.securities(_bus, TimeSpan.FromSeconds(5));
if (target_secs.Count > 0) {
var new_limits = result.apply(new RiskLimits());
await _model.add_or_update_risk_limits(account_id, target_secs.Keys.Select(k => (k, new_limits)));
}
}
}
}));
var edit_selected_rows_cmd = new DialogCommand(() => {
if (!(_vm?.selected_account).HasValue)
return null;
var account_id = _vm.selected_account.Value.id;
var targets = _vm.selected_rows.Select(r => r.create()).ToList();
var init_state = RiskLimitsModification.aggregate_common_props(targets);
return mk_row_editor_dialog(init_state, commit, false);
void commit(RiskLimitsModification mod) =>
_model.add_or_update_risk_limits(account_id, targets.Select(r => (r.security_key, mod.apply(r.limits)))).ignore();
}, () => _vm.selected_rows.Count > 0, true, off_x: -300, off_y: -60);
_vm.selected_rows_changed += edit_selected_rows_cmd.RaiseCanExecuteChanged;
yield return new SparkCommandCommand("Edit Selected Rows", Icons.ModifyOrder, edit_selected_rows_cmd);
var delete_selected_rows_cmd = new DelegateCommand(() => {
if (!(_vm?.selected_account).HasValue)
return;
var selected_rows = _vm.selected_rows;
var account_id = _vm.selected_account.Value.id;
if (BTSMessageBox.Confirm($"Are you sure you want to delete the selected limits ({selected_rows.Count})?", "Delete Limits"))
_model.delete_risk_limits(account_id, selected_rows.Select(r => r.security_key)).ignore();
}, () => _vm.selected_rows.Count > 0);
_vm.selected_rows_changed += delete_selected_rows_cmd.RaiseCanExecuteChanged;
yield return new SparkCommandCommand("Delete", Icons.Trash, delete_selected_rows_cmd);
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
yield return SparkCommands.window_settings_command(new DialogCommand(() => {
if (_vm is null)
return null;
_settings_manager.instance_settings = _vm.create().grid;
var settings_vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.risk_management_grid,
UserSettings.FileExtensions.GRID,
_grid_settings_manager.create_vm
);
settings_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => settings_vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = settings_vm };
settings_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
yield return _menu_display_vm.spark_cmd;
// although only the api only requires security keys,
// the actual securities are used in this method so the user can see them in the view
Window mk_row_editor_dialog(RiskLimitsModification init_state, Action<RiskLimitsModification> commit, bool is_add) {
var limits_vm = new RiskLimitsModificationVM(is_add) {
security_map = _security_map
};
limits_vm.load_from(init_state);
limits_vm.request_commit += () => commit(limits_vm.create());
var (title, icon) = is_add ? ("New Limits", Icons.AddBoxes) : ("Edit Limits", Icons.ModifyOrder);
var dialog = new RiskLimitParametersDialog {
DataContext = limits_vm,
Icon = icon,
Title = title
};
dialog.SetBinding(ContentControl.ContentProperty, new Binding());
WindowUtilities.listen_for_close_requests(dialog);
return dialog;
}
}
}
}
@@ -0,0 +1,34 @@
using bts.api;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace bts.shell.risk_management {
class RiskManagementWindowFactory : ShellWindowFactory<RiskManagementSettings> {
readonly IBus _bus;
readonly AdminModel _model;
readonly UserSettingsHub _settings_hub;
public RiskManagementWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, AdminModel model) : base(owner, "RiskManagement") {
_bus = bus;
_model = model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowRiskManagement)
.OnDispatcher()
.Subscribe(show);
async void show(RiskManagementSettings settings) {
var win = await load(settings);
bus.post(SparkTags.ShowWindow, win);
}
}
public override Task<IShellWindow> load(RiskManagementSettings state) =>
state is null ?
throw new ArgumentNullException(nameof(state)) :
Task.FromResult<IShellWindow>(new RiskManagementWindow(_bus, _model, _settings_hub.create_risk_management_settings_manager(state.grid), this, state));
}
}
@@ -0,0 +1,138 @@
using bts.admin.risk;
using bts.utils;
using mbts.spark.filters;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace bts.shell.risk_management {
[ProtoContract]
class RiskManagementFilterSettings : IEquatable<RiskManagementFilterSettings> {
static readonly IEqualityComparer<RiskManagementFilterSettings> _comparer =
ObjectSnapshot.property_equality_comparer<RiskManagementFilterSettings>();
public RiskManagementFilterSettings() { }
public RiskManagementFilterSettings(
string security_regex,
string security_key_regex,
UIntMinMaxState max_long_position,
UIntMinMaxState max_short_position,
bool? is_position_limit_enabled,
UIntMinMaxState clip_limit,
bool? is_clip_limit_enabled,
DoubleMinMaxState min_price,
DoubleMinMaxState max_price,
bool? is_price_limit_enabled) {
// security
this.security_regex = security_regex;
this.security_key_regex = security_key_regex;
// position
this.max_long_position = max_long_position;
this.max_short_position = max_short_position;
this.is_position_limit_enabled = is_position_limit_enabled;
// clip
this.clip_limit = clip_limit;
this.is_clip_limit_enabled = is_clip_limit_enabled;
// price
this.min_price = min_price;
this.max_price = max_price;
this.is_price_limit_enabled = is_price_limit_enabled;
}
// security
[ProtoMember(1)]
public string security_regex { get; }
[ProtoMember(2)]
public string security_key_regex { get; }
// position
[ProtoMember(3)]
public UIntMinMaxState max_long_position { get; }
[ProtoMember(4)]
public UIntMinMaxState max_short_position { get; }
[ProtoMember(5)]
public bool? is_position_limit_enabled { get; }
// clip
[ProtoMember(6)]
public UIntMinMaxState clip_limit { get; }
[ProtoMember(7)]
public bool? is_clip_limit_enabled { get; }
// price
[ProtoMember(8)]
public DoubleMinMaxState min_price { get; }
[ProtoMember(9)]
public DoubleMinMaxState max_price { get; }
[ProtoMember(10)]
public bool? is_price_limit_enabled { get; }
// 'is limit enabled' items (for filter dropdown)
public bool[] is_position_limit_enabled_items => yes_no_filter_items(is_position_limit_enabled);
public bool[] is_clip_limit_enabled_items => yes_no_filter_items(is_clip_limit_enabled);
public bool[] is_price_limit_enabled_items => yes_no_filter_items(is_price_limit_enabled);
public static bool[] yes_no_filter_items(bool? state) =>
state.HasValue ? new[] { state.Value } : Array.Empty<bool>();
public static bool? yes_no_filter_state(IEnumerable<bool> selected_items) {
var set = new HashSet<bool>(selected_items);
return set.Count == 1 ? set.First() : (bool?)null;
}
public Func<AccountSecurityRiskLimits, bool> build_predicate() {
var pb = new ColumnPredicateBuilder<AccountSecurityRiskLimits>();
// security
pb.add_regex(security_regex, l => l.security.ToString());
pb.add_regex(security_key_regex, l => l.security_key.ToString());
// position
pb.add_numeric(max_long_position, l => l.limits.max_long_position);
pb.add_numeric(max_short_position, l => l.limits.max_short_position);
pb.add_discrete_values(is_position_limit_enabled_items, l => l.limits.is_position_limit_enabled);
// clip
pb.add_numeric(clip_limit, l => l.limits.clip_limit);
pb.add_discrete_values(is_clip_limit_enabled_items, l => l.limits.is_clip_limit_enabled);
// price
pb.add_numeric(min_price, l => l.limits.min_price);
pb.add_numeric(max_price, l => l.limits.max_price);
pb.add_discrete_values(is_price_limit_enabled_items, l => l.limits.is_price_limit_enabled);
return pb.to_predicate();
}
public RiskManagementFilterSettings with(
string security_regex = null,
string security_key_regex = null,
UIntMinMaxState max_long_position = null,
UIntMinMaxState max_short_position = null,
OptionalNullable<bool>? is_position_limit_enabled = null,
UIntMinMaxState clip_limit = null,
OptionalNullable<bool>? is_clip_limit_enabled = null,
DoubleMinMaxState min_price = null,
DoubleMinMaxState max_price = null,
OptionalNullable<bool>? is_price_limit_enabled = null) =>
//
new RiskManagementFilterSettings(
// security
security_regex ?? this.security_regex,
security_key_regex ?? this.security_key_regex,
// position
max_long_position ?? this.max_long_position,
max_short_position ?? this.max_short_position,
is_position_limit_enabled.HasValue ? is_position_limit_enabled.Value.value : this.is_position_limit_enabled,
// clip
clip_limit ?? this.clip_limit,
is_clip_limit_enabled.HasValue ? is_clip_limit_enabled.Value.value : this.is_clip_limit_enabled,
// price
min_price ?? this.min_price,
max_price ?? this.max_price,
is_price_limit_enabled.HasValue ? is_price_limit_enabled.Value.value : this.is_price_limit_enabled);
public bool Equals(RiskManagementFilterSettings other) =>
_comparer.Equals(this, other);
public override bool Equals(object obj) =>
obj is RiskManagementFilterSettings other && Equals(other);
public override int GetHashCode() =>
_comparer.GetHashCode(this);
}
}
@@ -0,0 +1,93 @@
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Columns = bts.shell.risk_management.RiskManagementControl.Columns;
using ColumnWidths = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
using ColumnHeaders = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
namespace bts.shell.risk_management {
[ProtoContract]
class RiskManagementSettings {
[ProtoMember(3)]
SortingCriteria[] _sorting_criteria;
public RiskManagementSettings() { }
public RiskManagementSettings(
GridSettings grid = null,
RiskManagementFilterSettings filters = default,
IEnumerable<SortingCriteria> sorting_criteria = null,
uint account_id = 0,
SparkMenuDisplaySettings? menu_display = null) {
//
this.grid = grid;
this.filters = filters ?? new RiskManagementFilterSettings();
_sorting_criteria = sorting_criteria?.ToArray();
this.account_id = account_id;
this.menu_display = menu_display ?? SparkMenuDisplaySettings.Default;
}
public static RiskManagementSettings Default => new RiskManagementSettings(grid: DefaultGrid);
public static GridSettings DefaultGrid => new GridSettings(columns: DefaultColumns.all);
[ProtoMember(1)]
public GridSettings grid { get; }
[ProtoMember(2)]
public RiskManagementFilterSettings filters { get; }
[ProtoMember(4)]
public uint account_id { get; }
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display { get; }
public IReadOnlyList<SortingCriteria> sorting_criteria =>
_sorting_criteria ?? Array.Empty<SortingCriteria>();
public RiskManagementSettings with(
GridSettings grid = null,
RiskManagementFilterSettings filters = default,
IEnumerable<SortingCriteria> sorting_criteria = null,
uint? account_id = null,
SparkMenuDisplaySettings? menu_display = null) =>
//
new RiskManagementSettings(
grid ?? this.grid,
filters ?? this.filters,
sorting_criteria ?? this.sorting_criteria,
account_id ?? this.account_id,
menu_display ?? this.menu_display);
public static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), ColumnHeaders.Security, width: ColumnWidths.SecurityKey);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), ColumnHeaders.SecurityKey, width: ColumnWidths.SecurityKey, is_visible: false);
// position
public static GridColumnSettings max_long => new GridColumnSettings(Columns.MaxNetLongPosition.ToString(), "Max Long", width: 60, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings max_short => new GridColumnSettings(Columns.MaxNetShortPosition.ToString(), "Max Short", width: 60, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings is_position_limit_enabled => new GridColumnSettings(Columns.IsPositionLimitEnabled.ToString(), "Enforce Pos", width: 70, horizontal_alignment: HorizontalAlignment.Center);
// clip
public static GridColumnSettings clip_limit => new GridColumnSettings(Columns.ClipLimit.ToString(), "Clip Limit", horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings is_clip_limit_enabled => new GridColumnSettings(Columns.IsClipLimitEnabled.ToString(), "Enforce Clip", width: 70, horizontal_alignment: HorizontalAlignment.Center);
// price
public static GridColumnSettings min_price => new GridColumnSettings(Columns.MinPrice.ToString(), "Min Price", horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings max_price => new GridColumnSettings(Columns.MaxPrice.ToString(), "Max Price", horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings is_price_limit_enabled => new GridColumnSettings(Columns.IsPriceLimitEnabled.ToString(), "Enforce Price", width: 75, horizontal_alignment: HorizontalAlignment.Center);
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return max_long;
yield return max_short;
yield return is_position_limit_enabled;
yield return clip_limit;
yield return is_clip_limit_enabled;
yield return min_price;
yield return max_price;
yield return is_price_limit_enabled;
yield return security_key;
}
}
}
}
}
@@ -0,0 +1,179 @@
using bts.admin.risk;
using bts.api;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace bts.shell.risk_management {
class RiskLimitsModificationVM : ViewModelBase, IValueFactory<RiskLimitsModification>, IRequestViewClose {
readonly DeferrableActionWrapper _result_changed;
RiskLimitsModification _modifications;
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
public RiskLimitsModificationVM(bool can_edit_security) {
_result_changed = new DeferrableActionWrapper(() => result_changed?.Invoke());
this.can_edit_security = can_edit_security;
commit_cmd = new DelegateCommand(commit);
cancel_cmd = new DelegateCommand(close);
//
void commit() {
request_commit?.Invoke();
close();
}
void close() =>
RequestClose?.Invoke(this, EventArgs.Empty);
}
public event Action result_changed, request_commit;
public event EventHandler RequestClose;
public SecurityImmutable? security { get => _modifications.security; set => _set_limits(_modifications.with(security: value)); }
public SecurityCriteria security_criteria {
get => _modifications.security_criteria ?? (security.HasValue ? SecurityCriteria.from_securities(security.Value) : null);
set => _set_limits(_modifications.with(security_criteria: value));
}
public IReadOnlyDictionary<uint, SecurityMD> security_map {
get => _security_map;
set {
_security_map = value;
RaisePropertyChanged();
}
}
public bool can_edit_security { get; }
// position
public uint? max_long_position { get => _modifications.max_long_position; set => _set_limits(_modifications.with(max_long_position: value)); }
public uint? max_short_position { get => _modifications.max_short_position; set => _set_limits(_modifications.with(max_short_position: value)); }
public bool? is_position_limit_enabled { get => _modifications.is_position_limit_enabled; set => _set_limits(_modifications.with(is_position_limit_enabled: value)); }
// clip
public bool? is_clip_limit_enabled { get => _modifications.is_clip_limit_enabled; set => _set_limits(_modifications.with(is_clip_limit_enabled: value)); }
public uint? clip_limit { get => _modifications.clip_limit; set => _set_limits(_modifications.with(clip_limit: value)); }
// price
public double? max_price { get => _modifications.max_price; set => _set_limits(_modifications.with(max_price: value)); }
public double? min_price { get => _modifications.min_price; set => _set_limits(_modifications.with(min_price: value)); }
public bool? is_price_limit_enabled { get => _modifications.is_price_limit_enabled; set => _set_limits(_modifications.with(is_price_limit_enabled: value)); }
public ICommand commit_cmd { get; }
public ICommand cancel_cmd { get; }
public RiskLimitsModification create() =>
_modifications;
public void load_from(RiskLimitsModification value) {
using (_result_changed.defer_execution())
using (detect_property_changes())
_modifications = value;
}
void _set_limits(RiskLimitsModification new_modifications, [CallerMemberName] string prop = "") =>
_set_limits(new_modifications, new[] { prop });
void _set_limits(RiskLimitsModification new_modifications, params string[] invalidate_props) {
if (!_modifications.Equals(new_modifications)) {
_modifications = new_modifications;
RaisePropertyChanged(invalidate_props);
_result_changed.execute();
}
}
}
readonly struct RiskLimitsModification {
public RiskLimitsModification(
SecurityImmutable? security = null,
SecurityCriteria security_criteria = null,
uint? max_long_position = null,
uint? max_short_position = null,
bool? is_position_limit_enabled = null,
uint? clip_limit = null,
bool? is_clip_limit_enabled = null,
double? min_price = null,
double? max_price = null,
bool? is_price_limit_enabled = null) {
//
this.security = security;
this.security_criteria = security_criteria;
this.max_long_position = max_long_position;
this.max_short_position = max_short_position;
this.is_position_limit_enabled = is_position_limit_enabled;
this.clip_limit = clip_limit;
this.is_clip_limit_enabled = is_clip_limit_enabled;
this.min_price = min_price;
this.max_price = max_price;
this.is_price_limit_enabled = is_price_limit_enabled;
}
public SecurityImmutable? security { get; }
public SecurityCriteria security_criteria { get; }
// position
public uint? max_long_position { get; }
public uint? max_short_position { get; }
public bool? is_position_limit_enabled { get; }
// clip
public uint? clip_limit { get; }
public bool? is_clip_limit_enabled { get; }
// price
public double? min_price { get; }
public double? max_price { get; }
public bool? is_price_limit_enabled { get; }
public static RiskLimitsModification aggregate_common_props(IEnumerable<AccountSecurityRiskLimits> limit_items) =>
new RiskLimitsModification(
_common_value(limit_items, l => l.security, s => s),
null,
_common_value(limit_items, l => l.limits.max_long_position),
_common_value(limit_items, l => l.limits.max_short_position),
_common_value(limit_items, l => l.limits.is_position_limit_enabled),
_common_value(limit_items, l => l.limits.clip_limit),
_common_value(limit_items, l => l.limits.is_clip_limit_enabled),
_common_value(limit_items, l => l.limits.min_price),
_common_value(limit_items, l => l.limits.max_price),
_common_value(limit_items, l => l.limits.is_price_limit_enabled));
public RiskLimits apply(RiskLimits target) =>
new RiskLimits(
is_position_limit_enabled: is_position_limit_enabled ?? target.is_position_limit_enabled,
max_long_position: max_long_position ?? target.max_long_position,
max_short_position: max_short_position ?? target.max_short_position,
is_clip_limit_enabled: is_clip_limit_enabled ?? target.is_clip_limit_enabled,
clip_limit: clip_limit ?? target.clip_limit,
is_price_limit_enabled: is_price_limit_enabled ?? target.is_price_limit_enabled,
min_price: min_price ?? target.min_price,
max_price: max_price ?? target.max_price);
public RiskLimitsModification with(
OptionalNullable<SecurityImmutable>? security = null,
SecurityCriteria security_criteria = null,
OptionalNullable<uint>? max_long_position = null,
OptionalNullable<uint>? max_short_position = null,
OptionalNullable<bool>? is_position_limit_enabled = null,
OptionalNullable<uint>? clip_limit = null,
OptionalNullable<bool>? is_clip_limit_enabled = null,
OptionalNullable<double>? min_price = null,
OptionalNullable<double>? max_price = null,
OptionalNullable<bool>? is_price_limit_enabled = null) =>
//
new RiskLimitsModification(
security.HasValue ? security.Value.value : this.security,
security_criteria ?? this.security_criteria,
max_long_position.HasValue ? max_long_position.Value.value : this.max_long_position,
max_short_position.HasValue ? max_short_position.Value.value : this.max_short_position,
is_position_limit_enabled.HasValue ? is_position_limit_enabled.Value.value : this.is_position_limit_enabled,
clip_limit.HasValue ? clip_limit.Value.value : this.clip_limit,
is_clip_limit_enabled.HasValue ? is_clip_limit_enabled.Value.value : this.is_clip_limit_enabled,
min_price.HasValue ? min_price.Value.value : this.min_price,
max_price.HasValue ? max_price.Value.value : this.max_price,
is_price_limit_enabled.HasValue ? is_price_limit_enabled.Value.value : this.is_price_limit_enabled);
static T? _common_value<T>(IEnumerable<AccountSecurityRiskLimits> source, Func<AccountSecurityRiskLimits, T> get) where T : struct =>
_common_value(source, get, common => new T?(common));
static TResult _common_value<T, TResult>(IEnumerable<AccountSecurityRiskLimits> source, Func<AccountSecurityRiskLimits, T> get, Func<T, TResult> convert_common, TResult default_value = default) =>
source.Select(get).Distinct().TryGetSingle(out var common) ? convert_common(common) : default_value;
}
}
@@ -0,0 +1,90 @@
using bts.admin.risk;
using bts.api;
using bts.utils;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Runtime.CompilerServices;
namespace bts.shell.risk_management {
class RiskLimitsVM : ViewModelBase, IValueFactory<AccountSecurityRiskLimits> {
readonly DeferrableActionWrapper _result_changed;
// The user can modify some props directly in the grid.
// However, the sorting relies on immutable data
// (binary search may break if the applicable comparisons change,
// depending on the current sorting criteria).
// As a work-around, the rows are sorted by the committed_data.
// Any modifications made through the prop setters are assumed to be 'user-changes'
// (as the props are bound to the view),
/// and the committed data is only changed through <see cref="load_from(AccountSecurityRiskLimits)"/>.
AccountSecurityRiskLimits _committed_data, _mutable_data;
public RiskLimitsVM() =>
_result_changed = new DeferrableActionWrapper(() => result_changed?.Invoke());
public RiskLimitsVM(AccountSecurityRiskLimits data) : this() {
_committed_data = data;
_mutable_data = data;
}
public event Action result_changed;
public SecurityImmutable? security => _committed_data.security;
public uint security_key => _committed_data.security_key;
public string account_name => _committed_data.account_name;
public uint account_key => _committed_data.account_key;
// position limit
public bool is_position_limit_enabled { get => _mutable_limits.is_position_limit_enabled; set => _set_limits(_mutable_limits.with(is_position_limit_enabled: value)); }
public uint max_long_position { get => _mutable_limits.max_long_position; set => _set_limits(_mutable_limits.with(max_long_position: value)); }
public uint max_short_position { get => _mutable_limits.max_short_position; set => _set_limits(_mutable_limits.with(max_short_position: value)); }
// clip limit
public bool is_clip_limit_enabled { get => _mutable_limits.is_clip_limit_enabled; set => _set_limits(_mutable_limits.with(is_clip_limit_enabled: value)); }
public uint clip_limit { get => _mutable_limits.clip_limit; set => _set_limits(_mutable_limits.with(clip_limit: value)); }
// price limit
public bool is_price_limit_enabled { get => _mutable_limits.is_price_limit_enabled; set => _set_limits(_mutable_limits.with(is_price_limit_enabled: value)); }
public double max_price { get => _mutable_limits.max_price; set => _set_limits(_mutable_limits.with(max_price: value)); }
public double min_price { get => _mutable_limits.min_price; set => _set_limits(_mutable_limits.with(min_price: value)); }
public AccountSecurityKey row_id => new AccountSecurityKey(account_key, security_key);
public AccountSecurityRiskLimits committed_data => _committed_data;
RiskLimits _mutable_limits {
get => _mutable_data.limits;
set => _mutable_data = _mutable_data.with(limits: value);
}
public AccountSecurityRiskLimits create() => _mutable_data;
public void load_from(AccountSecurityRiskLimits value) {
using (_result_changed.defer_execution())
using (detect_property_changes()) {
_committed_data = value;
_mutable_data = value;
}
}
public void apply_modifications(RiskLimitsModification m) =>
load_from(_committed_data.with(
_committed_data.limits.with(m.is_position_limit_enabled,
m.max_long_position,
m.max_short_position,
m.is_clip_limit_enabled,
m.clip_limit,
m.is_price_limit_enabled,
m.min_price,
m.max_short_position),
new AccountTuple(_committed_data.account_key, _committed_data.account_name),
m.security));
void _set_limits(RiskLimits new_limits, [CallerMemberName] string prop = "") =>
_set_limits(new_limits, new[] { prop });
void _set_limits(RiskLimits new_limits, params string[] invalidate_props) {
if (!_mutable_limits.Equals(new_limits)) {
_mutable_limits = new_limits;
RaisePropertyChanged(invalidate_props);
_result_changed.execute();
}
}
}
}
@@ -0,0 +1,313 @@
using bts.admin.risk;
using bts.api.core;
using bts.collections;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Controls;
using Columns = bts.shell.risk_management.RiskManagementControl.Columns;
namespace bts.shell.risk_management {
class RiskManagementColumnModel : DIYGridColumnModel<Columns, AccountSecurityRiskLimits, RiskManagementFilterSettings> {
public RiskManagementColumnModel(Columns id, IComparer<AccountSecurityRiskLimits> data_comparer, IDataGridColumnFilter<AccountSecurityRiskLimits> filter, string filter_template, Func<RiskManagementFilterSettings, object> get_column_filter_settings, Func<RiskManagementFilterSettings, object, RiskManagementFilterSettings> set_column_filter_settings)
: base(id, data_comparer, filter, filter_template, get_column_filter_settings, set_column_filter_settings) {
}
}
class RiskManagementVM : DisposableViewModelBase, IHandleGridSorting, IHandleRowSelection, IValueFactory<RiskManagementSettings> {
static readonly IComparer<AccountSecurityRiskLimits> _data_row_id_comparer =
new ComparerBuilder<AccountSecurityKey>(new[] {
Comparer<uint>.Default.wrap<uint, AccountSecurityKey>(ask => ask.account),
Comparer<uint>.Default.wrap<uint, AccountSecurityKey>(ask => ask.security)
}).to_comparer()
.wrap<AccountSecurityKey, AccountSecurityRiskLimits>(r => r.key);
static IComparer<RiskLimitsVM> _row_id_comparer =>
_data_row_id_comparer.wrap<AccountSecurityRiskLimits, RiskLimitsVM>(r => r.committed_data);
readonly DeferrableActionWrapper _result_changed, _refresh_filter_changed, _selected_rows_changed;
readonly ColumnSortManager _sort_manager;
readonly ObservableCollection<RiskManagementColumnModel> _columns = new ObservableCollection<RiskManagementColumnModel>();
readonly Dictionary<Columns, RiskManagementColumnModel> _column_map = new Dictionary<Columns, RiskManagementColumnModel>();
readonly SelectorVM<AccountTuple?, uint> _accounts_vm = new SelectorVM<AccountTuple?, uint>(acc => acc?.id ?? 0) { comparer = Comparer<AccountTuple>.Default.to_nullable() };
IReadOnlyDictionary<AccountSecurityKey, AccountSecurityRiskLimits> _cache = DictionaryHelper.empty<AccountSecurityKey, AccountSecurityRiskLimits>();
ImmutableSortedSet<RiskLimitsVM> _selected_rows = ImmutableSortedSet.Create(_row_id_comparer);
IList<AccountSecurityRiskLimits> _data_rows;
IComparer<AccountSecurityRiskLimits> _data_comparer = _data_row_id_comparer;
IComparer<RiskLimitsVM> _row_comparer;
public RiskManagementVM(IObservable<TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>> data_source, IObservable<IReadOnlyList<AccountTuple>> accounts_source) {
_result_changed = new DeferrableActionWrapper(() => result_changed?.Invoke());
_selected_rows_changed = new DeferrableActionWrapper(() => selected_rows_changed?.Invoke());
_sort_manager = new ColumnSortManager(_columns.OfType<ISortableDIYGridColumn>(), null);
_data_rows = rows.WrapSelector(r => r.committed_data, lim => new RiskLimitsVM(lim));
_accounts_vm.selected_item_changed += () => {
RaisePropertyChanged(nameof(selected_account));
_result_changed.execute();
_refresh_filter_changed.execute();
};
column_map = FunctionalIndex.create<string, RiskManagementColumnModel>(_find_column);
grid_settings_manager = new SimpleGridSettingsManagerVM<Columns>(
_columns, null, ordered_cols => {
CollectionHelper.sync_with_never_duplicate(_columns, ordered_cols, cs => {
throw new InvalidOperationException();
}, c => c.id.ToString(), cs => cs.id);
}, null);
_refresh_filter_changed = new DeferrableActionWrapper(() => _filter_changed?.Invoke(_mk_filter()));
/////////////////
// Columns
/////////////////
// security
mk_column_model(
Columns.Security,
wrap_comp(r => r.security),
new RegexFilterVM<AccountSecurityRiskLimits>(r => r.security.ToString(), null),
DataGridFilterKnownTemplates.Regex,
fs => fs.security_regex,
(fs, v) => fs.with(security_regex: v));
// security key
mk_column_model(
Columns.SecurityKey,
wrap_comp(r => r.security_key),
new RegexFilterVM<AccountSecurityRiskLimits>(r => r.security_key.ToString(), null),
DataGridFilterKnownTemplates.Regex,
fs => fs.security_key_regex,
(fs, v) => fs.with(security_key_regex: v));
// max long
mk_column_model(
Columns.MaxNetLongPosition,
wrap_comp(r => r.limits.max_long_position),
new UIntMinMax<AccountSecurityRiskLimits>(
r => r.limits.max_long_position,
null),
DataGridFilterKnownTemplates.UIntMinMax,
fs => fs.max_long_position,
(fs, v) => fs.with(max_long_position: v));
// max short
mk_column_model(
Columns.MaxNetShortPosition,
wrap_comp(r => r.limits.max_short_position),
new UIntMinMax<AccountSecurityRiskLimits>(
r => r.limits.max_short_position,
null),
DataGridFilterKnownTemplates.UIntMinMax,
fs => fs.max_short_position,
(fs, v) => fs.with(max_short_position: v));
// is position limit enabled
mk_is_enabled_column(
Columns.IsPositionLimitEnabled,
r => r.limits.is_position_limit_enabled,
(fs, yes_no_items) => fs.with(is_position_limit_enabled: RiskManagementFilterSettings.yes_no_filter_state(yes_no_items)),
nameof(RiskLimitsVM.is_position_limit_enabled));
// clip size
mk_column_model(
Columns.ClipLimit,
wrap_comp(r => r.limits.clip_limit),
new UIntMinMax<AccountSecurityRiskLimits>(r => r.limits.clip_limit, null),
DataGridFilterKnownTemplates.UIntMinMax,
fs => fs.clip_limit,
(fs, v) => fs.with(clip_limit: v));
// is clip limit enabled
mk_is_enabled_column(
Columns.IsClipLimitEnabled,
r => r.limits.is_clip_limit_enabled,
(fs, yes_no_items) => fs.with(is_clip_limit_enabled: RiskManagementFilterSettings.yes_no_filter_state(yes_no_items)),
nameof(RiskLimitsVM.is_clip_limit_enabled));
// min price
mk_column_model(
Columns.MinPrice,
wrap_comp(r => r.limits.min_price),
new DoubleMinMax<AccountSecurityRiskLimits>(r => r.limits.min_price, nameof(RiskLimitsVM.min_price)),
DataGridFilterKnownTemplates.DoubleMinMax,
fs => fs.min_price,
(fs, v) => fs.with(min_price: v));
// max price
mk_column_model(
Columns.MaxPrice,
wrap_comp(r => r.limits.max_price),
new DoubleMinMax<AccountSecurityRiskLimits>(r => r.limits.max_price, nameof(RiskLimitsVM.max_price)),
DataGridFilterKnownTemplates.DoubleMinMax,
fs => fs.max_price,
(fs, v) => fs.with(max_price: v));
// is price limit enabled
mk_is_enabled_column(
Columns.IsPriceLimitEnabled,
r => r.limits.is_price_limit_enabled,
(fs, yes_no_items) => fs.with(is_price_limit_enabled: RiskManagementFilterSettings.yes_no_filter_state(yes_no_items)),
nameof(RiskLimitsVM.is_price_limit_enabled));
data_comparer = new ComparerBuilder<AccountSecurityRiskLimits>(new[] {
wrap_comp(r => r.security),
_data_row_id_comparer
}).to_comparer();
_column_map = _columns.ToDictionary(c => c.id);
columns = ObservableAutoWrapper.SelectObservable(_columns, c => c);
var filter_source =
Observable.FromEvent<Func<AccountSecurityRiskLimits, bool>>(cb => _filter_changed += cb, cb => _filter_changed -= cb)
.StartWith(_mk_filter);
var filtered_data_source =
data_source.Filter(filter_source, AccountSecurityRiskLimitsDataHelper.key_fn);
var filter_hooks = _columns.Select(c => {
var f = c.filter;
f.filter_changed += _refresh_filter_changed.execute;
return Disposable.Create(() => f.filter_changed -= _refresh_filter_changed.execute);
}).ToList();
_dispose = Disposable.Create(
ObservableAutoWrapper.connect_item_hooks(rows, subscribe_row_changes),
accounts_source.OnDispatcher().Subscribe(on_accounts),
Disposable.wrap_collection(filter_hooks),
filtered_data_source.OnDispatcher().Subscribe(on_data));
//////////////////
// local methods
//////////////////
IComparer<AccountSecurityRiskLimits> wrap_comp<T>(Func<AccountSecurityRiskLimits, T> get_value) =>
Comparer<T>.Default.wrap<T, AccountSecurityRiskLimits>(get_value);
RiskManagementColumnModel mk_column_model<T>(
Columns id,
IComparer<AccountSecurityRiskLimits> row_comp,
IDataGridColumnPersistentFilter<AccountSecurityRiskLimits, T> filter,
string filter_template,
Func<RiskManagementFilterSettings, T> get_filter_settings,
Func<RiskManagementFilterSettings, T, RiskManagementFilterSettings> set_filter_settings) {
//
var col = new RiskManagementColumnModel(id, row_comp,
filter,
filter_template,
fs => get_filter_settings(fs),
(fs, val) => val is T cfs ? set_filter_settings(fs, cfs) : fs);
_columns.Add(col);
return col;
}
// the 'is enabled' columns are special cases, bc their filters produces an array of bools
// (cheap way to reuse the discrete values filter template for true/false items),
// but the filter protobuf has a nullable<bool>, so an extra conversion must be done explicitly (hence the get_row_val param).
RiskManagementColumnModel mk_is_enabled_column(Columns id, Func<AccountSecurityRiskLimits, bool> get_row_val, Func<RiskManagementFilterSettings, IEnumerable<bool>, RiskManagementFilterSettings> set_filter_prop, string prop) {
var col = new RiskManagementColumnModel(
id,
wrap_comp(get_row_val),
DiscreteValuesFilterVM.Create(new[] { true, false }, get_row_val, prop),
DataGridFilterKnownTemplates.YesNo,
fs => RiskManagementFilterSettings.yes_no_filter_items(fs.is_position_limit_enabled),
(fs, fstate) => set_filter_prop(fs, fstate as IEnumerable<bool>));
_columns.Add(col);
return col;
}
IDisposable subscribe_row_changes(RiskLimitsVM r) {
r.result_changed += on_limits_changed;
return Disposable.Create(() => r.result_changed -= on_limits_changed);
void on_limits_changed() {
var lim = r.create();
if (_cache.TryGetValue(lim.key, out var curr_lim) && !lim.Equals(curr_lim))
request_update_limit?.Invoke(lim);
}
}
void on_accounts(IReadOnlyList<AccountTuple> new_accounts) =>
_accounts_vm.update_items(new_accounts.Cast<AccountTuple?>());
void on_data(TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey> table) {
_cache = table.state;
var comp = data_comparer;
_data_rows.RemoveSorted(table.removed.Concat(table.changes.Old()), comp);
_data_rows.InsertSorted(table.added.Concat(table.changes.New()), comp);
}
}
public event Action result_changed, selected_rows_changed;
public event Action<AccountSecurityRiskLimits> request_update_limit;
event Action<Func<AccountSecurityRiskLimits, bool>> _filter_changed;
public IReadOnlyIndex<string, RiskManagementColumnModel> column_map { get; }
// this collection is needed to control the column order
public IReadOnlyObservableCollection<IDIYGridColumn> columns { get; }
public ObservableCollection<RiskLimitsVM> rows { get; } = new ObservableCollection<RiskLimitsVM>();
public IReadOnlyList<RiskLimitsVM> selected_rows => _selected_rows;
public IReadOnlyObservableCollection<AccountTuple?> accounts => _accounts_vm.items;
public AccountTuple? selected_account {
get => _accounts_vm.selected_item ?? default;
set => _accounts_vm.selected_item = value;
}
public SimpleGridSettingsManagerVM<Columns> grid_settings_manager { get; }
public IComparer<AccountSecurityRiskLimits> data_comparer {
get => _data_comparer;
private set {
if (value != null) {
_data_comparer = value;
_row_comparer = null;
_data_rows.sync_with(_data_rows.Ordered(value).ToList());
_selected_rows = _selected_rows.WithComparer(row_comparer);
}
}
}
public IComparer<RiskLimitsVM> row_comparer =>
_row_comparer ?? (_row_comparer = data_comparer?.wrap<AccountSecurityRiskLimits, RiskLimitsVM>(r => r.committed_data));
public RiskManagementSettings create() {
var grid = grid_settings_manager.create();
var filters = _columns.Aggregate(new RiskManagementFilterSettings(), (fs, c) => c.assign_filter_settings(fs));
return new RiskManagementSettings(grid, filters, _sort_manager.SortingCriteria, selected_account?.id ?? 0);
}
public void load_from(RiskManagementSettings settings) {
if (settings is null)
throw new ArgumentNullException(nameof(settings));
using (_result_changed.defer_execution()) {
if (settings.grid != null)
grid_settings_manager.load_from(settings.grid);
foreach (var col in _columns)
col.load_from(null, settings.filters);
_accounts_vm.try_select(settings.account_id, true);
}
}
public void load_from(GridSettings grid) {
if (grid is null)
throw new ArgumentNullException(nameof(grid));
using (_result_changed.defer_execution())
grid_settings_manager.load_from(grid);
}
void IHandleRowSelection.row_selection_changed(SelectionChangedEventArgs e) {
var bd = _selected_rows.ToBuilder();
RowSelectionHelper.apply_changes(bd, e);
_selected_rows = bd.ToImmutable();
_selected_rows_changed.execute();
}
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) {
_sort_manager.OnColumnClick(e.Column, KeyboardExtensions.IsShiftDown());
data_comparer = _sort_manager.BuildComparer(id => _find_column(id)?.row_comparer, _data_comparer);
}
RiskManagementColumnModel _find_column(string key_str) =>
Enum.TryParse(key_str, true, out Columns key) ? _find_column(key) : null;
RiskManagementColumnModel _find_column(Columns key) =>
_column_map.GetValueOrDefault(key);
Func<AccountSecurityRiskLimits, bool> _mk_filter() {
var col_filter_pred = create().filters.build_predicate();
if (!selected_account.HasValue)
return lim => false;
var acc_id = selected_account.Value.id;
return lim => lim.account_key == acc_id && (col_filter_pred is null || col_filter_pred(lim));
}
}
}
@@ -0,0 +1,111 @@
<Window
x:Class="bts.shell.risk_management.RiskLimitParametersDialog"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:risk="clr-namespace:bts.shell.risk_management"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.AllowResize="False"
u:WindowStyle.SizeToContent="WidthAndHeight"
FocusVisualStyle="{x:Null}"
Style="{StaticResource BTSDialog}"
>
<Window.Resources>
<sys:Double x:Key="HorizontalPush">4</sys:Double>
<sys:Double x:Key="VerticalPush3">8</sys:Double>
<Thickness x:Key="ParamMargin" Left="{StaticResource HorizontalPush}"/>
<Style x:Key="HeaderText" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{x:Static u:BTSColors.header_text_brush}"/>
<Setter Property="Margin" Value="4"/>
</Style>
<Style x:Key="LimitToggle" TargetType="CheckBox">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Window.ContentTemplate>
<DataTemplate DataType="{x:Type risk:RiskLimitsModificationVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.ColumnSpan="17" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{x:Static u:BTSColors.header_brush}"/>
<TextBlock Grid.Column="0" Text="Security" Visibility="{Binding can_edit_security, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="2" Text="Max Long" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="4" Text="Max Short" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="6" Text="Clip Limit" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="8" Text="Min Price" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="10" Text="Max Price" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="12" Text="Position Limit Enabled" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="14" Text="Clip Limit Enabled" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="16" Text="Price Limit Enabled" Style="{StaticResource HeaderText}"/>
<c:ContractPickerPopup
Grid.Row="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CloseOnCancel="True"
CloseOnOK="True"
Header="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}"
Visibility="{Binding can_edit_security, Converter={StaticResource BooleanToVisibilityConverter}}"
IsEditMode="True"
IsLazy="True"
SecurityMap="{Binding security_map}"
SelectedCriteria="{Binding security_criteria}"
ShowDialogSkirt="True"
ToolTip="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
/>
<c:TickUpDownNullableUInt Grid.Row="2" Grid.Column="2" Value="{Binding max_long_position}"/>
<c:TickUpDownNullableUInt Grid.Row="2" Grid.Column="4" Value="{Binding max_short_position}"/>
<c:TickUpDownNullableUInt Grid.Row="2" Grid.Column="6" Value="{Binding clip_limit}"/>
<c:TickUpDownNullableDouble Grid.Row="2" Grid.Column="8" Value="{Binding min_price}"/>
<c:TickUpDownNullableDouble Grid.Row="2" Grid.Column="10" Value="{Binding max_price}"/>
<CheckBox Grid.Row="2" Grid.Column="12" IsChecked="{Binding is_position_limit_enabled}" Style="{StaticResource LimitToggle}"/>
<CheckBox Grid.Row="2" Grid.Column="14" IsChecked="{Binding is_clip_limit_enabled}" Style="{StaticResource LimitToggle}"/>
<CheckBox Grid.Row="2" Grid.Column="16" IsChecked="{Binding is_price_limit_enabled}" Style="{StaticResource LimitToggle}"/>
</Grid>
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button Content="OK" Command="{Binding commit_cmd}" IsDefault="True"/>
<Button Content="Cancel" Command="{Binding cancel_cmd}" IsCancel="True"/>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</DataTemplate>
</Window.ContentTemplate>
</Window>
@@ -0,0 +1,11 @@
using System.Windows;
namespace bts.shell.risk_management {
/// <summary>
/// Interaction logic for RiskLimitParametersDialog.xaml
/// </summary>
partial class RiskLimitParametersDialog : Window {
public RiskLimitParametersDialog() =>
InitializeComponent();
}
}
@@ -0,0 +1,165 @@
<UserControl
x:Class="bts.shell.risk_management.RiskManagementControl"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:risk="clr-namespace:bts.shell.risk_management"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance risk:RiskManagementVM, IsDesignTimeCreatable=True}"
Background="{StaticResource CommonBackground}"
>
<UserControl.Resources>
<Style x:Key="TextCell" TargetType="c:TextCell">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Padding" Value="2,0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding Path='(c:DIYGrid.Column).horizontal_alignment', RelativeSource={RelativeSource Self}}"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Background" Value="{Binding Path='(c:DIYGrid.Column).background_color', RelativeSource={RelativeSource Self}, Converter={x:Static cv:ColorToBrushConverter.Instance}}"/>
<Setter Property="Foreground" Value="{Binding Path='(c:DIYGrid.Column).font_color', RelativeSource={RelativeSource Self}, Converter={x:Static cv:ColorToBrushConverter.Instance}}"/>
<Setter Property="FontFamily" Value="{Binding Path='(c:DIYGrid.Column).font_family', RelativeSource={RelativeSource Self}}"/>
<Setter Property="FontWeight" Value="{Binding Path='(c:DIYGrid.Column).font_weight', RelativeSource={RelativeSource Self}}"/>
<Setter Property="FontSize" Value="{Binding Path='(c:DIYGrid.Column).font_size', RelativeSource={RelativeSource Self}}"/>
</Style>
<Style x:Key="CellBorder" TargetType="Border">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Background" Value="{Binding Path='(c:DIYGrid.Column).background_color', RelativeSource={RelativeSource Self}, Converter={x:Static cv:ColorToBrushConverter.Instance}}"/>
<Setter Property="Padding" Value="2,0"/>
</Style>
<Style x:Key="LimitToggleSwitch" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="Foreground" Value="{Binding Path='(c:DIYGrid.Column).font_color', RelativeSource={RelativeSource AncestorType=Border}, Converter={x:Static cv:ColorToBrushConverter.Instance}}"/>
<Setter Property="HorizontalAlignment" Value="{Binding Path='(c:DIYGrid.Column).horizontal_alignment', RelativeSource={RelativeSource AncestorType=Border}}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Margin="4,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Account:</TextBlock>
<ComboBox Grid.Column="1" Margin="8,0" ItemsSource="{Binding accounts}" SelectedItem="{Binding selected_account}"/>
</Grid>
<c:DIYGrid
Grid.Row="1"
c:DIYGrid.ColumnModels="{Binding columns}"
c:DIYGrid.SyncWithColumnGeometry="True"
Rows="{Binding rows}"
RowHeight="{u:DPIDouble 21.6}"
>
<c:DIYGrid.Resources>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</c:DIYGrid.Resources>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection/>
</c:DIYGrid.Columns>
<c:DIYGrid.CachedColumnParams>
<x:Array Type="{x:Type sys:String}">
<sys:String>font_weight</sys:String>
<sys:String>font_size</sys:String>
<sys:String>font_brush</sys:String>
<sys:String>font_family</sys:String>
<sys:String>horizontal_alignment</sys:String>
</x:Array>
</c:DIYGrid.CachedColumnParams>
<c:DIYGrid.RowTemplate>
<DataTemplate DataType="{x:Type risk:RiskLimitsVM}">
<Grid c:DIYGrid.SyncWithColumnGeometry="True" Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}">
<!--Security-->
<c:TextCell
c:DIYGrid.ColumnKey="Security"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[Security]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Text="{Binding security}"
Style="{StaticResource TextCell}"
/>
<c:TextCell
c:DIYGrid.ColumnKey="SecurityKey"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[SecurityKey]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Text="{Binding security_key}"
Style="{StaticResource TextCell}"
/>
<!--Position-->
<c:TextCell
c:DIYGrid.ColumnKey="MaxNetLongPosition"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[MaxNetLongPosition]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Text="{Binding max_long_position}"
Style="{StaticResource TextCell}"
/>
<c:TextCell
c:DIYGrid.ColumnKey="MaxNetShortPosition"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[MaxNetShortPosition]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Text="{Binding max_short_position}"
Style="{StaticResource TextCell}"
/>
<Border
c:DIYGrid.ColumnKey="IsPositionLimitEnabled"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[IsPositionLimitEnabled]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Style="{StaticResource CellBorder}"
>
<CheckBox
IsChecked="{Binding is_position_limit_enabled}"
Style="{StaticResource LimitToggleSwitch}"
/>
</Border>
<!--Clip-->
<c:TextCell
c:DIYGrid.ColumnKey="ClipLimit"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[ClipLimit]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Text="{Binding clip_limit}"
Style="{StaticResource TextCell}"
/>
<Border
c:DIYGrid.ColumnKey="IsClipLimitEnabled"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[IsClipLimitEnabled]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Style="{StaticResource CellBorder}"
>
<CheckBox
IsChecked="{Binding is_clip_limit_enabled}"
Style="{StaticResource LimitToggleSwitch}"
/>
</Border>
<!--Price-->
<c:TextCell
c:DIYGrid.ColumnKey="MinPrice"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[MinPrice]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Text="{Binding min_price, StringFormat='#,0.0000'}"
Style="{StaticResource TextCell}"
/>
<c:TextCell
c:DIYGrid.ColumnKey="MaxPrice"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[MaxPrice]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Text="{Binding max_price, StringFormat='#,0.0000'}"
Style="{StaticResource TextCell}"
/>
<Border
c:DIYGrid.ColumnKey="IsPriceLimitEnabled"
c:DIYGrid.Column="{Binding Path='DataContext.column_map[IsPriceLimitEnabled]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
Style="{StaticResource CellBorder}"
>
<CheckBox
IsChecked="{Binding is_price_limit_enabled}"
Style="{StaticResource LimitToggleSwitch}"
/>
</Border>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -0,0 +1,33 @@
using mbts.ui.controls;
using System.Windows.Controls;
namespace bts.shell.risk_management {
/// <summary>
/// Interaction logic for RiskManagementControl.xaml
/// </summary>
partial class RiskManagementControl : UserControl {
public RiskManagementControl() {
InitializeComponent();
}
public enum Columns {
None,
Security,
MaxNetLongPosition,
MaxNetShortPosition,
ClipLimit,
MinPrice,
MaxPrice,
IsPositionLimitEnabled,
IsClipLimitEnabled,
IsPriceLimitEnabled,
SecurityKey
}
}
class RiskManagementColumn : DIYGrid.ColumnBase<RiskManagementControl.Columns> {
protected override void on_key_initialized(RiskManagementControl.Columns key) {
id = key.ToString();
}
}
}
@@ -2,6 +2,7 @@
using bts.api.position;
using bts.shell.fills;
using bts.shell.manual_fills;
using bts.shell.risk_management;
using bts.spark;
using mbts.persist;
using mbts.spark;
@@ -27,6 +28,7 @@ namespace bts.shell {
readonly PNLWindowFactory _plwf;
readonly AlgoStudioWindowFactory _aswf;
readonly ManualFillsWindowFactory _mfwf;
readonly RiskManagementWindowFactory _rmwf;
//
List<SparkCommand> _actions;
@@ -54,6 +56,7 @@ namespace bts.shell {
_old_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
_mfwf = new ManualFillsWindowFactory(this, bus, orders_model);
_rmwf = new RiskManagementWindowFactory(this, bus, settings_hub, orders_model.admin_model);
}
public ImageSource icon => Icons.Trades;
@@ -77,7 +80,8 @@ namespace bts.shell {
SparkCommands.PickLadder,
SparkCommands.PickOrderTicket,
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default),
SparkCommands.ShowRiskManagement.complete(() => RiskManagementSettings.Default)
});
}
return _actions;
@@ -95,6 +99,7 @@ namespace bts.shell {
yield return _old_plwf;
yield return _aswf;
yield return _mfwf;
yield return _rmwf;
}
}
+35 -12
View File
@@ -155,9 +155,32 @@ namespace bts {
Disposable.dispose(ref _dispose);
}
public IObservable<Update<(SecurityImmutable? sec, T val), TKey>> join<T,TKey>(
IObservable<Update<T, TKey>> source, Func<T,security_key> skeyfn) where TKey : IEquatable<TKey> {
return Observable.Create<Update<(SecurityImmutable?, T), TKey>>(observer => {
public IObservable<Update<(SecurityImmutable? sec, T val), TKey>> join<T, TKey>(
IObservable<Update<T, TKey>> source, Func<T, security_key> skeyfn) where TKey : IEquatable<TKey> {
return join(source, skeyfn, get_incremental_props, mk_update);
(IEnumerable<T> all, IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, TKey> key_extractor) get_incremental_props(Update<T, TKey> upd_in) =>
(upd_in.all, upd_in.added, upd_in.changes, upd_in.removed, upd_in.key_extractor);
Update<(SecurityImmutable? sec, T val), TKey> mk_update(
IEnumerable<(SecurityImmutable? sec, T val)> all,
IEnumerable<(SecurityImmutable? sec, T val)> added,
IEnumerable<Change<(SecurityImmutable? sec, T val)>> changes,
IEnumerable<(SecurityImmutable? sec, T val)> removed,
Func<(SecurityImmutable?, T), TKey> key_extractor) =>
//
new Update<(SecurityImmutable? sec, T val), TKey>(
all, added, changes, removed, key_extractor);
}
public IObservable<TOut> join<TIn, T, TKey, TOut>(
IObservable<TIn> source,
Func<T,security_key> skeyfn,
Func<TIn, (IEnumerable<T> all, IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, TKey> key_extractor)> get_incremental_props,
Func<IEnumerable<(SecurityImmutable? sec, T val)>, IEnumerable<(SecurityImmutable? sec, T val)>, IEnumerable<Change<(SecurityImmutable? sec, T val)>>, IEnumerable<(SecurityImmutable? sec, T val)>, Func<(SecurityImmutable?, T), TKey>, TOut> create_result_update)
where TKey : IEquatable<TKey> {
//
return Observable.Create<TOut>(observer => {
var state = ImmutableDictionary<TKey, (SecurityImmutable? s, T t)>.Empty;
var gate = new object();
// We get thie function from the first source update
@@ -186,11 +209,11 @@ namespace bts {
state = builder.ToImmutable();
// fire update
observer.OnNext(
new Update<(SecurityImmutable?, T), TKey>(
state.Values,
Enumerable.Empty<(SecurityImmutable?,T)>(),
changed,
Enumerable.Empty<(SecurityImmutable?,T)>(),
create_result_update(
state.Values,
Enumerable.Empty<(SecurityImmutable?, T)>(),
changed,
Enumerable.Empty<(SecurityImmutable?, T)>(),
rowkeyfn));
}
}
@@ -198,10 +221,11 @@ namespace bts {
return Disposable.Create(
// Fire update when source changes
source.Subscribe((update) => {
source.Subscribe((update_in) => {
var update = get_incremental_props(update_in);
lock (gate) {
rowkeyfn = rowkeyfn ?? (i => update.key_extractor(i.Item2));
var relevant_securities = get_and_ensure_securities(update.added.Concat(update.changed).Select(skeyfn));
var relevant_securities = get_and_ensure_securities(update.added.Concat(update.changes.New()).Select(skeyfn));
var added = new List<(SecurityImmutable?, T)>();
var changed = new List<Change<(SecurityImmutable? s, T t)>>();
@@ -238,8 +262,7 @@ namespace bts {
}
state = builder.ToImmutable();
observer.OnNext(new Update<(SecurityImmutable?, T t), TKey>(
state.Values, added, changed, removed, rowkeyfn));
observer.OnNext(create_result_update(state.Values, added, changed, removed, rowkeyfn));
}
}),
// Also fire update when securities change
@@ -28,6 +28,9 @@ namespace bts.spark {
public static PartialCommand<bts.shell.orderbook.OrderBookStartupData> ShowOrders =>
SparkCommand.create("Order Book", Icons.Book, SparkTags.ShowOrderBookTag);
public static PartialCommand<shell.risk_management.RiskManagementSettings> ShowRiskManagement =>
SparkCommand.create("Risk Management", Icons.Risk, SparkTags.ShowRiskManagement);
// Fills
public static PartialCommand<FillsSettings> ShowFills(KeyGesture key_gesture = null) =>
SparkCommand.create("Fills", Icons.Payment, SparkTags.ShowFillsTag, key_gesture);
+1
View File
@@ -63,6 +63,7 @@ namespace mbts.spark {
public static ITag<IObservable<SecurityMD>> ShowLinkedLadder { get; } = new SparkTag<IObservable<SecurityMD>>("Linked Ladder");
public static ITag<SecurityCriteria> ShowMarketViewTag { get; } = new SparkTag<SecurityCriteria>("Show MarketView");
public static ITag<bts.shell.orderbook.OrderBookStartupData> ShowOrderBookTag { get; } = new SparkTag<bts.shell.orderbook.OrderBookStartupData>(nameof(ShowOrderBookTag));
public static ITag<bts.shell.risk_management.RiskManagementSettings> ShowRiskManagement { get; } = new SparkTag<bts.shell.risk_management.RiskManagementSettings>(nameof(ShowRiskManagement));
public static ITag<PAndLWindowFactory.PAndLStartupData> ShowPAndLTag { get; } = new SparkTag<PAndLWindowFactory.PAndLStartupData>(nameof(ShowPAndLTag));
public static ITag<bts.shell.pnl.PNLSettings> ShowPNLTag { get; } = new SparkTag<bts.shell.pnl.PNLSettings>(nameof(ShowPNLTag));
public static ITag<(SecurityImmutable security, ExecutionStrategyImmutable strategy)> ShowSyntheticCloneTag { get; } = new SparkTag<(SecurityImmutable security, ExecutionStrategyImmutable strategy)>(nameof(ShowSyntheticCloneTag));
@@ -17,6 +17,7 @@ namespace bts.settings {
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
interface IRiskManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
static class SettingsMigrationHelper {
@@ -61,6 +61,8 @@ namespace mbts.spark {
PNLSettings _pnl;
[ProtoMember(14)]
GlobalAlertSettings _alerts;
[ProtoMember(15)]
GridSettings _risk_management_grid;
public UserSettings() { }
@@ -74,6 +76,7 @@ namespace mbts.spark {
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.time_and_sales.TimeAndSalesSettings.DefaultColumns.all));
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings);
public GridSettings risk_management_grid => _risk_management_grid ?? (_risk_management_grid = bts.shell.risk_management.RiskManagementSettings.DefaultGrid);
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
public PNLSettings pnl => _pnl ?? (_pnl = PNLSettings.Default);
@@ -91,7 +94,8 @@ namespace mbts.spark {
GridSettings provided_pnl_gridsettings = null,
PNLSettings pnl = null,
bts.shell.orderbook.OrderBookGridSettings order_book_grid = null,
GlobalAlertSettings alerts = null) {
GlobalAlertSettings alerts = null,
GridSettings risk_management_grid = null) {
var us = new UserSettings(ladder_profiles ?? _ladder_profiles) {
_ladder = ladder ?? _ladder,
_notification = notification ?? _notification,
@@ -102,7 +106,8 @@ namespace mbts.spark {
_pnl_grid = provided_pnl_gridsettings ?? _pnl_grid,
_pnl = pnl ?? _pnl,
_order_book_grid = order_book_grid ?? _order_book_grid,
_alerts = alerts ?? _alerts
_alerts = alerts ?? _alerts,
_risk_management_grid = risk_management_grid ?? _risk_management_grid
};
us._last_ladder_profile_id = Math.Max(_last_ladder_profile_id, us.ladder_profiles.Select(p => p.id).MaxOrDefault());
return us;
@@ -26,6 +26,7 @@ namespace mbts.persist {
_fills_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.fills_grid, (us, gs) => us.with(provided_fills_gridsettings: gs)),
_mview_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs)),
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
_risk_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.risk_management_grid, (us, gs) => us.with(risk_management_grid: gs)),
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
static readonly IImmutableProperty<UserSettings, PNLSettings>
@@ -37,6 +38,7 @@ namespace mbts.persist {
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
readonly IEnumerable<IRiskManagementMigrationRule> _risk_migration_rules;
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
readonly IUserPersist _persist;
@@ -49,6 +51,7 @@ namespace mbts.persist {
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
[ImportMany]IEnumerable<IRiskManagementMigrationRule> risk_migration_rules,
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
//
_persist = persist;
@@ -56,6 +59,7 @@ namespace mbts.persist {
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
_risk_migration_rules = risk_migration_rules ?? Enumerable.Empty<IRiskManagementMigrationRule>();
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
init();
async void init() {
@@ -68,6 +72,7 @@ namespace mbts.persist {
_mview_migration_rules.merge().wrap(_mview_propi),
_pandl_migration_rules.merge().wrap(_pandl_propi),
_pnl_migration_rules.merge().wrap(_pnl_propi),
_risk_migration_rules.merge().wrap(_risk_propi),
_synth_migration_rules.merge().wrap(_synth_propi)
};
SettingsMigrationHelper.apply_quiet(ref s, wrapped_rules);
@@ -142,6 +147,12 @@ namespace mbts.persist {
_pnl_migration_rules,
_pnl_propi);
public IWindowSettingsManager<GridSettings> create_risk_management_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_risk_migration_rules,
_risk_propi);
public IWindowSettingsManager<GridSettings> create_synth_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
@@ -170,4 +181,4 @@ namespace mbts.persist {
}
}
}
}
}
@@ -3,6 +3,7 @@ using mbts;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -10,6 +11,13 @@ using System.Reactive.Linq;
using System.Reflection;
namespace bts.utils {
class PropertyEqualityComparerAttribute : Attribute {
public PropertyEqualityComparerAttribute(Type comparer_type) =>
this.comparer_type = comparer_type ?? throw new ArgumentNullException(nameof(comparer_type));
public Type comparer_type { get; }
}
interface IPropertyChangedDetector<T> where T : IRaiseNotifyPropertyChanged {
IDisposable detect_changes(T target);
}
@@ -68,6 +76,42 @@ namespace bts.utils {
public static IObservable<bool> dirty_monitor<T>(T instance, PropertyInfo[] props) where T : INotifyPropertyChanged =>
_dirty_monitor(instance, props.ToDictionary(p => p.Name, p => new PropNode(p, p.GetValue(instance))));
public static Func<T, int> property_hash_code_delegate<T>(string prop) =>
DelegateStore<T>.property_hash_code(prop);
public static Func<T, int> property_hash_code_delegate<T>(IEnumerable<string> props) {
var prop_hash_code_dels = props.Select(DelegateStore<T>.property_hash_code).ExceptNull().ToArray();
return impl;
int impl(T a) =>
EqualityComparer.auto_phase(prop_hash_code_dels.Select(peq => peq(a)));
}
public static Func<T, T, bool> property_equals_delegate<T>(string prop) =>
DelegateStore<T>.property_equals(prop);
public static Func<T, T, bool> property_equals_delegate<T>(IEnumerable<string> props) {
var prop_equals_dels = props.Select(DelegateStore<T>.property_equals).ExceptNull().ToArray();
return impl;
bool impl(T a, T b) =>
prop_equals_dels.All(peq => peq(a, b));
}
public static IEqualityComparer<T> property_equality_comparer<T>() {
var prop_infos = PropertyInfoStore<T>.all_props;
return property_equality_comparer<T>(
prop_infos.Select(p => p.Name),
// hash code should NEVER include mutable props
prop_infos.Where(pi => !pi.CanWrite).Select(p => p.Name));
}
public static IEqualityComparer<T> property_equality_comparer<T>(IEnumerable<string> props) =>
property_equality_comparer<T>(props, props);
public static IEqualityComparer<T> property_equality_comparer<T>(IEnumerable<string> equals_props, IEnumerable<string> hash_code_props) =>
EqualityComparer.Create(
property_equals_delegate<T>(equals_props),
property_hash_code_delegate<T>(hash_code_props));
static IObservable<bool> _dirty_monitor<T>(T instance, Dictionary<string, PropNode> prop_map) where T : INotifyPropertyChanged =>
Observable.Create<bool>(o => {
var dirty_props = new HashSet<string>();
@@ -106,6 +150,8 @@ namespace bts.utils {
PropertyInfo prop_info { get; }
Func<T, object> getter { get; }
Action<T, object> setter { get; }
Func<T, T, bool> property_equals { get; }
Func<T, int> property_hash_code { get; }
(Action<T> initialize, Func<T, bool> refresh) create_snapshot();
(Action initialize, Func<bool> refresh) create_snapshot(T source);
}
@@ -139,6 +185,15 @@ namespace bts.utils {
public static Action<T, object> setter(string prop_name) =>
accessor_data.TryGetValue(prop_name, out var dels) ? dels.setter : null;
public static Func<T, T, bool> property_equals(string prop_name) =>
accessor_data.TryGetValue(prop_name, out var dels) ? dels.property_equals : null;
public static Func<T, int> property_hash_code(string prop_name) =>
accessor_data.TryGetValue(prop_name, out var dels) ? dels.property_hash_code : null;
public static IPropertyAccessorData<T> get_accessor(string prop_name) =>
accessor_data.GetValueOrDefault(prop_name);
static DelegateStore() {
var prop_dels = new Dictionary<string, IPropertyAccessorData<T>>();
var accessor_type_cache = new Dictionary<Type, Type>();
@@ -185,6 +240,7 @@ namespace bts.utils {
class PropertyAccessorData<T, TValue> : IPropertyAccessorData<T, TValue> {
readonly Func<T, object> _ng_get;
readonly Action<T, object> _ng_set;
IEqualityComparer<TValue> _property_equals_comp;
public PropertyAccessorData(PropertyInfo prop_info) {
this.prop_info = prop_info;
@@ -200,11 +256,27 @@ namespace bts.utils {
setter = set_method.CreateDelegate(typeof(Action<T, TValue>)) as Action<T, TValue>;
_ng_set = (src, val) => setter(src, (TValue)val);
}
if (prop_info.GetCustomAttribute<PropertyEqualityComparerAttribute>()?.comparer_type is Type comp_type) {
_property_equals_comp = Activator.CreateInstance(comp_type) as IEqualityComparer<TValue>;
}
if (_property_equals_comp is null) {
_property_equals_comp = EqualityComparer<TValue>.Default;
}
}
public Func<T, TValue> getter { get; }
public Action<T, TValue> setter { get; }
public PropertyInfo prop_info { get; }
public Func<T, T, bool> property_equals {
get {
return impl;
bool impl(T a, T b) =>
_property_equals_comp.Equals(getter(a), getter(b));
}
}
public Func<T, int> property_hash_code =>
a => _property_equals_comp.GetHashCode(getter(a));
Func<T, object> IPropertyAccessorData<T>.getter => _ng_get;
Action<T, object> IPropertyAccessorData<T>.setter => _ng_set;
@@ -1,6 +1,11 @@
using System;
using bts.collections;
using bts.utils;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.ui.utility {
@@ -153,6 +158,94 @@ namespace mbts.ui.utility {
_try_match_item(_item_to_value(item), out match);
}
class SelectorVM<T, TKey> {
readonly IEqualityComparer<T> _eq_comparer;
readonly IEqualityComparer<TKey> _key_comparer;
readonly Func<T, TKey> _key_fn;
readonly ObservableCollection<T> _items = new ObservableCollection<T>();
readonly Dictionary<TKey, T> _map;
/// not as noisy as <see cref="ObservableCollection{T}.CollectionChanged"/>,
/// this event is only raised after a batch collection change has been completed.
/// <see cref="update_items(IEnumerable{T})"/>.
IComparer<T> _comparer;
T _selected_item;
TKey _pending_selected_item_key;
bool _is_pending_selection;
public SelectorVM(Func<T, TKey> key_fn)
: this(key_fn, EqualityComparer<TKey>.Default) { }
public SelectorVM(Func<T, TKey> key_fn, IEqualityComparer<TKey> key_comparer) {
_key_fn = key_fn ?? throw new ArgumentNullException(nameof(key_fn));
_key_comparer = key_comparer ?? throw new ArgumentNullException(nameof(key_comparer));
_eq_comparer = EqualityComparer.Create(_key_fn, _key_comparer);
_map = new Dictionary<TKey, T>(key_comparer);
items = ObservableAutoWrapper.create_read_only_wrapper(_items);
}
public event Action selected_item_changed;
public IReadOnlyObservableCollection<T> items { get; }
public T selected_item {
get => _selected_item;
set {
if (!_eq_comparer.Equals(_selected_item, value)) {
_selected_item = value;
selected_item_changed?.Invoke();
}
}
}
public IComparer<T> comparer {
get => _comparer;
set {
_comparer = value;
if (value != null)
_update_items(items, false);
}
}
public bool try_select(T item, bool wait_for_item_to_arrive = false) =>
try_select(_key_fn(item), wait_for_item_to_arrive);
public bool try_select(TKey item_key, bool wait_for_item_to_arrive = false) {
if (_map.TryGetValue(item_key, out var item)) {
selected_item = item;
return true;
} else if (wait_for_item_to_arrive) {
_pending_selected_item_key = item_key;
_is_pending_selection = true;
}
return false;
}
public void update_items(IEnumerable<T> items) =>
_update_items(items, true);
void _update_items(IEnumerable<T> items, bool map_changed) {
// The currently selected item may be removed during this collection change.
// After the items have been changed, attempt to re-select the last selected item;
// if there is already a particular 'pending selection,' attempt to select that instead.
var target_key = _is_pending_selection ? _pending_selected_item_key : _key_fn(_selected_item);
if (map_changed)
_map.Clear();
if (items is null) {
_items.Clear();
} else {
if (_comparer != null) {
items = items.Ordered(_comparer);
}
_items.sync_with(items);
}
if (map_changed) {
foreach (var i in this.items)
_map[_key_fn(i)] = i;
}
if (_map.Count != this.items.Count)
throw new InvalidOperationException("Map and items are out of sync (different item counts).");
try_select(target_key, _is_pending_selection || _items.Count == 0);
}
}
static class SelectionModelExtensions {
public static IObservable<TValue> observe_selected_item<TItem, TValue>(this SelectionModel<TItem, TValue> selection_model, bool notify_on_subscribe = false) =>
Observable.Create<TValue>(o => {
@@ -166,4 +259,4 @@ namespace mbts.ui.utility {
return Disposable.Create(() => selection_model.Changed -= callback);
}
}
}
}
@@ -229,7 +229,10 @@ namespace mbts.ui.controls {
public string Text { get => GetValue(TextProperty) as string; set => SetValue(TextProperty, value); }
#endregion
protected override Size ArrangeOverride(Size finalSize) => finalSize;
protected override Size ArrangeOverride(Size finalSize) {
_gcr.available_size = finalSize;
return finalSize;
}
protected override Size MeasureOverride(Size availableSize) {
_gcr.available_size = availableSize;
@@ -237,6 +237,15 @@ namespace mbts.ui.controls {
Color? _background_color_override;
object _header_copy;
bool _is_visible;
IDisposable _model_hooks;
static ColumnBase() {
WidthProperty.OverrideMetadata(typeof(ColumnBase),
new PropertyMetadata((d, e) => {
if (d is ColumnBase cb)
cb.width_changed?.Invoke((double)e.NewValue);
}));
}
protected ColumnBase() {
var gs = GridSettings.Default;
@@ -257,9 +266,20 @@ namespace mbts.ui.controls {
set => SetValue(SortDirectionProperty, value);
}
#region IsVisible
public static readonly DependencyProperty IsVisibleProperty =
WPFHelper.create_dp<bool, ColumnBase>(nameof(IsVisible), (o, v) => o.is_visible = v);
public bool IsVisible {
get => (bool)GetValue(IsVisibleProperty);
set => SetValue(IsVisibleProperty, value);
}
#endregion
public event Action<ColumnBase, ListSortDirection?> sort_direction_changed;
public event Action font_params_changed;
public event Action<bool> is_visible_changed;
public event Action<double> width_changed;
public string id { get; set; }
public string sort_member_path { get; set; }
@@ -273,6 +293,7 @@ namespace mbts.ui.controls {
_is_visible = value;
is_visible_changed?.Invoke(value);
raise_prop_changed();
SetCurrentValue(IsVisibleProperty, value);
}
}
}
@@ -297,6 +318,26 @@ namespace mbts.ui.controls {
set => base.Header = (_header_copy = value);
}
public void set_model(IDIYGridColumnModel model) {
Disposable.dispose(ref _model_hooks);
model.width = Width;
width_changed += on_width_changed;
model.width_changed += on_model_width_changed;
_model_hooks = Disposable.Create(() => {
width_changed -= on_width_changed;
model.width_changed -= on_model_width_changed;
});
void on_model_width_changed(double value) {
if (!DoubleUtil.AreClose(value, Width))
SetCurrentValue(WidthProperty, value);
}
void on_width_changed(double value) {
model.width_changed -= on_model_width_changed;
model.width = value;
model.width_changed += on_model_width_changed;
}
}
/// important: don't access any dependency props in here.
/// this may be called from a thread other than that of this dispatcher (i.e., when <see cref="IShellWindow.GetState(System.IO.Stream)"/> is called).
public GridColumnSettings create() =>
@@ -327,7 +368,7 @@ namespace mbts.ui.controls {
}
if (original_width != settings.width) {
original_width = settings.width;
Width = original_width;
SetCurrentValue(WidthProperty, original_width);
}
//
}
@@ -9,10 +9,10 @@ using System.Reactive.Linq;
namespace mbts.ui {
class ColumnSortManager {
readonly List<ISortableDIYGridColumn> _visible_sorting = new List<ISortableDIYGridColumn>();
readonly IList<ISortableDIYGridColumn> _columns;
readonly IEnumerable<ISortableDIYGridColumn> _columns;
readonly string _primary_key;
public ColumnSortManager(IList<ISortableDIYGridColumn> columns, string primary_key) {
public ColumnSortManager(IEnumerable<ISortableDIYGridColumn> columns, string primary_key) {
_columns = columns;
_primary_key = primary_key;
}
@@ -1,4 +1,19 @@
namespace mbts.ui.controls {
using bts.utils;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui.settings;
using mbts.ui.shell;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Media;
namespace mbts.ui.controls {
class DIYGridColumn : DIYGrid.ColumnBase<string> {
public DIYGridColumn()
: base() {
@@ -6,4 +21,191 @@
Width = 60;
}
}
interface IDIYGridColumnModel : DIYGrid.IFilterableColumn {
string id { get; }
string column_template { get; }
IComparer row_comparer { get; }
double width { get; set; }
event Action<double> width_changed;
}
interface IDIYGridColumnFactory {
DIYGrid.ColumnBase create_view();
}
class DIYGridColumnModel<TKey> : ViewModelBase, IDIYGridColumnModel, ISortableDIYGridColumn<TKey>, ISortableDIYGridColumn, IValueFactory<GridColumnSettings> where TKey : struct {
readonly DeferrableActionWrapper _result_changed;
FontWeight _fallback_font_weight;
Color _fallback_font_color;
Color _fallback_background_color;
double _fallback_font_size;
FontFamily _fallback_font_family;
double _width = 60;
bool _is_visible = true;
ListSortDirection? _sort_direction;
object _custom_header;
string _display_name;
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
Color? _background_color_override, _font_color_override;
FontWeight? _font_weight_override;
public DIYGridColumnModel(TKey id, IComparer row_comparer, IDataGridColumnFilter filter, string filter_template = null, string column_template = null) {
this.id = id;
this.row_comparer = row_comparer;
this.filter = filter;
this.filter_template = filter_template;
this.column_template = column_template;
_result_changed = new DeferrableActionWrapper(() => result_changed?.Invoke());
}
public event Action result_changed;
public event Action<double> width_changed;
public TKey id { get; }
string IDIYGridColumnModel.id => id.ToString();
public IComparer row_comparer { get; }
public string display_name {
get => _display_name;
set {
if (value != _display_name) {
_display_name = value;
_result_changed.execute();
RaisePropertyChanged();
if (_custom_header is null) {
RaisePropertyChanged(nameof(header));
}
}
}
}
public object header => _custom_header ?? display_name;
public object custom_header { get => _custom_header;
set {
if (value != _custom_header) {
_custom_header = value;
RaisePropertyChanged(nameof(custom_header), nameof(header));
}
}
}
public double width {
get => _width;
set {
if (_set(ref _width, value, DoubleUtil.AreClose))
width_changed?.Invoke(value);
}
}
double _actual_width;
public double actual_width {
get => _actual_width;
set {
_actual_width = value;
width = value;
RaisePropertyChanged(nameof(actual_width));
}
}
public bool is_visible { get => _is_visible; set => _set(ref _is_visible, value); }
public ListSortDirection? sort_direction { get => _sort_direction; set => Set(ref _sort_direction, value); }
public Color? background_color_override { get => _background_color_override; set => _set(ref _background_color_override, value); }
public Color? font_color_override { get => _font_color_override; set => _set(ref _font_color_override, value); }
public FontWeight? font_weight_override { get => _font_weight_override; set => _set(ref _font_weight_override, value); }
public FontFamily font_family => _fallback_font_family;
public FontWeight font_weight => _font_weight_override ?? _fallback_font_weight;
public Color font_color => font_color_override ?? _fallback_font_color;
public Color background_color => background_color_override ?? _fallback_background_color;
public double font_size => _fallback_font_size;
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => _set(ref _horizontal_alignment, value); }
public IDataGridColumnFilter filter { get; }
public string column_template { get; }
public string filter_template { get; }
string IDIYGridColumn.id => id.ToString();
ListSortDirection? ISortableDIYGridColumn.SortDirection { get => sort_direction; set => sort_direction = value; }
public GridColumnSettings create() =>
new GridColumnSettings(
id: id.ToString(),
display_name: display_name,
is_visible: is_visible,
font_color_override: font_color_override,
font_weight_override: font_weight_override,
width: width,
background_color_override: background_color_override,
horizontal_alignment: horizontal_alignment);
public void set_fallback_font_params(FontFamily font_family, double font_size, Color font_color, FontWeight font_weight, Color background_color) {
using (ReflectionHelper.detect_property_changes(this, RaisePropertyChanged, nameof(font_family), nameof(font_size), nameof(font_color), nameof(font_weight), nameof(background_color))) {
_fallback_font_family = font_family;
_fallback_font_size = font_size;
_fallback_font_color = font_color;
_fallback_background_color = background_color;
_fallback_font_weight = font_weight;
}
}
bool _set<T>(ref T backing, T new_value, [CallerMemberName]string prop = null) =>
_set(ref backing, new_value, EqualityComparer<T>.Default, prop);
bool _set<T>(ref T backing, T new_value, Func<T, T, bool> equals, [CallerMemberName] string prop = null) =>
// Technically, the GetHashCode impl could disagree with the equals delegate.
// However, this doesn't matter because the _set method only uses IEqualityComparer.Equals (not GetHashCode).
// It didn't seem worth the trouble of changing the method signature, as there is no conceivable reason why that method
// would ever be changed to use GetHashCode.
_set(ref backing, new_value, EqualityComparer.Create(equals, a => a.GetHashCode()), prop);
bool _set<T>(ref T backing, T new_value, IEqualityComparer<T> comparer, [CallerMemberName]string prop = null) {
if (Set(ref backing, new_value, comparer, prop)) {
_result_changed.execute();
return true;
}
return false;
}
public void load_from(GridColumnSettings value) {
if (!value.strong_id<TKey>().Equals(id))
throw new ArgumentException("Column id in settings object does not match the column model's id.", nameof(value));
using (_result_changed.defer_execution()) {
display_name = value.display_name;
is_visible = value.is_visible;
font_color_override = value.font_color_override;
font_weight_override = value.font_weight_override;
background_color_override = value.background_color_override;
width = value.width;
horizontal_alignment = value.horizontal_alignment;
}
}
}
class DIYGridColumnModel<TKey, TRow, TGridFilter> : DIYGridColumnModel<TKey> where TKey : struct {
readonly Func<TGridFilter, object> _get_column_filter_settings;
readonly Func<TGridFilter, object, TGridFilter> _set_column_filter_settings;
public DIYGridColumnModel(TKey id, IComparer<TRow> row_comparer, IDataGridColumnFilter<TRow> filter, string filter_template, Func<TGridFilter, object> get_column_filter_settings, Func<TGridFilter, object, TGridFilter> set_column_filter_settings)
: base(id, row_comparer.to_non_generic(), filter, filter_template) {
_get_column_filter_settings = get_column_filter_settings;
_set_column_filter_settings = set_column_filter_settings;
this.row_comparer = row_comparer;
this.filter = filter;
}
public new IComparer<TRow> row_comparer { get; }
public new IDataGridColumnFilter<TRow> filter { get; }
public void load_from(GridColumnSettings column_settings, TGridFilter grid_filter_settings) {
if (column_settings != null)
load_from(column_settings);
if (_get_column_filter_settings != null && grid_filter_settings != null)
filter?.load_state(_get_column_filter_settings(grid_filter_settings));
}
public TGridFilter assign_filter_settings(TGridFilter target) {
if (_set_column_filter_settings != null) {
var filter_settings = filter.get_state();
target = _set_column_filter_settings(target, filter_settings);
}
return target;
}
}
}
@@ -1,4 +1,5 @@
using bts;
using bts.collections;
using bts.utils;
using mbts.spark.filters;
using mbts.ui.controls.adorners;
@@ -867,6 +868,83 @@ namespace mbts.ui.controls {
public static void SetColumnKey(DependencyObject d, object value) => d.SetValue(ColumnKeyProperty, value);
#endregion
#region Column
public static readonly DependencyProperty ColumnProperty =
DependencyProperty.RegisterAttached("Column", typeof(object), typeof(DIYGrid), new PropertyMetadata());
public static object GetColumn(DependencyObject d) => d.GetValue(ColumnProperty);
public static void SetColumn(DependencyObject d, object value) => d.SetValue(ColumnProperty, value);
#endregion
#region ColumnModels
public static readonly DependencyProperty ColumnModelsProperty =
DependencyProperty.RegisterAttached("ColumnModels", typeof(IReadOnlyObservableCollection<IDIYGridColumn>),
typeof(DIYGrid),
new PropertyMetadata(null, OnColumnModelsChanged));
static void OnColumnModelsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
IDisposable hook = null;
if (e.NewValue is IEnumerable<IDIYGridColumnModel> col_models) {
resort();
if (e.NewValue is INotifyCollectionChanged col_models_notifier) {
hook = CollectionHelper.buffer_collection_changed_callback(col_models_notifier, resort, TimeSpan.FromSeconds(0.3), TimeSpan.Zero);
}
void resort() {
if (d is DIYGrid grid) {
var cols = grid.Columns;
if (cols != null) {
var col_map = cols.ToDictionary(c => c.id);
var sorted_cols = col_models.Select(cm => {
if (col_map.TryGetValue(cm.id, out var cb))
return cb;
if (cm is IDIYGridColumnFactory col_factory) {
cb = col_factory.create_view();
} else if (!string.IsNullOrEmpty(cm.column_template)) {
cb = grid.TryFindResource(cm.column_template) as ColumnBase;
}
if (cb is null) {
cb = new DIYGridColumn { id = cm.id };
BindingOperations.SetBinding(cb, ColumnBase.HeaderProperty, new Binding(nameof(DIYGridColumnModel<int>.header)) { Source = cm });
BindingOperations.SetBinding(cb, ColumnBase.SortDirectionProperty, new Binding(nameof(DIYGridColumnModel<int>.sort_direction)) { Source = cm });
cb.set_model(cm);
BindingOperations.SetBinding(cb, ColumnBase.IsVisibleProperty, new Binding(nameof(DIYGridColumnModel<int>.is_visible)) { Source = cm });
if (cm is IFilterableColumn fc) {
DataGridFilterProperties.SetFilter(cb, fc.filter);
DataGridFilterProperties.SetFilterTemplate(cb, grid.FindResource(fc.filter_template) as DataTemplate);
BindingOperations.SetBinding(cb, DataGridFilterProperties.FilterActiveProperty, new Binding(nameof(IDataGridColumnFilter.active)) { Source = fc.filter });
BindingOperations.SetBinding(cb, DataGridFilterProperties.FilterOpenProperty, new Binding(nameof(IDataGridColumnFilter.open)) { Source = fc.filter });
}
}
return cb;
}).ToList();
CollectionHelper.sync_with_never_duplicate(cols, sorted_cols);
}
}
}
}
SetColumnModelsHooks(d, hook);
}
public static IReadOnlyObservableCollection<IDIYGridColumn> GetColumnModels(DependencyObject d) =>
d.GetValue(ColumnModelsProperty) as IReadOnlyObservableCollection<IDIYGridColumn>;
public static void SetColumnModels(DependencyObject d, IReadOnlyObservableCollection<IDIYGridColumn> value) =>
d.SetValue(ColumnModelsProperty, value);
static readonly DependencyPropertyKey ColumnModelsHooksPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("ColumnModelsHooks", typeof(IDisposable),
typeof(DIYGrid),
new PropertyMetadata(null, FrameworkElementLifeDependencyHelper.DisposeOld));
internal static DependencyProperty ColumnModelsHooksProperty => ColumnModelsHooksPropertyKey.DependencyProperty;
internal static IDisposable GetColumnModelsHooks(DependencyObject d) =>
d.GetValue(ColumnModelsHooksProperty) as IDisposable;
static void SetColumnModelsHooks(DependencyObject d, IDisposable value) =>
d.SetValue(ColumnModelsHooksPropertyKey, value);
#endregion
#region SyncWithColumnGeometrySource
public static readonly DependencyProperty SyncWithColumnGeometrySourceProperty =
DependencyProperty.RegisterAttached(
@@ -1,5 +1,6 @@
using bts.utils;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.utils;
@@ -121,4 +122,111 @@ namespace mbts.ui {
}
}
}
class SimpleGridSettingsManagerVM<TColumnKey> : ViewModelBase, IObserver<GridSettings> where TColumnKey : struct {
readonly IReadOnlyList<DIYGridColumnModel<TColumnKey>> _columns;
readonly Property<GridSettings> _grid_settings;
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<double> _font_size;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
readonly Action<IEnumerable<GridColumnSettings>> _sync_column_order;
readonly Action _invalidate_rows;
public SimpleGridSettingsManagerVM(
IReadOnlyList<DIYGridColumnModel<TColumnKey>> columns,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags,
Action<IEnumerable<GridColumnSettings>> sync_column_order,
Action invalidate_rows) {
//
_columns = columns;
_sync_column_order = sync_column_order;
_invalidate_rows = invalidate_rows;
_column_flags = (column_flags ?? DictionaryHelper.empty<string, GridColumnSettingsFeatureFlags>())
.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
}
public GridSettings grid_settings => _grid_settings.Value;
public FontFamily font_family => _font_family.Value;
public FontWeight font_weight => _font_weight.Value;
public double font_size => _font_size.Value;
public SolidColorBrush font_color => _font_color.Value;
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SolidColorBrush background_color => _background_color.Value;
public SimpleGridSettingsVM create_vm(GridSettings settings) {
var vm = new SimpleGridSettingsVM(_column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
vm.load_from(settings);
return vm;
}
public GridSettings create() {
// IMPORTANT: don't overwrite current sorting
_grid_settings.set(grid_settings.with(_columns.Select(c => {
var col_settings = c.create();
if (_column_flags.TryGetValue(c.id.ToString(), out ImmutableColumnSettingStore store))
col_settings = store.apply(col_settings);
return col_settings;
})));
return grid_settings;
}
/// <summary>
/// This will overwrite the columns' immutable-value store (unlike the IObserver&lt;<see cref="GridSettings"/>&gt; 'OnNext' implementation)
/// </summary>
public void load_from(GridSettings grid) => _load_from_core(grid, true);
void IObserver<GridSettings>.OnCompleted() { }
void IObserver<GridSettings>.OnError(Exception error) { }
void IObserver<GridSettings>.OnNext(GridSettings grid) {
if (grid.columns != null) {
var cols_with_applied_flags = grid.columns.ToList();
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
}
}
grid = grid.with(cols_with_applied_flags);
}
_load_from_core(grid, false);
}
void _load_from_core(GridSettings grid, bool reset_immutable_flag_stores) {
grid = grid ?? GridSettings.Default;
_grid_settings.set(grid);
foreach (var col in _columns) {
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight, grid.background_color.argb_color());
}
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
// don't add insult to injury by crashing **
if (grid_settings.columns != null) {
if (reset_immutable_flag_stores) {
foreach (var kv in _column_flags) {
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
kv.Value.store(cs);
}
}
if (_columns != null) {
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => cs.id);
var columns_id_map = _columns.ToDictionary(c => c.id.ToString());
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count)
_sync_column_order?.Invoke(grid_settings.columns);
// ** don't assume that columns:column_settings have a perfect 1:1 relationship (they should, but it is possible we accidentally break the settings as grids/uis change) **
foreach (var col in _columns) {
if (col_settings_id_map.TryGetValue(col.id.ToString(), out GridColumnSettings cs))
col.load_from(cs);
}
}
_invalidate_rows?.Invoke();
}
}
}
}
@@ -60,6 +60,7 @@ namespace mbts.spark.filters {
TimeFilter = "TimeFilterTemplate",
DiscreteValues = "DiscreteValuesTemplate",
DiscreteValuesCollectionView = "DiscreteValuesCollectionViewTemplate",
SecurityCriteriaCriteria = "SecurityCriteriaCriteria",
EnumValues = "EnumValuesTemplate",
Regex = "RegexTemplate",
YesNo = "YesNoTemplate";
@@ -12,6 +12,8 @@ namespace mbts.spark.filters {
ICommand cancel { get; }
bool filter(object row);
event Action filter_changed;
void load_state(object state);
object get_state();
}
interface IDataGridColumnFilter<in TRow> : IDataGridColumnFilter {
@@ -20,6 +22,6 @@ namespace mbts.spark.filters {
interface IDataGridColumnPersistentFilter<in TRow, TState> : IDataGridColumnFilter<TRow> {
void load_state(TState state);
TState get_state();
new TState get_state();
}
}
}
@@ -1,5 +1,6 @@
using mbts.ui;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Windows.Input;
@@ -53,6 +54,15 @@ namespace mbts.spark.filters {
_raise_filter_changed();
}
object IDataGridColumnFilter.get_state() =>
get_state();
void IDataGridColumnFilter.load_state(object state) {
if (state is TState fstate)
load_state(fstate);
else if (state != null) throw ExceptionBuilder.Argument.not_of_type<TState>(nameof(state), state?.GetType());
}
protected abstract bool _get_active();
protected abstract bool _filter(TValue value);
protected abstract void _load_state_internal(TState state);
@@ -111,4 +121,4 @@ namespace mbts.spark.filters {
bool IDataGridColumnFilter.filter(object row) =>
filter(ConvertUtils.try_convert<TRow>(row));
}
}
}
@@ -1,5 +1,7 @@
using bts.api;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark.filters {
class SecurityCriteriaFilter<TRow> : FilterVMBase<TRow, SecurityImmutable, SecurityCriteria>, IDisposable {
@@ -43,4 +45,50 @@ namespace mbts.spark.filters {
void _set_default_values() =>
_security_criteria = model.result_criteria;
}
}
class SecurityCriteriaCriteriaFilter<TRow> : FilterVMBase<TRow, SecurityCriteria, SecurityCriteria>, IDisposable {
SecurityCriteria _security_criteria;
public SecurityCriteriaCriteriaFilter(Func<TRow, SecurityCriteria> get_row_val, string prop_name)
: base(get_row_val, prop_name) { }
public ContractPickerModel model { get; }
public void Dispose() =>
model.security_criteria_clause_changed -= _on_security_criteria_clause_changed;
public override SecurityCriteria get_state() =>
_security_criteria;
protected override bool _filter(SecurityCriteria value) {
if ((_security_criteria?.clauses).is_null_or_empty())
return true;
if (!_security_criteria.is_defined())
return true;
if ((value?.clauses).is_null_or_empty())
return false;
var result = value.clauses.Any(c => c.is_defined && _security_criteria.clauses.Any(fclause => c.is_subset_of(fclause)));
return result;
}
protected override void _load_state_internal(SecurityCriteria state) =>
_security_criteria = state;
protected override void _clear() {
_close();
using (_clear_properties())
_set_default_values();
}
protected override bool _get_active() =>
_security_criteria != null && _security_criteria.clauses.Length > 0;
void _on_security_criteria_clause_changed(object sender, SecurityCriteriaClauseChangedEventsArgs e) {
_security_criteria = model.result_criteria;
_raise_filter_changed();
}
void _set_default_values() =>
_security_criteria = model.result_criteria;
}
}
@@ -1,6 +1,7 @@
using bts;
using mbts.ui;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Windows.Input;
@@ -60,16 +61,24 @@ namespace mbts.spark.filters {
public TCompoundState get_state() =>
_get_state(_filter1.get_state(), _filter2.get_state());
object IDataGridColumnFilter.get_state() =>
get_state();
public void load_state(TCompoundState state) {
_filter1.load_state(_get_filter1_state(state));
_filter2.load_state(_get_filter2_state(state));
}
void IDataGridColumnFilter.load_state(object state) {
if (state is TCompoundState cstate)
load_state(cstate);
else throw ExceptionBuilder.Argument.not_of_type<TCompoundState>(nameof(state), state?.GetType());
}
protected void _raise_request_update() =>
RequestUpdate?.Invoke();
bool IDataGridColumnFilter.filter(object row) =>
filter(ConvertUtils.try_convert<TRow>(row));
}
}
}
@@ -165,6 +165,18 @@
/>
</usp:FilterContentControl>
</DataTemplate>
<DataTemplate x:Key="{x:Static filters:DataGridFilterKnownTemplates.SecurityCriteriaCriteria}">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<msp:ContractPickerView DataContext="{Binding model}">
</msp:ContractPickerView>
</usp:FilterContentControl>
</DataTemplate>
<DataTemplate x:Key="AlgoTypeTemplate">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
@@ -5,6 +5,8 @@ using System.Windows.Media;
namespace mbts.ui.converters {
class ColorToBrushConverter : IValueConverter {
public static ColorToBrushConverter Instance => new ColorToBrushConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is Color color) return new SolidColorBrush(color);
if (value is BTSColor bts_color) return new SolidColorBrush(bts_color);
@@ -9,19 +9,13 @@ using System.Reactive.Linq;
namespace mbts.spark {
[ProtoContract]
class SecurityCriteria : IEquatable<SecurityCriteria> {
public SecurityCriteria()
{
public SecurityCriteria() {
clauses = new SecurityCriteriaClause[] { };
}
public SecurityCriteria(IEnumerable<SecurityCriteriaClause> c)
{
public SecurityCriteria(IEnumerable<SecurityCriteriaClause> c) {
clauses = c.ToArray();
}
internal SecurityCriteria(IEnumerable<SecurityImmutable> securities) {
clauses = new[] { new SecurityCriteriaClause(securities) };
}
internal SecurityCriteria(params SecurityImmutable[] securities) : this(securities.AsEnumerable()) { }
[ProtoMember(1)]
public SecurityCriteriaClause[] clauses;
@@ -31,8 +25,11 @@ namespace mbts.spark {
new SecurityCriteriaClause (security_keys: keys.ToArray())
});
internal static SecurityCriteria from_securities(ISet<SecurityImmutable> securities) =>
new SecurityCriteria(securities ?? Enumerable.Empty<SecurityImmutable>());
internal static SecurityCriteria from_securities(params SecurityImmutable[] securities) =>
from_securities(securities.AsEnumerable());
internal static SecurityCriteria from_securities(IEnumerable<SecurityImmutable> securities) =>
new SecurityCriteria(new[] { SecurityCriteriaClause.from_securities(securities) });
internal static Func<SecurityCriteria, IObservable<IEnumerable<SecurityImmutable>>> get_to_securities(IBus bus) =>
new Func<SecurityCriteria, IObservable<IEnumerable<SecurityImmutable>>>(sc => to_securities(sc, bus));
@@ -61,8 +58,27 @@ namespace mbts.spark {
this.pretty_name = pretty_name;
}
public SecurityCriteriaClause(IEnumerable<SecurityImmutable> securities)
: this(security_keys: securities.Select(s => s.Id).ToArray()) { }
public static SecurityCriteriaClause from_securities(IEnumerable<SecurityImmutable> securities) {
var num_secs = securities.SoftCount();
uint[] keys;
string pretty_name = null;
if (num_secs.HasValue) {
keys = new uint[num_secs.Value];
int i = 0;
if (keys.Length == 1) {
foreach (var s in securities) {
keys[i++] = s.Id;
pretty_name = s.ToString();
}
} else {
foreach (var s in securities)
keys[i++] = s.Id;
}
} else {
keys = securities.Select(s => s.Id).ToArray();
}
return new SecurityCriteriaClause(security_keys: keys, pretty_name: pretty_name);
}
[ProtoMember(1)]
public readonly string pretty_name;
@@ -24,6 +24,12 @@ namespace mbts.spark {
return self == null ? all_secs : all_secs.Values.Where(s => ContractPickerModel.security_in_criteria(self, s)).ToDictionary(key => key.Id);
}
public static async Task<IReadOnlyDictionary<uint,SecurityImmutable>> securities(this SecurityCriteria self, IBus bus, TimeSpan timeout) {
var md_map = await bus.subscribe(SparkTags.SecuritiesTag).FirstOrDefaultAsync(timeout, DictionaryHelper.empty<uint, SecurityMD>());
var all_secs = md_map.Values.Select(md => md.security).ToDictionary(key => key.Id);
return self == null ? all_secs : all_secs.Values.Where(s => ContractPickerModel.security_in_criteria(self, s)).ToDictionary(key => key.Id);
}
public static IObservable<IEnumerable<SecurityImmutable>> subscribe(this SecurityCriteria self, IBus bus) {
var md_source = bus.subscribe(SparkTags.SecuritiesTag);
return self == null ? md_source.Select(kv => kv.Values.Select(smd => smd.security)) :
@@ -0,0 +1,20 @@
using System;
namespace mbts.ui {
interface IValueChangedEventArgs<out T> {
T value { get; }
}
static class ValueChangedEventArgs {
public static IValueChangedEventArgs<T> create<T>(Func<T> get_value) =>
new LazyImpl<T>(new Lazy<T>(get_value));
class LazyImpl<T> : IValueChangedEventArgs<T> {
readonly Lazy<T> _value;
public LazyImpl(Lazy<T> value) =>
_value = value;
public T value => _value.Value;
}
}
}
+2 -2
View File
@@ -260,13 +260,13 @@ namespace mbts.co2 {
}
// If Size is wrong remember to update from
[StructLayout(LayoutKind.Sequential, Size = 704)]
[StructLayout(LayoutKind.Sequential, Size = 712)]
internal struct ColdEntry {
public int exchange_status;
// Last trade
public AtomicPriceSize last_trade;
public ulong total_volume;
public utils.LLDateTime last_trade_time;
public utils.LLDateTime last_trade_time;
//// Settlement
public UInt64 misc_timestamp;
@@ -24,7 +24,7 @@ static class ExchangeSpecificUitlities{
}
public static bool is_ice_lots(SecurityImmutable sec){
return sec.Ice.MarketDescription.Contains("ICE Lots");
return sec.Ice.IsIceLots;
}
public static bool is_tas(SecurityImmutable sec){
@@ -33,25 +33,36 @@ static class ExchangeSpecificUitlities{
#endregion
#region CPP
#region CPP
public static double get_future_cpp (SecurityImmutable sec){
if(sec.Symbol == "HHD")
// only futures in HHD are the dailies, everything
// else is a strip or spread
return 1;
// TODO, this should be part of the security
if(sec.Symbol == "HHD")
return 2500;
if(is_ice_lots(sec))
return 2500;
else{
var days_in_month = DateTime.DaysInMonth(Convert.ToInt32(sec.Future.ExpirationYear),
Convert.ToInt32(sec.Future.ExpirationMonth));
return days_in_month; // because the size is multiplied by 2500 already
// other futures - HNG, HHD, HEN - are like daily
// strips
UInt32 days_in_month = 31;
var mm = sec.Future.ExpirationMonth;
if(mm <=11)
days_in_month = Convert.ToUInt32(
DateTime.DaysInMonth(
Convert.ToInt32(sec.Future.ExpirationYear),
// expects 1-12
Convert.ToInt32(sec.Future.ExpirationMonth+1)));
else
// TODO : sometimes Jan is 12 !
days_in_month = 31;
return days_in_month * 2500;
}
}
public static double get_spread_cpp(SecurityImmutable sec){
// for purpose of pnl we are 0'ing out the spread pnl
return 0;
}
public static double get_strip_cpp(PNLUnitImmutable pnl,
SecurityImmutable sec){
// add up all the legs
@@ -77,16 +77,17 @@ namespace bts.utils {
public static int auto_phase(params int[] hash_codes) =>
auto_phase(Array.AsReadOnly(hash_codes));
public static int auto_phase<T>(IEnumerable<T> items) =>
auto_phase(items.Take(_max_auto_phase_buckets).ToList());
public static int auto_phase<T>(IReadOnlyList<T> hash_codes) {
public static int auto_phase<T>(IReadOnlyList<T> hash_codes, int bit_span = 32) {
Log.assert(bit_span > 0 && bit_span <= 32);
// don't divide by zero
if (hash_codes.Count == 0)
return 0;
int size = Math.Min(hash_codes.Count, _max_auto_phase_buckets);
int bits_per_hash = 32 / size;
int bits_per_hash = bit_span / size;
int phase = 1 << (bits_per_hash - 1);
int rv = hash_codes[0].GetHashCode() % phase;
/// The idea is to split the 32 to bits into evenly sized buckets for each hash code item.
-1
View File
@@ -36,7 +36,6 @@ namespace bts {
Plugin,
Sdk,
Http,
iLink3
// Please keep this list in sync with logging.h/logging.cpp
}
@@ -252,6 +252,17 @@ namespace System.Collections.Generic {
return false;
}
public static bool TryGetFirstIndex<T>(this IEnumerable<T> source, Func<T, bool> predicate, out int index) {
if (source == null) throw new ArgumentNullException(nameof(source));
index = 0;
foreach (T element in source) {
if (predicate(element))
return true;
index++;
}
return false;
}
public static bool TryGetSingle<T>(this IEnumerable<T> source, out T result) {
if (source == null) throw new ArgumentNullException(nameof(source));
using (var en = source.GetEnumerator()) {
@@ -716,4 +727,4 @@ namespace System.Collections.Generic {
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
}
}
}
}
@@ -0,0 +1,21 @@
using System;
namespace bts.collections {
interface IReadOnlyIndex<TKey, TValue> {
TValue this[TKey key] { get; }
}
static class FunctionalIndex {
public static FunctionalIndex<TKey, TValue> create<TKey, TValue>(Func<TKey, TValue> get_value) =>
new FunctionalIndex<TKey, TValue>(get_value);
}
class FunctionalIndex<TKey, TValue> : IReadOnlyIndex<TKey, TValue> {
readonly Func<TKey, TValue> _get_value;
public FunctionalIndex(Func<TKey, TValue> get_value) =>
_get_value = get_value ?? throw new ArgumentNullException(nameof(get_value));
public TValue this[TKey key] => _get_value(key);
}
}
@@ -0,0 +1,88 @@
using mbts;
using System;
using System.Threading.Tasks;
namespace bts.utils {
public static class CallbackBuffer {
public static IDisposable buffer_callback(Func<Action, IDisposable> attach_callback, Action callback, TimeSpan delay_before_callback, TimeSpan delay_after_callback) {
bool is_disposed = false, is_busy = false, dirty = false;
object locker = new object();
var detach = attach_callback(callback_buffer_impl);
return Disposable.Create(() => {
lock (locker) {
if (is_disposed)
return;
is_disposed = true;
dirty = false;
}
Disposable.dispose(ref detach);
});
async void callback_buffer_impl() {
lock (locker) {
if (is_busy) {
dirty = true;
return;
}
is_busy = true;
}
while (true) {
if (delay_before_callback > TimeSpan.Zero) {
await Task.Delay(delay_before_callback);
}
lock (locker) {
if (is_disposed) {
is_busy = false;
dirty = false;
break;
}
}
callback();
if (delay_after_callback > TimeSpan.Zero) {
await Task.Delay(delay_after_callback);
}
lock (locker) {
if (!dirty) {
is_busy = false;
break;
}
dirty = false;
}
}
}
}
public static IDisposable buffer_async_callback(Func<Action, IDisposable> attach_callback, Func<Task> async_callback) {
bool is_disposed = false, is_busy = false, dirty = false;
object locker = new object();
var detach = attach_callback(callback_buffer_impl);
return Disposable.Create(() => {
lock (locker) {
if (is_disposed)
return;
is_disposed = true;
dirty = false;
}
Disposable.dispose(ref detach);
});
async void callback_buffer_impl() {
lock (locker) {
if (is_busy) {
dirty = true;
return;
}
is_busy = true;
}
while (true) {
await async_callback();
lock (locker) {
if (!dirty || is_disposed) {
is_busy = false;
break;
}
dirty = false;
}
}
}
}
}
}
@@ -1,9 +1,11 @@
using System;
using mbts;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
namespace bts.utils {
static class CollectionHelper {
@@ -77,6 +79,30 @@ namespace bts.utils {
return ~hi;
}
public static IDisposable buffer_collection_changed_callback(INotifyCollectionChanged source, Action callback, TimeSpan delay_before_callback, TimeSpan delay_after_callback) {
// return CallbackBuffer.buffer_callback(attach_callback, callback, delay_before_callback, delay_after_callback);
// IDisposable attach_callback(Action cb) {
// source.CollectionChanged += on_collection_changed;
// return Disposable.Create(() => source.CollectionChanged -= on_collection_changed);
// void on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) =>
// cb();
// }
return CallbackBuffer.buffer_async_callback(attach_callback, impl);
IDisposable attach_callback(Action cb) {
source.CollectionChanged += on_collection_changed;
return Disposable.Create(() => source.CollectionChanged -= on_collection_changed);
void on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) =>
cb();
}
async Task impl() {
if (delay_before_callback > TimeSpan.Zero)
await Task.Delay(delay_before_callback);
callback();
if (delay_after_callback > TimeSpan.Zero)
await Task.Delay(delay_after_callback);
}
}
public static void reflect_change<T, TData>(IList<T> target, IEnumerable<TData> source, Func<TData, T> create_item, Action<T> on_removed, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
@@ -9,11 +9,16 @@ namespace mbts.utils {
public static ArgumentException unexpected_case<T>(T value, string name) =>
new ArgumentException($"Unexpected {typeof(T)} '{value}.'", name);
public static ArgumentException not_of_type<T>(object value, string name) =>
not_of_type(value, name, typeof(T));
public static ArgumentException not_of_type<T>(string name, Type actual = null) =>
actual is null ?
not_of_type(name, typeof(T)) :
not_of_type(name, typeof(T), actual);
public static ArgumentException not_of_type(object value, string name, Type t) =>
new ArgumentException($"Value is not of the expected type '{t}.'");
public static ArgumentException not_of_type(string name, Type t) =>
new ArgumentException(Format.NotOfType(t), name);
public static ArgumentException not_of_type(string name, Type expected, Type actual) =>
new ArgumentException(Format.NotOfType(expected, actual), name);
public static ArgumentException null_or_empty(string name) =>
new ArgumentException("Value is null or empty.", name);
@@ -49,6 +54,8 @@ namespace mbts.utils {
public static string LessThanOrEqualTo(int value) => $"{Messages.LessThanOrEqualTo} {value}.";
public static string GreaterThan(int value) => $"{Messages.GreaterThan} {value}.";
public static string GreaterThanOrEqualTo(int value) => $"{Messages.GreaterThanOrEqualTo} {value}.";
public static string NotOfType(Type type) => $"Argument is not of expected type '{type}.'";
public static string NotOfType(Type expected, Type actual) => $"Argument is not of expected type '{expected}' (actually '{actual}').";
}
public static class Messages {
@@ -59,4 +66,4 @@ namespace mbts.utils {
GreaterThanOrEqualTo = "Greater than or equal to";
}
}
}
}
@@ -1,230 +1,238 @@
using mbts;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
namespace System.Reactive.Linq {
public static class ObservableExtensions {
/// <summary>
/// <para>Creates an "Observable Heartbeat" that will publish true/false to indicate whether or not the heartbeat is detected, it will only send updates when the 'heartbeat status' has changed.</para>
/// <para>Whenever the source produces an update, the heartbeat evaluates to true and the heartbeat duration clock is reset.</para>
/// <para>Whenever heartbeat duration clock expires, the heartbeat evaluates to false.</para>
/// </summary>
/// <typeparam name="T">Type of data produced by the source (bears no impact on the resultant observable heartbeat</typeparam>
/// <param name="source">The source that will indicate alive/dead by publishing updates within the duration period</param>
/// <param name="duration">The amount of time that must pass without receiving an update from the source to determine the heartbeat is not detected</param>
/// <returns>An "Observable Heartbeat"</returns>
public static IObservable<bool> AsHeartbeat<T>(this IObservable<T> source, TimeSpan duration) {
IObservable<bool> dead = source.Throttle(duration).Select(_ => false);
IObservable<bool> alive = source.Select(_ => true);
return dead.Merge(alive).DistinctUntilChanged();
}
public static IObservable<T> BufferLatest<T>(this IObservable<T> source, TimeSpan throttle) =>
source.Buffer(throttle).Where(vals => vals.Count > 0).Select(vals => vals[vals.Count - 1]);
public static IObservable<(T1, T2)> CombineLatest<T1, T2>(this IObservable<T1> source, IObservable<T2> second) =>
source.CombineLatest(second, (v1, v2) => (v1, v2));
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, Func<T, bool> predicate, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
/// <summary>
/// A less verbose way to call <see cref="Observable.FromEvent{Action{TEventArgs}, TEventArgs}(Action{Action{TEventArgs}}, Action{Action{TEventArgs}})"/>.
/// </summary>
public static IObservable<T> FromAction<T>(Action<T> event_source) =>
Observable.FromEvent<Action<T>, T>(cb => event_source += cb, cb => event_source -= cb);
public static IObservable<T> FromEvent<T>(Action<Action> add_handler, Action<Action> remove_handler, Func<T> selector) =>
Observable.Create<T>(o => {
callback();
add_handler(callback);
return Disposable.Create(() => remove_handler(callback));
void callback() => o.OnNext(selector());
});
public static IObservable<T> FromEventThrottle<T>(Action<Action<T>> add_handler, Action<Action<T>> remove_handler, TimeSpan throttle) =>
FromEventThrottle<T>(cb => {
add_handler(cb);
return Disposable.Create(() => remove_handler(cb));
}, throttle);
public static IObservable<T> FromEventThrottle<T>(Func<Action<T>, IDisposable> add_handler, TimeSpan throttle) =>
Observable.Create<T>(o => {
T last_value = default;
bool busy = false, dirty = false;
return add_handler(callback);
async void callback(T value) {
last_value = value;
if (busy) {
dirty = true;
} else {
busy = true;
do {
dirty = false;
o.OnNext(last_value);
await Task.Delay(throttle);
} while (dirty);
busy = false;
}
}
});
public static IObservable<TResult> FromEventThrottle<T, TResult>(Action<Action<T>> add_handler, Action<Action<T>> remove_handler, Func<T, TResult> selector, TimeSpan throttle) =>
FromEventThrottle(cb => {
add_handler(cb);
return Disposable.Create(() => remove_handler(cb));
}, selector, throttle);
public static IObservable<TResult> FromEventThrottle<T, TResult>(Func<Action<T>, IDisposable> add_handler, Func<T, TResult> selector, TimeSpan throttle) =>
Observable.Create<TResult>(o => {
T last_value = default;
bool busy = false, dirty = false;
return add_handler(callback);
async void callback(T value) {
last_value = value;
if (busy) {
dirty = true;
} else {
busy = true;
do {
var result = selector(last_value);
dirty = false;
o.OnNext(result);
await Task.Delay(throttle);
} while (dirty);
busy = false;
}
}
});
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source) =>
PairWithPrevious(source, default);
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
public static IObservable<T> RefCountReplay<T>(this IObservable<T> source, int buffer_size) {
if (buffer_size < 0) throw new ArgumentException("Buffer size is less than zero.", nameof(buffer_size));
if (buffer_size == 0) return source.Publish().RefCount();
int num_subscribers = 0;
ReplaySubject<T> replay = new ReplaySubject<T>(buffer_size);
IDisposable source_sub = null;
return Observable.Create<T>(o => {
if (Interlocked.Increment(ref num_subscribers) == 1)
source_sub = source.Subscribe(replay.OnNext);
IDisposable sub = replay.Subscribe(o);
return Disposable.Create(() => {
sub.Dispose();
if (Interlocked.Decrement(ref num_subscribers) == 0)
source_sub.Dispose();
});
});
}
/// <summary>
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
/// each inner sequence is terminated when the next one begins.
/// <para/>In other words, observers are notified by the inner observerables in &quot;series,&quot; rather than &quot;parallel.&quot;
/// </summary>
/// <typeparam name="TSource">The type of elements in the source (outer) sequence.</typeparam>
/// <typeparam name="TResult">The type of elements in the inner sequences.</typeparam>
/// <param name="source">A sequence of elements to create a series of inner sequences.</param>
/// <param name="select_next">A function to create an inner sequence.</param>
/// <returns>An IDisposable object used to unsubscribe from the source (outer) sequence, and the latest inner sequence.</returns>
public static IObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
Observable.Create<TResult>(o => {
object locker = new object();
bool is_disposed = false;
IDisposable inner_sub = null;
IDisposable outer_sub = source.Subscribe(on_outer);
return Disposable.Create(dispose);
// local methods
void on_outer(TSource value) {
lock (locker) {
if (!is_disposed) {
inner_sub?.Dispose();
inner_sub = select_next(value)?.Subscribe(o.OnNext);
}
}
}
void dispose() {
IDisposable temp_outer_sub = null, temp_inner_sub = null;
lock (locker) {
is_disposed = true;
temp_outer_sub = outer_sub;
outer_sub = null;
temp_inner_sub = inner_sub;
inner_sub = null;
}
temp_outer_sub?.Dispose();
temp_inner_sub?.Dispose();
}
});
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value) =>
Observable.Create<T>(o => {
o.OnNext(get_value());
return source.Subscribe(o);
});
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value, Func<bool> can_get_value) =>
Observable.Create<T>(o => {
if (can_get_value())
o.OnNext(get_value());
return source.Subscribe(o);
});
public static IDisposable Subscribe<T>(this IObservable<T> source, params IObserver<T>[] observers) {
return source.Subscribe(on_next, on_error, on_completed);
// local methods
void on_next(T value) {
for (int i = 0; i < observers.Length; i++)
observers[i].OnNext(value);
}
void on_error(Exception error) {
for (int i = 0; i < observers.Length; i++)
observers[i].OnError(error);
}
void on_completed() {
for (int i = 0; i < observers.Length; i++)
observers[i].OnCompleted();
}
}
/// <summary>
/// An inner subscription is made for each value in the source sequence using the specified delegate (<paramref name="subscribe_value"/>).
/// When a new value arrives, the subscription for the previous value is disposed before <paramref name="subscribe_value"/> is invoked with the new value.
/// </summary>
/// <typeparam name="T">The type of elements in the source sequence.</typeparam>
/// <param name="source">A sequence of elements to invoke a subscribe function on.</param>
/// <param name="subscribe_value">A function used to subscribe each source element.</param>
/// <returns>An IDisposable object used to unsubscribe from the source sequence, and dispose the latest value subscription.</returns>
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
IDisposable value_sub = null;
IDisposable source_sub = source.Subscribe(on_source);
return Disposable.Create(dispose);
// local methods
void on_source(T value) {
Disposable.dispose(ref value_sub);
value_sub = subscribe_value(value);
}
void dispose() {
Disposable.dispose(ref source_sub);
Disposable.dispose(ref value_sub);
}
}
// was sick of this scenario: observable_source.Select(enumerable => enumerable.FirstOrDefault(where)).Where(val => val != null)
// with structs it was even more annoying because they can't be null
public static IObservable<TResult> TrySelect<T, TResult>(this IObservable<T> source, Func<T, (TResult result, bool success)> try_select) =>
source.Select(try_select).Where(ts => ts.success).Select(ts => ts.result);
}
}
using mbts;
using System.Reactive.Subjects;
using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
namespace System.Reactive.Linq {
public static class ObservableExtensions {
/// <summary>
/// <para>Creates an "Observable Heartbeat" that will publish true/false to indicate whether or not the heartbeat is detected, it will only send updates when the 'heartbeat status' has changed.</para>
/// <para>Whenever the source produces an update, the heartbeat evaluates to true and the heartbeat duration clock is reset.</para>
/// <para>Whenever heartbeat duration clock expires, the heartbeat evaluates to false.</para>
/// </summary>
/// <typeparam name="T">Type of data produced by the source (bears no impact on the resultant observable heartbeat</typeparam>
/// <param name="source">The source that will indicate alive/dead by publishing updates within the duration period</param>
/// <param name="duration">The amount of time that must pass without receiving an update from the source to determine the heartbeat is not detected</param>
/// <returns>An "Observable Heartbeat"</returns>
public static IObservable<bool> AsHeartbeat<T>(this IObservable<T> source, TimeSpan duration) {
IObservable<bool> dead = source.Throttle(duration).Select(_ => false);
IObservable<bool> alive = source.Select(_ => true);
return dead.Merge(alive).DistinctUntilChanged();
}
public static IObservable<T> BufferLatest<T>(this IObservable<T> source, TimeSpan throttle) =>
source.Buffer(throttle).Where(vals => vals.Count > 0).Select(vals => vals[vals.Count - 1]);
public static IObservable<(T1, T2)> CombineLatest<T1, T2>(this IObservable<T1> source, IObservable<T2> second) =>
source.CombineLatest(second, (v1, v2) => (v1, v2));
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, Func<T, bool> predicate, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
public static Task<T> FirstOrDefaultAsync<T>(this IObservable<T> source, TimeSpan timeout, T default_value = default) {
var cts = new CancellationTokenSource(timeout);
var main_task = source.FirstOrDefaultAsync().ToTask(cts.Token);
var result_task = main_task.ContinueWith(t => t.Status == TaskStatus.RanToCompletion ? t.Result : default_value);
return result_task;
}
/// <summary>
/// A less verbose way to call <see cref="Observable.FromEvent{Action{TEventArgs}, TEventArgs}(Action{Action{TEventArgs}}, Action{Action{TEventArgs}})"/>.
/// </summary>
public static IObservable<T> FromAction<T>(Action<T> event_source) =>
Observable.FromEvent<Action<T>, T>(cb => event_source += cb, cb => event_source -= cb);
public static IObservable<T> FromEvent<T>(Action<Action> add_handler, Action<Action> remove_handler, Func<T> selector) =>
Observable.Create<T>(o => {
callback();
add_handler(callback);
return Disposable.Create(() => remove_handler(callback));
void callback() => o.OnNext(selector());
});
public static IObservable<T> FromEventThrottle<T>(Action<Action<T>> add_handler, Action<Action<T>> remove_handler, TimeSpan throttle) =>
FromEventThrottle<T>(cb => {
add_handler(cb);
return Disposable.Create(() => remove_handler(cb));
}, throttle);
public static IObservable<T> FromEventThrottle<T>(Func<Action<T>, IDisposable> add_handler, TimeSpan throttle) =>
Observable.Create<T>(o => {
T last_value = default;
bool busy = false, dirty = false;
return add_handler(callback);
async void callback(T value) {
last_value = value;
if (busy) {
dirty = true;
} else {
busy = true;
do {
dirty = false;
o.OnNext(last_value);
await Task.Delay(throttle);
} while (dirty);
busy = false;
}
}
});
public static IObservable<TResult> FromEventThrottle<T, TResult>(Action<Action<T>> add_handler, Action<Action<T>> remove_handler, Func<T, TResult> selector, TimeSpan throttle) =>
FromEventThrottle(cb => {
add_handler(cb);
return Disposable.Create(() => remove_handler(cb));
}, selector, throttle);
public static IObservable<TResult> FromEventThrottle<T, TResult>(Func<Action<T>, IDisposable> add_handler, Func<T, TResult> selector, TimeSpan throttle) =>
Observable.Create<TResult>(o => {
T last_value = default;
bool busy = false, dirty = false;
return add_handler(callback);
async void callback(T value) {
last_value = value;
if (busy) {
dirty = true;
} else {
busy = true;
do {
var result = selector(last_value);
dirty = false;
o.OnNext(result);
await Task.Delay(throttle);
} while (dirty);
busy = false;
}
}
});
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source) =>
PairWithPrevious(source, default);
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
public static IObservable<T> RefCountReplay<T>(this IObservable<T> source, int buffer_size) {
if (buffer_size < 0) throw new ArgumentException("Buffer size is less than zero.", nameof(buffer_size));
if (buffer_size == 0) return source.Publish().RefCount();
int num_subscribers = 0;
ReplaySubject<T> replay = new ReplaySubject<T>(buffer_size);
IDisposable source_sub = null;
return Observable.Create<T>(o => {
if (Interlocked.Increment(ref num_subscribers) == 1)
source_sub = source.Subscribe(replay.OnNext);
IDisposable sub = replay.Subscribe(o);
return Disposable.Create(() => {
sub.Dispose();
if (Interlocked.Decrement(ref num_subscribers) == 0)
source_sub.Dispose();
});
});
}
/// <summary>
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
/// each inner sequence is terminated when the next one begins.
/// <para/>In other words, observers are notified by the inner observerables in &quot;series,&quot; rather than &quot;parallel.&quot;
/// </summary>
/// <typeparam name="TSource">The type of elements in the source (outer) sequence.</typeparam>
/// <typeparam name="TResult">The type of elements in the inner sequences.</typeparam>
/// <param name="source">A sequence of elements to create a series of inner sequences.</param>
/// <param name="select_next">A function to create an inner sequence.</param>
/// <returns>An IDisposable object used to unsubscribe from the source (outer) sequence, and the latest inner sequence.</returns>
public static IObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
Observable.Create<TResult>(o => {
object locker = new object();
bool is_disposed = false;
IDisposable inner_sub = null;
IDisposable outer_sub = source.Subscribe(on_outer);
return Disposable.Create(dispose);
// local methods
void on_outer(TSource value) {
lock (locker) {
if (!is_disposed) {
inner_sub?.Dispose();
inner_sub = select_next(value)?.Subscribe(o.OnNext);
}
}
}
void dispose() {
IDisposable temp_outer_sub = null, temp_inner_sub = null;
lock (locker) {
is_disposed = true;
temp_outer_sub = outer_sub;
outer_sub = null;
temp_inner_sub = inner_sub;
inner_sub = null;
}
temp_outer_sub?.Dispose();
temp_inner_sub?.Dispose();
}
});
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value) =>
Observable.Create<T>(o => {
o.OnNext(get_value());
return source.Subscribe(o);
});
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value, Func<bool> can_get_value) =>
Observable.Create<T>(o => {
if (can_get_value())
o.OnNext(get_value());
return source.Subscribe(o);
});
public static IDisposable Subscribe<T>(this IObservable<T> source, params IObserver<T>[] observers) {
return source.Subscribe(on_next, on_error, on_completed);
// local methods
void on_next(T value) {
for (int i = 0; i < observers.Length; i++)
observers[i].OnNext(value);
}
void on_error(Exception error) {
for (int i = 0; i < observers.Length; i++)
observers[i].OnError(error);
}
void on_completed() {
for (int i = 0; i < observers.Length; i++)
observers[i].OnCompleted();
}
}
/// <summary>
/// An inner subscription is made for each value in the source sequence using the specified delegate (<paramref name="subscribe_value"/>).
/// When a new value arrives, the subscription for the previous value is disposed before <paramref name="subscribe_value"/> is invoked with the new value.
/// </summary>
/// <typeparam name="T">The type of elements in the source sequence.</typeparam>
/// <param name="source">A sequence of elements to invoke a subscribe function on.</param>
/// <param name="subscribe_value">A function used to subscribe each source element.</param>
/// <returns>An IDisposable object used to unsubscribe from the source sequence, and dispose the latest value subscription.</returns>
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
IDisposable value_sub = null;
IDisposable source_sub = source.Subscribe(on_source);
return Disposable.Create(dispose);
// local methods
void on_source(T value) {
Disposable.dispose(ref value_sub);
value_sub = subscribe_value(value);
}
void dispose() {
Disposable.dispose(ref source_sub);
Disposable.dispose(ref value_sub);
}
}
// was sick of this scenario: observable_source.Select(enumerable => enumerable.FirstOrDefault(where)).Where(val => val != null)
// with structs it was even more annoying because they can't be null
public static IObservable<TResult> TrySelect<T, TResult>(this IObservable<T> source, Func<T, (TResult result, bool success)> try_select) =>
source.Select(try_select).Where(ts => ts.success).Select(ts => ts.result);
}
}
@@ -0,0 +1,15 @@
namespace bts.utils {
// Mainly used as an optional override of nullable data arg.
// Explanation: the 'with' method pattern typically interprets null args to mean "keep the existing value."
// However, this makes it impossible for the caller to overwrite an existing value with null.
// This struct is designed (or at least was originally) to address this use-case.
// It is analagous to escape chars.
readonly struct OptionalNullable<T> where T : struct {
public OptionalNullable(T? value) => this.value = value;
public T? value { get; }
public static implicit operator OptionalNullable<T>(T? value) =>
new OptionalNullable<T>(value);
}
}
+1 -1
View File
@@ -1 +1 @@
set(sources files/audit_ilink3.py;files/audit_upload.sh;files/audit_uploader.py;files/cert_secure_logon.txt;files/example.conf;files/futures.coconut.xz;files/onixs_ice/ConnectivityConfiguration.APITest.xml;files/onixs_ice/ConnectivityConfiguration.APITest.xml.orig;files/onixs_ice/ConnectivityConfiguration.PerfTest.xml;files/onixs_ice/ConnectivityConfiguration.Production.xml;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js)
set(sources files/cert_secure_logon.txt;files/futures.coconut.xz;files/onixs_ice/ConnectivityConfiguration.APITest.xml;files/onixs_ice/ConnectivityConfiguration.PerfTest.xml;files/onixs_ice/ConnectivityConfiguration.Production.xml;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js)
-405
View File
@@ -1,405 +0,0 @@
import json
import csv
import datetime, time
import argparse, os
def valid_file(f):
if not os.path.exists(f):
# Argparse uses the ArgumentTypeError to give a rejection message like:
# error: argument input: x does not exist
raise argparse.ArgumentTypeError("{0} does not exist".format(f))
return f
def valid_date(s):
try:
return datetime.datetime.strptime(s, "%Y%m%d")
except ValueError:
raise argparse.ArgumentTypeError("Not a valid date: '{0}'.".format(s))
def print_headers():
# From https://www.cmegroup.com/confluence/display/EPICSANDBOX/iLink+3+-+Minimum+Acceptable+Audit+Trail+Elements+-+Data+Definitions+and+Validation+Rules
line_to_write = "Sending Timestamps,Message Direction,Operator ID,Session ID,Manual Order Indicator,Message Type,Instrument,Order Flow ID,\
CME Globex Order ID,Client Order ID,Order Request ID,Buy/Sell Indicator,Quantity,Limit Price,Stop Price,Order Type,Order Qualifier,IFM Flag,\
Display Quantity,Minimum Quantity,Country of Origin,Fill Price,Fill Quantity,Cumulative Quantity,Remaining Quantity,Aggressor Flag,Source of Cancellation,\
Reject Reason,CME Globex Message ID,Cross ID,Quote Request ID,Message Quote ID,Quote Set ID,Quote Entry ID,Bid Price,Bid Size,Offer Price,Offer Size,\
Processed Entries,Party Details List Request ID,ListUpdateAction,Self-Matching Prevention ID,Self-Match Prevention Instr,Customer Type Indicator,\
AvgPxGroupID,Average Px Ind,CmtaGiveUpCD,Origin,Clearing Trade Price Type,Customer Handling Instr,Executing Firm ID,Account Number,Take Up Firm,Take up Account"
with open (args.output_file, 'w') as the_file:
the_file.write(line_to_write)
the_file.write("\n")
def set_blanks():
return ""
def get_value(json_key):
prices = ["Price", "LastPx", "StopPx"]
if json_key in json_data:
if json_key in prices:
x = json_data[json_key]
return x['mantissa']/100000000000
else:
return json_data[json_key]
else:
return ""
# Column 1 - Sending Timestamps
def SendingTimeEpoch(epochtime): #1
#TIME = time.strftime('%Y%m%d-%H:%M:%S.{}'.format(epochtime/1000000000), time.gmtime(epochtime/1000000000))
TIME = datetime.datetime.fromtimestamp(int(epochtime) / 1e9).strftime('%Y%m%d-%H:%M:%S.%f')
return TIME
# Column 2 - Message Direction
def direction(whichway):
if whichway == '<':
DIRECTION = "FROM CME"
else:
DIRECTION = "TO CME"
return DIRECTION
# Column 3 - Operator ID
def SenderID(sender):
if "SenderID" in json_data:
SENDER = json_data["SenderID"]
return SENDER
else:
return SESSIONID
# Column 4 - Session ID
# Column 5 - Manual Order Indicator
def ManualOrderIndicator(manual):
if "ManualOrderIndicator" in json_data:
if manual == "Manual":
return "1"
else:
return "0"
# Column 6 - Message Type
def MessageType(msg_type,orderstatus):
if msg_type == "NewOrderSingle514":
return "D"
elif msg_type == "ExecutionReportNew522":
return "8--0"
if "ExecutionReportTrade" in msg_type:
if orderstatus == "PartiallyFilled":
return "8--1"
else:
return "8--2"
elif msg_type == "ExecutionReportCancel534":
return "8--4"
elif msg_type == "ExecutionReportModify531":
return "8--5"
elif msg_type == "ExecutionReportCancel534":
return "8--H"
elif msg_type == "ExecutionReportReject523":
return "8--8"
elif msg_type == "OrderCancelReplaceReject536":
return "9--2"
elif msg_type == "OrderCancelReject535":
return "9--1"
elif msg_type == "PartyDetailsDefinitionRequest518":
return "CX"
elif msg_type == "PartyDetailsDefinitionRequestAck519":
return "CY"
elif msg_type == "OrderCancelRequest516":
return "F"
elif msg_type == "OrderCancelReplaceRequest515":
return "G"
# Column 12 - Buy-Sell Indicator/Side
def Side(indicator):
if "Side" in json_data:
if indicator == "Buy":
return "1"
else:
return "2"
# Column 16 - Order Type/OrdType
def OrdType(ordertype):
if "OrdType" in json_data:
if ordertype == "MarketWithProtection" or ordertype == "MarketwithProtection":
return "1"
elif ordertype == "Limit":
return "2"
elif ordertype == "StopWithProtection" or ordertype == "StopwithProtection":
return "3"
elif ordertype == "StopLimit":
return "4"
elif ordertype == "MarketLimit":
return "K"
else:
return ""
# Column 17 - Order Qualifier/TimeInForce
def TimeInForce(orderqual):
if "TimeInForce" in json_data:
if orderqual == "Day":
return "0"
elif orderqual == "GTC":
return "1"
elif orderqual == "FAK":
return "3"
elif orderqual == "GTD":
return "6"
else:
return ""
# Column 18 IFM Flag/OFMOverride
def OFMOverride(imfflag):
if "OFMOverride" in json_data:
if imfflag == "Enabled":
return "1"
else:
return "0"
# Column 19 Display Quantity/DisplayQty
def DisplayQty(qty):
if "DisplayQty" in json_data:
if str(qty) == "0":
return ""
elif str(qty) == "4294967295":
return ""
else:
return qty
# Column 26 - Aggressor Flag/AggressorIndicator
def AggressorIndicator(aggressor):
if "AggressorIndicator" in json_data:
if aggressor == "True":
return "1"
else:
return "0"
# Column 27 - Source of Cancellation/ExecRestatementReason
def ExecRestatementReason(sourcecancel):
if "ExecRestatementReason" in json_data:
if sourcecancel == "Exchange":
return "8"
elif sourcecancel == "CancelledNotBest":
return "9"
elif sourcecancel == "CancelOnDisconnect":
return "100"
elif sourcecancel == "CancelRestingDueToSelfMatchPrevention":
return "103"
elif sourcecancel == "CancelFromCMEGlobexCreditControlsViolation":
return "104"
elif sourcecancel == "CancelFromCMEOne":
return "105"
elif sourcecancel == "CancelFromRiskManagementAPI":
return "106"
elif sourcecancel == "CancelAggressingDueToSelfMatchPrevention":
return "107"
elif sourcecancel == "CancelDueToVolatilityQuoteOptionLessThanMinOrderSize":
return "108"
else:
return ""
# Column 43 - Self-Match Prevention Instr/SelfMatchPreventionInstruction
def SelfMatchPreventionInstruction(instr):
if "SelfMatchPreventionInstruction" in json_data:
if instr == "CancelOldest":
return "O"
elif instr == "CancelNewest":
return "N"
else:
return ""
# Column 44 - Customer Type Indicator/CustOrderCapacity
def CustOrderCapacity(custord):
if "CustOrderCapacity" in json_data:
if custord == "NULL_VALUE":
return ""
elif custord == "Membertradingfortheirownaccount":
return "1"
elif custord == "ClearingFirmtradingforitsproprietaryaccount":
return "2"
elif custord == "Membertradingforanothermember":
return "3"
else:
return "4"
# Column 46 - Average Px Ind/AvgPxInd
def AvgPxInd(avgpxindicator):
if "AvgPxInd" in json_data:
if avgpxindicator == "NULL_VALUE":
return ""
else:
return avgpxindicator
# Column 48 - Origin/ClearingAccountType
def ClearingAccountType(accounttype):
if "ClearingAccountType" in json_data:
if accounttype == "Customer":
return "0"
elif accounttype == "Firm":
return "1"
else:
return ""
# Column 49 - Clearing Trade Price Type/ClearingTradePriceType
def ClearingTradePriceType(clrtrdprice):
if "ClearingTradePriceType" in json_data:
if clrtrdprice == "NULL_VALUE":
return "0"
else:
return "3"
# Column 50 - Customer Handling Instr/CustOrderHandlingInst
def CustOrderHandlingInst(custorderinst):
if "CustOrderHandlingInst" in json_data:
if custorderinst == "ClientElectronic":
return "C"
elif custorderinst == "TEST":
return "D"
else:
return ""
# Column 51 and 52 - Getting the Firm and Account Number
def partyid(partydetailid,column):
for x in partydetailid:
role = x['PartyDetailRole']
id = x['PartyDetailID']
if column == "51" and role == "ExecutingFirm":
return id
if column == "52" and role == "CustomerAccount":
return id
parser = argparse.ArgumentParser()
parser.add_argument("json_log", help="the json log[.gz] you wish to process", type=valid_file)
parser.add_argument("output_file", help="the destination csv file you wish to write")
parser.add_argument("-d", "--date", help="the date you wish to parse from the log, specified as YYYYmmdd",
default=datetime.datetime.today().strftime("%Y%m%d"), type=valid_date)
args = parser.parse_args()
print_headers()
list_types = ['NewOrderSingle514', 'OrderCancelReplaceRequest515', 'OrderCancelRequest516', 'PartyDetailsDefinitionRequest518', 'PartyDetailsDefinitionRequestAck519', \
'ExecutionReportNew522', 'ExecutionReportReject523', 'ExecutionReportTradeOutright525', 'ExecutionReportTradeSpread526', 'ExecutionReportTradeSpreadLeg527', \
'ExecutionReportModify531', 'ExecutionReportCancel534', 'OrderCancelReject535', 'OrderCancelReplaceReject536', ]
# Note that the timedeltas specificed in trade_day_start and trade_day_end
# are based on Chicago times; as such, this script should run on a machine
# set to a Chicago timezone. A future impromovement would be to determine
# the current machine's timezone and determine the appropriate offset
# versus Chicago time, but for now we assume this will run on a Chicago
# tz machine.
input_as_datetime = args.date
trade_day_start = input_as_datetime - datetime.timedelta(days=1) + datetime.timedelta(hours=16, minutes=30)
trade_day_utc_start = time.gmtime((time.mktime(trade_day_start.timetuple())))
trade_day_end = input_as_datetime + datetime.timedelta(hours=16)
trade_day_utc_end = time.gmtime((time.mktime(trade_day_end.timetuple())))
file_opener = None
if args.json_log.endswith(".gz"):
import gzip
file_opener = gzip.open
else:
file_opener = open
#inputFile = open(fileInput) #open json file
#outputFile = open(fileOutput, 'w') #load csv file
# Get Session
with file_opener(args.json_log) as json_file:
for line in json_file:
data = json.loads(line)
json_data = data['msg_json']
if "Session" in json_data:
SESSIONID = json_data["Session"]
#inputFile = open(fileInput) #open json file
#outputFile = open(fileOutput, 'w') #load csv file
with file_opener(args.json_log) as json_file:
for line in json_file:
data = json.loads(line)
line_timestamp = time.strptime(data['time'], "%Y%m%d-%H:%M:%S.%f")
if trade_day_utc_start <= line_timestamp <= trade_day_utc_end:
json_data = data['msg_json']
NAME = json_data["Name"]
if NAME in list_types:
EXECUTING_FIRM_ID = ""
ACCOUNT_NUMBER = ""
DIRECTION = direction(data['direction'])
# Set Blanks
blank_json = {'QuoteReqID': '', 'QuoteID': '', 'QuoteSetID': '', 'QuoteEntryID': '', 'BidPx': '', 'BidSize': '', 'OfferPx': '', \
'OfferSize': '', 'NoProcessedEntries': '', 'ListUpdateAction': '', 'MinQty': '', 'AvgPxGroupID': '', \
'CmtaGiveUpCD': '', 'TAKE_UP_FIRM': '', 'TAKE_UP_ACCOUNT': '',}
# No Data Processing
straight_json = {'SecurityID': '', 'ClOrdID': '', 'OrderID': '', 'OrderRequestID': '', 'OrderQty': '', \
'Location': '', 'LastQty': '', 'CumQty': '', 'LeavesQty': '', 'Text': '', 'SideTradeID': '', 'CrossID': '', 'SelfMatchPreventionID': '', \
'Price': '', 'StopPx': '', 'LastPx': '', 'PartyDetailsListReqID': '', 'OrdStatus': ''}
# Processing Data
process_json = {'SendingTimeEpoch': '', 'ManualOrderIndicator': '', 'SenderID': '', 'Side': '', 'OrdType': '', 'TimeInForce': '', \
'OFMOverride': '', 'AggressorIndicator': '', 'ExecRestatementReason': '', 'SelfMatchPreventionInstruction': '', 'CustOrderCapacity': '', \
'AvgPxInd': '', 'ClearingAccountType': '', 'ClearingTradePriceType': '', 'CustOrderHandlingInst': '', 'DisplayQty': ''}
for d in straight_json:
test = get_value(d)
if test != "None" or test != "":
if "," in str(test):
test = test.replace(",",";")
if "4294967295" in str(test):
test = ""
if "us;" in str(test):
test = test.upper()
if d == "SelfMatchPreventionID" and len(str(test)) > 8:
test = ""
straight_json[d] = test
else:
straight_json[d] = ''
for d in process_json:
if d in json_data:
function = locals()[d]
test = function(json_data[d])
if test != "None":
process_json[d] = test
else:
process_json[d] = ''
elif d == "SenderID":
function = locals()[d]
test = function(SenderID)
if test != "None":
process_json[d] = test
# Party Message Fields
# Column 51 - Executing Firm ID
if "NoPartyDetails" in json_data:
EXECUTING_FIRM_ID = partyid(json_data['NoPartyDetails'], "51")
# Column 52 - Party Account Number
ACCOUNT_NUMBER = partyid(json_data['NoPartyDetails'], "52")
line_to_write = process_json['SendingTimeEpoch'] + "," + DIRECTION + "," + process_json['SenderID'] + "," + SESSIONID + "," + process_json['ManualOrderIndicator'] + "," + \
MessageType(NAME,straight_json['OrdStatus']) + "," + str(straight_json['SecurityID']) + "," + str(straight_json['ClOrdID']) + "," + str(straight_json['OrderID']) + "," + str(straight_json['ClOrdID']) + "," + \
str(straight_json['OrderRequestID']) + "," + process_json['Side'] + "," + str(straight_json['OrderQty']) + "," + str(straight_json['Price']) + "," + \
str(straight_json['StopPx']) + "," + process_json['OrdType'] + "," + process_json['TimeInForce'] + "," + process_json['OFMOverride'] + "," + str(process_json['DisplayQty']) + "," + \
blank_json['MinQty'] + "," + straight_json['Location'] + "," + str(straight_json['LastPx']) + "," + str(straight_json['LastQty']) + "," + str(straight_json['CumQty']) + "," + \
str(straight_json['LeavesQty']) + "," + process_json['AggressorIndicator'] + "," + process_json['ExecRestatementReason'] + "," + str(straight_json['Text']) + "," + \
str(straight_json['SideTradeID']) + "," + str(straight_json['CrossID']) + "," + blank_json['QuoteReqID'] + "," + blank_json['QuoteID'] + "," + blank_json['QuoteSetID'] + "," + \
blank_json['QuoteEntryID'] + "," + blank_json['BidPx'] + "," + blank_json['BidSize'] + "," + blank_json['OfferPx'] + "," + blank_json['OfferSize'] + "," + \
blank_json['NoProcessedEntries'] + "," + str(straight_json['PartyDetailsListReqID']) + "," + blank_json['ListUpdateAction'] + "," + str(straight_json['SelfMatchPreventionID']) + "," + \
process_json['SelfMatchPreventionInstruction'] + "," + process_json['CustOrderCapacity'] + "," + blank_json['AvgPxGroupID'] + "," + process_json['AvgPxInd'] + "," + \
blank_json['CmtaGiveUpCD'] + "," + str(process_json['ClearingAccountType']) + "," + process_json['ClearingTradePriceType'] + "," + process_json['CustOrderHandlingInst'] + "," + \
EXECUTING_FIRM_ID + "," + str(ACCOUNT_NUMBER) + "," + blank_json['TAKE_UP_FIRM'] + "," + blank_json['TAKE_UP_ACCOUNT']
#print(line_to_write)
with open (args.output_file, 'a') as the_file:
the_file.write(line_to_write)
the_file.write("\n")
-73
View File
@@ -1,73 +0,0 @@
#/bin/bash
function help_and_exit()
{
echo
echo Usage: $0 [option]... config_file
echo
echo Can also be used in cron like the following
echo 40 16 * * 1-5 /usr/bin/docker exec spark_10000 bash /sparkbin/data_files/audit_upload.sh /sparkdata/data/audit_uploads/example.conf
echo example.conf included. The location of the conf file should be relative to the docker container, not the host
echo
echo Options are as follows:
echo " -d|--date YYYYMMDD Example is 20180731"
echo " Help: "
echo " -h|--help Display this message."
echo
exit 1
}
DATE=$(date +%Y%m%d)
ORIG_DIR=$(pwd)
ERROR_LEVEL="0"
while getopts "d:h" option; do
case $option in
d) DATE=$OPTARG ;;
h) help_and_exit;;
\?) echo "Invalid option: -$OPTARG" >&2;
exit 1;;
esac
done
shift $((OPTIND-1))
CONFIG_FILE=$1
declare -A conf_vars; while read line; do conf_vars[${line%=*}]=${line#*=}; done <$CONFIG_FILE
SESSION_ID=${conf_vars[session_id]}
FCM=${conf_vars[fcm]}
CLIENT=${conf_vars[client]}
cd /sparkbin/data_files
for SESSION in $(ls /sparkdata/data/ilink3/); do
if [[ $SESSION == *$SESSION_ID* ]]; then
echo "Session is $SESSION"
SESSION_DAY=$(date +%Y-%m-%d)
if [[ $SESSION == "cme_"* ]]; then
SESSION_DAY=$(date -d "$DATE -$(date -d $DATE +%u) days" +"%Y-%m-%d")
AUDIT_SCRIPT="audit_ilink3.py"
fi
echo $SESSION_DAY
if [[ ! -d /sparkdata/data/ilink3/$SESSION/audit_logs ]]; then
mkdir -p /sparkdata/data/ilink3/$SESSION/audit_logs
fi
if [[ -d /sparkdata/data/ilink3/$SESSION/$SESSION_DAY ]]; then
echo "Starting the creation of the audit log"
python $AUDIT_SCRIPT -d $DATE /sparkdata/data/ilink3/$SESSION/$SESSION_DAY/log /sparkdata/data/ilink3/$SESSION/audit_logs/"$FCM"_audit_"$SESSION"_"$DATE".csv
fi
if [[ ! -d /sparkdata/data/md_audit ]]; then
mkdir /sparkdata/data/md_audit
fi
if [[ -d /sparkdata/data/md_audit ]]; then
# Running the MD Audit csv generation
cd /sparkbin
./sparkd adminreport md > /sparkdata/data/md_audit/"$FCM"_md_"$CLIENT"_"$DATE".csv
cd /sparkbin/data_files
fi
echo "Uploading to the (s)FTP Server"
python audit_uploader.py --config_file="$CONFIG_FILE" --datestamp $DATE --session="$SESSION" --client="$CLIENT" --fcm="$FCM"
fi
done
-150
View File
@@ -1,150 +0,0 @@
import getopt, sys, datetime
import paramiko, ftplib
#########################################################
# Commandline Args #
#########################################################
def usage():
print "-------------------------------------------------------"
print "-Usage: -"
print "-------------------------------------------------------"
print "- --config_file= : Path to upload config file -"
print "- -if no path is specified look -"
print "- in the working directory. -"
print "- -"
print "- --help : Print this message -"
print "- -"
print "- --datestamp= : Optional, defaults to today -"
print "- -replaces *datestamp in the -"
print "- the upload config file -"
print "- -"
print "- --fcm : Optional, spark input -"
print "- -"
print "- --session : Optional, spark jf session -"
print "-------------------------------------------------------"
fcm = ''
session = ''
try:
options, arguments = getopt.getopt(sys.argv[1:],"h",['config_file=', 'datestamp=', 'help', 'fcm=', 'client=', 'session='])
except getopt.GetoptError, err:
print str(err)
usage()
sys.exit(2)
for option, value in options:
if option in ("-h","--h","-help","--help"):
usage()
sys.exit()
elif option in ("-config_file","--config_file"):
config_file = value
elif option in ("-datestamp","--datestamp"):
datestamp = value
elif option in ("-fcm","--fcm"):
fcm = value
elif option in ("-client","--client"):
client = value
elif option in ("-session","--session"):
session = value
for argument in arguments:
if argument in ("h", "help"):
usage()
sys.exit()
else:
print "Unrecognized commandline argument. Type help for usage."
sys.exit()
if len(options) <= 0:
print "Requires a config file to run."
usage()
sys.exit()
#########################################################
# Read Config Function #
#########################################################
def read_config_file(config_file, datestamp=datetime.datetime.today().strftime('%Y%m%d'), fcm=fcm, session=session):
'''
Parse our config file that gets passed in as a commandline parameter.
All config keys must be of the form key=value. White space is ignored.
All config keys must be seperated by a newline character.
Called during initialization.
'''
upload_config = {}
config_file = open(config_file, 'r')
config_file = config_file.read()
for config in config_file.split("\n"):
if '=' in config:
cleaned_key = config.split('=')[0].strip().upper()
cleaned_value = config.split('=')[1].strip()
if '*datestamp*' in cleaned_value:
cleaned_value = cleaned_value.replace('*datestamp*',datestamp)
if '*fcm*' in cleaned_value:
cleaned_value = cleaned_value.replace('*fcm*',fcm)
if '*client*' in cleaned_value:
cleaned_value = cleaned_value.replace('*client*',client)
if '*session*' in cleaned_value:
cleaned_value = cleaned_value.replace('*session*',session)
upload_config[cleaned_key] = cleaned_value
else:
pass
return upload_config
#########################################################
# FTP File Transfer Function #
#########################################################
def putfile_ftp(file, put_path, site, user, password, port=21, verbose=True):
if verbose: print 'Uploading', file
local = open(file, 'rb')
remote = ftplib.FTP(site)
remote.login(user, password)
remote.storbinary(put_path, local, 1024)
remote.quit()
local.close()
if verbose: print 'Upload done.'
#########################################################
# SFTP File Transfer Function #
#########################################################
def putfile_sftp(file, put_path, site, username, password, port=22, verbose=True):
transport = paramiko.Transport((site, port))
transport.connect(username = username, password = password)
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put(file, put_path)
sftp.close()
transport.close()
#########################################################
# Main #
#########################################################
#if datestamp != None:
# upload_config = read_config_file(config_file, datestamp)
#else:
# upload_config = read_config_file(config_file)
try:
datestamp
except NameError:
upload_config = read_config_file(config_file)
else:
upload_config = read_config_file(config_file, datestamp)
print upload_config
if upload_config["PROTOCOL_PUT"] in ['FTP','ftp']:
print "Protocol = FTP"
putfile_ftp(upload_config["GET_ILINK3_FILE_PATH"] + upload_config["GET_ILINK3_FILE_NAME"], upload_config["PUT_ILINK3_FILE_PATH"] + upload_config["PUT_ILINK3_FILE_NAME"], upload_config["PUT_HOST"], upload_config["USERNAME_PUT"], upload_config["PASSWORD_PUT"], upload_config["PORT_PUT"] )
putfile_ftp(upload_config["GET_MD_FILE_PATH"] + upload_config["GET_MD_FILE_NAME"], upload_config["PUT_MD_FILE_PATH"] + upload_config["PUT_MD_FILE_NAME"], upload_config["PUT_HOST"], upload_config["USERNAME_PUT"], upload_config["PASSWORD_PUT"], upload_config["PORT_PUT"] )
elif upload_config["PROTOCOL_PUT"] in ['SFTP','sftp']:
print "Protocol = SFTP"
putfile_sftp(upload_config["GET_ILINK3_FILE_PATH"] + upload_config["GET_ILINK3_FILE_NAME"], upload_config["PUT_ILINK3_FILE_PATH"] + upload_config["PUT_ILINK3_FILE_NAME"], upload_config["PUT_HOST"], upload_config["USERNAME_PUT"], upload_config["PASSWORD_PUT"], int(upload_config["PORT_PUT"]) )
putfile_sftp(upload_config["GET_MD_FILE_PATH"] + upload_config["GET_MD_FILE_NAME"], upload_config["PUT_MD_FILE_PATH"] + upload_config["PUT_MD_FILE_NAME"], upload_config["PUT_HOST"], upload_config["USERNAME_PUT"], upload_config["PASSWORD_PUT"], int(upload_config["PORT_PUT"]) )
+12 -19
View File
@@ -1,19 +1,12 @@
Session ID Access ID Secret Key Creation Date Expiration Date Environment
M4D lLtjH74y1Kh5XUJTLyH2 f1K_tROQp2lTIplVp34WxKa7xDUgfFnIteolfik061o 2019-09-03 2020-09-03 CERT
MC1 VEMlkDuLz74OVHSuacQB IuHyYT3b-9KUo3WsuXykt-fXsjzp7L-JOrdCSQZLNl0 2019-09-03 2020-09-03 CERT
MC5 77lzSUwPLlM9obaXh2mK z6aA9qfASDKfZkYTVKFqJd-78i0HZ2Q9ec3CKCYIqHg 2019-09-03 2020-09-03 CERT
MC7 RphzI3rn71oGUEiYnX9e y6OvvPPyoyj2S8YAnE1gFduXT8v29HIMjO5EngYxSNg 2019-09-03 2020-09-03 CERT
MC8 PYBIm2TI9gFPIxmHbuYl 6o-ZolGH2DdeQOlGtJ5Zqoh_kg7VdbxkmjeOHmigKwU 2019-09-03 2020-09-03 CERT
NGG RYcigz3bSLIIBBF2A2Hq qB1dm9VkTKeXcdQpFzla8B3_jwTgya_-Fk5iyEJvr2E 2020-06-15 2021-06-15 NEWRELEASE
NGF o4OTmmTLQ7Lv4g6bCm26 HbD3FneMyb9Fu-FkCDLscCczHqFcFE2PyWv978RXGyA 2019-12-20 2020-12-20 NEWRELEASE
UZE EeFdZ1FPepk6U4k3xBZA 2LjEtdLvBLLB1QuDJ4KiJW6M4dF6IHWd_UKmo7p3_Us 2020-06-24 2021-06-24 NEWRELEASE
VAE Y8iqatTewuoHX16gghw8 FNcCB3Ks1qleS56quYzH4V7zAu78YY2bCdLRi9tmU9E 2020-06-24 2021-06-24 NEWRELEASE
Session ID Access ID Secret Key Creation Date Expiration Date Environment
M4D lLtjH74y1Kh5XUJTLyH2 f1K_tROQp2lTIplVp34WxKa7xDUgfFnIteolfik061o 2019-09-03 2020-09-03 CERT
MC1 VEMlkDuLz74OVHSuacQB IuHyYT3b-9KUo3WsuXykt-fXsjzp7L-JOrdCSQZLNl0 2019-09-03 2020-09-03 CERT
MC5 77lzSUwPLlM9obaXh2mK z6aA9qfASDKfZkYTVKFqJd-78i0HZ2Q9ec3CKCYIqHg 2019-09-03 2020-09-03 CERT
MC7 RphzI3rn71oGUEiYnX9e y6OvvPPyoyj2S8YAnE1gFduXT8v29HIMjO5EngYxSNg 2019-09-03 2020-09-03 CERT
MC8 PYBIm2TI9gFPIxmHbuYl 6o-ZolGH2DdeQOlGtJ5Zqoh_kg7VdbxkmjeOHmigKwU 2019-09-03 2020-09-03 CERT
-16
View File
@@ -1,16 +0,0 @@
fcm=FCM
client=NAME
session_id=ses
put_host=ftpc.example.com
port_put=2222
username_put=user
password_put=password
protocol_put=sftp
get_ilink3_file_path=/sparkdata/data/ilink3/*session*/audit_logs/
put_ilink3_file_path=/
get_ilink3_file_name=*fcm*_audit_*session*_*datestamp*.csv
put_ilink3_file_name=*fcm*_audit_*session*_*datestamp*.csv
get_md_file_path=/sparkdata/data/md_audit/
put_md_file_path=/
get_md_file_name=*fcm*_md_*client*_*datestamp*.csv
put_md_file_name=*fcm*_md_*client*_*datestamp*.csv
+1 -1
View File
@@ -1 +1 @@
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/AlgoTypes.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cLine truncated
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/AlgoTypes.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cLine truncated
+1 -1
View File
@@ -319,7 +319,7 @@ if __name__ == "__main__":
if DEBUG:
for f in response.file:
print (f.content)
print f.content
else:
output = response.SerializeToString()
sys.stdout.write(output)
@@ -292,6 +292,22 @@ const BTSAccount* AdminServiceUtils::find_account (const AdminData& data,
return account_ptr;
return nullptr;
}
SecurityRiskLimitUnit const* AdminServiceUtils::get_sec_limit (
const AdminData& data,
uint32_t account_id,
uint32_t sec_key){
const BTSAccount* acct_ptr = find_account(data, account_id,false);
if(acct_ptr != nullptr){
if(acct_ptr->has_proprietary_risk_limits()){
const auto& prop_limits = acct_ptr->proprietary_risk_limits();
for (const auto& l : prop_limits.security_limits()){
if(l.security_key() == sec_key)
return &l;
}
}
}
return nullptr;
}
std::vector<const BTSAccount*> AdminServiceUtils::accounts (
const AdminData& data,
bool include_deleted){
@@ -580,9 +596,7 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(const bt
rv.set_session(conn_ptr->name());
rv.set_operator_id(cme_user_data->operator_id());
rv.set_location(cme_user_data->location());
rv.set_smp_id(cme_user_data->smp_id());
if (cme_user_data->smp_instruction()== "O")
rv.set_smp_instruction(api::orderman::SelfMatchPreventionInstruction::SELF_MATCH_PREVENTION_INSTRUCTION_CANCEL_RESTING);
else if (cme_user_data->smp_instruction()== "N")
@@ -696,7 +710,26 @@ void AdminServiceUtils::update_account(bts::api::admin::AdminData& admin_data,
validate_account_update (admin_data, updated_account, result);
if (result.is_success()){
bts::api::admin::BTSAccount* account_ptr = find_account(admin_data, updated_account.id());
// prop_limits are not overwritten when the account is updated
// from the risk portal
const ProprietaryRiskLimits prop_limits = account_ptr->proprietary_risk_limits();
account_ptr->CopyFrom(updated_account);
account_ptr->mutable_proprietary_risk_limits()->CopyFrom(prop_limits);
}
}
void AdminServiceUtils::update_proprietary_risk(
AdminData& data,
const ProprietaryRiskLimitsAPIArgument& arg,
AdminServiceOperationResult& result){
BTSAccount* acct_ptr = find_account(data,arg.account_id());
if(acct_ptr == nullptr){
result.set_is_success(false);
result.set_error_text("Could not find account id");
return;
}
else{
result.set_is_success(true);
acct_ptr->mutable_proprietary_risk_limits()->CopyFrom(arg.proprietary_risk_limits());
}
}
void AdminServiceUtils::delete_account (bts::api::admin::AdminData& admin_data,
@@ -83,6 +83,9 @@ public:
static const BTSAccount* find_account (const AdminData& data,
uint32_t id,
bool include_deleted=false);
static SecurityRiskLimitUnit const* get_sec_limit (const AdminData& data,
uint32_t account_id,
uint32_t sec_key);
// this function is an indirection layer for account access
// so deletions are dealt in a more deliberate manner
static std::vector<const BTSAccount*> accounts (const AdminData& data,
@@ -142,6 +145,10 @@ public:
static void update_account(AdminData& data,
const BTSAccount& updated_account,
AdminServiceOperationResult& result);
static void update_proprietary_risk(
AdminData& data,
const ProprietaryRiskLimitsAPIArgument& arg,
AdminServiceOperationResult& result);
static void delete_account (AdminData& admin_data,
const IDAPIArgument& id,
AdminServiceOperationResult& result);
@@ -81,9 +81,9 @@ void PositionUtilities::apply_fill (PositionEntry& pos,
const Fill& f,
bool is_add){
// strip legs are not applied to positions
if(f.fill_kind() == Fill::FILL_KIND_STRIP_LEG ||
f.fill_kind() == Fill::FILL_KIND_SOD_STRIP_LEG)
return;
// if(f.fill_kind() == Fill::FILL_KIND_STRIP_LEG ||
// f.fill_kind() == Fill::FILL_KIND_SOD_STRIP_LEG)
// return;
PositionUtilities::apply_fill(pos,
f.side(),
f.last_price(),
@@ -113,6 +113,13 @@ void PositionUtilities::total_position(const PositionUnit& p,
p.admin_day().short_pos() + p.admin_sod().short_pos();
}
void PositionUtilities::day_position(const PositionUnit& p,
uint32_t& total_long,
uint32_t& total_short){
total_long = p.day().long_pos() + p.admin_day().long_pos();
total_short = p.day().short_pos() + p.admin_day().short_pos();
}
PositionEntry PositionUtilities::add_positions (
const std::vector<PositionEntry const*>& v){
double long_pos = 0, long_price = 0, short_pos = 0, short_price = 0;
@@ -39,6 +39,10 @@ public:
uint32_t& total_long,
uint32_t& total_short);
static void day_position(const bts::api::position::PositionUnit& p,
uint32_t& total_long,
uint32_t& total_short);
static bts::api::position::PositionEntry add_positions (
const std::vector<bts::api::position::PositionEntry const*>& v);
+28
View File
@@ -7,6 +7,31 @@ import "lib/api/apicore.proto";
import "lib/domain_model/Security.proto";
import "lib/api/services/OrderService.proto";
/************************************************************************/
/********************************DJ prop*********************************/
/************************************************************************/
message SecurityRiskLimitUnit{
uint32 security_key = 10;
bool enforce_net_pos_limits = 20;
uint32 net_long_position_limit = 30;
uint32 net_short_position_limit = 40;
bool enforce_clip_limits = 50;
uint32 clip_size = 60;
bool enforce_price_bands = 70;
double min_sell_price = 80;
double max_buy_price = 90;
}
message ProprietaryRiskLimits{
repeated SecurityRiskLimitUnit security_limits = 10;
}
message ProprietaryRiskLimitsAPIArgument{
uint32 account_id = 10;
ProprietaryRiskLimits proprietary_risk_limits = 20;
}
/************************************************************************/
/********************************Gen (temp)******************************/
/************************************************************************/
@@ -152,6 +177,7 @@ message BTSAccount{
// extra parameters for risk enforcement
RiskLimitEnforcement risk_limit_enforcement=120;
AccountLossLimit account_loss_limit = 130;
ProprietaryRiskLimits proprietary_risk_limits = 1000;
}
message RiskLimitEnforcement{
@@ -497,4 +523,6 @@ service AdminService {
rpc md_entitlement_data(bts.api.VoidMessage) returns (stream MDEntitlementReportUnit);
// misc
rpc md_permissions_data(bts.api.VoidMessage) returns (stream UserSymbolMDPermissionUnit);
// prop sec limits
rpc update_proprietary_risk_limits (ProprietaryRiskLimitsAPIArgument) returns (AdminServiceOperationResult);
}
+1 -4
View File
@@ -386,9 +386,7 @@ message NativeOrderState {
RoutingRecord routing_record = 5;
NativeOrderType native_type = 6;
string last_transact_time = 7;
uint64 last_transact_time_unix_ns = 9;
double stop_px = 8;
uint64 exchange_order_id_u64 = 10;
}
// Custom order states
@@ -457,8 +455,7 @@ enum TimeInForce {
TIME_IN_FORCE_UNDEFINED = 0;
TIME_IN_FORCE_DAY = 48;
TIME_IN_FORCE_GTC = 49;
TIME_IN_FORCE_IOC = 51; // FAK
TIME_IN_FORCE_DATE = 52;
TIME_IN_FORCE_IOC = 51;
}
enum ManualOrderIndicator {
-6
View File
@@ -45,12 +45,6 @@ CMEOptions {
}
}
ICEPublisherOptions {
value {
network_interface: "eth1"
}
}
#to make this work in dev clone the operations repo
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
#modify the easyTunnelProxy.properties file to replace
File diff suppressed because it is too large. Load diff
+1
View File
@@ -58,6 +58,7 @@ message MultiThreadedWindows {
optional bool algo_studio = 6 [default = true];
optional bool ladder = 7 [default = true];
optional bool synthetic_management = 8 [default = true];
optional bool risk_management = 10 [default = false];
}
message LadderKnobs {
+4 -5
View File
@@ -9,9 +9,9 @@ message JetFIX {
enum JetFIXPlatform {
GLBX = 1;
CBOE = 6;
SIM = 2;
ICE = 3;
SIMICE = 4;
SIM = 2;
ICE = 3;
SIMICE = 4;
}
optional JetFIXPlatform platform = 15 [default = GLBX];
@@ -87,7 +87,6 @@ message JetFIX {
optional uint32 max_connection_attempts = 346 [default=10];
optional bool auto_fix_seq_num = 347 [default=false];
optional bool use_ilink3 = 348 [default=false] ;
}
repeated Connection connection = 10;
@@ -117,4 +116,4 @@ message CMECannedSecureLogon {
"MC8 AbEEtBn63WIAuItPDz1c n3ndjYb03Hx6wBO3me35HoONyDgO5TZKWk0tDSaX9KA 2018-04-05 2019-04-05 CERT \n"
"T39 UWggBgHWAe07aqW3ps2I tbb3it0rUQSSTa0TC9QcRwkrHEMDLWhaJH4VQ0FqGB4 2018-04-05 2019-04-05 NEWRELEASE \n"
];
}
}
+110 -4
View File
@@ -219,13 +219,32 @@ inline bool test_price_equality(double price_1, double price_2) {
// ice specialization /////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
inline bool is_ice_spread(const bts::api::ICEExchangeData& ice_data){
const auto& strip = ice_data.strip_name();
return strip.find("/") != std::string::npos;
// TODO, move this to its own file
static const uint32_t days_in_month[] = {31,28,31,30,31,30,31,31,30,31,30,31};
inline Quantity from_exchange_qty (Quantity exchange_qty,
const bts::api::Security& sec){
return Quantity(exchange_qty.value()/sec.ice().exchange_qty_mult());
}
inline Quantity to_exchange_qty (Quantity internal_qty,
const bts::api::Security& sec){
return Quantity(internal_qty.value()*sec.ice().exchange_qty_mult());
}
inline bool is_spread(const bts::api::Security& sec){
return sec.has_ice() && sec.has_spread() &&
sec.ice().strip_name().find("/") != std::string::npos;
}
inline bool is_strip(const bts::api::Security& sec){
return sec.has_ice() && sec.has_spread() && !is_ice_spread(sec.ice());
auto rv = sec.has_ice() && sec.has_spread() &&
!is_spread(sec);
if(rv != sec.ice().is_strip())
LOG(OrderMan, Error, "Sec:{} is_strip incorrect, expected:{}",
sec.id(),rv);
return rv;
}
inline bool is_spread_of_strips(const bts::api::Security& sec){
@@ -239,5 +258,92 @@ inline bool is_spread_of_strips(const bts::api::Security& sec){
return false;
}
inline bool is_strip_with_no_leg_fills(const bts::api::Security& sec){
return sec.has_ice() &&
sec.symbol() == "HHD" &&
is_strip(sec);
}
inline bool is_daily_contract(const api::Security& s){
return s.symbol() == "HHD";
}
inline bool is_tas(const api::Security& s){
return s.has_ice() &&
s.ice().market_description().find("TAS") != std::string::npos;
}
// risk qty is the qty used for risk purposes for
// 1 lot of this security.
// daily futures and ice lots are considered 1,
// other futures (HNG,HIS,HEN etc) are the days
// in the month
inline uint32_t get_fut_risk_qty(const api::Security& s){
bts_assert(s.has_future());
bts_assert(s.has_ice());
if(is_daily_contract(s))
return 1;
else if(s.ice().is_ice_lots())
return 1;
else{
auto mm = s.future().expiration_month();
if(mm <=11)
return days_in_month[mm];
else{
LOG(persist, Error, "Illegal month:{} for sec:{}",
mm, s.id());
// TODO
// Jan is showing as 12 sometimes
return 31;
}
}
}
inline uint32_t get_spread_direct_risk_qty(const api::Security& s){
bts_assert(s.has_spread());
bts_assert(s.has_ice());
if(s.ice().is_ice_lots())
return 1;
else
return 30;
}
inline uint32_t get_risk_qty (const api::Security& s){
if(s.has_future())
return get_fut_risk_qty(s);
else if (s.has_spread())
return get_spread_direct_risk_qty(s);
else{
bts_assert(false);
return 0.0;
}
}
inline void get_parent_child_relationships(
const std::map<uint32_t, api::Security const*>& sec_map,
std::map<uint32_t, api::Security const*>& out_res){
auto find_master_future =
[&](const api::Security& child_f) -> api::Security const* {
for (const auto& kp : sec_map){
const auto& f = *kp.second;
if(f.has_ice() &&
f.has_future() &&
!f.ice().is_ice_lots() &&
!is_tas(f) &&
f.symbol() == child_f.symbol() &&
f.future().expiration_year() == child_f.future().expiration_year() &&
f.future().expiration_month() == child_f.future().expiration_month() &&
f.future().expiration_day() == child_f.future().expiration_day())
return &f;
}
return nullptr;
};
for (const auto& kp : sec_map){
const auto& f = *kp.second;
if(f.has_ice() && f.has_future() && f.ice().is_ice_lots())
out_res[kp.first] = find_master_future(f);
}
}
} // namespace securities
} // namespace bts
+3
View File
@@ -116,6 +116,9 @@ message ICEExchangeData {
string strip_name = 140;
string mic_code = 150;
bool is_strip = 160;
bool is_ice_lots = 170;
// for example 2500 for HNG, this is the qty the exchange needs to see
uint32 exchange_qty_mult = 180;
}
message FutureContract {
+1 -1
View File
@@ -52,7 +52,7 @@ class FIXEncodingStream {
auto msg_offset = _buffer.size();
_current_msg = FIXFragment(_buffer);
_begin_string_bm = _current_msg.add<fix::BeginString>(std::string_view {"FIX.4.2"});
_begin_string_bm = _current_msg.add<fix::BeginString>("FIX.4.2");
_body_length_bm = _current_msg.add<fix::BodyLength>(0, 6);
char mt_encoding[4];
-1
View File
@@ -85,7 +85,6 @@ typedef StringField<307> UnderlyingSecurityDesc;
typedef StringField<336> TradingSessionID;
typedef StringField<35> MsgType;
typedef StringField<37> OrderID;
typedef BigIntField<37> OrderIDu64;
typedef StringField<41> OrigClOrdID;
typedef StringField<440> ClearingAccount;
typedef StringField<48> SecurityID;
-9
View File
@@ -2,8 +2,6 @@
#include <memory>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <string_view>
#include "lib/jetfix/codec/ReaderCursor.h"
#include "lib/utils/meta_programming.h"
#include "lib/utils/datetime.h"
@@ -205,13 +203,6 @@ public:
static std::pair<uint32_t,uint32_t> write (char*buffer,std::string const &str)
{return write (buffer,str.data (),str.size ());}
#if __cplusplus >= 201703L
static uint32_t max_value_size (std::string_view const&str) {return str.size ();}
static std::pair<uint32_t,uint32_t> write (char*buffer,std::string_view const &str)
{return write (buffer,str.data (),str.size ());}
#endif
static std::pair<uint32_t,uint32_t> write (char*buffer,const char*str,uint32_t sz)
{
memcpy (buffer,str,sz);
@@ -23,9 +23,9 @@ SocketConnection::SocketConnection(bts::config::JetFIX::Connection const &cfg,
: _fd(-1), _cfg(cfg), _log(cfg.log_fix_stream()), _in_log(), _out_log(),
_perf_log(perf_log) {
if (_log) {
_in_log.open(std::string("/tmp/fix_raw_read_log_") + session_name(cfg).as_string,
_in_log.open(std::string("/tmp/fix_raw_read_log_") + session_name(cfg),
std::ios_base::app);
_out_log.open(std::string("/tmp/fix_raw_write_log_") + session_name(cfg).as_string,
_out_log.open(std::string("/tmp/fix_raw_write_log_") + session_name(cfg),
std::ios_base::app);
}
}
+21 -19
View File
@@ -788,7 +788,7 @@ OutFIXPtr make_logon(uint32_t seq_num, uint32_t last_seq_num_processed,
{
std::string sender_comp_id = session_cfg.sender_subid();
buffer.add<ICEApplicationSystemName>(sender_comp_id);
buffer.add<ICEApplicationSystemVersion>(std::string_view{"1.0"});
buffer.add<ICEApplicationSystemVersion>("1.0");
buffer.add<ICEApplicationSystemVendor>(sender_comp_id);
buffer.add<UserName>(session_cfg.user_name());
@@ -797,18 +797,19 @@ OutFIXPtr make_logon(uint32_t seq_num, uint32_t last_seq_num_processed,
buffer.add<EncryptMethod>(0);
buffer.add<ICEStrategyPreference>(1);
buffer.add<ICETimeStampPreference>(1);
} else {
buffer.add<ApplicationSystemName>(std::string_view{application});
buffer.add<ApplicationSystemVersion>(std::string_view{"1.0"});
buffer.add<ApplicationSystemVendor>(std::string_view{"BTS"});
}
else
{
buffer.add<ApplicationSystemName>(application);
buffer.add<ApplicationSystemVersion>("1.0");
buffer.add<ApplicationSystemVendor>("BTS");
if (session_cfg.has_trading_session_id()) {
buffer.add<fix::NoTradingSessions>(1);
buffer.add<fix::TradingSessionID>(session_cfg.trading_session_id());
}
if (session_cfg.has_udf_support_indicator())
buffer.add<fix::UDFSupportIndicator>(
session_cfg.udf_support_indicator());
buffer.add<fix::UDFSupportIndicator>(session_cfg.udf_support_indicator());
if (session_cfg.use_cme_secure_logon()) {
bts_assert(session_cfg.sender_compid().size() >= 3);
@@ -819,24 +820,25 @@ OutFIXPtr make_logon(uint32_t seq_num, uint32_t last_seq_num_processed,
std::string last_seq_num_str(9, ' ');
i32_toa(last_seq_num_processed, &last_seq_num_str[0], 9);
auto ld = glbx::calculate_hmac(
session_id, seq_num_str, session_cfg.sender_compid(),
session_cfg.sender_subid(), time_str, session_cfg.target_subid(),
std::to_string(session_cfg.seconds_between_hbs()),
session_cfg.sender_locationid(), last_seq_num_str, application,
"1.0", "BTS");
#ifndef BTS_EDGE
LOG(Jetfix, Info, "Canonical string for secure logon: {}",
ld.canonical);
#endif
session_id, seq_num_str, session_cfg.sender_compid(),
session_cfg.sender_subid(), time_str, session_cfg.target_subid(),
std::to_string(session_cfg.seconds_between_hbs()),
session_cfg.sender_locationid(), last_seq_num_str, application, "1.0",
"BTS");
#ifndef BTS_EDGE
LOG(Jetfix, Info, "Canonical string for secure logon: {}", ld.canonical);
#endif
buffer.add<EncodedTextLen>(ld.access_key.size());
buffer.add<EncodedText>(ld.access_key);
buffer.add<EncryptedPasswordMethod>(std::string_view{"CME-1-SHA-256"});
buffer.add<EncryptedPasswordMethod>("CME-1-SHA-256");
buffer.add<EncryptedPasswordLen>(ld.signature.size());
buffer.add<EncryptedPassword>(ld.signature);
} else {
}
else
{
buffer.add<RawDataLength>(session_cfg.raw_data().size());
buffer.add<RawData>(session_cfg.raw_data());
if (session_cfg.user_name() != "")
if(session_cfg.user_name() != "")
buffer.add<UserName>(session_cfg.user_name());
}
}
+1 -1
View File
@@ -185,7 +185,7 @@ void load_seq_nums(std::string const &path, uint32_t *last_seq_num,
FIXFileStore::FIXFileStore(config::JetFIX::Connection const &cfg,
std::string base_path)
: me(new PImpl(cfg)) {
auto sn = session_name(cfg).as_string;
auto sn = session_name(cfg);
me->active = sn.size() > 0;
if (me->active) {
+14 -19
View File
@@ -75,55 +75,49 @@ bool in_session (datetime::PTime const &ref_date,
return closest_earlier_end<closest_earlier_start;
}
SessionName session_name_from_ptime (datetime::PTime t,
std::string session_name_from_ptime (datetime::PTime t,
boost::posix_time::time_duration time_of_day_start,
boost::posix_time::time_duration time_of_day_end)
{
SessionName result;
result.as_string = std::string(10,' ');
std::string result (10,' ');
auto start_of_session = t;
if (time_of_day_start > time_of_day_end &&
start_of_session.time_of_day () <= time_of_day_end)
start_of_session -= boost::gregorian::date_duration (1);
using namespace boost::posix_time;
ptime epoch(boost::gregorian::date(1970,1,1));
ptime session_start_date(start_of_session.date());
result.as_uint64_t = (session_start_date - epoch).total_milliseconds();
sprintf (&(result.as_string[0]), "%04d-%02d-%02d",
sprintf (&result[0], "%04d-%02d-%02d",
(unsigned short) start_of_session.date ().year (),
start_of_session.date ().month ().as_number (),
start_of_session.date ().day ().as_number ());
return result;
}
SessionName session_name (datetime::PTime const &ref_date,
boost::gregorian::greg_weekday weekday_start,
boost::posix_time::time_duration time_of_day_start,
boost::gregorian::greg_weekday weekday_end,
boost::posix_time::time_duration time_of_day_end)
std::string session_name (datetime::PTime const &ref_date,
boost::gregorian::greg_weekday weekday_start,
boost::posix_time::time_duration time_of_day_start,
boost::gregorian::greg_weekday weekday_end,
boost::posix_time::time_duration time_of_day_end)
{
if (in_session (ref_date,weekday_start,time_of_day_start,weekday_end,time_of_day_end))
return session_name_from_ptime (closest_before (ref_date,weekday_start,time_of_day_start),
boost::posix_time::time_duration(0, 0, 0),
boost::posix_time::time_duration(0, 0, 1));
else
return {};
return std::string ();
}
SessionName session_name (datetime::PTime const &ref_date,
std::string session_name (datetime::PTime const &ref_date,
boost::posix_time::time_duration time_of_day_start,
boost::posix_time::time_duration time_of_day_end)
{
if (in_session (ref_date,time_of_day_start,time_of_day_end))
return session_name_from_ptime (ref_date, time_of_day_start, time_of_day_end);
else
return {};
return "";
}
SessionName session_name (config::JetFIX::Connection const &session_cfg)
std::string session_name (config::JetFIX::Connection const &session_cfg)
{
auto now = datetime::local_now ();
@@ -140,6 +134,7 @@ SessionName session_name (config::JetFIX::Connection const &session_cfg)
parse_time (session_cfg.end_time ()));
}
}}
+28 -34
View File
@@ -1,9 +1,9 @@
#pragma once
#include "lib/config/protos/jetfix.pb.h"
#include "lib/utils/datetime.h"
#include <memory>
#include <string>
#include "lib/utils/datetime.h"
#include "lib/config/protos/jetfix.pb.h"
namespace boost {
namespace gregorian {
@@ -11,45 +11,39 @@ class greg_weekday;
}
namespace posix_time {
class time_duration;
}
} // namespace boost
}}
namespace bts {
namespace jetfix {
boost::gregorian::greg_weekday parse_weekday(std::string weekday_str);
bool parse_time(std::string const &time_str,
boost::posix_time::time_duration *out);
boost::gregorian::greg_weekday parse_weekday (std::string weekday_str);
bool parse_time (std::string const &time_str,boost::posix_time::time_duration *out);
datetime::PTime closest_before(datetime::PTime const &ref_date,
boost::gregorian::greg_weekday weekday,
boost::posix_time::time_duration time_of_day);
datetime::PTime closest_before (datetime::PTime const &ref_date,
boost::gregorian::greg_weekday weekday,
boost::posix_time::time_duration time_of_day);
bool in_session(datetime::PTime const &ref_date,
boost::gregorian::greg_weekday weekday_start,
boost::posix_time::time_duration time_of_day_start,
boost::gregorian::greg_weekday weekday_end,
boost::posix_time::time_duration time_of_day_end);
bool in_session (datetime::PTime const &ref_date,
boost::gregorian::greg_weekday weekday_start,
boost::posix_time::time_duration time_of_day_start,
boost::gregorian::greg_weekday weekday_end,
boost::posix_time::time_duration time_of_day_end);
bool in_session(datetime::PTime const &ref_date,
boost::posix_time::time_duration time_of_day_start,
boost::posix_time::time_duration time_of_day_end);
bool in_session (datetime::PTime const &ref_date,
boost::posix_time::time_duration time_of_day_start,
boost::posix_time::time_duration time_of_day_end);
struct SessionName {
std::string as_string;
uint64_t as_uint64_t;
bool empty() const {return as_string.empty();};
};
std::string session_name (datetime::PTime const &ref_date,
boost::gregorian::greg_weekday weekday_start,
boost::posix_time::time_duration time_of_day_start,
boost::gregorian::greg_weekday weekday_end,
boost::posix_time::time_duration time_of_day_end);
SessionName session_name(datetime::PTime const &ref_date,
boost::gregorian::greg_weekday weekday_start,
boost::posix_time::time_duration time_of_day_start,
boost::gregorian::greg_weekday weekday_end,
boost::posix_time::time_duration time_of_day_end);
SessionName session_name(datetime::PTime const &ref_date,
boost::posix_time::time_duration time_of_day_start,
boost::posix_time::time_duration time_of_day_end);
SessionName session_name(config::JetFIX::Connection const &session_cfg);
std::string session_name (datetime::PTime const &ref_date,
boost::posix_time::time_duration time_of_day_start,
boost::posix_time::time_duration time_of_day_end);
} // namespace jetfix
} // namespace bts
std::string session_name (config::JetFIX::Connection const &session_cfg);
}}
+7 -7
View File
@@ -81,9 +81,9 @@ TEST_CASE ("JetFIX Field2Tests", "[jetfix]")
std::string out (20, ' ');
typedef StringField<123> Field;
REQUIRE(Field::max_value_size (std::string_view{"hello"})==5);
REQUIRE(Field::max_value_size ("hello")==5);
auto result = Field::write (&out[0],std::string_view{"hello"});
auto result = Field::write (&out[0],"hello");
REQUIRE (result.first==5);
REQUIRE (result.second=='h'+'e'+'l'+'l'+'o');
REQUIRE (out.compare (0,5,"hello") == 0);
@@ -112,7 +112,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
FIXFragment g (0);
g.add<CharField<1>> ('a');
g.add<FloatField<2>> (1.2,-1,6);
g.add<StringField<3>> (std::string_view{"hello"});
g.add<StringField<3>> ("hello");
g.add<IntField<4>> (123,3);
const char*t_s = "20150427-19:05:18.922";
g.add<TimeField<5>> (datetime::parse_FIX_timestamp (t_s));
@@ -141,14 +141,14 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
FIXFragment g;
auto c_bm = g.add<CharField<1>> ('a');
auto f_bm = g.add<FloatField<2>> (1.2,-1,6);
auto s_bm = g.add<StringField<3>> (std::string_view{"hello"});
auto s_bm = g.add<StringField<3>> ("hello");
auto i_bm = g.add<IntField<4>> (123,3);
auto t_bm = g.add<TimeField<5>> (datetime::parse_FIX_timestamp (t_s1));
auto b_bm = g.add<BoolField<6>> (true);
g.overwrite (c_bm,'b');
g.overwrite (f_bm,1.3,-1,6);
g.overwrite (s_bm,std::string_view {"olleh"});
g.overwrite (s_bm,"olleh");
g.overwrite (i_bm,321,3);
g.overwrite (t_bm,(datetime::parse_FIX_timestamp (t_s2)));
g.overwrite (b_bm,false);
@@ -172,11 +172,11 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
FIXFragment g1;;
g1.add<CharField<1>> ('a');
g1.add<FloatField<2>> (1.2,-1,6);
g1.add<StringField<3>> (std::string_view{"hello"});
g1.add<StringField<3>> ("hello");
auto chk1 = g1.checksum ();
FIXFragment g2;
g2.add<StringField<4>> (std::string_view{"goodbye"});
g2.add<StringField<4>> ("goodbye");
g2.add<IntField<5>> (321,3);
auto chk2 = g2.checksum ();
+211 -202
View File
@@ -6,270 +6,279 @@
#include "lib/jetfix/common/OutFIXPtr.h"
#include "lib/jetfix/store/FIXFileStore.h"
#include "lib/jetfix/store/session_name.h"
#include "lib/logging/logging.h"
#include "lib/utils/TempDir.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/datetime.h"
#include "lib/logging/logging.h"
#include <boost/date_time.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/filesystem.hpp>
#include <boost/date_time.hpp>
namespace bts {
namespace jetfix {
TEST_CASE("JetFIX SessionNameTests", "[jetfix]") {
SECTION("greg_weekday_works") {
const char *days[] = {"sun", "Monday", "tuesday", "Wedn",
"Thursday", "Friday", "sat"};
TEST_CASE ("JetFIX SessionNameTests", "[jetfix]")
{
SECTION("greg_weekday_works")
{
const char*days [] = {"sun","Monday","tuesday","Wedn","Thursday","Friday","sat"};
int i = 0;
int i =0;
for (auto d : days)
REQUIRE(parse_weekday(d) == boost::gregorian::greg_weekday(i++));
REQUIRE (parse_weekday (d)==boost::gregorian::greg_weekday (i++));
}
SECTION("closest_before_works")
{
auto same_day_before = closest_before (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::gregorian::Friday,
boost::posix_time::time_duration(16, 59, 59));
REQUIRE (datetime::FIX_date_time_string (same_day_before)=="20150717-16:59:59");
auto same_day_after = closest_before (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::gregorian::Friday,
boost::posix_time::time_duration(17,0,1));
REQUIRE (datetime::FIX_date_time_string (same_day_after)=="20150710-17:00:01");
auto another_day_same_week = closest_before (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::gregorian::Thursday,
boost::posix_time::time_duration(16, 59, 59));
REQUIRE (datetime::FIX_date_time_string (another_day_same_week)=="20150716-16:59:59");
auto another_day_previous_week = closest_before (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::gregorian::Saturday,
boost::posix_time::time_duration(16, 59, 59));
REQUIRE (datetime::FIX_date_time_string (another_day_previous_week)=="20150711-16:59:59");
}
SECTION("closest_before_works") {
auto same_day_before =
closest_before(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::gregorian::Friday,
boost::posix_time::time_duration(16, 59, 59));
REQUIRE(datetime::FIX_date_time_string(same_day_before) ==
"20150717-16:59:59");
SECTION("in_session_works")
{
REQUIRE (in_session (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::gregorian::Sunday,
boost::posix_time::time_duration(16, 59, 59),
boost::gregorian::Friday,
boost::posix_time::time_duration(17, 0, 1)));
auto same_day_after = closest_before(
datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::gregorian::Friday, boost::posix_time::time_duration(17, 0, 1));
REQUIRE(datetime::FIX_date_time_string(same_day_after) ==
"20150710-17:00:01");
REQUIRE (!in_session (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::gregorian::Sunday,
boost::posix_time::time_duration(16, 59, 59),
boost::gregorian::Friday,
boost::posix_time::time_duration(16, 59, 59)));
auto another_day_same_week =
closest_before(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::gregorian::Thursday,
boost::posix_time::time_duration(16, 59, 59));
REQUIRE(datetime::FIX_date_time_string(another_day_same_week) ==
"20150716-16:59:59");
REQUIRE (in_session (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::posix_time::time_duration(16, 59, 59),
boost::posix_time::time_duration(17, 59, 59)));
auto another_day_previous_week =
closest_before(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::gregorian::Saturday,
boost::posix_time::time_duration(16, 59, 59));
REQUIRE(datetime::FIX_date_time_string(another_day_previous_week) ==
"20150711-16:59:59");
REQUIRE (!in_session (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::posix_time::time_duration(0,0,0),
boost::posix_time::time_duration(16, 0,0)));
REQUIRE (in_session (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)));
REQUIRE (!in_session (datetime::make_ptime (2015,7,17,17,47,0,0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)));
}
SECTION("in_session_works") {
REQUIRE(in_session(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::gregorian::Sunday,
boost::posix_time::time_duration(16, 59, 59),
SECTION("session_name_works")
{
REQUIRE (session_name (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::gregorian::Sunday,
boost::posix_time::time_duration(16, 59, 59),
boost::gregorian::Friday,
boost::posix_time::time_duration(17, 0, 1)) ==
"2015-07-12");
boost::gregorian::Friday,
boost::posix_time::time_duration(17, 0, 1)));
REQUIRE(!in_session(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::gregorian::Sunday,
boost::posix_time::time_duration(16, 59, 59),
REQUIRE (session_name (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::gregorian::Sunday,
boost::posix_time::time_duration(16, 59, 59),
boost::gregorian::Friday,
boost::posix_time::time_duration(16, 59, 59)) == "");
boost::gregorian::Friday,
boost::posix_time::time_duration(16, 59, 59)));
REQUIRE (session_name (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::posix_time::time_duration(16, 59, 59),
boost::posix_time::time_duration(17, 59, 59)) ==
"2015-07-17");
REQUIRE(in_session(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::posix_time::time_duration(16, 59, 59),
boost::posix_time::time_duration(17, 59, 59)));
REQUIRE (session_name (datetime::make_ptime (2015,7,17,18,0,0,0),
boost::posix_time::time_duration(16, 59, 59),
boost::posix_time::time_duration(17, 59, 59)) ==
"");
REQUIRE(!in_session(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::posix_time::time_duration(0, 0, 0),
boost::posix_time::time_duration(16, 0, 0)));
REQUIRE (session_name (datetime::make_ptime (2015,7,17,17,0,0,0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)) ==
"2015-07-16");
REQUIRE(in_session(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)));
REQUIRE (session_name (datetime::make_ptime (2015,7,17,17,51,0,0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)) ==
"2015-07-17");
REQUIRE(!in_session(datetime::make_ptime(2015, 7, 17, 17, 47, 0, 0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)));
}
REQUIRE (session_name (datetime::make_ptime (2015,7,17,17,47,0,0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)) ==
"");
SECTION("session_name_works") {
REQUIRE(session_name(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::gregorian::Sunday,
boost::posix_time::time_duration(16, 59, 59),
boost::gregorian::Friday,
boost::posix_time::time_duration(17, 0, 1)).as_string ==
"2015-07-12");
REQUIRE(session_name(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::gregorian::Sunday,
boost::posix_time::time_duration(16, 59, 59),
boost::gregorian::Friday,
boost::posix_time::time_duration(16, 59, 59)).as_string == "");
REQUIRE(session_name(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::posix_time::time_duration(16, 59, 59),
boost::posix_time::time_duration(17, 59, 59)).as_string ==
"2015-07-17");
REQUIRE(session_name(datetime::make_ptime(2015, 7, 17, 18, 0, 0, 0),
boost::posix_time::time_duration(16, 59, 59),
boost::posix_time::time_duration(17, 59, 59)).as_string == "");
REQUIRE(session_name(datetime::make_ptime(2015, 7, 17, 17, 0, 0, 0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)).as_string ==
"2015-07-16");
REQUIRE(session_name(datetime::make_ptime(2015, 7, 17, 17, 51, 0, 0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)).as_string ==
"2015-07-17");
REQUIRE(session_name(datetime::make_ptime(2015, 7, 17, 17, 47, 0, 0),
boost::posix_time::time_duration(17, 50, 59),
boost::posix_time::time_duration(17, 45, 59)).as_string == "");
}
}
TEST_CASE("JetFIX FileStoreTests", "[jetfix]") {
SECTION("empty_store_works") {
config::JetFIX::Connection c;
c.set_name("test");
c.set_sender_compid("CME");
c.set_sender_subid("G");
c.set_target_compid("1U7001N");
c.set_target_subid("BLUESTREAM");
c.set_start_time("00:00:00");
c.set_end_time("23:59:59");
TEST_CASE ("JetFIX FileStoreTests", "[jetfix]")
{
SECTION("empty_store_works")
{
config::JetFIX::Connection c;
c.set_name ("test");
c.set_sender_compid ("CME");
c.set_sender_subid ("G");
c.set_target_compid ("1U7001N");
c.set_target_subid ("BLUESTREAM");
c.set_start_time ("00:00:00");
c.set_end_time ("23:59:59");
utils::TempDir td;
FIXFileStore fs(c, td.name());
REQUIRE(fs.session_active());
REQUIRE(fs.replay().size() == 0);
REQUIRE(fs.last_seq_num() == 0);
REQUIRE(fs.last_seq_num_processed() == 0);
FIXFileStore fs (c,td.name ());
REQUIRE (fs.session_active ());
REQUIRE (fs.replay ().size ()==0);
REQUIRE (fs.last_seq_num ()==0);
REQUIRE (fs.last_seq_num_processed ()==0);
}
SECTION("in_msg_is_stored") {
auto td = std::make_shared<utils::TempDir>();
SECTION("in_msg_is_stored")
{
auto td = std::make_shared<utils::TempDir> ();
config::JetFIX::Connection c;
c.set_name("test");
c.set_sender_compid("CME");
c.set_sender_subid("G");
c.set_target_compid("1U7001N");
c.set_target_subid("BLUESTREAM");
c.set_name ("test");
c.set_sender_compid ("CME");
c.set_sender_subid ("G");
c.set_target_compid ("1U7001N");
c.set_target_subid ("BLUESTREAM");
c.set_start_time("00:00:00");
c.set_end_time("23:59:59");
c.set_start_time ("00:00:00");
c.set_end_time ("23:59:59");
FIXEncodingStream stream(c);
auto &buf = stream.new_msg(MessageTypes::Logon);
buf.add<fix::TargetLocationID>(std::string_view{"US,NC"});
buf.add<fix::EncryptMethod>(0);
FIXEncodingStream stream (c);
auto &buf = stream.new_msg (MessageTypes::Logon);
buf.add<fix::TargetLocationID> ("US,NC");
buf.add<fix::EncryptMethod> (0);
buf.add<fix::HeartBtInt>(30);
buf.add<fix::ApplicationSystemName>(std::string_view{"O2"});
buf.add<fix::ApplicationSystemVersion>(std::string_view{"1.0"});
buf.add<fix::ApplicationSystemVendor>(std::string_view{"BTS"});
stream.close();
stream.stamp(2, 3, datetime::FIX_timestamp(datetime::now()));
auto fix = stream.to_str();
buf.add<fix::ApplicationSystemName> ("O2");
buf.add<fix::ApplicationSystemVersion> ("1.0");
buf.add<fix::ApplicationSystemVendor> ("BTS");
stream.close ();
stream.stamp (2,3,datetime::FIX_timestamp (datetime::now ()));
auto fix = stream.to_str ();
{
FIXFileStore fs(c, td->name());
fs.store(InMsgBuffer(fix));
fs.set_last_msg_processed(3);
FIXFileStore fs (c,td->name ());
fs.store (InMsgBuffer (fix));
fs.set_last_msg_processed (3);
}
FIXFileStore fs(c, td->name());
auto replay = fs.replay();
REQUIRE(replay.size() == 1);
REQUIRE(replay[0].type() == MessageTypes::Logon);
REQUIRE(replay[0].to_str() == fix);
REQUIRE(fs.last_seq_num() == 0);
REQUIRE(fs.last_seq_num_processed() == 3);
FIXFileStore fs (c,td->name ());
auto replay = fs.replay ();
REQUIRE (replay.size ()==1);
REQUIRE (replay[0].type ()==MessageTypes::Logon);
REQUIRE (replay[0].to_str ()==fix);
REQUIRE (fs.last_seq_num ()==0);
REQUIRE (fs.last_seq_num_processed ()==3);
}
SECTION("out_msg_is_stored") {
auto td = std::make_shared<utils::TempDir>();
SECTION("out_msg_is_stored")
{
auto td = std::make_shared<utils::TempDir> ();
config::JetFIX::Connection c;
c.set_name("test");
c.set_sender_compid("CME");
c.set_sender_subid("G");
c.set_target_compid("1U7001N");
c.set_target_subid("BLUESTREAM");
c.set_name ("test");
c.set_sender_compid ("CME");
c.set_sender_subid ("G");
c.set_target_compid ("1U7001N");
c.set_target_subid ("BLUESTREAM");
c.set_start_time("00:00:00");
c.set_end_time("23:59:59");
c.set_start_time ("00:00:00");
c.set_end_time ("23:59:59");
auto msg_vec = std::make_shared<OutFIX>(c);
auto &stream = msg_vec->fix.back();
auto &buf = stream.new_msg(MessageTypes::Logon);
buf.add<fix::TargetLocationID>(std::string_view{"US,NC"});
buf.add<fix::EncryptMethod>(0);
auto &stream = msg_vec->fix.back ();
auto &buf = stream.new_msg (MessageTypes::Logon);
buf.add<fix::TargetLocationID> ("US,NC");
buf.add<fix::EncryptMethod> (0);
buf.add<fix::HeartBtInt>(30);
buf.add<fix::ApplicationSystemName>(std::string_view{"O2"});
buf.add<fix::ApplicationSystemVersion>(std::string_view{"1.0"});
buf.add<fix::ApplicationSystemVendor>(std::string_view{"BTS"});
stream.close();
stream.stamp(2, 3, datetime::FIX_timestamp(datetime::now()));
buf.add<fix::ApplicationSystemName> ("O2");
buf.add<fix::ApplicationSystemVersion> ("1.0");
buf.add<fix::ApplicationSystemVendor> ("BTS");
stream.close ();
stream.stamp (2,3,datetime::FIX_timestamp (datetime::now ()));
{
FIXFileStore fs(c, td->name());
fs.store(msg_vec);
FIXFileStore fs (c,td->name ());
fs.store (msg_vec);
}
FIXFileStore fs(c, td->name());
auto replay = fs.replay();
REQUIRE(replay.size() == 0);
REQUIRE(fs.last_seq_num() == 3);
REQUIRE(fs.last_seq_num_processed() == 0);
FIXFileStore fs (c,td->name ());
auto replay = fs.replay ();
REQUIRE (replay.size ()==0);
REQUIRE (fs.last_seq_num ()==3);
REQUIRE (fs.last_seq_num_processed ()==0);
}
SECTION("test_performance") {
auto td = std::make_shared<utils::TempDir>();
SECTION("test_performance")
{
auto td = std::make_shared<utils::TempDir> ();
config::JetFIX::Connection c;
c.set_name("test");
c.set_sender_compid("CME");
c.set_sender_subid("G");
c.set_target_compid("1U7001N");
c.set_target_subid("BLUESTREAM");
c.set_name ("test");
c.set_sender_compid ("CME");
c.set_sender_subid ("G");
c.set_target_compid ("1U7001N");
c.set_target_subid ("BLUESTREAM");
c.set_start_time("00:00:00");
c.set_end_time("23:59:59");
c.set_start_time ("00:00:00");
c.set_end_time ("23:59:59");
auto start = utils::TimeStamp::now();
auto start = utils::TimeStamp::now ();
{
FIXFileStore fs(c, td->name());
for (int i = 0; i < 10000; i++) {
FIXFileStore fs (c,td->name ());
for (int i = 0; i < 10000;i++) {
auto msg_vec = std::make_shared<std::vector<FIXEncodingStream>>();
msg_vec->emplace_back(c);
auto &stream = msg_vec->back();
auto &buf = stream.new_msg(MessageTypes::Logon);
buf.add<fix::TargetLocationID>(std::string_view{"US,NC"});
buf.add<fix::EncryptMethod>(0);
msg_vec->emplace_back (c);
auto &stream = msg_vec->back ();
auto &buf = stream.new_msg (MessageTypes::Logon);
buf.add<fix::TargetLocationID> ("US,NC");
buf.add<fix::EncryptMethod> (0);
buf.add<fix::HeartBtInt>(30);
buf.add<fix::ApplicationSystemName>(std::string_view{"O2"});
buf.add<fix::ApplicationSystemVersion>(std::string_view{"1.0"});
buf.add<fix::ApplicationSystemVendor>(std::string_view{"BTS"});
stream.close();
stream.stamp(i, 3,
datetime::FIX_timestamp(datetime::now() -
boost::gregorian::days(1)));
fs.store(InMsgBuffer(stream.to_str()));
buf.add<fix::ApplicationSystemName> ("O2");
buf.add<fix::ApplicationSystemVersion> ("1.0");
buf.add<fix::ApplicationSystemVendor> ("BTS");
stream.close ();
stream.stamp (i,3,datetime::FIX_timestamp (datetime::now () - boost::gregorian::days (1)));
fs.store (InMsgBuffer (stream.to_str ()));
}
}
auto stop = utils::TimeStamp::now();
LOG(Testing, Info, "jetfix store gen: {} ms",
(stop - start).milliseconds());
start = utils::TimeStamp::now();
FIXFileStore fs(c, td->name());
stop = utils::TimeStamp::now();
LOG(Testing, Info, "jetfix store load: {} ms",
(stop - start).milliseconds());
auto stop = utils::TimeStamp::now ();
LOG(Testing,Info,"jetfix store gen: {} ms", (stop-start).milliseconds () );
start = utils::TimeStamp::now ();
FIXFileStore fs (c,td->name ());
stop = utils::TimeStamp::now ();
LOG(Testing,Info,"jetfix store load: {} ms", (stop-start).milliseconds () );
}
}
} // namespace jetfix
} // namespace bts
}}
-1
View File
@@ -120,7 +120,6 @@ void configure_logging(bts::config::Logging const&cfg, bts::config::ThreadGroupC
{log::Plugin,"Plugin"},
{log::Sdk,"Sdk"},
{log::Http,"Http"},
{log::iLink3,"iLink3"},
};
real_cfg.sinks["console"] =
-1
View File
@@ -46,7 +46,6 @@ constexpr log::Subsystem DataFlow {Latency.ordinal+1 };
constexpr log::Subsystem Plugin {DataFlow.ordinal+1 };
constexpr log::Subsystem Sdk {Plugin.ordinal+1 };
constexpr log::Subsystem Http {Sdk.ordinal+1 };
constexpr log::Subsystem iLink3 {Http.ordinal+1 };
struct LoggerHandle {
LoggerHandle(LoggerHandle const&) = delete;
-5
View File
@@ -1,5 +0,0 @@
#pragma once
#include "lib/utils/ValueWrapper.h"
namespace bts {
using ExchangeSecurityKey = utils::ValueWrapper<struct ExchangeSecurityKeyTag,uint64_t>;
}
+3 -28
View File
@@ -60,13 +60,10 @@ class IntPrice {
return delta.mantissa() < 0;
}
}
bool operator <=(const IntPrice& other) const {
bool operator <=(const IntPrice& other) {
return *this == other || *this < other;
}
bool operator >(const IntPrice& other) const {
return other < *this;
}
bool operator >=(const IntPrice& other) const {
bool operator >=(const IntPrice& other) {
return !(*this < other);
}
IntPrice operator - (const IntPrice& o) const {
@@ -75,31 +72,9 @@ class IntPrice {
IntPrice operator + (const IntPrice& o) const {
return op(o, [](int64_t m1, int64_t m2) { return m1 + m2; });
}
IntPrice& operator +=(const IntPrice& o) {
auto temp = *this + o;
_mantissa = temp._mantissa;
_exponent = temp._exponent;
return *this;
}
IntPrice& operator -=(const IntPrice& o) {
auto temp = *this - o;
_mantissa = temp._mantissa;
_exponent = temp._exponent;
return *this;
}
IntPrice operator -() {
return IntPrice(-_mantissa, _exponent);
}
IntPrice operator * (const IntPrice& other) const {
return op(other, [](int64_t m1, int64_t m2) { return m1 * m2; });
}
IntPrice operator * (const int& other) const {
return IntPrice(_mantissa * other, _exponent);
}
IntPrice& operator *=(const int& other) {
_mantissa *= other;
return *this;
}
IntPrice operator / (double v) const {
return { (int64_t)(_mantissa / v), exponent() };
}
@@ -208,4 +183,4 @@ inline std::ostream& operator<<(std::ostream& os, const IntPrice& p) {
return os;
}
}}
}}
Loaded 100 of 323 files, more files were not shown because too many files have changed in this diff. Show more