Compare commits

...
Author SHA1 Message Date
Eric Harding dd5c680155 work in progress 2019-04-29 11:18:11 -04:00
Eric Harding 5a1cc4cf2b Update release notes 2019-04-29 09:38:02 -04:00
Eric Harding 9be3a09749 Merged in SPARK-2179-PIQ (pull request #219)
SPARK-2179 PIQ
2019-04-29 13:36:49 +00:00
Eric Harding 61745d0ed7 Update release notes 2019-04-29 09:35:13 -04:00
Eric Harding db1610591e merge master 2019-04-29 09:34:31 -04:00
Eric Harding bb1571540a Merged in sparkcmd_commandline (pull request #218)
RTM: Sparkcmd commandline

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Michael Harding <michael.harding@bluetradesys.com>
2019-04-29 13:13:23 +00:00
Eric Harding 342d89724e Update release notes 2019-04-29 09:13:09 -04:00
Michael Harding 07d3903d11 Merged in Documentation (pull request #215)
RTM Remove SDK docs from the manual
2019-04-29 13:12:35 +00:00
Jake Norbut d08b134d7f Merged in SPARK-2285 (pull request #214)
RTM - SPARK-2285 - add null check on r.newval

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-04-29 13:10:49 +00:00
Eric Harding 92f67d1d2d piq: Add security key to log message 2019-04-26 16:40:59 -04:00
Eric Harding 43a18941a4 Change debug assert to logged warning in PIQ 2019-04-26 16:18:37 -04:00
Jake Norbut b102b271b0 PR edit - use single_order instead; this will be gone from attach/detach 2019-04-26 10:27:07 -05:00
Jake Norbut b129bea129 PR edit - method signature all on one line 2019-04-26 10:27:07 -05:00
Eric Harding 38d79eb58b Merge branch 'master' of bitbucket.org:bluetradesys/spark into SPARK-2179-PIQ 2019-04-26 09:38:43 -04:00
Eric Harding dfd0cfc217 templatize mbo callbacks for older templates 2019-04-25 17:16:35 -04:00
Eric Harding a123ac2ed8 Turn on MBO background processing 2019-04-25 15:14:27 -04:00
Eric Harding ad4fd13189 MBO: Add seconary index to handle mbo change 2019-04-25 14:25:49 -04:00
Eric Harding 8a1180835c MBO diagnostics/logging 2019-04-25 11:06:39 -04:00
Eric Harding f65f34c2fe Limit returned MBOSnapshot size to avoid memory leaking 2019-04-24 14:12:50 -04:00
Eric Harding 34401960f9 Fix mbo bid sort order 2019-04-24 11:08:46 -04:00
Eric Harding 34b7c99c81 Toggle mbostats off by default 2019-04-24 11:05:26 -04:00
Eric Harding 044ef0b812 merge master 2019-04-24 10:15:07 -04:00
Eric Harding 9cc4b5aa87 Update unit/system tests command line parser 2019-04-24 10:12:08 -04:00
Eric Harding 821274f544 Merge branch 'SPARK-2179-PIQ' of bitbucket.org:bluetradesys/spark into PIQachu 2019-04-23 09:48:54 -04:00
Eric Harding ca6c0f984c mbo bugfixes 2019-04-23 09:11:30 -04:00
Eric Harding 80e337c599 sparkcmd should respect '-configserver' 2019-04-22 16:45:36 -04:00
Eric Harding 5b71918a1f Revert "Make sure every sparkcmd command respects -configserver"
This reverts commit dab95d7e91.
2019-04-22 16:41:48 -04:00
Eric Harding d2a9d21700 Handle incremental MBO messages from cme 2019-04-22 15:26:50 -04:00
Kevin Darby 16fee6e478 Merged in stunnel_win7 (pull request #217)
RTM: Stunnel Win7

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-04-22 17:57:27 +00:00
Eric Harding 3cf701a211 swap map for btree 2019-04-18 14:42:26 -04:00
Eric Harding 5392d05ec8 MBO perf work 2019-04-18 14:39:15 -04:00
Kevin Darby 7838bdf04f too much whitespace for even me 2019-04-18 14:16:08 -04:00
Kevin Darby 546553406b PR comments 2019-04-18 14:13:09 -04:00
Kevin Darby 02d0f6191c -added a manifest to our csproj that we'd meant to add anyway for win7 subprocess creation
-added a fallback for stunnel if we can't create a windows job object
2019-04-18 14:06:44 -04:00
Michael Harding 36722e27ad Remove SDK docs from the manual 2019-04-18 11:08:32 -04:00
Jake Norbut dcfbb6e62f need a new security order demuxer for the new piq observable 2019-04-18 08:58:56 -05:00
Jake Norbut ff2d150dc4 restricting orders with piqs to just ladders for now 2019-04-17 17:33:46 -05:00
Jake Norbut 6692797c3f changing from .Publish to .Replay b/c apparently Publish doesn't send old values i.e. previously working orders won't show up, but new ones will 2019-04-17 13:03:43 -05:00
Jake Norbut b059aedbce adding PlacementInQueue key to toggles so that we have a config option to enable/disable once deployed; defaults to enabled 2019-04-17 08:53:26 -05:00
Eric Harding 73c9be3bec SPARK-2197 bugfix 2019-04-16 15:47:11 -04:00
Jake Norbut bc35e773ce piq_column settings should never return null, which allevites doing the same in sanitize_ladder_settings and another time when dealing with WindowSpecifics 2019-04-16 14:19:43 -05:00
Jake Norbut 4c8d0a4217 add null check on r.newval 2019-04-16 14:13:06 -05:00
Jake Norbut 05abbc4a50 don't show uint.Max piqs during server restart 2019-04-16 12:03:33 -05:00
Eric Harding 95500187f0 Indicate our orders in diagnostic 2019-04-16 12:44:23 -04:00
Jake Norbut 8716099ac6 Removing OrderSecPiq struct since OrderSecurity does everything that did; we still may want to rename it down the line 2019-04-16 11:15:19 -05:00
Eric Harding d7850d6b70 MBO diagnostic api 2019-04-16 12:14:27 -04:00
Eric Harding 905c1cab2c SPARK-2206 tests 2019-04-16 12:14:27 -04:00
Jake Norbut 6181548902 piq background color now matches orders background color 2019-04-16 10:56:31 -05:00
Jake Norbut c32eada3ac the big enchilada - adding PIQ column to LadderGridHost 2019-04-16 10:49:03 -05:00
Jake Norbut 42feedfd36 adding piq column to settings 2019-04-16 10:33:48 -05:00
Jake Norbut d76c2a30ce price_vs_piq property on LadderVM; appropriate changes for LadderOrderVM constructor 2019-04-16 10:32:54 -05:00
Jake Norbut c312a880a7 adding MBOService for piq, left joining piq on orders 2019-04-16 10:29:29 -05:00
Jake Norbut b2ed88fb4a adding binding expression for new PIQColumnWidth property 2019-04-16 10:23:57 -05:00
Jake Norbut 567edafe18 including the piq here on this new 2019-04-16 10:23:29 -05:00
Jake Norbut 9a39d84739 tuple to OrderSecPiq in here too 2019-04-16 10:20:42 -05:00
Jake Norbut d9b4e21c61 changing from tuple to simple OrderSecPiq for sanity 2019-04-16 10:20:09 -05:00
Jake Norbut fa287b14ef OrderSecurity now also has a piq; maybe we change the class name? 2019-04-16 10:16:40 -05:00
Jake Norbut 096bf3338c adding PIQ 2019-04-16 10:15:06 -05:00
Jake Norbut a646f28831 added min_piq property; now stores (OrderRecordImmutable, uint?) instead of just OrderRecordImmutable 2019-04-16 10:14:05 -05:00
Jake Norbut 391397692c adding PIQ case option 2019-04-16 10:08:28 -05:00
Jake Norbut 8130d5a0f8 adding render_piq method 2019-04-16 10:06:32 -05:00
Jake Norbut d89f93c323 Adding MBOService as an export in gui 2019-04-15 11:05:40 -05:00
Eric Harding a376fe9e22 Fix pool_stats crash for the pool paupers 2019-04-12 09:56:28 -04:00
Eric Harding 7381088d6d Remove unnecessary error log 2019-04-11 11:07:49 -04:00
Eric Harding 5f9a421490 Additional logging for QueryServer::get_names error 2019-04-11 10:28:21 -04:00
Eric Harding f7fd81f3af Improve Servicetail 2019-04-10 14:47:50 -04:00
Eric Harding f6c0f393bd SPARK-2244 Suppress empty updates 2019-04-10 11:04:28 -04:00
Eric Harding adec1bdfd0 SPARK-678 Dont' crash on null array 2019-04-10 10:57:44 -04:00
Eric Harding a3de83dd04 SPARK-2244 Update position on timer tick 2019-04-10 10:24:33 -04:00
Eric Harding 5ba7cd561c SPARK-2244 remove parameterless ActiveOrder constructor 2019-04-10 10:16:18 -04:00
Eric Harding 5ff9f13e7e Update cmakedeps 2019-04-10 10:11:17 -04:00
Eric Harding f4b65b21bb merge master 2019-04-09 15:27:15 -04:00
Eric Harding c1789bb4a1 SPARK-2244 Helps to enable the service 2019-04-09 15:14:35 -04:00
Eric Harding 0cf4b5c2c8 SPARK-2224 3/3 basic piq stream (unblocks jake) 2019-04-09 15:07:10 -04:00
Eric Harding a101a267ed More consistent to call old previous for Change 2019-04-09 14:58:15 -04:00
Eric Harding d76b098c51 SPARK-2179 streaming piq 2/3 2019-04-09 10:00:26 -04:00
Eric Harding 06aa540110 SPARK-2248 Update api subscribe clients for this api change 2019-04-09 09:59:05 -04:00
Eric Harding 9fd7db55ca SPARK-2248 Update.h should have add/remove/change instead of just add/remove 2019-04-09 09:59:00 -04:00
Pedro Pinto 5b118d8fa0 Fixed build. 2019-04-08 09:56:46 -04:00
Pedro Pinto b570762054 Updated release notes. 2019-04-08 09:56:39 -04:00
Eric Harding b664c42cae SPARK-2179 streaming piq 1/2 2019-04-08 09:52:45 -04:00
Pedro Pinto a25c17306f Merge remote-tracking branch 'origin/stunnel' 2019-04-08 09:27:00 -04:00
Pedro Pinto b0c52f542a Updated release notes. 2019-04-08 09:23:21 -04:00
Pedro Pinto dabeb63eac Merge remote-tracking branch 'origin/SPARK-2179-spark-login' 2019-04-08 09:22:39 -04:00
Pedro Pinto f0d3cabd14 Updated release notes. 2019-04-08 09:19:50 -04:00
Pedro Pinto 418a1078c2 Updated release notes. 2019-04-08 09:17:20 -04:00
John Colvin 3d37dc481f SPARK-2192 NullRef when hitting enter in order ticket after typing in a qty then price 2019-04-06 14:39:39 -05:00
Pedro Pinto d24b7a3707 Possibly fixed SPARK-2225.
This bug is probably behind a number of different crashes.
2019-04-05 12:30:44 -04:00
Kevin Darby 12362d9719 Merge branch 'master' of bitbucket.org:bluetradesys/spark into stunnel 2019-04-04 13:06:10 -04:00
Kevin Darby 01128639ce improve error logging 2019-04-04 13:05:40 -04:00
Eric Harding 71d2e01187 SPARK-2197 Add some MBO stats logged every 5 minutes 2019-04-04 11:18:15 -04:00
Eric Harding c923be6ced SPARK 2206 add test command to print out PIQ data for all orders 2019-04-03 16:24:53 -04:00
Michael Harding 4cf9d544aa Update PnL test 2019-04-03 09:44:10 -04:00
Michael Harding 79f37f7130 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-04-03 09:43:10 -04:00
Kevin Darby 19c0b94803 unsigned... 2019-04-02 18:13:31 -04:00
Kevin Darby 57fc50ce61 typo 2019-04-02 18:12:05 -04:00
Kevin Darby 3cff5343a3 PR review and support of -multiple 2019-04-02 18:10:53 -04:00
Kevin Darby a6ec93e87e Merge branch 'master' of bitbucket.org:bluetradesys/spark into stunnel 2019-04-02 16:08:36 -04:00
Kevin Darby 25acf771ab changes to conditionally start stunnel if the config file is present 2019-04-02 16:05:05 -04:00
Kevin Darby 22cc946f52 Merged in ice_clang (pull request #206)
changes to compile ice market data with clang
2019-04-02 20:04:09 +00:00
Kevin Darby 6d0eb62712 changes to compile ice work with clang 2019-04-02 15:55:29 -04:00
Eric Harding 369b89b3a7 Update default symbol list for compat with deployed config 2019-04-02 15:15:12 -04:00
Eric Harding 8793c6c527 Replace ManyConsole with CommandLineParser 2019-04-02 15:00:02 -04:00
Eric Harding df14522433 SPARK-2204 QueryServer api 2019-04-02 13:07:42 -04:00
Eric Harding 086e90acbe Move queryserver to linux only 2019-04-02 11:16:00 -04:00
Michael Harding 467dba98f8 Update Fills window test 2019-04-01 17:06:16 -04:00
Eric Harding f513f7d9f2 SPARK-2204 mbo api in QueryServer 2019-04-01 12:11:03 -04:00
Eric Harding 69f9f2791e SPARK-2203 2019-03-29 15:39:22 -04:00
Eric Harding ad6fc6d23b Cannot have a const pointer returned to the pool 2019-03-29 15:38:54 -04:00
Eric Harding a4575e3fa3 Fix windows build 2019-03-29 15:38:26 -04:00
Eric Harding dc1a80afd2 Make MBOReader available to MDConsumerHandle 2019-03-29 14:07:48 -04:00
Eric Harding e1cfa7bf0b Fix build break 2019-03-29 14:06:25 -04:00
Eric Harding 2020221e65 MBOProducer cleanup and const correctness 2019-03-29 14:02:29 -04:00
Eric Harding ce438e93a6 Revert IntPrice regression (unit test failure) 2019-03-29 14:00:26 -04:00
Eric Harding bad5bc3097 SPARK-2202 MBOProcessor producer side 2019-03-29 11:08:37 -04:00
Eric Harding e9da338402 optimize IntPrice lessthan 2019-03-29 10:58:16 -04:00
Eric Harding be6c916691 Allow std::function to move into Dispatcher 2019-03-28 16:49:59 -04:00
Eric Harding 4d60376cb1 SPARK-2179 Spark login screen should show error message instead of blank 2019-03-20 10:43:46 -04:00
98 changed files with 2532 additions and 444 deletions

No files matched your search

+1 -1
View File
@@ -215,7 +215,7 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": ["test", "[md]"],
"args": ["test", "[mbo]"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
@@ -0,0 +1,46 @@
+++
title = "BTS Basic Algos"
weight = 10
sort_by = "weight"
insert_anchor_links = "right"
+++
## Lean
The goal of the lean order is to watch the opposite-inside market size and cross when that size goes below the threshold (this also implies a cross if that size goes to zero and the market trades away from you) If the market trades toward you, you can opt into several different behaviors
##### Parameters
* Cross Thresh - size thresh in absolute or ratio terms
* Max Chase Ticks - max levels to pay up, in ticks
## Join
The goal of the join order is to watch the opposite-inside market and join when the market flips (that is, when the best offer becomes the best bid) and has at least threshold size.
##### Parameters
* Join Thresh - size thresh in absolute or ratio terms
## Top
The goal of the top order is to not trade by yourself, but the intent at the time of order entry is to get filled.If the order is on the market (say current bid price) AND the quantity on that bid price is below top thresh then cancel the order else remain working.
##### Parameters
* Top Thresh - size thresh in absolute or ratio terms
## Cover
A Cover order’s purpose is to fire a hedge order (of any type) in response to fills from it’s child order on the opposite side of the market N ticks away
##### Parameters
* Profit Ticks - ticks above or below fill price to place profit / cover order
## OCO
An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill.
Composing this order with Cover simulates scalping functionality
##### Parameters
* Stop Ticks - ticks below or above main order to place stop / other order
@@ -1,6 +0,0 @@
+++
title = "Client"
weight = 1
sort_by = "weight"
redirect_to = "bts_spark_sdk/Client/overview/"
+++
@@ -1,6 +0,0 @@
+++
title = "Server"
weight = 2
sort_by = "weight"
insert_anchor_links = "right"
+++
-6
View File
@@ -1,6 +0,0 @@
+++
title = "BTS Spark SDK"
weight = 2
sort_by = "weight"
insert_anchor_links = "right"
+++
+17
View File
@@ -4,6 +4,23 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 1.6.0
### User Visible Changes
#### New Features
- You can now view your order's position in queue in the ladder
#### Bug Fixes
- Fix a bug in the order popup in the ladder which could lead to a crash.
## 1.5.9
### User Visible Changes
- Fix for stunnel connections on Windows 7
## 1.5.8
### User Visible Changes
- Fixed a bug where it was possible to crash the UI by entering an order through the order ticket if Enter was used to dismiss the dialog.
- Fixed a spreader reporting bug which could cause the server to crash if an algo was used to control certain types of synthetics
- Improved user message when the UI fails to login
- Integrated stunnel into UI to make VPN setup unnecessary in unsecured environments.
## 1.5.7
### User Visible Changes
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/index.md#algo-studio) and [Spreader](./bts_spark/Spreader/index.md)
+64 -4
View File
@@ -1,4 +1,6 @@
using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -72,13 +74,71 @@ namespace mbts.ui.shell
base.OnExit(e);
Environment.Exit(0);
}
void start_stunnel(string path, uint job_error)
{
string config_error;
if (!mbts.config.Configuration.try_initialize(out config_error))
{
_stunnel_process = Process.Start(path + "bin\\stunnel.exe");
if (_stunnel_process != null)
{
for (int i = 0; i < 10; i++)
{
if (mbts.config.Configuration.try_initialize(out config_error))
break;
System.Threading.Thread.Sleep(500);
}
}
else
stunnel_job_error = "Stunnel Job Error code: " + job_error +
" could not start stunnel " + (config_error ?? string.Empty);
}
}
void kill_stunnel()
{
try
{
Process[] ps = Process.GetProcessesByName("stunnel");
ps.ForEach(p =>
{
try { p.Kill(); }
catch {}
});
}
catch { }
}
Process _stunnel_process;
static internal string stunnel_job_error { get; private set;}
void init_stunnel(string[] argv)
{
try
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
if (File.Exists(path + "config\\stunnel.conf"))
{
uint job_error = BTSUINativeMethods.setup_process_job_object();
//if we can't put this process and stunnel into a job, try to stop stunnel then restart
if(job_error != 0)
kill_stunnel();
start_stunnel(path, job_error);
}
}
catch(Exception ex)
{
stunnel_job_error = "Error starting stunnel: " + ex.Message;
}
}
public new int Run() {
var argv = Environment.GetCommandLineArgs();
try {
if (argv.Contains("-multiple") ||
_mutex == null ||
_mutex.WaitOne(TimeSpan.Zero, true)) {
try {
if (argv.Contains("-multiple") || _mutex == null || _mutex.WaitOne(TimeSpan.Zero, true))
{
if(_mutex == null || _mutex.WaitOne(TimeSpan.Zero, true))
init_stunnel(argv);
return base.Run();
}
else {
@@ -6,6 +6,7 @@ using bts.api.persistence;
using bts.api.position;
using bts.api.synthetic;
using bts.co2;
using bts.md;
using bts.metrics;
using bts.support;
using System.ComponentModel.Composition;
@@ -50,6 +51,9 @@ namespace Spark {
AlgoServiceWrapper _algo_service_wrapper;
[Export]
public AlgoServiceWrapper algo_service_wrapper => _algo_service_wrapper ?? (_algo_service_wrapper = new AlgoServiceWrapper(context));
[Export]
public MBOService mbo_service => context.locate<MBOService>();
}
class AlgoServiceWrapper {
@@ -64,7 +64,7 @@ namespace mbts.spark {
}),
_columns,
_current_row.SubscribeHookLatest(
r => r.newval.subscribe_data().Subscribe(
r => r.newval?.subscribe_data().Subscribe(
data => selected_order_changed?.Invoke(data.order)
)
)
@@ -55,7 +55,7 @@ namespace bts.shell {
void _on_activity(Activity activity) =>
_activity_notifications.process_item(activity);
void _on_orders(TableUpdate<(SecurityImmutable?,OrderRecordImmutable),uint> update) {
void _on_orders(TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> update) {
// must update notification_service_data_cache first, some of the predicate/triggers (esp. ToastNotifications) will use the notification_service_data_cache to lookup order-history
_data_cache.update(update.as_iincremental());
foreach (var o in update.added.Concat(update.changed))
@@ -27,7 +27,7 @@ namespace bts.spark {
static readonly IEqualityComparer<OrderRecordImmutable> _id_comparer =
bts.utils.EqualityComparer.Create<OrderRecordImmutable, uint>(a => a.Key);
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<(SecurityImmutable? sec,OrderRecordImmutable order), uint>> orders) {
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint>> orders) {
_bus = bus;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(sec => _security = sec),
+15 -2
View File
@@ -7,18 +7,31 @@ namespace bts.api.orderman {
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order) {
this.security = sec;
this.order = order;
piq = null;
}
public OrderSecurity((SecurityImmutable? sec, OrderRecordImmutable order) t) {
security = t.sec;
order = t.order;
piq = null;
}
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq) {
this.security = sec;
this.order = order;
this.piq = piq;
}
//public OrderSecurity(OrderSecurity t) {
// security = t.security;
// order = t.order;
// piq = t.piq;
//}
public static implicit operator OrderSecurity(ValueTuple<SecurityImmutable?, OrderRecordImmutable> x) => new OrderSecurity(x);
public SecurityImmutable? security { get; }
public OrderRecordImmutable order { get; }
public uint? piq { get; }
public override bool Equals(object obj) => (obj is OrderSecurity os) && Equals(os);
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security;
public override int GetHashCode() => (security, order).GetHashCode();
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security && piq == other.piq;
public override int GetHashCode() => (security, order, piq).GetHashCode();
public static bool operator == (OrderSecurity left, OrderSecurity right) => left.Equals(right);
public static bool operator != (OrderSecurity left, OrderSecurity right) => !left.Equals(right);
+70 -5
View File
@@ -3,9 +3,11 @@ using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.api.position;
using bts.md;
using bts.spark;
using bts.utils;
using mbts.collections;
using mbts.config;
using mbts.spark.utilities;
using mbts.utils;
using System;
@@ -16,8 +18,10 @@ using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using OrderUpdate = bts.api.core.TableUpdate<(bts.api.SecurityImmutable? sec, bts.api.orderman.OrderRecordImmutable order), uint>;
using OrderUpdatePiq = bts.api.core.TableUpdate<bts.api.orderman.OrderSecurity, uint>;
namespace mbts.spark {
[Export]
@@ -26,21 +30,26 @@ namespace mbts.spark {
readonly OrderFactory _factory;
readonly OrderService _service;
readonly PositionService _position_service;
readonly MBOService _mbo_service;
readonly SpreaderModel _spreader;
readonly ErrorReporter _error;
readonly DynamicOrderHelper _dynamic_helper;
readonly OrderActivitySummarizer _activity;
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer;
// we need a new one of these for the time being since the ladder is subscribing to orders with piq
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_piq_demuxer;
readonly TableUpdateDemuxer<PositionUnitImmutable, int, uint> _security_positions_demuxer;
IDisposable _dispose;
static readonly bool _piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
[ImportingConstructor]
OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service,
OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, MBOService mbo_service,
SpreaderModel spreader, DynamicOrderHelper dynamic_helper, AdminModel admin_model, IAlgoStudioModel algo_model, SecurityStore sstore, ErrorReporter error) {
_bus = bus;
_factory = factory;
_service = service;
_position_service = position_service;
_mbo_service = mbo_service;
_spreader = spreader;
_error = error;
_dynamic_helper = dynamic_helper;
@@ -48,13 +57,34 @@ namespace mbts.spark {
this.algo_model = algo_model;
var scheduler = ThreadPoolScheduler.Instance;
var interval = TimeSpan.FromMilliseconds(100);
on_orders =
on_orders =
_service
.subscribei_orders()
.Naggle(interval, scheduler)
.PermissionFilter(admin_model.on_admin_data)
.join(sstore, order => order.security_key())
.Table().Cache();
.Table()
.Cache();
if (_piq_enabled) {
on_orders_with_piq = on_orders
.LeftJoin<
(SecurityImmutable?, OrderRecordImmutable),
OrderPIQImmutable,
OrderSecurity,
uint
>(mbo_service.subscribei_piq().Table(),
si_ort => si_ort.Item2.Key,
p => p.OrderId,
(si_ort, r, p) => new OrderSecurity(si_ort.Item1, si_ort.Item2, p.Position),
(si_ort, r) => new OrderSecurity(si_ort.Item1, si_ort.Item2, null),
osp => osp.order.Key);
} else {
on_orders_with_piq = on_orders
.Select((ft) => new OrderSecurity(ft.Item1, ft.Item2, null), os => os.order.Key)
.Replay(1);
}
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).Table().Cache();
working_orders =
on_orders.Scan(
@@ -62,6 +92,13 @@ namespace mbts.spark {
(workers, table) => _update_working_orders(workers, table))
.Replay(1).RefCount();
working_orders_with_piq =
on_orders_with_piq.Scan(
ImmutableDictionary<uint, OrderSecurity>.Empty,
(workers, table) => _update_working_orders(workers, table))
.Replay(1).RefCount();
on_order_securities =
on_orders
.as_iincremental()
@@ -70,6 +107,7 @@ namespace mbts.spark {
_activity = new OrderActivitySummarizer(bus, on_orders);
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
_security_orders_piq_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
_security_positions_demuxer = new TableUpdateDemuxer<PositionUnitImmutable, int, uint>(o => o.RecordId, p => p.SecurityId);
var algo_meta_join = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
algo_model.metadata_service.subscribe_metadata().Table(m => m.id),
@@ -104,6 +142,7 @@ namespace mbts.spark {
on_fill_tuples = fill_order_tuples_join;
_dispose = Disposable.Create(
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
on_orders_with_piq.RefCount().Select(t => t.order, o => o.Key).Subscribe(_security_orders_piq_demuxer),
on_positions.Subscribe(_security_positions_demuxer),
order_secs_algo_meta_join.Connect(),
algo_meta_join.Connect(),
@@ -121,11 +160,14 @@ namespace mbts.spark {
public SpreaderModel spreader => _spreader;
public DynamicOrderHelper dynamic_helper => _dynamic_helper;
public IObservable<OrderUpdate> on_orders { get; }
public IConnectableObservable<OrderUpdatePiq> on_orders_with_piq { get; }
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples { get; }
public IObservable<IIncrementalUpdate<SecurityImmutable, uint>> on_order_securities { get; }
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples { get; }
public IObservable<IReadOnlyDictionary<uint,OrderSecurity>> working_orders { get; }
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders_with_piq { get; }
public IObservable<TableUpdate<PositionUnitImmutable, int>> on_positions { get; }
public AdminModel admin_model { get; }
public IAlgoStudioModel algo_model { get; }
@@ -197,8 +239,30 @@ namespace mbts.spark {
public void Dispose() =>
Disposable.dispose(ref _dispose);
static ImmutableDictionary<uint, OrderSecurity>
_update_working_orders(ImmutableDictionary<uint, OrderSecurity> target, OrderUpdate table) {
static ImmutableDictionary<uint, OrderSecurity> _update_working_orders(ImmutableDictionary<uint, OrderSecurity> target, OrderUpdate table) {
var clone = target.ToBuilder();
foreach (var o in table.removed) {
if (o.order.is_active()) {
clone.Remove(o.order.Key);
}
}
foreach (var o in table.changes) {
if (o.Old.order.is_active()) {
clone.Remove(o.Old.order.Key);
}
if (o.New.order.is_active()) {
clone.Add(o.New.order.Key, o.New);
}
}
foreach (var o in table.added) {
if (o.order.is_active()) {
clone.Add(o.order.Key, o);
}
}
return clone.ToImmutableDictionary();
}
static ImmutableDictionary<uint, OrderSecurity> _update_working_orders(ImmutableDictionary<uint, OrderSecurity> target, OrderUpdatePiq table) {
var clone = target.ToBuilder();
foreach (var o in table.removed) {
if (o.order.is_active()) {
@@ -222,6 +286,7 @@ namespace mbts.spark {
}
}
// usinig new OrdersModel extensions to reduce the amount of code change needed to switch uis over to the new OrdersModel,
// without changing any code in the actual new OrdersModel class.
static class OrdersModelExtensions {
@@ -82,7 +82,7 @@ namespace mbts {
// old order will be a copy of new order, except with filledQuantity set to 0
var old_state = ort.order.State.CloneToMutable();
old_state.FilledQuantity = 0;
return (new OrderSecurity(ort.security, ort.order.with(state: old_state)), ort);
return (new OrderSecurity(ort.security, ort.order.with(state: old_state), ort.piq), ort);
}
}
return null;
+2 -1
View File
@@ -4,7 +4,8 @@
<Authors>Blue Trading Systems</Authors>
<Company>Blue Trading Systems</Company>
<Product>Spark</Product>
<Description>The game has changed. Make your Move.</Description>
<Description>Advanced Derivatives Trading</Description>
<ApplicationManifest>spark.manifest</ApplicationManifest>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
@@ -11,6 +11,9 @@ namespace mbts.ui.native
[DllImport("libspark")]
public static extern void enable_limits();
[DllImport("libspark")]
public static extern uint setup_process_job_object();
[DllImport("libspark", CharSet = CharSet.Unicode)]
public static extern void set_window_app_id(IntPtr hwnd, string stack_id);
}
@@ -97,13 +97,13 @@ namespace mbts.ui.controls {
TotalPnL = "Total PnL",
PnLPrice = "PnL Price",
Cancel = "Cancel",
MarketDescription = "Market Description",
ClearedAlias = "Cleared Alias",
ProductDescription = "Product Description",
HubAlias = "Hub Alias",
StripName = "Strip Name",
MicCode = "Mic Code"
MicCode = "Mic Code",
PositionInQueue = "PIQ"
;
}
@@ -103,10 +103,8 @@ namespace mbts.ui {
}
static void tb_keydown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
var binding = BindingOperations.GetBindingExpression(sender as DependencyObject, TextBox.TextProperty);
binding.UpdateSource();
}
if (e.Key == Key.Enter)
BindingOperations.GetBindingExpression(sender as DependencyObject, TextBox.TextProperty)?.UpdateSource();
}
public static bool GetCommitOnEnter(DependencyObject obj) => (bool)obj.GetValue(CommitOnEnterProperty);
@@ -94,7 +94,7 @@ namespace mbts.ui.shell {
Configuration.initialize();
}
catch (ApplicationException e) {
ErrorMessage = e.Message;
ErrorMessage = (e.Message ?? string.Empty) + " " + (App.stunnel_job_error ?? string.Empty);
HasFatalError = true;
return;
}
@@ -106,7 +106,13 @@ namespace mbts.ui.shell {
var auth = _ctx.locate<AuthService>();
var response = await auth.get_users_async(new GetUsersRequest());
if (response.success) {
Users = response.value.Users.Select(x => new User(x.Login,x.Id)).OrderBy(u => u.name).ToArray();
if (!string.IsNullOrEmpty(response.value.Error)) {
ErrorMessage = response.value.Error;
HasFatalError = true;
}
else {
Users = response.value.Users.Select(x => new User(x.Login,x.Id)).OrderBy(u => u.name).ToArray();
}
}
else {
ErrorMessage = "Logon: " + response.error_message;
@@ -42,7 +42,8 @@ namespace mbts.spark {
_ask_vis_tup = new VisualGlyphTuple(),
_price_vis_tup = new VisualGlyphTuple(),
_last_vis_tup = new VisualGlyphTuple(),
_volume_vis_tup = new VisualGlyphTuple();
_volume_vis_tup = new VisualGlyphTuple(),
_piq_vis_tup = new VisualGlyphTuple();
readonly RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>> _volume_container =
new RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>>();
@@ -92,7 +93,7 @@ namespace mbts.spark {
};
}
public enum ColumnType { Order, Bid, Price, Ask, Last, Volume, None }
public enum ColumnType { Order, Bid, Price, Ask, Last, Volume, None, PIQ }
public event Action visible_columns_changed;
@@ -192,6 +193,18 @@ namespace mbts.spark {
set => SetValue(LastColumnWidthProperty, value);
}
public static readonly DependencyProperty PIQColumnWidthProperty =
WPFHelper.create_dp<uint, LadderGridHost>(
nameof(PIQColumnWidth),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, oldval, newval) => o._adjust_column_width(o.columns.FirstOrDefault(c => c.column_type == ColumnType.PIQ), (double)newval - (double)oldval),
50u);
public uint PIQColumnWidth {
get => (uint)GetValue(PIQColumnWidthProperty);
set => SetValue(PIQColumnWidthProperty, value);
}
#region Command
public ICommand Command {
get => (ICommand)GetValue(CommandProperty);
@@ -404,7 +417,8 @@ namespace mbts.spark {
double x = 0;
var col_list = new List<Column> {
new Column(_settings.orders_column, ColumnType.Order, null, x),
new Column(_settings.bid_column, ColumnType.Bid, _bid_vis_tup, (x += _settings.orders_column.show ? _settings.orders_column.width : 0)),
new Column(_settings.piq_column, ColumnType.PIQ, _piq_vis_tup, (x += _settings.orders_column.show ? _settings.orders_column.width : 0)),
new Column(_settings.bid_column, ColumnType.Bid, _bid_vis_tup, (x += _settings.piq_column.show ? _settings.piq_column.width : 0)),
(_price_column = new Column(_settings.price_column, ColumnType.Price, _price_vis_tup, (x += _settings.bid_column.show ? _settings.bid_column.width : 0))),
new Column(_settings.ask_column, ColumnType.Ask, _ask_vis_tup, (x += _settings.price_column.show ? _settings.price_column.width : 0))
};
@@ -509,6 +523,8 @@ namespace mbts.spark {
if (depth_changed)
_refresh_sizes();
}
// TODO - detect changes in PIQ to selectively redraw
_refresh_piq();
_refresh_volume_at_price();
_last_origin_price = _mkt_break_lines.origin;
_price_dirty = false;
@@ -545,6 +561,14 @@ namespace mbts.spark {
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
}
void _refresh_piq() {
if (!_settings.piq_column.show)
return;
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
var piq_list = _vm.price_vs_piq;
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, piq_list.ToArray(), _settings.row_height, security.ContractSpec, _settings.piq_column);
}
void _refresh_sizes() {
Log.assert_d(_price_list != null);
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
@@ -693,6 +717,7 @@ namespace mbts.spark {
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
_price_vis_tup.glyph_context = glyph_context(s.price_column);
_last_vis_tup.glyph_context = glyph_context(s.last_column);
_piq_vis_tup.glyph_context = glyph_context(s.piq_column);
_volume_vis_tup.glyph_context = glyph_context(s.volume_column.general);
GlyphContext glyph_context(LadderColumnSettings cs) =>
@@ -720,6 +745,7 @@ namespace mbts.spark {
case ColumnType.Price: SetCurrentValue(price_column_widthProperty, col.settings.width); break;
case ColumnType.Ask: SetCurrentValue(ask_column_widthProperty, col.settings.width); break;
case ColumnType.Last: SetCurrentValue(LastColumnWidthProperty, col.settings.width); break;
case ColumnType.PIQ: SetCurrentValue(PIQColumnWidthProperty, col.settings.width); break;
case ColumnType.Volume:
SetCurrentValue(VolumeColumnWidthProperty, col.settings.width);
_volume_container.mark_dirty();
@@ -46,9 +46,9 @@ namespace mbts.spark {
int working_quantity;
LadderModel model => _parent.model;
Dictionary<uint, OrderRecordImmutable> _orders = new Dictionary<uint, OrderRecordImmutable>();
Dictionary<uint, (OrderRecordImmutable order, uint? p)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p)>();
public LadderOrderVM(OrderRecordImmutable o, double real_price, LadderVM parent, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
public LadderOrderVM((OrderRecordImmutable order, uint? p) op, double real_price, LadderVM parent, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
_parent = parent;
_bus = bus;
_bs = bs;
@@ -73,7 +73,7 @@ namespace mbts.spark {
double get_font_size(LadderSettings s) => s.orders_column.font_size;
Brush get_foreground(LadderSettings s) => s.orders_column.foreground.rgb_brush();
//
add_order(o);
add_order(op);
_dispose = Disposable.Create(
_bs.Subscribe(badge => {
@@ -109,19 +109,19 @@ namespace mbts.spark {
// if this badge only represents a single order, then is_exchange_order and the border (= algo color)
// should be set appropriately
if (_orders.Count == 1) {
var single_order = _orders.Values.First();
var single_order = _orders.Values.First().order;
var single_order_metadata = _parent.model.order_gateway.factory.get_metadata(single_order);
border = single_order_metadata.Color.rgb_brush();
is_exchange_order = OrderUtils.is_exchange_order(_orders.Values.First());
is_exchange_order = OrderUtils.is_exchange_order(single_order);
} else {
border = new SolidColorBrush(color);
is_exchange_order = false;
}
has_multiple_orders_at_same_level = _orders.Count > 1;
is_hedge_order = _orders.Any(o => o.Value.sub_type() == OrderSubType.HedgingLeg);
is_hedge_order = _orders.Any(op => op.Value.order.sub_type() == OrderSubType.HedgingLeg);
if (_orders.Any()) {
summary = _orders.Values.Select(order => order.short_description(_parent.security.ContractSpec)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
summary = _orders.Values.Select(op => op.order.short_description(_parent.security.ContractSpec)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
}
_set_badge_text();
@@ -132,8 +132,8 @@ namespace mbts.spark {
RaisePropertyChanged(nameof(text_lines));
}
public void add_order(OrderRecordImmutable new_order) {
_orders[new_order.Key] = new_order;
public void add_order((OrderRecordImmutable order, uint? p) new_op) {
_orders[new_op.order.Key] = new_op;
}
public void remove_order(uint order_key) {
@@ -149,10 +149,11 @@ namespace mbts.spark {
public uint security_key => _parent.security.Id;
public double price { get => double.IsNaN(_phantom_price) ? _real_price : _phantom_price; }
public uint? min_piq => _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
public double real_price => _real_price;
public Side side {
get {
var distinct_sides = _orders.Values.Select(o => o.side()).Distinct();
var distinct_sides = _orders.Values.Select(op => op.order.side()).Distinct();
if (distinct_sides.Count() > 1) {
return Side.Undefined;
}
@@ -160,11 +161,11 @@ namespace mbts.spark {
}
}
IEnumerable<OrderRecordImmutable> _parent_and_child_not_sharing_slot() => _orders.Values.Where(o => !_orders.ContainsKey(o.ParentKey));
IEnumerable<OrderRecordImmutable> _parent_and_child_not_sharing_slot() => _orders.Values.Select(op => op.order).Where(o => !_orders.ContainsKey(o.ParentKey));
public IReadOnlyList<string> text_lines { get; private set; }
public bool has_parent => _orders.Values.Any( order => order.has_parent());
public bool has_parent => _orders.Values.Any( op => op.order.has_parent());
public bool has_phantom_price => !double.IsNaN(_phantom_price);
public ICommand detach_command => new DelegateCommand(() => _execute_command(_crack_order), () => !has_parent);
@@ -191,16 +192,16 @@ namespace mbts.spark {
// the setter is public so that the LadderVM can set this field
public CustomOrderDefinition.CustomOneofCase parent_algo_type { get => _parent_algo_type.Value; set => _parent_algo_type.Value = value; }
public IEnumerable<OrderRecordImmutable> orders => _orders.Values;
public IEnumerable<OrderRecordImmutable> orders => _orders.Values.Select(o => o.order);
void _cancel_order() {
foreach (var order in _orders.Values) {
model.order_gateway.cancel_order(order).ignore();
foreach (var op in _orders.Values) {
model.order_gateway.cancel_order(op.order).ignore();
}
}
void _crack_order() {
var distinct_parent_keys = _orders.Values.Where(o => o.has_parent()).Select(x => x.ParentKey).Distinct();
var distinct_parent_keys = _orders.Values.Where(op => op.order.has_parent()).Select(x => x.order.ParentKey).Distinct();
model.order_gateway.crack_orders(distinct_parent_keys).ignore();
}
@@ -279,7 +280,7 @@ namespace mbts.spark {
}
public override string ToString() {
return $"Price: {this.price} Order Count: {this.orders.Count()}";
return $"Price: {this.price} Order Count: {this.orders.Count()} PIQ: {(this.min_piq.HasValue ? this.min_piq.Value.ToString() : "")}";
}
}
}
@@ -249,7 +249,8 @@ namespace mbts.spark {
bid_column = LadderColumnSettings.Default,
ask_column = LadderColumnSettings.Default,
price_column = LadderColumnSettings.Default,
last_column = LadderColumnSettings.Default;
last_column = LadderColumnSettings.Default,
piq_column = LadderColumnSettings.Default;
orders_column.background = BTSColors.LadderSpecial.default_column_background;
bid_column.foreground = 0xFFFFFF;
bid_column.background = BTSColors.MarketData.bid;
@@ -257,6 +258,7 @@ namespace mbts.spark {
ask_column.background = BTSColors.MarketData.ask;
price_column.background = BTSColors.LadderSpecial.default_column_background;
last_column.background = 0xFFFFFF;
piq_column.background = BTSColors.LadderSpecial.default_column_background;
return new LadderSettings {
row_height = 20,
grid_line_color = 0x535353,
@@ -265,6 +267,7 @@ namespace mbts.spark {
ask_column = ask_column,
price_column = price_column,
last_column = last_column,
piq_column = piq_column,
volume_column = VolumeColumnSettings.Default,
calculator_buttons = new[] { 1, 5, 10, 25, 50, 100 },
scroll_bar_visible = false,
@@ -398,7 +401,7 @@ namespace mbts.spark {
public bool clear_visible { get; set; }
//
[ProtoMember(43), DefaultValue(_default_order_entry_panel_width)]
public double order_entry_panel_initial_width { get; set; }
public double order_entry_panel_initial_width { get; set; }
[ProtoMember(44), DefaultValue(true)]
public bool use_defaults { get; set; }
@@ -417,6 +420,13 @@ namespace mbts.spark {
[ProtoMember(48)]
public NetChangeTerms net_change { get; set; }
[ProtoMember(49)]
LadderColumnSettings _piq_column;
public LadderColumnSettings piq_column {
get => _piq_column ?? (_piq_column = LadderSettings.Default.piq_column);
set => _piq_column = value;
}
public bool Equals(LadderSettings other) =>
other != null &&
row_height == other.row_height &&
@@ -426,6 +436,7 @@ namespace mbts.spark {
ask_column.safe_equals(other.ask_column) &&
price_column.safe_equals(other.price_column) &&
last_column.safe_equals(other.last_column) &&
piq_column.safe_equals(other.piq_column) &&
calculator_buttons.safe_equals(other.calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
scroll_bar_visible == other.scroll_bar_visible &&
default_order_type == other.default_order_type &&
@@ -476,6 +487,7 @@ namespace mbts.spark {
EqualityComparer.safe_get_hash_code(ask_column),
EqualityComparer.safe_get_hash_code(price_column),
EqualityComparer.safe_get_hash_code(last_column),
EqualityComparer.safe_get_hash_code(piq_column),
EqualityComparer.safe_get_hash_code(calculator_buttons),
scroll_bar_visible.GetHashCode(),
default_order_type.GetHashCode(),
@@ -517,12 +529,12 @@ namespace mbts.spark {
public struct WindowSpecifics {
public readonly double order_entry_panel_width, zoom_factor;
public readonly uint orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width;
public readonly uint orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width, piq_column_width;
public WindowSpecifics(LadderSettings s)
: this(s.order_entry_panel_initial_width, s.orders_column.width, s.bid_column.width, s.price_column.width, s.ask_column.width, s.last_column.width, s.volume_column.general.width, s.zoom_factor) { }
: this(s.order_entry_panel_initial_width, s.orders_column.width, s.bid_column.width, s.price_column.width, s.ask_column.width, s.last_column.width, s.volume_column.general.width, s.zoom_factor, s.piq_column.width) { }
public WindowSpecifics(double order_entry_panel_width, uint orders_column_width, uint bid_column_width, uint price_column_width, uint ask_column_width, uint last_column_width, uint volume_column_width, double zoom_factor) {
public WindowSpecifics(double order_entry_panel_width, uint orders_column_width, uint bid_column_width, uint price_column_width, uint ask_column_width, uint last_column_width, uint volume_column_width, double zoom_factor, uint piq_column_width) {
this.order_entry_panel_width = order_entry_panel_width;
this.orders_column_width = orders_column_width;
this.bid_column_width = bid_column_width;
@@ -531,6 +543,7 @@ namespace mbts.spark {
this.last_column_width = last_column_width;
this.volume_column_width = volume_column_width;
this.zoom_factor = zoom_factor;
this.piq_column_width = piq_column_width;
}
}
}
@@ -543,6 +556,7 @@ namespace mbts.spark {
yield return settings.ask_column;
yield return settings.volume_column.general;
yield return settings.last_column;
yield return settings.piq_column;
}
public static IEnumerable<LadderColumnSettings> visible_columns(this LadderSettings settings) =>
@@ -560,6 +574,7 @@ namespace mbts.spark {
source.price_column.width = specifics.price_column_width;
source.ask_column.width = specifics.ask_column_width;
source.last_column.width = specifics.last_column_width;
source.piq_column.width = specifics.piq_column_width;
source.volume_column.general.width = specifics.volume_column_width;
source.zoom_factor = specifics.zoom_factor;
return source;
@@ -29,7 +29,8 @@ namespace mbts.spark {
_bid = new LadderColumnSettingsVM("Bid"),
_price = new LadderColumnSettingsVM("Price"),
_ask = new LadderColumnSettingsVM("Ask"),
_last = new LadderColumnSettingsVM("Last", true);
_last = new LadderColumnSettingsVM("Last", true),
_piq = new LadderColumnSettingsVM("PIQ", true);
VolumeColumnVM _volume = new VolumeColumnVM();
LadderMarketBreakLineVM
_bid_mkt_line = new LadderMarketBreakLineVM("Bid"),
@@ -82,6 +83,7 @@ namespace mbts.spark {
_price.font_size_changed += on_font_size_changed;
_ask.font_size_changed += on_font_size_changed;
_last.font_size_changed += on_font_size_changed;
_piq.font_size_changed += on_font_size_changed;
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
}
@@ -120,6 +122,7 @@ namespace mbts.spark {
if (!is_synthetic) {
yield return _volume.general;
yield return _last;
yield return _piq;
}
}
}
@@ -130,6 +133,7 @@ namespace mbts.spark {
public LadderColumnSettingsVM ask_column => _ask;
public LadderColumnSettingsVM volume_column => _volume.general;
public LadderColumnSettingsVM last_column => _last;
public LadderColumnSettingsVM piq_column => _piq;
public VolumeColumnVM volume => _volume;
public LadderMarketBreakLineVM session_range_fill => _session_range_fill;
public ObservableEditableCollection<IMutableValueWrapperVM<uint>> size_presets { get; }
@@ -209,6 +213,7 @@ namespace mbts.spark {
_price.assign_from(_current.price_column);
_volume.assign_from(_current.volume_column);
_last.assign_from(_current.last_column);
_piq.assign_from(_current.piq_column);
_bid_mkt_line.assign_from(_current.bid_break_line);
_ask_mkt_line.assign_from(_current.ask_break_line);
_mid_mkt_line.assign_from(_current.mid_break_line);
@@ -254,6 +259,7 @@ namespace mbts.spark {
clone.ask_column = _ask.get_settings();
clone.volume_column = _volume.get_settings();
clone.last_column = _last.get_settings();
clone.piq_column = _piq.get_settings();
clone.bid_break_line = _bid_mkt_line.get_settings();
clone.ask_break_line = _ask_mkt_line.get_settings();
clone.mid_break_line = _mid_mkt_line.get_settings();
+11 -5
View File
@@ -26,7 +26,7 @@ namespace mbts.spark {
readonly LadderModel _model;
readonly Property<double> _high, _low, _change, _open, _settlement, _position, _left_panel_width, _zoom_factor;
//readonly Property<double> _window_width, _window_height;
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width;
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width, _piq_column_width;
readonly Property<ulong> _volume;
readonly Property<bool> _auto_clear, _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
_show_tif, _show_order_type, _show_account_position, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
@@ -58,6 +58,7 @@ namespace mbts.spark {
_price_column_width = backing(nameof(price_column_width), ws.price_column_width);
_ask_column_width = backing(nameof(ask_column_width), ws.ask_column_width);
_last_column_width = backing(nameof(last_column_width), ws.last_column_width);
_piq_column_width = backing(nameof(piq_column_width), ws.piq_column_width);
_volume_column_width = backing(nameof(volume_column_width), ws.volume_column_width);
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
@@ -132,7 +133,7 @@ namespace mbts.spark {
account_positions,
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
model.order_gateway.working_orders.OnDispatcher().Subscribe(working => {
model.order_gateway.working_orders_with_piq.OnDispatcher().Subscribe(working => {
// Here we manage N orders to 1 price slot in the ladder
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
@@ -157,9 +158,9 @@ namespace mbts.spark {
}
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order(wo);
slot_vm.add_order((wo, osec.piq));
} else {
slot_vm = new LadderOrderVM(wo, slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
slot_vm = new LadderOrderVM((wo, osec.piq), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
_slot_price_vs_orders[slot_price_target] = slot_vm;
}
@@ -232,6 +233,10 @@ namespace mbts.spark {
public event Action on_tick;
public MDConsumerHandle md => _model.md;
// we return only entries who have piqs and whose piqs are not uint.Max
// uint.Max happens when the server is restarted but the client is still up
// maybe long term we don't show this data while the exchange connection is down
public IEnumerable<(double, uint)> price_vs_piq => _slot_price_vs_orders.Values.Where(result => result.min_piq.HasValue && result.min_piq.Value != uint.MaxValue).Select(o => (o.price, o.min_piq.Value));
public bool is_synthetic => _model.security.ContractCase == Security.ContractOneofCase.Synthetic;
Property<string> _status;
@@ -306,13 +311,14 @@ namespace mbts.spark {
public uint price_column_width { get => _price_column_width.Value; set => _price_column_width.set(value); }
public uint ask_column_width { get => _ask_column_width.Value; set => _ask_column_width.set(value); }
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
public LadderSettings get_save_state() {
var state = _settings_manager.current_settings();
state = state.with_window_specifics(new LadderSettings.WindowSpecifics(left_panel_width, orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width, zoom_factor));
state = state.with_window_specifics(new LadderSettings.WindowSpecifics(left_panel_width, orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width, zoom_factor, piq_column_width));
state.use_defaults = _settings_manager.instance_settings == null;
return state;
}
@@ -541,6 +541,7 @@
price_column_width="{Binding price_column_width, Mode=TwoWay}"
ask_column_width="{Binding ask_column_width, Mode=TwoWay}"
LastColumnWidth="{Binding last_column_width, Mode=TwoWay}"
PIQColumnWidth="{Binding piq_column_width, Mode=TwoWay}"
VolumeColumnWidth="{Binding volume_column_width, Mode=TwoWay}"
>
<spark:LadderGridHost.Resources>
@@ -141,6 +141,10 @@ namespace mbts.spark {
header = "Last";
width_prop = nameof(LadderVM.last_column_width);
break;
case LadderGridHost.ColumnType.PIQ:
header = "PIQ";
width_prop = nameof(LadderVM.piq_column_width);
break;
default: throw ExceptionBuilder.Argument.unexpected_case(type, nameof(type));
}
return _create(host, header, $"DataContext.{width_prop}");
@@ -277,6 +277,30 @@ namespace mbts.spark {
}
}
// this is basically a copy of redner_sizes up above
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, (double price, uint piq)[] piqs, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
using (var dc = dv.RenderOpen()) {
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
if (!prices.within_range(origin_index) || piqs.is_null_or_empty())
return;
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
for (int i = 0; i < piqs.Length; i++) {
double price = piqs[i].price;
// prices are in descending order, so get diff from curr_price - price
int tick_diff = spec.tickdiff_unchecked(curr_price, price);
curr_index += tick_diff;
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Length) {
var gr = format_text(piqs[i].piq.ToString(), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
}
}
}
public static void render_mkt_break(DrawingVisual dv, int index, double row_height, uint color) =>
render_single_grid_line(dv, index, row_height, color, 0, SystemParameters.FullPrimaryScreenWidth);
@@ -110,19 +110,21 @@ namespace bts.api.core {
the_update.Clear();
try {
if (event_type == ApiNativeMethods.EventType.TABLE_UPDATED) {
foreach (var change in changes) {
switch (change.type) {
case ApiNativeMethods.ChangeType.REMOVE:
the_update.removed_keys.Add(change.key);
break;
case ApiNativeMethods.ChangeType.REPLACE:
the_update.changed_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
the_update.changed_keys.Add(change.key);
break;
case ApiNativeMethods.ChangeType.CREATE:
the_update.added_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
the_update.added_keys.Add(change.key);
break;
if (changes != null) {
foreach (var change in changes) {
switch (change.type) {
case ApiNativeMethods.ChangeType.REMOVE:
the_update.removed_keys.Add(change.key);
break;
case ApiNativeMethods.ChangeType.REPLACE:
the_update.changed_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
the_update.changed_keys.Add(change.key);
break;
case ApiNativeMethods.ChangeType.CREATE:
the_update.added_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
the_update.added_keys.Add(change.key);
break;
}
}
}
observer.OnNext(the_update);
+6
View File
@@ -51,6 +51,12 @@ namespace mbts.config
reset();
initialize();
}
public static bool try_initialize(out string error)
{
reset();
return initialize(out error);
}
////////////
/// private
@@ -0,0 +1,19 @@
namespace bts.api.orderman {
public static class OrderUtilities {
public static NativeOrderStateImmutable? native_order_state(this OrderStateImmutable order) {
switch(order.CustomCase) {
case OrderState.CustomOneofCase.Market:
return order.Market.NativeState;
case OrderState.CustomOneofCase.Limit:
return order.Limit.NativeState;
case OrderState.CustomOneofCase.Stop:
return order.Stop.NativeState;
case OrderState.CustomOneofCase.StopLimit:
return order.StopLimit.NativeState;
default:
return null;
}
}
}
}
+13 -8
View File
@@ -1,14 +1,19 @@
using ManyConsole;
using System.Linq;
using System;
using System.Collections.Generic;
using CommandLine;
namespace bts {
public class Configcheck : ConsoleCommand {
public Configcheck() {
IsCommand("configcheck", "Ye olde Configcheck");
HasAdditionalArguments(null, " [Keys to check]");
}
public override int Run(string[] args) {
mbts.config.Configuration.configcheck(args.Length, args.ToArray());
[Verb("configcheck", HelpText="Ye olde Configcheck")]
public class ConfigcheckOptions {
[Value(0, MetaName="keys", HelpText="Keys to print")]
public IEnumerable<string> keys { get; set; }
}
public class Configcheck {
public static int Run(ConfigcheckOptions options) {
var keys = options.keys.ToArray();
mbts.config.Configuration.configcheck(keys.Length, keys);
return 0;
}
}
@@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ManyConsole {
public static class ConsoleCommandEx {
public static string get_argument(this ConsoleCommand _, string arg_name) {
var arg = Environment.GetCommandLineArgs().FirstOrDefault(str => str.StartsWith("--"+arg_name));
if (arg != null) {
var parts = arg.Split(new[] { '=' }, 2);
if (parts.Length > 1) {
return parts[1];
}
}
return "";
}
}
}
+18 -17
View File
@@ -6,32 +6,33 @@ using System.Reflection;
using System.Threading.Tasks;
using System.Reactive.Linq;
using System.Reactive;
using ManyConsole;
using System.Linq;
using CommandLine;
namespace sparkcmd {
public class ScriptClientCommand : ConsoleCommand {
bool _server = false;
string _host;
string _logfile;
[Verb("script", HelpText="Remote control for shell diagnostic host")]
public class ScriptClientOptions {
[Option('s', "server", HelpText="Run a script server for testing.")]
public bool server { get; set; }
public ScriptClientCommand() {
IsCommand("script", "Remote control for shell diagnostic host");
HasOption("s|server", "Run a script server for testing.", x => _server = true);
HasOption("h|host", "Connect to {host}", h => _host = h);
HasOption("f|fromfile", "Read commands from file instead of stdin", f => _logfile = f);
}
public override int Run(string[] args) {
if (_server) {
[Option('h', "host", HelpText="Connect to {host}")]
public string host { get; set; }
[Option('f', "fromfile", HelpText="Read commands from file instead of stdin")]
public string file { get; set; }
}
public class ScriptClientCommand {
public static int Run(ScriptClientOptions o) {
if (o.server) {
ScriptClient.runserver();
}
else {
var host = string.IsNullOrEmpty(_host) ? args.First() : _host;
if (string.IsNullOrEmpty(_logfile) && File.Exists(_logfile)) {
ScriptClient.start(host, File.OpenRead(_logfile));
if (string.IsNullOrEmpty(o.file) && File.Exists(o.file)) {
ScriptClient.start(o.host, File.OpenRead(o.file));
}
else {
ScriptClient.start(host, Console.OpenStandardInput());
ScriptClient.start(o.host, Console.OpenStandardInput());
}
}
return 0;
+45 -17
View File
@@ -1,6 +1,6 @@
using bts.api.core;
using bts.test;
using ManyConsole;
using CommandLine;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,26 +8,44 @@ using System.Text;
using System.Threading.Tasks;
namespace bts {
class TailService : ConsoleCommand {
enum PrintFormat {
ToString,
Json
}
[Verb("tail", HelpText="'console dsqa'")]
class TailServiceOptions {
[Option('m', "method", Required=true, HelpText = "'[ServiceName].[Method]' to call")]
public string method { get;set; }
[Option('t', "time", HelpText="Include timestamp")]
public bool time { get; set; }
[Option('p', "path", HelpText="Path inside the row to use for display. Ex: 'Id' or 'Name' ")]
public string path { get; set; }
[Option('a', "all", HelpText="Include 'all' rows on every print")]
public bool all { get; set; }
[Option('j', "json", HelpText="Format rows as json")]
public bool json { get; set; }
}
class TailService {
static bool _all = false;
static PrintFormat _format = PrintFormat.ToString;
static bool _timestamp = false;
public TailService() {
void ignore(string _) { }
IsCommand("tailservice", "Tail updates to named service api. Ex: tail --method=SyntheticSecurityService.metadata");
HasRequiredOption("method", "Service.method to tail", ignore);
HasOption("time", "Include timestamp", _ => _timestamp = true);
// The following are not implemented. If you need them get to work.
HasOption("path", "Path inside the row to use for display. Ex: 'Id' or 'Name' ", ignore);
HasOption("all", "Invlude 'all' rows on every print", _ => _all = true);
HasAdditionalArguments(null, " [nothing]");
}
public override int Run(string[] remainingArguments) {
var arg = this.get_argument("method");
var split = arg.Split('.');
public static int Run(TailServiceOptions o) {
var split = o.method.Split('.');
if (split.Length != 2) {
Console.WriteLine("Try --method=[ServiceName].[Method]");
Console.WriteLine("Try --method [ServiceName].[Method]");
return -1;
}
if (o.json) {
_format = PrintFormat.Json;
}
tail(split[0], split[1]);
return 0;
}
@@ -46,7 +64,17 @@ namespace bts {
Console.Write(" ");
}
if (_all) { }
Console.WriteLine(value);
switch(_format) {
case PrintFormat.Json:
dynamic dv = value;
var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { dv.added, dv.removed, dv.changed });
Console.WriteLine(json);
break;
case PrintFormat.ToString:
Console.WriteLine(value);
break;
}
}
}
private static void tail(string service_name, string method_name) {
+54 -15
View File
@@ -2,35 +2,74 @@ using bts.api;
using bts.api.core;
using bts.api.synthetic;
using bts.test;
using ManyConsole;
using bts.md;
using CommandLine;
using System;
using System.Threading;
namespace bts {
public class ServiceTest : ConsoleCommand {
bool _synthetic;
bool _user;
bool _admin;
public ServiceTest() {
IsCommand("service", "Put code here for testing during service development");
HasOption("synthetic", "Run synthetic test", _ => _synthetic = true);
HasOption("user", "user test", _ => _user = true);
HasOption("admin", "admin test", _ => _admin = true);
}
public override int Run(string[] remainingArguments) {
if (_synthetic) {
public class ServiceTestOptions {
[Option(nameof(synthetic))]
public bool synthetic { get; set; }
[Option(nameof(user))]
public bool user { get; set; }
[Option(nameof(admin))]
public bool admin { get; set; }
[Option(nameof(mbo))]
public bool mbo { get; set; }
[Option("key")]
public UInt32 key { get; set; }
[Option("side")]
public api.Side side { get; set; }
}
public class ServiceTest {
public static int Run(ServiceTestOptions o) {
if (o.synthetic) {
synthetic_test();
}
if (_user) {
if (o.user) {
user_test();
}
if (_admin) {
if (o.admin) {
admin_test();
}
if (o.mbo) {
mbo_test(o);
}
return 0;
}
static string try_get(string[] args, int index) {
if (args.Length < index) {
return args[index];
}
return "";
}
private static void mbo_test(ServiceTestOptions o) {
var key = o.key;
var side = o.side;
using (var ctx = new ServiceContext()) {
var mbo = ctx.locate<MBOService>();
var data = mbo.get_orders_for_security(new MBOSecurityQuery { SecurityId = key });
Console.WriteLine($"Order data for {data.SecurityId}");
var vector = side == api.Side.Buy ? data.Bid : data.Ask;
var price = 0.0;
foreach(var e in vector) {
if (e.Price != price) {
price = e.Price;
Console.WriteLine("================");
}
Console.WriteLine(e);
}
}
}
private static void synthetic_test() {
using (var ctx = new ServiceContext()) {
@@ -1,14 +1,13 @@
using ManyConsole;
using System.Reflection;
using CommandLine;
namespace bts {
public class SettinsManagerCommand : ConsoleCommand {
public SettinsManagerCommand() {
IsCommand("SettingsManager", "Copy workspaces around");
}
[Verb("settingsmanager", HelpText="Copy workspaces around")]
public class SettingsManagerOptions { }
public override int Run(string[] remainingArguments) {
public class SettingsManagerCommand {
public static int Run(SettingsManagerOptions _) {
var asm = Assembly.Load("spark");
var type = asm.GetType("bts.settings.SettingsManagerTool");
var run = type.GetMethod("run", BindingFlags.Static | BindingFlags.Public);
+41 -33
View File
@@ -1,6 +1,5 @@
using bts.test;
using ManyConsole;
using System;
using mbts;
using System.Collections.Generic;
@@ -8,64 +7,73 @@ using bts.api.test;
using System.Threading.Tasks;
using mbts.config;
using System.Linq;
using CommandLine;
using Newtonsoft.Json;
namespace sparkcmd {
public class TestCommand : ConsoleCommand {
uint _timeout = 0;
uint _parallel = 1;
bool _list;
TestType test_type = TestType.Unit;
[Verb("test", HelpText="Run Tests")]
public class TestCommandOptions {
bool self_contained = false;
[Option('t', "timeout", HelpText="Timeout unit tets", Default=0u)]
public uint timeout { get; set; }
[Option('l', "list", HelpText="List tests")]
public bool list { get; set; }
[Option('i', "insular", HelpText="Tests run sparkd and use their own config (if specified)")]
public bool self_contained { get; set; }
public TestCommand() {
IsCommand("test", "Run managed unit tests");
// i comes thorugh as "t" which seems daft.
HasOption("t|timeout", "Timeout unit tests", i => _timeout = 10);
HasOption<uint>("n|parallel", "How many tests to run in parallel", i => _parallel = i);
HasOption("l|list", "List all tests", _ => _list = true);
HasOption("unit","Run unit tests", _ => test_type = TestType.Unit);
HasOption("system","Run system tests", _ => test_type = TestType.System);
HasOption("shell","Run shell tests", _ => test_type = TestType.Shell);
HasOption("i|insular|selfcontained", "Tests run sparkd and use their own config (if specified)", _ => self_contained = true);
HasAdditionalArguments(null, " [Test List]");
[Option("type", HelpText ="Run all tests of type [Unit|System|Shell]")]
public TestType type { get; set; }
[Value(1, MetaName="Tests", HelpText="Test list")]
public IEnumerable<string> tests { get; set; }
}
public static class TestCommand {
static TestType test_type(string t) {
switch(t.ToLower()) {
case "unit": return TestType.Unit;
case "system": return TestType.System;
case "shell": return TestType.Shell;
}
return TestType.Unit;
}
public override int Run(string[] tests) {
if (_list) {
list_tests();
public static int Run(TestCommandOptions o) {
Console.WriteLine(JsonConvert.SerializeObject(o));
if (o.list) {
list_tests((o.type));
return 0;
}
//Configuration.initialize_for_test();
if (_timeout == 0 && tests.Length == 1 && !self_contained) {
if (o.timeout == 0 && o.tests.Count() == 1 && !o.self_contained) {
//System.Threading.Thread.Sleep(5000);
using (var trr = new TestResultReceiver(true)) {
InProcessTestRunner.run_test_synchronously(tests[0], Enumerator, trr.get_reporter());
InProcessTestRunner.run_test_synchronously(o.tests.First(), Enumerator, trr.get_reporter());
trr.print_summary();
return trr.ErrorCount;
}
}
else if (tests.Length > 0) {
return run_tests(Enumerator.match(tests));
else if (o.tests.Count() > 0) {
return run_tests(o, Enumerator.match(o.tests));
}
else {
return run_tests(Enumerator.get_of_type(test_type));
return run_tests(o, Enumerator.get_of_type(o.type));
}
}
Lazy<TestEnumerator> _enumerator = new Lazy<TestEnumerator>(() => new TestEnumerator("spark_test"));
TestEnumerator Enumerator => _enumerator.Value;
static Lazy<TestEnumerator> _enumerator = new Lazy<TestEnumerator>(() => new TestEnumerator("spark_test"));
static TestEnumerator Enumerator => _enumerator.Value;
int run_tests(IEnumerable<TestMethod> tests) {
static int run_tests(TestCommandOptions o, IEnumerable<TestMethod> tests) {
var runner = new TestRunner();
// waits for all child processes to exit or time out
return runner.run_tests(tests, _timeout, self_contained);
return runner.run_tests(tests, o.timeout, o.self_contained);
}
void list_tests() {
static void list_tests(TestType test_type) {
Console.WriteLine("All tests: ");
foreach (var t in Enumerator.get_groups_of_type(test_type)) {
ConsoleEx.WriteLine(t.name, ConsoleColor.Green);
+46 -4
View File
@@ -2,16 +2,58 @@
using bts.api.core;
using bts.api.synthetic;
using bts.api.test;
using ManyConsole;
using System;
using System.Linq;
using CommandLine;
using bts;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace sparkcmd {
class Program {
static object option<T>(Func<T, int> run) { return run; }
static Type argType(object arg) {
return arg.GetType().GetGenericArguments().First();
}
static int Run(Func<IEnumerable<Error>, int> on_error, params Type[] runners) {
var run_methods =
runners
.Select(r => r.GetMethod("Run", BindingFlags.Static | BindingFlags.Public))
.ToList();
var args =
run_methods
.Select(m => m.GetParameters().First().GetType());
var parser = Parser.Default.GetType().GetMethod("ParseArguments", new[] { typeof(IEnumerable<string>) });
//var types = args.Select(argType).ToArray();
//var parse = CommandLine.Parser.Default.GetType().GetMethod("ParseArguments", BindingFlags.Public).MakeGenericMethod(types);
return 3;
}
static int Main(string[] args) {
mbts.config.Configuration.initialize();
var commands = ConsoleCommandDispatcher.FindCommandsInSameAssemblyAs(typeof(Program));
return ConsoleCommandDispatcher.DispatchCommand(commands, args, Console.Out);
mbts.utils.BTSNativeMethods.provide_command_line(args, args.Length);
//return Parser.Default
//.ParseArguments<ConfigcheckOptions,ScriptClientOptions,TailServiceOptions,ServiceTestOptions,SettingsManagerOptions,TestCommandOptions>(args)
//.MapResult<ConfigcheckOptions,ScriptClientOptions,TailServiceOptions,ServiceTestOptions,SettingsManagerOptions,TestCommandOptions,int>(
// Configcheck.Run,
// ScriptClientCommand.Run,
// TailService.Run,
// ServiceTest.Run,
// SettingsManagerCommand.Run,
// TestCommand.Run,
// errors => 1);
return Run(
errors => 1,
typeof(Configcheck),
typeof(ScriptClientCommand),
typeof(TailService),
typeof(ServiceTest),
typeof(SettingsManagerCommand),
typeof(TestCommand));
}
}
}
+30 -27
View File
@@ -1,27 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net471;netcoreapp2.1</TargetFrameworks>
<RuntimeIdentifiers>win7-x64;ubuntu.16.04-x64</RuntimeIdentifiers>
<Authors>Blue Trading Systems</Authors>
<Company>Blue Trading Systems</Company>
<Product>Spark</Product>
<Platform>x64</Platform>
<PlatformTarget>x64</PlatformTarget>
<Version>1.0.0</Version>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../spark_api/spark_api.csproj" />
<!--<ProjectReference Include="..\spark\Spark.csproj" />-->
<ProjectReference Include="..\spark_core\spark_core.csproj" />
<ProjectReference Include="..\spark_test\spark_test.csproj" />
<!--<Proto3 Include="**/*.proto" />-->
</ItemGroup>
<ItemGroup>
<PackageReference Include="ManyConsole" Version="1.0.0.4" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net471;netcoreapp2.1</TargetFrameworks>
<RuntimeIdentifiers>win7-x64;ubuntu.16.04-x64</RuntimeIdentifiers>
<Authors>Blue Trading Systems</Authors>
<Company>Blue Trading Systems</Company>
<Product>Spark</Product>
<Platform>x64</Platform>
<PlatformTarget>x64</PlatformTarget>
<Version>1.0.0</Version>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../spark_api/spark_api.csproj" />
<!--<ProjectReference Include="..\spark\Spark.csproj" />-->
<ProjectReference Include="..\spark_core\spark_core.csproj" />
<ProjectReference Include="..\spark_test\spark_test.csproj" />
<!--<Proto3 Include="**/*.proto" />-->
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.4.3" />
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
</ItemGroup>
</Project>
+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/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.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/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.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/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/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/quickbus_cfg.proto;config/protos/spreader.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/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_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/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/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.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/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/md2_tests.cpp;md/test/synthetic_book_merge_tests.cpp;os/os.cpp;os/tests/os_tests.cpp;quickbus/core/LocalNode.cpp;quickbus/core/Quickbus.cpp;quickbus/core/SubscriptionID.cpp;quickbus/core/TableID.cpp;quickbus/message_model/Update.cpp;quickbus/message_model/merge_updates.cpp;quickbus/network/QBConnection.cpp;quickbus/network/QBConnectionManager.cpp;quickbus/network/endpoint_description.cpp;quickbus/network/quickbus_msgs.proto;quickbus/pubsub/TablePublisher.cpp;quickbus/pubsub/TableSubscriber.cpp;quickbus/pubsub/tests/qb_pubsub_tests.cpp;quickbus/routing/INode.cpp;quickbus/routing/NodeID.cpp;quickbus/routing/Router.cpp;quickbus/rpc/RPCClient.cpp;quickbus/rpc/RPCServer.cpp;quickbus/rpc/RPCSessionID.cpp;quickbus/rpc/rpc_utils.cpp;quickbus/rpc/tests/qb_rpc_tests.cpp;quickbus/tests/quickbus_tests.cpp;sparkproto/SparkProtos.proto;testing/catch_test_runner.cpp;utils/AuthToken.cpp;utils/Dispatcher.cpp;utils/GrowableSPSCQueue.cpp;utils/HTTPStream.cpp;utils/PBUtils.cpp;utils/Puid.cpp;utils/SecureLogon.cpp;utils/SharedMemory.cpp;utils/Sqlite_utils.cpp;utils/Uuid.cpp;utils/datetime.cpp;utils/download.cpp;utils/file_utils.cpp;utils/init_spark_lib.cpp;utils/is_unittest_environment.cpp;utils/stats.cpp;utils/tests/AuthTokenTests.cpp;utils/thread_manager.cpp)
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/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.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/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.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/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/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/quickbus_cfg.proto;config/protos/spreader.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/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_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/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.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/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;os/os.cpp;os/tests/os_tests.cpp;quickbus/core/LocalNode.cpp;quickbus/core/Quickbus.cpp;quickbus/core/SubscriptionID.cpp;quickbus/core/TableID.cpp;quickbus/message_model/Update.cpp;quickbus/message_model/merge_updates.cpp;quickbus/network/QBConnection.cpp;quickbus/network/QBConnectionManager.cpp;quickbus/network/endpoint_description.cpp;quickbus/network/quickbus_msgs.proto;quickbus/pubsub/TablePublisher.cpp;quickbus/pubsub/TableSubscriber.cpp;quickbus/pubsub/tests/qb_pubsub_tests.cpp;quickbus/routing/INode.cpp;quickbus/routing/NodeID.cpp;quickbus/routing/Router.cpp;quickbus/rpc/RPCClient.cpp;quickbus/rpc/RPCServer.cpp;quickbus/rpc/RPCSessionID.cpp;quickbus/rpc/rpc_utils.cpp;quickbus/rpc/tests/qb_rpc_tests.cpp;quickbus/tests/quickbus_tests.cpp;sparkproto/SparkProtos.proto;testing/catch_test_runner.cpp;utils/AuthToken.cpp;utils/Dispatcher.cpp;utils/GrowableSPSCQueue.cpp;utils/HTTPStream.cpp;utils/PBUtils.cpp;utils/Puid.cpp;utils/SecureLogon.cpp;utils/SharedMemory.cpp;utils/Sqlite_utils.cpp;utils/Uuid.cpp;utils/datetime.cpp;utils/download.cpp;utils/file_utils.cpp;utils/init_spark_lib.cpp;utils/is_unittest_environment.cpp;utils/stats.cpp;utils/tests/AuthTokenTests.cpp;utils/thread_manager.cpp)
+1 -1
View File
@@ -176,7 +176,7 @@ void safe_call_subscription_callback(raw_subscription_callback cb, EventType typ
if (updates.size() > 0)
cb(type, &updates[0], updates.size(), extra);
else
cb(type, nullptr, updates.size(), extra);
cb(type, nullptr, 0, extra);
});
}
+30 -17
View File
@@ -5,6 +5,8 @@
#include "ApiException.h"
#include "RawUpdate.h"
#include "ServiceContext.h"
#include "lib/logging/logging.h"
#include <rxcpp/rx.hpp>
#include <functional>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
@@ -12,6 +14,17 @@
namespace bts {
namespace api {
template <typename T>
std::shared_ptr<T> deserialize(const char* buffer, size_t length) {
google::protobuf::io::ArrayInputStream stream(buffer, length);
auto item = std::make_shared<T>();
if (item->ParseFromZeroCopyStream(&stream)) {
return item;
}
LOG(Api,Error,"Failed to deserialize proto from api stream. This is likely fatal.");
return nullptr;
}
class ClientChannel
{
int _id;
@@ -80,29 +93,29 @@ public:
}
else {
bool first_update = true;
auto cb = [observer, first_update](EventType event_type, const StreamUpdate* changes, int change_count) mutable {
auto cb = [observer, first_update, method, qualifier](EventType event_type, const StreamUpdate* changes, int change_count) mutable {
if (event_type == EventType::TABLE_UPDATED) {
std::vector<std::shared_ptr<const T>> added_items;
std::vector<std::shared_ptr<const T>> added_items, changed_items;
std::vector<int> added_keys,
removed_keys;
changed_keys,
removed_keys;
for (size_t x = 0; x < change_count; ++x) {
const StreamUpdate& change = changes[x];
if (change.type != StreamUpdate::REMOVE) {
added_keys.reserve(change_count);
added_items.reserve(change_count);
added_keys.push_back(change.key);
google::protobuf::io::ArrayInputStream stream(change.buffer, change.buffer_len);
auto item = std::make_shared<T>();
if (item->ParseFromZeroCopyStream(&stream)) {
added_items.push_back(move(item));
}
}
if (change.type != StreamUpdate::CREATE) {
removed_keys.reserve(change_count);
removed_keys.push_back(change.key);
switch(change.type) {
case StreamUpdate::REMOVE:
removed_keys.push_back(change.key);
break;
case StreamUpdate::REPLACE:
changed_items.push_back(deserialize<T>(change.buffer, change.buffer_len));
changed_keys.push_back(change.key);
break;
case StreamUpdate::CREATE:
added_items.push_back(deserialize<T>(change.buffer, change.buffer_len));
added_keys.push_back(change.key);
break;
}
}
observer.on_next(RawUpdate<std::shared_ptr<const T>>(added_items, added_keys, removed_keys));
observer.on_next(RawUpdate<std::shared_ptr<const T>>(added_items, added_keys, changed_items, changed_keys, removed_keys));
}
else if (event_type == EventType::TABLE_CREATED) {
if (!first_update) {
+12 -2
View File
@@ -6,10 +6,16 @@ namespace api {
template <typename T>
struct RawUpdate {
RawUpdate(std::vector<T>& added_items, std::vector<int>& added_keys, std::vector<int>& removed_keys) :
RawUpdate(std::vector<T>& added_items,
std::vector<int>& added_keys,
std::vector<T>& changed_items,
std::vector<int>& changed_keys,
std::vector<int>& removed_keys) :
is_reset(false),
added_items(std::move(added_items)),
added_keys(std::move(added_keys)),
changed_items(std::move(changed_items)),
changed_keys(std::move(changed_keys)),
removed_keys(std::move(removed_keys))
{}
@@ -20,12 +26,16 @@ namespace api {
bool is_reset;
std::vector<T> added_items;
std::vector<int> added_keys;
std::vector<T> changed_items;
std::vector<int> changed_keys;
std::vector<int> removed_keys;
private:
RawUpdate() :
is_reset(true),
added_items(), added_keys(), removed_keys()
added_items(), added_keys(), changed_items(), changed_keys(), removed_keys()
{}
};
+29 -3
View File
@@ -9,12 +9,21 @@
namespace bts {
namespace api {
template <typename T>
class Change {
T _old, _new;
public:
Change(const T& old_value, const T& new_value) : _old(old_value), _new(new_value) { }
const T& previous() const { return _old; }
const T& current() const { return _new; }
};
template <typename T, typename TKey, typename ServiceType>
struct Update {
Update(std::vector<T>& all, std::vector<T>& added, std::vector<T>& removed) :
Update(std::vector<T>& all, std::vector<T>& added, std::vector<Change<T>> changed, std::vector<T>& removed) :
_all_items(std::move(all)),
_added_items(std::move(added)),
_changed_items(std::move(changed)),
_removed_items(std::move(removed))
{}
@@ -22,17 +31,20 @@ namespace api {
Update(Update&& other) :
_all_items(std::move(other._all_items)),
_added_items(std::move(other._added_items)),
_changed_items(std::move(other._changed_items)),
_removed_items(std::move(other._removed_items))
{}
TKey key(const T& t) const { return ServiceType::key(t); }
const std::vector<T>& all() const { return _all_items; }
const std::vector<T>& added() const { return _added_items; }
const std::vector<Change<T>>& changed() const { return _changed_items; }
const std::vector<T>& removed() const { return _removed_items; }
private:
std::vector<T> _all_items;
std::vector<T> _added_items;
std::vector<Change<T>> _changed_items;
std::vector<T> _removed_items;
};
@@ -49,9 +61,12 @@ namespace api {
}
state->clear();
std::vector<T> all, added;
return Update<T, TKey, ServiceType>(all, added, removed);
std::vector<Change<T>> changed;
return Update<T, TKey, ServiceType>(all, added, changed, removed);
}
else {
//
// Remove
std::vector<T> removed;
removed.reserve(raw.removed_keys.size());
for (int key : raw.removed_keys) {
@@ -61,6 +76,8 @@ namespace api {
state->erase(iter);
}
}
//
// Add
for (size_t x = 0; x < raw.added_keys.size(); ++x) {
(*state)[raw.added_keys[x]] = raw.added_items[x];
}
@@ -69,7 +86,16 @@ namespace api {
for (const auto& item : *state) {
all.push_back(item.second);
}
return Update<T, TKey, ServiceType>(all, raw.added_items, removed);
//
// Change
std::vector<Change<T>> changed;
changed.reserve(raw.changed_keys.size());
for (size_t x = 0; x < raw.changed_keys.size(); ++x) {
changed.push_back(Change<T>{(*state)[raw.changed_keys[x]], raw.changed_items[x]});
(*state)[raw.changed_keys[x]] = raw.changed_items[x];
}
return Update<T, TKey, ServiceType>(all, raw.added_items, changed, removed);
}
});
};
+32
View File
@@ -0,0 +1,32 @@
syntax="proto3";
package bts.md;
option csharp_namespace="bts.md";
option cc_enable_arenas=true;
import "lib/api/apioptions.proto";
import "lib/api/apicore.proto";
message OrderPIQ {
uint32 order_id = 1 [(bts.api.key)=true];
uint32 position = 2;
}
message OrderPosition {
uint64 exchange_id = 1;
uint32 order_id = 2;
float price = 10;
int32 size = 11;
}
message PositionData {
uint32 security_id = 1;
repeated OrderPosition bid = 10;
repeated OrderPosition ask = 20;
}
message MBOSecurityQuery {
uint32 security_id = 1;
}
service MBOService {
rpc piq (bts.api.VoidMessage) returns (stream OrderPIQ);
rpc get_orders_for_security (MBOSecurityQuery) returns (PositionData);
}
@@ -17,7 +17,6 @@ message ExpirationNameList {
message ExpirationNameListReply {
repeated ExpirationNameList securities = 2;
}
message SecurityKeyList {
repeated uint32 keys = 1;
}
@@ -28,6 +27,5 @@ service MarketDataRelay {
rpc request_products(bts.api.VoidMessage) returns (ProductListReply);
rpc request_names(bts.api.VoidMessage) returns (ExpirationNameListReply);
rpc get_securities(SecurityKeyList) returns (bts.api.SecurityList);
}
+67 -30
View File
@@ -1,11 +1,71 @@
SymbolLists
{
value
SymbolList {
value {
# This key is here for backwards compatability ONLY.
# If you need to update symbols you should update SymbolLists (plural)
symbol: "6B"
symbol: "6E"
symbol: "6J"
symbol: "6P"
symbol: "ARE"
symbol: "BK"
symbol: "BOB"
symbol: "BUB"
symbol: "BZ"
symbol: "CL"
symbol: "EH"
symbol: "ES"
symbol: "FYT"
symbol: "FOB"
symbol: "GC"
symbol: "GE"
symbol: "GLI"
symbol: "HE"
symbol: "HG"
symbol: "HO"
symbol: "KE"
symbol: "LE"
symbol: "NB"
symbol: "NBY"
symbol: "NOB"
symbol: "NON"
symbol: "NG"
symbol: "NQ"
symbol: "NUB"
symbol: "RB"
symbol: "SI"
symbol: "SOM"
symbol: "SR1"
symbol: "SR3"
symbol: "TN"
symbol: "TUF"
symbol: "TUT"
symbol: "UB"
symbol: "XC"
symbol: "XK"
symbol: "YM"
symbol: "ZB"
symbol: "ZC"
symbol: "ZCW"
symbol: "ZF"
symbol: "ZL"
symbol: "ZM"
symbol: "ZN"
symbol: "ZO"
symbol: "ZQ"
symbol: "ZR"
symbol: "ZS"
symbol: "ZT"
symbol: "ZW"
}
}
SymbolLists
{
value
{
list
{
key: "ICE"
# symbol: "BRN"
# symbol: "WBS"
# symbol: "OJ"
@@ -13,31 +73,8 @@ SymbolLists
list
{
key: "GLBX"
symbol: "HE"
symbol: "LE"
symbol: "CL"
symbol: "RB"
symbol: "HO"
symbol: "BZ"
symbol: "NG"
symbol: "GC"
symbol: "SI"
symbol: "HG"
symbol: "ZW"
symbol: "ZC"
symbol: "ZS"
symbol: "ZM"
symbol: "ZL"
symbol: "ZO"
symbol: "ZR"
symbol: "ZB"
symbol: "ZN"
symbol: "ZF"
symbol: "ZT"
symbol: "UB"
symbol: "GE"
}
symbol: @SymbolList.symbol
}
}
}
@@ -74,6 +111,6 @@ Symbols {
ice {
future: @SymbolLists.list[ICE].symbol
spread: @SymbolLists.list[ICE].symbol
}
}
}
}
+2 -1
View File
@@ -123,7 +123,8 @@ message CMEOptions
optional bool load_security_db = 33 [default = true];
reserved 34;
// optional BookType book_type = 34 [default = BOOKTYPE_CONSOLIDATED];
// optional BookType book_type = 34 [default = BOOKTYPE_CONSOLIDATED];
optional bool validate_mbo = 35 [default = false];
}
optional string onixs_license = 1 [default = "{default}"];
+6
View File
@@ -34,4 +34,10 @@ message SoundNotifications {
message VolumeAtPrice{
optional bool enabled = 1 [default = true];
}
message MBOStats {
optional bool enabled = 1 [default = false];
}
message PlacementInQueue {
optional bool enabled = 1 [default = true];
}
+1 -1
View File
@@ -160,7 +160,7 @@ void run_periodic_dumper(const std::set<UserSymbol>& symbols, uint32_t seq_mod,
threading::configure_current_thread(thread_name);
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols);
MDConsumer consumer(cache, nullptr, nullptr, &symbols);
// read all securities into a vector
std::vector<Entry> entries;
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "lib/utils/ValueWrapper.h"
namespace bts {
namespace md {
using ExchangeOrderKey = utils::ValueWrapper<struct ExchangeOrderKeyTag,uint64_t>;
using SparkOrderKey = utils::ValueWrapper<struct SparkOrderKeyTag,uint64_t>;
} }
+10 -2
View File
@@ -54,9 +54,18 @@ class IntPrice {
return _mantissa < other._mantissa;
}
else {
return price() < other.price();
// avoiding conversion to double is 4-5% faster
// return price() < other.price();
auto delta = *this - other;
return delta.mantissa() < 0;
}
}
bool operator <=(const IntPrice& other) {
return *this == other || *this < other;
}
bool operator >=(const IntPrice& other) {
return !(*this < other);
}
IntPrice operator - (const IntPrice& o) const {
return op(o, [](int64_t m1, int64_t m2) { return m1 - m2; });
}
@@ -169,7 +178,6 @@ inline md::IntPrice round_to_quote_price(const IntPrice& tick, const IntPrice& p
return price;
}
inline std::ostream& operator<<(std::ostream& os, const IntPrice& p) {
os << p.price();
return os;
+270
View File
@@ -0,0 +1,270 @@
#include "MBOProcessor.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/ConcurrentPool.h"
#include "lib/config/config.h"
#include "lib/config/protos/toggles.pb.h"
#include <map>
#include <functional>
#include <vector>
#include <sstream>
#include <tbb/concurrent_hash_map.h>
namespace bts {
namespace md {
struct SecurityKeyHashCompare {
static size_t hash(const SecurityKey& k) {
return k.value();
}
static bool equal(const SecurityKey& a, const SecurityKey& b) {
return a.value() == b.value();
}
};
void print_pool_stats(const utils::ConcurrentPool<MBOSnapshot>& pool) {
size_t count = 0;
std::vector<int> bid_sz,ask_sz;
pool.unsafe_iterate([&](const MBOSnapshot& s) {
if (s.bids.size() > 0 || s.asks.size() > 0) {
count++;
bid_sz.push_back(s.bids.size());
ask_sz.push_back(s.asks.size());
}
});
if (count == 0) return;
std::sort(bid_sz.begin(), bid_sz.end());
std::sort(ask_sz.begin(), ask_sz.end());
size_t bid_tot =0, ask_tot = 0;
for(size_t i : bid_sz) { bid_tot += i; }
for(size_t i : ask_sz) { ask_tot += i; }
std::stringstream bid_percentiles, ask_percentiles;
for (double x : { 0.5, 0.75, 0.9, 0.95, 0.99 }) {
size_t index = count * x;
bid_percentiles << bid_sz[index] << " ";
ask_percentiles << ask_sz[index] << " ";
}
bid_percentiles << bid_sz[count-1] << " ";
ask_percentiles << ask_sz[count-1] << " ";
LOG(md,Info,
"MBO statistics: count: {} - %[50,75,90,95,99,100] bid: [{}] - ask: [{}]",
count, bid_percentiles.str(), ask_percentiles.str());
}
typedef tbb::concurrent_hash_map<SecurityKey, MBOSnapshot*, SecurityKeyHashCompare> MapType;
struct MBOProcessorImpl {
utils::ConcurrentPool<MBOSnapshot> pool;
MapType snapshots;
utils::Dispatcher& dispatcher;
utils::DispatcherTimer timer;
MBOProcessorImpl(utils::Dispatcher& d) :
pool(2048,512),
snapshots(),
dispatcher(d),
timer(d, 600*1000, [&]() { print_pool_stats(pool); })
{
if (config::get<config::toggles::MBOStats>().enabled()) {
timer.start();
}
}
};
//
// Processor
MBOProcessor::MBOProcessor(utils::Dispatcher* d) :
_me(std::make_shared<MBOProcessorImpl>(d == nullptr ? utils::Dispatcher::background() : *d)),
_producer(_me),
_consumer(_me)
{}
MBOWriter& MBOProcessor::writer() { return _producer;}
MBOReader& MBOProcessor::reader() { return _consumer; }
//
// Writer
MBOWriter::MBOWriter(std::shared_ptr<MBOProcessorImpl>& me): _me(me) { }
MBOWriter::~MBOWriter() { }
MBOSnapshot* MBOWriter::allocate_snapshot(size_t, size_t) {
return _me->pool.allocate();
}
void MBOWriter::release_snapshot(MBOSnapshot** s) {
_me->pool.release(s);
}
void MBOWriter::publish_snapshot(SecurityKey key, MBOSnapshot** snapshot) {
bts_assert(*snapshot != nullptr);
MapType::accessor accessor;
_me->snapshots.insert(accessor, key);
std::swap(accessor->second, *snapshot);
if (*snapshot != nullptr) {
// Note: relase() must happen inside accessor lifetime to ensure correctness
_me->pool.release(snapshot);
}
}
bool MBOWriter::validate_snapshot(uint32_t exchid, SecurityKey key, MBOSnapshot* control) {
// bts_assert_msg(false, "TO USE THIS VALIDATION YOU MUST MAKE enqueue_incremental SYNCRONOUS");
#define REQUIRE(a,b,y) \
if ((a) != (b)) { \
LOG(md,Error, "Validation error exch:{} sec:{} {}!={} other:{}", exchid, key.value(), a, b, y); \
return false; \
}
// retrieve current snapshot and check they are equal
_me->dispatcher.post([this,exchid,key,control]() {
MBOReader reader(_me);
auto snap = reader.acquire_snapshot(key);
if (snap) {
REQUIRE (snap->bids.size(), control->bids.size(), '!');
REQUIRE (snap->asks.size(), control->asks.size(), '!');
for (size_t x = 0; x < snap->bids.size(); ++x) {
REQUIRE (snap->bids[x].id, control->bids[x].id, x);
REQUIRE (snap->bids[x].size, control->bids[x].size, x);
}
for (size_t x = 0; x < snap->asks.size(); ++x) {
REQUIRE (snap->asks[x].id, control->asks[x].id, x);
REQUIRE (snap->asks[x].size, control->asks[x].size, x);
}
}
return true;
});
#undef REQUIRE
return true;
}
void MBOWriter::enqueue_incremental(MBOWorkingState& state, const inc::Update& update) {
LOG(md,Diag, "sec:{} : {}", state.handle.key(), update);
_me->dispatcher.post([this,&state,update]() {
// fast part
apply_incremental(state,update);
// slow part
publish_incremental(state);
});
}
void MBOWriter::publish_incremental(MBOWorkingState& state) {
// determine if we need to publish
auto key = SecurityKey(state.handle.key());
if (key.value() != invalid_security_key) {
auto snap = allocate_snapshot(state.bid.size(), state.ask.size());
copy_to_snapshot(state, snap);
publish_snapshot(key, &snap);
}
}
void copy_to_snapshot(MBOWorkingState& state, MBOSnapshot* dest) {
if (dest == nullptr) return;
// copy working state to snapshot
dest->updated = state.updated;
auto type_map = [](auto pair) {
return SnapshotOrder { pair.second.id, std::get<IntPrice>(pair.first), pair.second.size };
};
std::transform(state.bid.begin(), state.bid.end(), std::back_inserter(dest->bids), type_map);
std::transform(state.ask.begin(), state.ask.end(), std::back_inserter(dest->asks), type_map);
}
template <class C>
void apply_incremental(uint32_t security_key, C& collection, std::unordered_map<ExchangeOrderKey, MBOWorkingState::key_t>& index, const inc::Update& update) {
auto key = std::make_tuple(update.price, update.priority);
switch(update.action) {
case inc::UpdateAction::New:
collection.insert(std::make_pair(key, MBOWorkingState::Data { update.order_id, update.size }));
index[update.order_id] = key;
break;
case inc::UpdateAction::Change: {
// look up old key
auto r = index.find(update.order_id);
if (r != index.end()) {
auto oldkey = r->second;
collection.erase(oldkey);
index.erase(r);
}
else {
LOG(md,Warn, "MBO Update w/out Add for: sec:{} {}", security_key, update);
}
collection.insert(std::make_pair(key, MBOWorkingState::Data { update.order_id, update.size }));
index[update.order_id] = key;
}
break;
case inc::UpdateAction::Delete:
collection.erase(key);
index.erase(update.order_id);
break;
}
}
void apply_incremental(MBOWorkingState& state, const inc::Update& update) {
// apply the update to the working state
if (update.type == inc::UpdateType::Bid) {
apply_incremental(state.handle.key(), state.bid, state.orderid_index, update);
}
else {
apply_incremental(state.handle.key(),state.ask, state.orderid_index, update);
}
}
//
// Reader
MBOReader::MBOReader(std::shared_ptr<MBOProcessorImpl>& me): _me(me) { }
MBOReader::~MBOReader() { }
MBOSnapshot* MBOReader::acquire_snapshot(SecurityKey key) const {
// get a result eagerly so we don't have to do it while accessor has the entry locked
auto result = _me->pool.allocate();
MapType::const_accessor accessor;
if (_me->snapshots.find(accessor, key) && accessor->second != nullptr) {
result->copy_from(*accessor->second);
}
else {
release_snapshot(&result);
}
return result;
}
void MBOReader::release_snapshot(MBOSnapshot**s) const {
_me->pool.release(s);
}
}}
namespace std {
using namespace bts;
using namespace bts::md;
ostream& operator<<(ostream& os, const inc::Update& update) {
os << "Update: { "
<< "id:" << update.order_id.value() << " "
<< "pri:" << update.priority << " "
<< update.price.price() << " "
<< update.size << " ";
switch(update.type) {
case inc::UpdateType::Bid:
os << "B ";
break;
case inc::UpdateType::Ask:
os << "A ";
break;
}
switch(update.action) {
case inc::UpdateAction::New:
os << "+";
break;
case inc::UpdateAction::Change:
os << "C";
break;
case inc::UpdateAction::Delete:
os << "-";
break;
}
os << "}";
return os;
}
}
+186
View File
@@ -0,0 +1,186 @@
#pragma once
#include "lib/domain_model/SecurityKey.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/Dispatcher.h"
#include "IntPrice.h"
#include "ExchangeOrderKey.h"
#include "MDProducerHandle.h"
#ifndef _WIN32
#include "google/btree/btree_map.h"
#endif
#include <vector>
#include <memory>
#include <map>
#include <tuple>
namespace bts {
namespace md {
struct SnapshotOrder {
ExchangeOrderKey id;
IntPrice price;
int32_t size;
};
struct MBOSnapshot {
MBOSnapshot() = default;
MBOSnapshot(const MBOSnapshot&) = delete;
MBOSnapshot(MBOSnapshot&& other) = delete;
utils::TimeStamp updated;
std::vector<SnapshotOrder> bids;
std::vector<SnapshotOrder> asks;
void copy_from(const MBOSnapshot& src) {
bids = src.bids;
asks = src.asks;
updated = src.updated;
}
void reset() {
updated = utils::TimeStamp();
constexpr int max = 256;
if (bids.size() > max) {
bids.resize(max);
}
if (asks.size() > max) {
asks.resize(max);
}
bids.clear();
asks.clear();
}
};
namespace inc {
// according to this link MBO book only has these updates types
// https://www.cmegroup.com/confluence/display/EPICSANDBOX/MDP+3.0+-+Market+Data+Snapshot+Full+Refresh#MDP3.0-MarketDataSnapshotFullRefresh-MarketbyOrder(MBO)DataBlock
enum class UpdateType {
Bid = 0,
Ask = 1,
// ImpliedBid, // 'E'
// ImpliedAsk, // 'F'
// BookReset // 'J'
};
// OrderUpdateAction=37708
enum class UpdateAction {
New = 0,
Change = 1,
Delete = 2
};
typedef uint64_t Priority;
struct Update {
ExchangeOrderKey order_id;
Priority priority;
IntPrice price;
int32_t size;
UpdateType type;
UpdateAction action;
};
}
struct MBOWorkingState {
typedef std::tuple<IntPrice,inc::Priority> key_t;
struct Data {
ExchangeOrderKey id;
int32_t size;
};
struct LessBid {
bool operator()(const key_t& left, const key_t& right) const {
const auto& left_price = std::get<IntPrice>(left);
const auto& right_price = std::get<IntPrice>(right);
if (left_price == right_price) {
return
std::get<inc::Priority>(left) <
std::get<inc::Priority>(right);
}
return !(left_price < right_price);
}
};
MBOWorkingState(MDProducerHandle& h) : handle(h), updated(), bid(),ask() {}
MDProducerHandle handle;
utils::TimeStamp updated;
// Used for 'change' messages which update price
std::unordered_map<ExchangeOrderKey, key_t> orderid_index;
#ifdef _WIN32
std::map<key_t,Data,LessBid> bid;
std::map<key_t,Data> ask;
#else
btree::btree_map<key_t,Data,LessBid> bid;
btree::btree_map<key_t,Data> ask;
#endif
};
void copy_to_snapshot(MBOWorkingState& state, MBOSnapshot* destination);
void apply_incremental(MBOWorkingState&, const inc::Update&);
template <class V>
void apply_incremental(MBOWorkingState& state, const V& vec) {
for (const auto& update : vec) {
apply_incremental(state, update);
}
}
struct MBOProcessorImpl;
class MBOProcessor;
class MBOWriter {
std::shared_ptr<MBOProcessorImpl> _me;
friend class MBOProcessor;
MBOWriter(std::shared_ptr<MBOProcessorImpl>&);
~MBOWriter();
public:
//
// Snapshot api
MBOSnapshot* allocate_snapshot(size_t bid_count=0, size_t ask_count=0);
void release_snapshot(MBOSnapshot**);
void publish_snapshot(SecurityKey, MBOSnapshot**);
bool validate_snapshot(uint32_t, SecurityKey, MBOSnapshot*);
//
// Incremental api
// Enqueue the update to the mbo work queue.
void enqueue_incremental(MBOWorkingState&, const inc::Update&);
// Update working state and publish directly.
void publish_incremental(MBOWorkingState&);
};
class MBOReader {
std::shared_ptr<MBOProcessorImpl> _me;
friend class MBOProcessor;
friend class MBOWriter;
MBOReader(std::shared_ptr<MBOProcessorImpl>&);
~MBOReader();
public:
MBOSnapshot* acquire_snapshot(SecurityKey key) const;
void release_snapshot(MBOSnapshot**) const;
};
class MBOProcessor {
std::shared_ptr<MBOProcessorImpl> _me;
MBOWriter _producer;
MBOReader _consumer;
public:
MBOProcessor(utils::Dispatcher* _=nullptr);
MBOWriter& writer();
MBOReader& reader();
};
}}
namespace std {
std::ostream& operator<<(std::ostream& os, const bts::md::inc::Update&);
}
+340
View File
@@ -0,0 +1,340 @@
#ifndef _WIN32
#include "MBOServiceServer.h"
#include "lib/api/services/OrderService.client.pb.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/TimeStamp.h"
#include "MDClient.h"
#include "ExchangeOrderKey.h"
#include "MBOProcessor.h"
#include "lib/utils/LockBox.h"
#include <tuple>
#include <vector>
#include <unordered_map>
using namespace bts::api::orderman;
namespace bts {
namespace md {
//
// Simple class to handle MD recovery and maintain an index of SecurityKey=>MDConsumerHandle
class HandleIndex {
MDClient& _client;
int _sub;
std::optional<MDConsumer> _consumer;
public:
utils::LockBox<std::unordered_map<SecurityKey,MDConsumerHandle>> index;
HandleIndex(md::MDClient& client):
_client(client),
_sub(0),
_consumer(std::nullopt),
index() {
_sub = _client.on_consumer().subscribe([=](const MDConsumer& c) { on_consumer(c); });
on_consumer(_client.current_consumer());
}
~HandleIndex() { _client.on_consumer().unsubscribe(_sub); }
private:
void on_consumer(const MDConsumer& c) {
MDConsumer temp(c);
if (temp.all_heartbeats_ok()) {
auto existing = _consumer ? _consumer->id() : 0;
LOG(md,Info, "Updateing MBOService MDConsumer. Old {} new {}", existing, temp.id());
// clear the index
index.lock([](auto& index) { index.clear(); });
// Swap to new consumer
if (_consumer) {
_consumer->swap(temp);
}
else { _consumer = temp; }
// Subscribe to on_security
auto id = _consumer->id();
_consumer->on_security([this,id](const SecurityPtr&, MDConsumerHandle handle) {
if (_consumer->id() == id) {
index.lock([handle](auto& index) {
index[SecurityKey(handle.key())] = handle;
});
}
});
}
}
};
class ActiveOrder {
SecurityKey _security;
SparkOrderKey _id;
ExchangeOrderKey _ex_id;
int32_t _position;
public:
// empty constructor needed for unordered map operator[]
// ActiveOrder(): _security(), _id(), _ex_id(), _position(-1) { bts_assert(false); }
ActiveOrder(SecurityKey sec, SparkOrderKey s, ExchangeOrderKey ex): _security(sec), _id(s), _ex_id(ex), _position(-1) {}
ActiveOrder(SecurityKey sec, SparkOrderKey s, const std::string& ex): _security(sec), _id(s), _ex_id(), _position(-1) {
_ex_id = ExchangeOrderKey(std::stoul(ex));
}
SecurityKey security_id() const { return _security; }
SparkOrderKey id() const { return _id; }
ExchangeOrderKey exchange_id() const { return _ex_id; }
int32_t& position() { return _position; };
int32_t position() const { return _position; };
bool set_position(int32_t pos) {
if (pos != _position) {
_position = pos;
return true;
}
return false;
}
OrderPIQ to_piq() const {
OrderPIQ result;
result.set_order_id(_id.value());
result.set_position(_position);
return result;
}
};
//
// MBOService
struct MBOServiceImpl {
MBOServiceImpl(
api::ServiceContext& ctx,
utils::Dispatcher& d,
md::MDClient& c,
MBOServiceBase::PiqPublisher&& pub
):
dispatcher(d),
client(c),
orders(ctx.locate<api::orderman::OrderService>()),
handle_index(c),
publish(pub),
timer(d, 200, [&]() {timer_tick();}),
orders_sub()
{}
utils::Dispatcher& dispatcher;
md::MDClient& client;
OrderService orders;
HandleIndex handle_index;
MBOServiceBase::PiqPublisher publish;
utils::DispatcherTimer timer;
rxcpp::composite_subscription orders_sub;
std::unordered_map<SparkOrderKey, ActiveOrder> active;
void timer_tick();
void flush();
};
///////////////////////
std::optional<uint32_t> search_orders(const ExchangeOrderKey oid, const std::vector<SnapshotOrder>& orders) {
IntPrice price = {};
size_t position = 0;
for (size_t x = 0; x < orders.size(); ++x) {
const auto& oo = orders[x];
if (oo.id == oid) {
if (oo.price == price) return { position };
else return 0 ;
}
else if (price == oo.price) {
position += 1;
}
else {
price = oo.price;
position = 1;
}
}
return std::nullopt;
}
bool update_position(const HandleIndex& handles, ActiveOrder& order) {
const auto& index = handles.index;
auto handle = index.lock([id=order.security_id()](const auto& index) {
auto iter = index.find(id);
return iter != index.end() ? iter->second : MDConsumerHandle {};
});
if (handle.is_initialized()) {
auto mbo = handle.get_mbo();
if (mbo) {
auto search_result = search_orders(order.exchange_id(), mbo->bids);
if (!search_result) {
search_result = search_orders(order.exchange_id(), mbo->asks);
}
if (search_result) {
return order.set_position(search_result.value());
}
}
}
return false;
}
void MBOServiceImpl::timer_tick() {
//Dispatcher thread
std::vector<OrderPIQ> rows;
for (auto& pair : active) {
if (update_position(handle_index, pair.second)) {
rows.push_back(pair.second.to_piq());
}
}
if (!rows.empty()) {
publish(nullptr, &rows, nullptr);
}
}
const NativeOrderState* native_order_state(const OrderRecord& record) {
switch(record.state().custom_case()) {
case OrderState::kMarket:
return &record.state().market().native_state();
case OrderState::kLimit:
return &record.state().limit().native_state();
case OrderState::kStop:
return &record.state().stop().native_state();
case OrderState::kStopLimit:
return &record.state().stop_limit().native_state();
default:
return nullptr;
}
}
bool is_active_exchange_order(const OrderRecord& record) {
return record.state().status() == ORDER_STATUS_WORKING &&
native_order_state(record) != nullptr;
}
void try_remove(MBOServiceImpl& me, std::set<uint32_t>& to_remove, const OrderRecord& record) {
auto key = SparkOrderKey(record.key());
auto iter = me.active.find(key);
if (iter != me.active.end()) {
me.active.erase(key);
to_remove.insert(key.value());
}
}
void try_add(MBOServiceImpl& me, std::vector<OrderPIQ>& to_add, const OrderRecord& record) {
if (is_active_exchange_order(record)) {
auto native = native_order_state(record);
bts_assert(native);
ActiveOrder order(
SecurityKey(record.def().security_key()),
SparkOrderKey(record.key()),
native->exchange_order_id());
update_position(me.handle_index, order);
to_add.push_back(order.to_piq());
me.active.insert(std::make_pair(order.id(), order));
}
}
MBOServiceServer::MBOServiceServer(
api::ServiceContext& ctx,
utils::Dispatcher& d,
md::MDClient& c
) :
MBOServiceBase(ctx),
_me(new MBOServiceImpl(ctx,d,c,make_piq_publisher()))
{
// subscribe to mdclient ✔
// make publishers ✔
// subscribe to orders => stash active orders dict
_me->orders_sub =
_me->orders.subscribe_orders().subscribe([=](const auto& update) {
// api thread
const auto& added = update.added();
const auto& changed = update.changed();
const auto& removed = update.removed();
_me->dispatcher.post([this,added,changed,removed]() {
// dispatcher thread
std::vector<OrderPIQ> to_add, to_change;
std::set<uint32_t> to_remove;
// Remove
for(const auto& r : removed) {
// If it was active remove it and add to to_remove
try_remove(*_me, to_remove, *r);
}
// Added
for(const auto& add : added) {
// If active add look up the position and add to the state
try_add(*_me, to_add, *add);
}
// Changed
for(const auto& ch : changed) {
// new order is active and old is not look up piq and add to state
bool is_active = is_active_exchange_order(*ch.current());
bool was_active = is_active_exchange_order(*ch.previous());
if (is_active && !was_active) {
try_add(*_me, to_add, *ch.current());
}
else if (was_active && !is_active) {
try_remove(*_me, to_remove, *ch.current());
}
// is_active && was_active => don't care
// !is_active && !was_active => don't care
}
_me->publish(&to_add, &to_change, &to_remove);
if (!_me->timer.running()) _me->timer.start();
});
});
}
MBOServiceServer::~MBOServiceServer() { }
void MBOServiceServer::get_orders_for_security(const MBOSecurityQuery& request, const api::Response<PositionData>& reply) {
PositionData response;
response.set_security_id(request.security_id());
auto handle = _me->handle_index.index.lock([id=request.security_id()](const auto& index) {
auto iter = index.find(SecurityKey(id));
return iter != index.end() ? iter->second : MDConsumerHandle {};
});
std::unordered_map<ExchangeOrderKey, SparkOrderKey> reverse;
for (const auto& pair : _me->active) {
reverse.insert(std::make_pair(pair.second.exchange_id(), pair.first));
}
if(handle.is_initialized()) {
auto mbo = handle.get_mbo();
if (mbo) {
for (const auto& o : mbo->bids) {
auto* entry = response.add_bid();
entry->set_exchange_id(o.id.value());
entry->set_price(o.price.price());
entry->set_size(o.size);
auto iter = reverse.find(o.id);
if (iter != reverse.end()) {
entry->set_order_id(iter->second.value());
}
}
for (const auto& o : mbo->asks) {
auto* entry = response.add_ask();
entry->set_exchange_id(o.id.value());
entry->set_price(o.price.price());
entry->set_size(o.size);
auto iter = reverse.find(o.id);
if (iter != reverse.end()) {
entry->set_order_id(iter->second.value());
}
}
}
}
reply(response);
}
}} //namespace
#endif
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/MBOService.server.pb.h"
#include <memory>
namespace bts {
namespace utils { class Dispatcher; }
namespace api { namespace orderman { class OrderService; } }
namespace md {
class MDClient;
struct MBOServiceImpl;
class MBOServiceServer : public MBOServiceBase {
std::unique_ptr<MBOServiceImpl> _me;
public:
MBOServiceServer(
api::ServiceContext& ctx,
utils::Dispatcher&,
md::MDClient&);
~MBOServiceServer();
void get_orders_for_security(const MBOSecurityQuery&, const api::Response<PositionData>&) override;
};
}} //namespace
+10 -5
View File
@@ -3,6 +3,7 @@
#include "SecurityCache.h"
#include "SecurityOracle.h"
#include "lib/utils/TimeStamp.h"
#include "MBOProcessor.h"
#include <set>
namespace bts {
@@ -15,19 +16,22 @@ struct MDClientImpl{
std::unique_ptr<utils::DispatcherTimer> _security_timer;
std::unique_ptr<utils::DispatcherTimer> _recovery_timer;
std::set<UserSymbol> _symbols;
MBOReader* _mbo;
MDConsumer _consumer;
utils::Event<MDConsumer> _event;
MDClientImpl(
utils::Dispatcher* d,
api::ISyntheticSecurityService* s,
MBOReader* mbo,
const std::set<UserSymbol>* symbols):
_dispatcher(d),
_ss(s),
_security_timer(),
_recovery_timer(),
_symbols(symbols ? *symbols : get_system_md_symbols()),
_consumer(_cache, _ss, &_symbols),
_mbo(mbo),
_consumer(_cache, _ss, mbo, &_symbols),
_event()
{
if (d) {
@@ -42,7 +46,7 @@ struct MDClientImpl{
void check_heartbeats() {
if (!_consumer.all_heartbeats_ok()) {
MDConsumer temp(_cache,_ss);
MDConsumer temp(_cache,_ss,_mbo,&_symbols);
if (temp.all_heartbeats_ok()) {
_consumer.swap(temp);
LOG(md,Info, "Recovering from bad MD. Old consumer {} new consumer {}", _consumer.id(), temp.id());
@@ -71,11 +75,12 @@ struct MDClientImpl{
MDClient::MDClient(
utils::Dispatcher* d,
api::ISyntheticSecurityService* s,
MBOReader* mbo,
const std::set<UserSymbol>* symbols) :
_self(new MDClientImpl(d,s, symbols))
_self(new MDClientImpl(d,s,mbo,symbols))
{ }
MDClient::~MDClient() {
}
MDClient::~MDClient() { }
size_t MDClient::scan_securities() { return _self->scan_securities(); }
void MDClient::check_heartbeats() { return _self->check_heartbeats(); }
+3
View File
@@ -11,7 +11,9 @@ namespace bts {
namespace md {
class SecurityOracle;
class MBOReader;
struct MDClientImpl;
class MDClient {
std::unique_ptr<MDClientImpl> _self;
@@ -20,6 +22,7 @@ public:
MDClient(
utils::Dispatcher* d=nullptr,
api::ISyntheticSecurityService* ss=nullptr,
MBOReader* mbo=nullptr,
const std::set<UserSymbol>* s=nullptr);
~MDClient();
+15 -4
View File
@@ -3,6 +3,7 @@
#include "MemorySegmentUtils.h"
#include "PlatformLoader.h"
#include "SecurityCache.h"
#include "MBOProcessor.h"
#include "lib/config/config.h"
#include "lib/utils/SharedList.h"
#include "lib/utils/WaitFreeQueue.h"
@@ -535,6 +536,7 @@ class MDConsumerImpl {
Mutex _workmutex;
vector<shared_ptr<MDConsumerInst>> _instances;
std::shared_ptr<SyntheticConsumerInst> _sci;
MBOReader* _mbo;
map<security_key, shared_ptr<MDConsumerHandle>> _deferred_handles;
vector<SecurityCallback> _callbacks;
mutable SecurityCache _cache;
@@ -545,10 +547,12 @@ public:
const set<UserSymbol>& symbols,
SecurityCache& cache,
api::ISyntheticSecurityService* ss,
MBOReader* mbo,
bool shared, co2::Platform platform) :
_workmutex(),
_instances(),
_sci(),
_mbo(mbo),
_deferred_handles(),
_callbacks(),
_cache(cache)
@@ -625,7 +629,7 @@ public:
for (auto& inst : _instances) {
if (inst->find_handle(key, &cold, &hot)) {
MDConsumerHandle handle;
handle.init(inst->cold_header(), *cold, hot);
handle.init(inst->cold_header(), *cold, hot, _mbo);
return handle;
}
}
@@ -657,9 +661,15 @@ public:
}
// Erase anything that wasn't found from the resulting map
// Set the mbo pointer on anything else
for (auto it = result.begin(); it != result.end(); ) {
if (!it->second.is_initialized()) it = result.erase(it);
else ++it;
if (!it->second.is_initialized()) {
it = result.erase(it);
}
else {
it->second.set_mbo(_mbo);
++it;
}
}
return result;
@@ -910,6 +920,7 @@ public:
MDConsumer::MDConsumer(
SecurityCache& cache,
api::ISyntheticSecurityService* ss,
MBOReader* mbo,
const std::set<UserSymbol>* symbols,
bool shared,
co2::Platform platform) : _pimpl() {
@@ -920,7 +931,7 @@ MDConsumer::MDConsumer(
symbols = &all_symbols;
}
_pimpl.reset(new MDConsumerImpl(*symbols, cache, ss, shared, platform));
_pimpl.reset(new MDConsumerImpl(*symbols, cache, ss, mbo, shared, platform));
}
MDConsumer::~MDConsumer() {}
+2
View File
@@ -26,6 +26,7 @@ class MDConsumerImpl;
class MDConsumerInst;
class MDConsumerChannel;
class SecurityCache;
class MBOReader;
class MDConsumer
{
@@ -33,6 +34,7 @@ public:
MDConsumer(
SecurityCache&,
api::ISyntheticSecurityService* ss,
MBOReader* mbo=nullptr,
const std::set<UserSymbol>* sym = nullptr,
bool shared=true,
co2::Platform platform=co2::PLATFORM_UNSET);
+14 -2
View File
@@ -1,5 +1,6 @@
#ifndef NO_CPP0X
#include "MDConsumerHandle.h"
#include "MBOProcessor.h"
#include "lib/domain_model/Security.h"
#include <boost/container/small_vector.hpp>
@@ -14,7 +15,7 @@ namespace md {
MDConsumerHandle::MDConsumerHandle() {
memset(this, 0, sizeof(MDConsumerHandle));
_type = HANDLETYPE_VOID;
static_assert(sizeof(MDConsumerHandle) <= 32, "");
static_assert(sizeof(MDConsumerHandle) <= 40, "");
}
MDConsumerHandle::MDConsumerHandle(const std::shared_ptr<const SyntheticStrategy>& def) {
memset(this, 0, sizeof(MDConsumerHandle));
@@ -33,9 +34,10 @@ MDConsumerHandle::MDConsumerHandle(const MDConsumerHandle& other) {
MDConsumerHandle::~MDConsumerHandle() {}
void MDConsumerHandle::init(const ColdHeader& cheader, const ColdEntry& centry, const void* pvhot) {
void MDConsumerHandle::init(const ColdHeader& cheader, const ColdEntry& centry, const void* pvhot, const MBOReader* mbo) {
set_cold(&cheader, &centry);
set_hot(pvhot);
set_mbo(mbo);
}
TimeStamp copy_bbo(const HotEntry& hot, PriceSize& out_bid, PriceSize& out_ask) {
@@ -371,6 +373,16 @@ CoconutSecurityStatus MDConsumerHandle::status() const {
never;
}
std::unique_ptr<MBOSnapshot,std::function<void(MBOSnapshot*)>> MDConsumerHandle::get_mbo() const {
if (_type == HANDLETYPE_DEPTH) {
MBOSnapshot* snapshot = book.mbo->acquire_snapshot(SecurityKey(key()));
if (snapshot != nullptr) {
return {snapshot,[mbo=book.mbo](auto* s) { mbo->release_snapshot(&s); }};
}
}
return nullptr;
}
}}//namespace
+25 -1
View File
@@ -13,6 +13,9 @@ namespace bts {
namespace md {
class MDConsumerHandle;
class MBOReader;
struct MBOSnapshot;
struct SyntheticStrategy {
std::unique_ptr<spark::ExecutionStrategy> strategy;
api::Security security;
@@ -141,6 +144,9 @@ TimeStamp optimistic_copy(
return t;
}
//
// MDConsumerHandle
//
// Can be an invalid handle if you request a security which has not been discovered yet.
class BTS_EXPORT MDConsumerHandle
@@ -151,7 +157,7 @@ public:
~MDConsumerHandle();
MDConsumerHandle(const MDConsumerHandle& other);
void init(const ColdHeader&, const ColdEntry&, const void* hot);
void init(const ColdHeader&, const ColdEntry&, const void* hot, const MBOReader*);
bool is_initialized() const { return _type != HANDLETYPE_VOID; }
bool is_synthetic() const { return _type == HANDLETYPE_SYNTHETIC; }
@@ -267,8 +273,11 @@ public:
return *this;
}
std::unique_ptr<MBOSnapshot,std::function<void(MBOSnapshot*)>> get_mbo() const;
private:
friend class MDConsumerInst;
friend class MDConsumerImpl;
template <class Read>
HandleReadResult get_book(Read read, TimeStamp& time, uint32_t copydepth, PriceSize* bids, PriceSize* asks) const {
@@ -326,6 +335,19 @@ private:
_type = HANDLETYPE_BBO;
}
}
void set_mbo(const MBOReader* mbo) {
switch(_type) {
case HANDLETYPE_DEPTH:
book.mbo = mbo;
break;
case HANDLETYPE_BBO:
bbo.mbo = mbo;
break;
default:
//ok
break;
}
}
enum HandleType : uint8_t {
HANDLETYPE_VOID,
@@ -337,11 +359,13 @@ private:
const HotDepthEntry* hot;
const ColdEntry* cdata;
const ColdHeader* cheader;
const MBOReader* mbo;
};
struct BBOHandle {
const HotEntry* hot;
const ColdEntry* cdata;
const ColdHeader* cheader;
const MBOReader* mbo;
};
struct SyntheticHandle {
std::shared_ptr<const SyntheticStrategy> def;
+1 -1
View File
@@ -32,7 +32,7 @@ CAPI MDConsumer* create_md_consumer(BlitUserSymbol* usersymbols, uint32_t count,
for (size_t x = 0; x < count; ++x) {
symbols.insert(UserSymbol(usersymbols[x].symbol, usersymbols[x].exchange, usersymbols[x].type));
}
return new MDConsumer(cache, nullptr/*synthetics*/, &symbols, shared);
return new MDConsumer(cache, nullptr/*synthetics*/, nullptr /*mbo*/, &symbols, shared);
}
CAPI void destroy_md_consumer(MDConsumer* consumer) {
+2 -2
View File
@@ -26,7 +26,7 @@ std::string ICEChannel::to_string() const
{
std::string mt = name;
for(const auto& x : market_types)
mt += " " + x;
mt += " " + std::to_string(x);
return mt;
}
@@ -123,4 +123,4 @@ const std::vector<ICEChannel> get_configured_ice_channels() {
}}} //namespace
#endif
#endif
+6 -7
View File
@@ -1,4 +1,4 @@
#ifndef NO_CPP0X
#ifndef _WIN32
#include "QueryServer.h"
#include "lib/config/config.h"
#include "lib/utils/Dispatcher.h"
@@ -9,6 +9,7 @@
#include "../MDConsumer.h"
#include "../MDClient.h"
#include "../PlatformLoader.h"
#include "../MBOProcessor.h"
#include "lib/logging/logging.h"
@@ -30,6 +31,7 @@
#include <memory>
#include <set>
#include <unordered_map>
#include <optional>
#include <google/protobuf/arena.h>
#include <boost/container/flat_map.hpp>
@@ -129,7 +131,6 @@ struct MDConsumerGroup {
};
void convert(PriceSize ps, PriceSizeMessage* pps) {
pps->set_mantissa(ps.mantissa());
pps->set_exponent(ps.exponent());
@@ -211,6 +212,7 @@ public:
for (auto& pair : _groups) {
pair.second->set_consumer(_consumer);
}
// _handle_index.clear();
subscribe_securities();
utils::Dispatcher::background().post([temp]() { LOG(md,Info,"QServer destroying consumer {}", temp.id()); }, 60000);
}
@@ -380,10 +382,7 @@ public:
google::protobuf::MapPair<uint32_t,std::string> v(h.handle.key(), security.ice().security_desc());
x->insert(v);
}
else
{
LOG(md,Error, "Invalid ExchangeData_case {}", security.ExchangeData_case());
}
// There is currently no ExchangeData for synthetics
}
}
}
@@ -399,7 +398,6 @@ public:
}
}
}
};
struct MDRelayServer : bts::co2::AuthenticatedMarketDataRelayBase {
@@ -497,6 +495,7 @@ struct MDRelayServer : bts::co2::AuthenticatedMarketDataRelayBase {
}
};
struct QueryServer {
QueryServer(utils::Dispatcher& threadpool,
const bts::api::ServiceContext& ctx,
+1 -1
View File
@@ -26,7 +26,7 @@ extern "C" BTS_EXPORT void co2_get_1234_handle(size_t& size, MDConsumerHandle&
auto pheader = reinterpret_cast<ColdHeader*>(&buffer[0]);
pheader->has_depth = false;
size = sizeof(MDConsumerHandle);
handle.init(*pheader, *pcold, phot.get());
handle.init(*pheader, *pcold, phot.get(), nullptr);
// Copy the addresses for comparison
*hotaddr = (uintptr_t)phot.get();
*coldaddr = (uintptr_t)pcold.get();
+1 -1
View File
@@ -43,7 +43,7 @@ MDProducerHandle Co2TestInstrument::producer() {
MDConsumerHandle Co2TestInstrument::consumer() {
MDConsumerHandle handle;
handle.init(*_cheader, *_cold, _hot.get());
handle.init(*_cheader, *_cold, _hot.get(), nullptr/*mbo*/);
return handle;
}
+295
View File
@@ -0,0 +1,295 @@
#ifndef _WIN32
#include "catch/catch.hpp"
#include "../IntPrice.h"
#include "../MBOProcessor.h"
#include "Co2TestInstrument.h"
#include <random>
#include <algorithm>
namespace bts {
namespace md {
namespace test {
int mbo_tests = 0;
TEST_CASE("MBO publish snapshot", "[md][mbo]") {
MBOProcessor p;
// publish a snapshot
auto sec = SecurityKey(99);
{
auto s = p.writer().allocate_snapshot();
s->bids.push_back({ ExchangeOrderKey(1), { 100, 0 }, 42 });
p.writer().publish_snapshot(sec, &s);
}
auto s = p.reader().acquire_snapshot(sec);
REQUIRE(s);
REQUIRE(s->bids.size() == 1);
REQUIRE(s->bids[0].id.value() == 1);
REQUIRE(s->asks.size() == 0);
}
std::vector<inc::Update> gen_test_orders(uint64_t id=100, inc::UpdateType type=inc::UpdateType::Bid) {
std::vector<inc::Update> result;
inc::Priority priority = 10000;
for (int p = 100; p < 501; p+=100) {
// 6 sizes
for (int32_t s = 1; s <= 5; ++s) {
IntPrice price { p + 5 , -2};
auto order_id = ExchangeOrderKey(++id);
auto size = s;
priority++;
result.push_back( inc::Update {
order_id,
priority,
price,
size,
type,
inc::UpdateAction::New
});
// std::cout << std::endl << order_id.value() << ": " << price.price() << "-" << size;
}
}
std::random_shuffle(result.begin(), result.end());
return result;
}
TEST_CASE("MBO state to snapshot", "[md][mbo]") {
// convert state to snapshot
Co2TestInstrument instr { "MBO", api::SECURITYTYPE_FUTURE };
auto handle = instr.producer();
MBOWorkingState state(handle);
state.updated = utils::TimeStamp::now();
auto bids = gen_test_orders(1000, inc::UpdateType::Bid);
apply_incremental(state, bids);
auto asks = gen_test_orders(2000, inc::UpdateType::Ask);
apply_incremental(state, asks);
MBOSnapshot result;
copy_to_snapshot(state, &result);
REQUIRE(result.updated == state.updated);
REQUIRE(result.bids.size() == state.bid.size());
REQUIRE(result.asks.size() == state.ask.size());
REQUIRE(bids.size() == result.bids.size());
REQUIRE(asks.size() == result.asks.size());
for (size_t x = 0; x < result.bids.size() - 1; ++x) {
REQUIRE(result.bids[x].price >= result.bids[x+1].price);
}
for (size_t x = 0; x < result.asks.size() - 1; ++x) {
REQUIRE(result.asks[x].price <= result.asks[x+1].price);
}
}
template <class T>
std::vector<T> concat(const std::vector<T>& a, const std::vector<T>& b) {
std::vector<T> res;
std::copy(a.begin(), a.end(), std::back_inserter(res));
std::copy(b.begin(), b.end(), std::back_inserter(res));
return res;
}
TEST_CASE("MBO incremental", "[md][mbo]") {
// basic incremental test
Co2TestInstrument instr { "MBO", api::SECURITYTYPE_FUTURE };
auto handle = instr.producer();
MBOWorkingState state(handle);
auto orders =
concat( gen_test_orders(128, inc::UpdateType::Bid),
gen_test_orders(256, inc::UpdateType::Ask));
std::random_shuffle(orders.begin(), orders.end());
apply_incremental(state, orders);
REQUIRE(state.bid.size() + state.ask.size() == orders.size());
// generate some updates/deletes
std::vector<inc::Update> updates;
// - change all size=2 to size=6
// - delete all size=3
size_t removed = 0;
for (const auto& e : orders) {
if (e.size == 2) {
updates.push_back( inc::Update {
e.order_id,
e.priority,
e.price,
6,
e.type,
inc::UpdateAction::Change
});
}
else if (e.size == 3) {
++removed;
updates.push_back( inc::Update {
e.order_id,
e.priority,
e.price,
e.size,
e.type,
inc::UpdateAction::Delete
});
}
}
apply_incremental(state,updates);
REQUIRE(state.bid.size() + state.ask.size() == (orders.size() - removed));
MBOSnapshot result;
copy_to_snapshot(state, &result);
auto all = concat (result.bids, result.asks);
std::map<int,int> counts;
for (const auto& order : all) {
REQUIRE(order.size != 3);
REQUIRE(order.size != 2);
counts[order.size] ++;
}
REQUIRE(counts[6] > 0);
REQUIRE(counts[6] == counts[1]);
REQUIRE(counts[5] == counts[4]);
REQUIRE(counts[6] == counts[5]);
}
TEST_CASE("MBO sort order", "[md][mbo]") {
Co2TestInstrument instr { "MBO", api::SECURITYTYPE_FUTURE };
auto handle = instr.producer();
MBOWorkingState state(handle);
std::vector<inc::Update> updates;
// multiple updates at the same price
for (unsigned x=0; x < 100; ++x) {
// bid
auto p = 1000 + x;
updates.push_back( inc::Update {
ExchangeOrderKey(1000 + x),
1000 + x,
{ 1000, -1 },
(int)x+1,
inc::UpdateType::Bid,
inc::UpdateAction::New
});
// ask
if (x < 75) { // ensure bid.size != ask.size
updates.push_back( inc::Update {
ExchangeOrderKey(2000 + x),
2000 + x,
{ 2000, -1 },
(int)x+1,
inc::UpdateType::Ask,
inc::UpdateAction::New
});
}
}
std::random_shuffle(updates.begin(), updates.end());
apply_incremental(state, updates);
MBOSnapshot result;
copy_to_snapshot(state, &result);
// Since all orders are at the same price we can assert they are in priority order
// Since priority is not explicit in the shapshot we set order_id to the same value
for (size_t x = 0; x < result.bids.size() - 1; ++x) {
REQUIRE(result.bids[x].id.value() < result.bids[x+1].id.value());
}
for (size_t x = 0; x < result.asks.size() - 1; ++x) {
REQUIRE(result.asks[x].id.value() < result.asks[x+1].id.value());
}
}
TEST_CASE("MBO change price and size", "[md][mbo]") {
// When an order's size goes up it gets a new priority
Co2TestInstrument instr { "MBO", api::SECURITYTYPE_FUTURE };
auto handle = instr.producer();
MBOWorkingState state(handle);
constexpr auto bid = inc::UpdateType::Bid;
constexpr auto ask = inc::UpdateType::Ask;
constexpr auto add = inc::UpdateAction::New;
constexpr auto remove = inc::UpdateAction::Delete;
constexpr auto change = inc::UpdateAction::Change;
auto p = [](double price) -> IntPrice { return IntPrice(price * 1000, -3); };
// snapshot
std::vector<inc::Update> snapshot = {
{ ExchangeOrderKey(701267154552), 1160179309, p(2.975), 1, ask, add},
{ ExchangeOrderKey(701267991871), 1161068569, p(2.925), 9, bid, add},
{ ExchangeOrderKey(701267963307), 1161037482, p(2.975), 1, ask, add},
{ ExchangeOrderKey(701267991967), 1161068670, p(2.900), 1, bid, add},
{ ExchangeOrderKey(701267928346), 1160999982, p(3.000), 1, ask, add},
{ ExchangeOrderKey(701267993125), 1161069878, p(2.900), 3, bid, add},
{ ExchangeOrderKey(701267037972), 1160052467, p(2.875), 1, bid, add},
{ ExchangeOrderKey(701267099137), 1160119020, p(2.875), 5, bid, add},
{ ExchangeOrderKey(701267299079), 1160330214, p(2.875), 1, bid, add},
{ ExchangeOrderKey(701267308619), 1160340068, p(2.875), 5, bid, add},
{ ExchangeOrderKey(701267310436), 1160341952, p(2.875), 3, bid, add},
{ ExchangeOrderKey(701267872642), 1160939720, p(2.875), 1, bid, add},
{ ExchangeOrderKey(701267895533), 1160964252, p(2.875), 1, bid, add},
{ ExchangeOrderKey(701267953989), 1161027476, p(2.875), 3, bid, add},
{ ExchangeOrderKey(701267993135), 1161069890, p(2.875), 2, bid, add},
{ ExchangeOrderKey(701266410447), 1159379621, p(2.850), 3, bid, add},
{ ExchangeOrderKey(701267324626), 1160356597, p(2.850), 3, bid, add},
{ ExchangeOrderKey(701267992564), 1161069297, p(2.850), 1, bid, add},
{ ExchangeOrderKey(701266420294), 1159389827, p(2.775), 5, bid, add},
{ ExchangeOrderKey(701267334462), 1160366796, p(2.775), 5, bid, add},
{ ExchangeOrderKey(701266411103), 1159380297, p(2.625), 5, bid, add},
{ ExchangeOrderKey(701267325284), 1160357275, p(2.625), 5, bid, add},
};
apply_incremental(state, snapshot);
REQUIRE(state.bid.size() == 19);
REQUIRE(state.ask.size() == 3);
std::vector<inc::Update> incremental = {
{ ExchangeOrderKey(701267993738), 1161070510, p(2.925), 2, bid, add},
{ ExchangeOrderKey(701267993759), 1161070532, p(2.975), 3, ask, add},
{ ExchangeOrderKey(701267993884), 1161070662, p(2.950), 3, ask, add},
{ ExchangeOrderKey(701267994228), 1161071029, p(2.925), 2, bid, add},
{ ExchangeOrderKey(701267994243), 1161071044, p(2.925), 4, bid, add},
{ ExchangeOrderKey(701267994228), 1161071029, p(2.925), 2, bid, remove},
{ ExchangeOrderKey(701267993135), 1161071281, p(2.875), 3, bid, change},
};
apply_incremental(state, incremental);
REQUIRE(state.bid.size() == 21);
REQUIRE(state.ask.size() == 5);
{
auto iter = state.bid.begin();
auto next = state.bid.begin();
next++;
for (; next != state.bid.end(); ++iter, ++next) {
auto cur_price = std::get<IntPrice>(iter->first);
auto cur_prio = std::get<1>(iter->first);
auto next_price = std::get<IntPrice>(next->first);
auto next_prio = std::get<1>(next->first);
REQUIRE(cur_price >= next_price);
if (cur_price == next_price) {
REQUIRE(cur_prio < next_prio);
}
}
}
{
auto iter = state.ask.begin();
auto next = state.ask.begin();
next++;
for (; next != state.ask.end(); ++iter, ++next) {
auto cur_price = std::get<IntPrice>(iter->first);
auto cur_prio = std::get<1>(iter->first);
auto next_price = std::get<IntPrice>(next->first);
auto next_prio = std::get<1>(next->first);
REQUIRE(cur_price <= next_price);
if (cur_price == next_price) {
REQUIRE(cur_prio < next_prio);
}
}
}
}
}}}
#endif
+20 -44
View File
@@ -1,3 +1,4 @@
#ifndef _WIN32
#include "catch/catch.hpp"
#include "../SettlementRecorder.h"
#include "../coconut2.h"
@@ -15,6 +16,7 @@
#include "lib/config/protos/MarketData.pb.h"
#include "lib/utils/Sqlite_utils.h"
#include "md_test_utils.h"
#include <vector>
#include <string>
@@ -55,29 +57,8 @@ Platform TEST_PLATFORM = PLATFORM_SIM;
//
// Helper functions for Producer/Consumer tests
//
vector<MDProducerHandle> create_test_securities(MDProducer& producer, const char* postfix ="") {
FixedFutureSecurity ffs;
FixedOptionSecurity fos;
ffs.symbol("FOO"+string(postfix));
fos.symbol("FOOX"+string(postfix));
ffs.exchange = VENUE_BTS;
fos.exchange = VENUE_BTS;
ffs.key = invalid_security_key;
fos.key = invalid_security_key;
ffs.expiration_year = fos.expiration_year = 2015;
ffs.expiration_day = fos.expiration_day = 27;
fos.strike = 1000;
fos.call_put = PUTORCALL_CALL;
// Add 12 expirations to 2 different shared memory regions
vector<MDProducerHandle> result;
for (uint32_t x = 1; x <= 12; ++x) {
ffs.expiration_month = fos.expiration_month = x;
result.push_back(producer.add_security(ffs));
result.push_back(producer.add_security(fos));
// In a real app these securities would now be passed to the SecurityIDRecorder
}
return result;
}
std::vector<MDProducerHandle> create_test_securities(MDProducer& producer, const char* postfix = "");
set<UserSymbol> get_test_symbols(const char* postfix="") {
set<UserSymbol> symbols;
@@ -146,12 +127,6 @@ Security mkspread(string symbol, security_key key, Security leg1, Security leg2,
return mkspread(symbol, key, vlegs);
}
void assign_ids(vector<MDProducerHandle>::iterator begin, vector<MDProducerHandle>::iterator end, security_key start_key = 1000) {
for (; begin != end; ++begin) {
(*begin).set_key(++start_key);
}
}
class Co2Tests
{
@@ -370,7 +345,7 @@ public:
UserSymbol option_symbol( "FOOX", VENUE_BTS, SECURITYTYPE_OPTION );
symbols.insert(option_symbol);
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &symbols, true, TEST_PLATFORM);
vector<Security> consumer_securities;
// Poll for security list - none visible until they have keys
@@ -441,7 +416,7 @@ public:
UserSymbol option_symbol( "FOOX", VENUE_BTS, SECURITYTYPE_OPTION );
symbols.insert(option_symbol);
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &symbols, true, TEST_PLATFORM);
// Add the on_security callback
vector<Security> securities;
@@ -481,7 +456,7 @@ public:
UserSymbol option_symbol( "FOOX", VENUE_BTS, SECURITYTYPE_OPTION );
symbols.insert(option_symbol);
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &symbols, true, TEST_PLATFORM);
// Get a handle to 24 consumers which haven't been created yet
vector<MDConsumerHandle> consumers;
@@ -539,7 +514,7 @@ public:
UserSymbol option_symbol( "FOOX", VENUE_BTS, SECURITYTYPE_OPTION );
symbols.insert(option_symbol);
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &symbols, true, TEST_PLATFORM);
// Get a handle to 24 consumers which haven't been created yet
vector<MDConsumerHandle> consumers;
@@ -600,7 +575,7 @@ public:
// Symbol list for the consumer
auto symbols = get_test_symbols();
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &symbols, true, TEST_PLATFORM);
// Add the on_security callback - callback fires for existing securities
vector<Security> securities;
@@ -628,7 +603,7 @@ public:
// Symbol list for the consumer
SecurityCache cache;
auto symbols = get_test_symbols();
MDClient client(nullptr, nullptr, &symbols);
MDClient client(nullptr, nullptr, nullptr, &symbols);
MDConsumer consumer = client.current_consumer();
map<security_key, MDConsumerHandle> products;
consumer.on_security([&](const SecurityPtr& security, MDConsumerHandle handle) {
@@ -761,7 +736,7 @@ public:
UserSymbol option_symbol( "FOOX", VENUE_BTS, SECURITYTYPE_OPTION );
symbols.insert(option_symbol);
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &symbols, true, TEST_PLATFORM);
set<security_key> keys;
consumer.walk_security_list([&](const SecurityPtr& security, MDConsumerHandle) { keys.insert(security->id()); });
@@ -806,7 +781,7 @@ public:
csymbols.insert(UserSymbol("S3", VENUE_BTS, SECURITYTYPE_SPREAD));
csymbols.insert(UserSymbol("S4", VENUE_BTS, SECURITYTYPE_SPREAD));
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &csymbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &csymbols, true, TEST_PLATFORM);
auto check_sec = [&](const SecurityPtr& security, MDConsumerHandle handle) {
REQUIRE(security->id() == handle.key());
@@ -853,7 +828,7 @@ public:
csymbols.insert(UserSymbol("OO", VENUE_BTS, SECURITYTYPE_OPTION));
csymbols.insert(UserSymbol("SS", VENUE_BTS, SECURITYTYPE_SPREAD));
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &csymbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &csymbols, true, TEST_PLATFORM);
auto check_sec = [&](const SecurityPtr& security, MDConsumerHandle handle) {
REQUIRE(security->id() == handle.key());
@@ -905,7 +880,7 @@ public:
consumer_symbols.insert(UserSymbol("OO", VENUE_BTS, SECURITYTYPE_OPTION));
consumer_symbols.insert(UserSymbol("SS", VENUE_BTS, SECURITYTYPE_SPREAD));
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &consumer_symbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &consumer_symbols, true, TEST_PLATFORM);
size_t security_count = 0;
// Indirection for check function
@@ -974,7 +949,7 @@ public:
set<UserSymbol> consumer_symbols;
consumer_symbols.insert(UserSymbol("OO", VENUE_BTS, SECURITYTYPE_OPTION));
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &consumer_symbols, true, TEST_PLATFORM);
MDConsumer consumer(cache, nullptr, nullptr, &consumer_symbols, true, TEST_PLATFORM);
auto h1 = consumer.get_handle(k1);
REQUIRE(h1.is_initialized());
auto h2 = consumer.get_handle(k2);
@@ -1260,7 +1235,7 @@ struct Co2RelayTests {
UserSymbol option_symbol("FOOX", VENUE_BTS, SECURITYTYPE_OPTION);
symbols.insert(option_symbol);
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols);
MDConsumer consumer(cache, nullptr, nullptr, &symbols);
return consumer;
}
@@ -1327,7 +1302,7 @@ struct Co2RelayTests {
// todo: setup_query_server should use client.symbols() instead of it's own list
//
const auto symbols = setup_symbols();
MDClient client(nullptr, nullptr, &symbols);
MDClient client(nullptr, nullptr, nullptr, &symbols);
auto server = setup_query_server(client, producer, &entitlements, &producers);
// assign prices
@@ -1460,7 +1435,7 @@ struct Co2RelayTests {
vector<MDProducerHandle> producers;
Entitlements entitlements;
const auto symbols = setup_symbols();
MDClient client(nullptr, nullptr, &symbols);
MDClient client(nullptr, nullptr, nullptr, &symbols);
auto server = setup_query_server(client, producer, &entitlements, &producers);
// assign prices to all levels
@@ -1638,7 +1613,7 @@ namespace ImpliedTests {
UserSymbol option_symbol( "FOOX", VENUE_BTS, SECURITYTYPE_OPTION );
symbols.insert(option_symbol);
SecurityCache cache;
MDConsumer consumer(cache, nullptr, &symbols);
MDConsumer consumer(cache, nullptr, nullptr, &symbols);
vector<Security> consumer_securities;
// Poll for security list - none visible until they have keys
size_t count = consumer.walk_security_list([&](const SecurityPtr& security, MDConsumerHandle) {consumer_securities.push_back(*security);});
@@ -2417,3 +2392,4 @@ TEST_CASE("IntPrice", "[md]") {
}}}
#endif
+41
View File
@@ -0,0 +1,41 @@
#include "md_test_utils.h"
#include "../MDProducer.h"
#include <vector>
#include <string>
namespace bts {
namespace md {
namespace test {
std::vector<MDProducerHandle> create_test_securities(MDProducer& producer, const char* postfix ="") {
FixedFutureSecurity ffs;
FixedOptionSecurity fos;
ffs.symbol("FOO"+std::string(postfix));
fos.symbol("FOOX"+std::string(postfix));
ffs.exchange = api::VENUE_BTS;
fos.exchange = api::VENUE_BTS;
ffs.key = invalid_security_key;
fos.key = invalid_security_key;
ffs.expiration_year = fos.expiration_year = 2015;
ffs.expiration_day = fos.expiration_day = 27;
fos.strike = 1000;
fos.call_put = api::PUTORCALL_CALL;
// Add 12 expirations to 2 different shared memory regions
std::vector<MDProducerHandle> result;
for (uint32_t x = 1; x <= 12; ++x) {
ffs.expiration_month = fos.expiration_month = x;
result.push_back(producer.add_security(ffs));
result.push_back(producer.add_security(fos));
// In a real app these securities would now be passed to the SecurityIDRecorder
}
return result;
}
void assign_ids(std::vector<MDProducerHandle>::iterator begin, std::vector<MDProducerHandle>::iterator end, security_key start_key) {
for (; begin != end; ++begin) {
(*begin).set_key(++start_key);
}
}
}}}//namespace
+2
View File
@@ -2,6 +2,7 @@
#include "lib/md/IntPrice.h"
#include "lib/md/PriceSize.h"
#include "../MDProducerHandle.h"
#include <vector>
namespace bts {
@@ -28,5 +29,6 @@ Book compute_synthetic_book(const api::Security& security, const std::vector<Boo
std::ostream& operator<< (std::ostream& os, const PS& val);
void assign_ids(std::vector<MDProducerHandle>::iterator begin, std::vector<MDProducerHandle>::iterator end, security_key start_key = 1000);
}}}//namespace
@@ -166,6 +166,8 @@ class OrderServices {
void report_state(const api::orderman::OrderState &state,
trace::CorID const &trace = trace::CorID()) {
bts_assert(state.status() != api::orderman::ORDER_STATUS_INACTIVE);
OrderStateUpdateEvent ev(_owner->key(), _owner->parent_key(),
_owner->definition(), state, trace);
_data->owner.publish(_data->report_order_state_pub, ev);
@@ -452,7 +452,7 @@ bts::api::orderman::OrderStatus SpreaderOrder::_get_order_status () const{
bts::api::orderman::OrderStatus::ORDER_STATUS_COMPLETED;
case SpreaderState::START:
case SpreaderState::WAITING_FOR_INIT_DATA:
return bts::api::orderman::OrderStatus::ORDER_STATUS_INACTIVE;
return bts::api::orderman::OrderStatus::ORDER_STATUS_WORKING;
default:
bts_assert(false);
return bts::api::orderman::OrderStatus::ORDER_STATUS_INACTIVE;
+25
View File
@@ -1000,6 +1000,31 @@ extern "C" BTS_EXPORT char *bts_data_path() {
}
extern "C" BTS_EXPORT void free_bts_data_path(char *dp) { delete[] dp; }
extern "C" BTS_EXPORT unsigned int setup_process_job_object()
{
#ifdef _WIN32
if (IsDebuggerPresent())
return 0;
HANDLE hjob = CreateJobObject(nullptr, nullptr);
if (hjob == nullptr)
return 100; //Failed to create JOB object.
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
BOOL success = SetInformationJobObject(hjob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
DWORD error = GetLastError();
if(!success)
return error;
success = AssignProcessToJobObject(hjob, GetCurrentProcess());
error = GetLastError();
if(!success)
return error;
#endif
return 0;
}
}
}
} // end namespaces
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include <tbb/concurrent_queue.h>
#include <tbb/concurrent_vector.h>
#include <atomic>
namespace bts {
namespace utils {
template <class T>
class ConcurrentPool {
tbb::concurrent_vector<std::unique_ptr<T[]>> _all;
tbb::concurrent_queue<T*> _free;
size_t _initial_size;
size_t _grow_size;
size_t _outstanding;
public:
ConcurrentPool(size_t initial_size=256, size_t grow_size=32):
_all(), _free(), _initial_size(initial_size), _grow_size(grow_size), _outstanding(0) {
reserve(initial_size);
}
~ConcurrentPool() { }
T* allocate() {
T* result = nullptr;
++_outstanding;
if (_free.try_pop(result)) {
return result;
}
else {
reserve(_grow_size);
return allocate();
}
}
void reserve(size_t count) {
std::unique_ptr<T[]> buffer {new T[count]};
_outstanding += count;
for (size_t x = 0; x < count; ++x) {
T* temp = &buffer[x];
release(&temp);
}
_all.push_back(std::move(buffer));
}
void release(T** item) {
if (*item) {
(*item)->reset();
_free.push(*item);
*item = nullptr;
--_outstanding;
}
}
template <class CB>
void unsafe_iterate(CB cb) const {
for (auto iter = _all.begin(); iter != _all.end(); ++iter) {
size_t size = (iter == _all.begin()) ? _initial_size : _grow_size;
for (size_t x = 0; x < size; ++x) {
const std::unique_ptr<T[]>& bucket = *iter;
const T* start = bucket.get();
cb(start[x]);
}
}
}
};
}} //namespace
+1 -1
View File
@@ -200,7 +200,7 @@ void Dispatcher::invoke(Action action)
}
}
void Dispatcher::post(Action action, int64_t delay_ms)
void Dispatcher::post(Action&& action, int64_t delay_ms)
{
if (delay_ms > 0)
{
+1 -1
View File
@@ -87,7 +87,7 @@ public:
// Perform an action on the dispatcher thread. Block until the action is complete.
void invoke(Action action);
// Queue an action on the dispatcher thread and return immediately.
void post(Action action, int64_t delay_ms=0);
void post(Action&& action, int64_t delay_ms=0);
boost::asio::io_service* io_service();
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <mutex>
namespace bts {
namespace utils {
template <class T>
class LockBox {
mutable std::mutex _mutex;
T _data;
public:
template <class CB>
auto lock(CB cb) {
std::lock_guard<std::mutex> guard(_mutex);
return cb(_data);
}
template <class CB>
auto lock(CB cb) const {
std::lock_guard<std::mutex> guard(_mutex);
return cb(_data);
}
};
}} //namespace
+15
View File
@@ -640,6 +640,21 @@ static void perf_log(benchmark::State &state) {
}
BENCHMARK(perf_log);
void int_price_less(benchmark::State& state) {
for (auto _ : state) {
state.PauseTiming();
int64_t count = state.range(0);
std::vector<md::IntPrice> prices;
prices.reserve((int)count);
for (int64_t x = 0; x < count; ++x) {
prices.push_back({x % 100, (int8_t)-(x%5)});
}
state.ResumeTiming();
std::sort(prices.begin(), prices.end());
}
}
BENCHMARK(int_price_less)->Range(8, 8 << 11);
std::string Benchmark::help() {
return "Run micro-benchmarks. Use --benchmark_filter=regex to run only "
"specific benchmarks.";
+9 -3
View File
@@ -14,6 +14,8 @@
#include "lib/md/MDClient.h"
#include "lib/md/SyntheticSecurityService.h"
#include "lib/md/relay/QueryServer.h"
#include "lib/md/MBOProcessor.h"
#include "lib/md/MBOServiceServer.h"
#include "lib/metrics/metrics.h"
#include "lib/metrics/system_metrics.h"
#include "lib/orderman/order_factory/DynamicOrderRegistry.h"
@@ -57,15 +59,19 @@ int Run::execute(std::vector<std::string> const &) {
persistence::PersistenceServiceServer settings_saver(sc, "settings");
user::AuthServiceServer login_server(sc);
md::run_market_datapublishers();
md::MBOProcessor mbo(&utils::Dispatcher::get_named("MBO"));
auto* mbowriter = config::get<config::toggles::PlacementInQueue>().enabled() ? &mbo.writer() : nullptr;
md::run_market_datapublishers(mbowriter);
api::synthetic::SyntheticSecurityService synthetic_service(sc);
md::MDClient mdclient(&utils::Dispatcher::background(), &synthetic_service);
md::MDClient mdclient(&utils::Dispatcher::background(), &synthetic_service, &mbo.reader());
md::QueryServerPtr md_query_server(
md::run_query_server(sc, mdclient, stats));
md::MBOServiceServer mboserver(sc, utils::Dispatcher::background(), mdclient);
// initialize order plugins
orderman::DynamicOrderRegistry dyn_order_registry;
SparkServices plugin_services(dyn_order_registry);
+1 -1
View File
@@ -37,7 +37,7 @@ int Web::execute(const std::vector<std::string>& args) {
user::AuthServiceServer login_server(sc);
if (std::find(args.begin(), args.end(), "-md") != args.end()) {
md::run_market_datapublishers();
md::run_market_datapublishers(nullptr);
}
api::synthetic::SyntheticSecurityService synthetic_service(sc);
+2 -1
View File
@@ -7,6 +7,7 @@ class IMDPlatform
{
public:
virtual void send_heartbeats();
virtual ~IMDPlatform() {}
};
}}
}}
@@ -56,17 +56,14 @@ void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , cons
void ICEListener::onError(const onixs_ice::Error& error)
{
std::string error_desc(error.description());
tracepoint (bts_trace,trace_order,(char*) "onError", 0, (uint16_t) 0, 0);
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
}
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
{
tracepoint (bts_trace,trace_order,(char*) "onFeedError", 0, (uint16_t) 0, 0);
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
}
void ICEListener::onWarning(const onixs_ice::Warning& warning)
{
tracepoint (bts_trace,trace_order,(char*) "onWarning", 0, (uint16_t) 0, 0);
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
}
@@ -74,31 +71,27 @@ void ICEListener::onWarning(const onixs_ice::Warning& warning)
// Security discovery
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
{
tracepoint (bts_trace,trace_order,(char*) "onFuturesProductDefinition", 0, (uint16_t) 0, 0);
if(msg.isSpread)
on_future_strip(msg, is_interested);
else
on_future(msg, is_interested);
}
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
{
tracepoint (bts_trace,trace_order,(char*) "onNewExpiry", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
on_future(msg, is_interested);
}
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& def, bool& is_interested)
{
tracepoint (bts_trace,trace_order,(char*) "onFuturesStrategyDefinition", 0, (uint16_t) 0, 0);
//TODO need to find out how to produce data for testing
//on_uds_future_spread(def, is_interested);
UNUSED(def);
UNUSED(is_interested);
}
//This is only for UDS
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
{
tracepoint (bts_trace,trace_order,(char*) "onFuturesStrategyDefinition", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
//TODO need to find out how to produce data for testing
//on_uds_future_spread(msg, is_interested);
UNUSED(msg);
@@ -152,7 +145,7 @@ void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPric
// Book
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
{
tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto data = _index.get_security(onixs_book.marketId());
if (data == nullptr)
@@ -214,9 +207,8 @@ void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size, on
data->handle.add_trade(ts, ps, date, map_aggressor(agside), size);
}
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
{
tracepoint (bts_trace,trace_order,(char*) "onTrade", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto* data = _index.get_security(msg.marketId);
if (data != NULL)
{
@@ -230,9 +222,8 @@ void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageI
//
// RFQ
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
{
tracepoint (bts_trace,trace_order,(char*) "onRfq", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
@@ -267,9 +258,8 @@ void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo&
//
// Misc
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
{
tracepoint (bts_trace,trace_order,(char*) "onSettlementPrice", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
@@ -287,9 +277,8 @@ void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs
UNUSED(msg_info);
}
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
{
tracepoint (bts_trace,trace_order,(char*) "onOpenPrice", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
@@ -301,9 +290,8 @@ void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::
}
}
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
{
tracepoint (bts_trace,trace_order,(char*) "onClosePrice", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
@@ -315,9 +303,8 @@ void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice
}
}
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
{
tracepoint (bts_trace,trace_order,(char*) "onEndOfDayMarketSummary", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
@@ -333,18 +320,15 @@ void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary
//
// Feed
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& msg_info)
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
{
UNUSED(feedId);
tracepoint (bts_trace,trace_order,(char*) "onMulticastMessageBlockBegin", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
}
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
{
UNUSED(feedId);
UNUSED(marketIds);
tracepoint (bts_trace,trace_order,(char*) "onMulticastMessageBlockEnd.begin", 0, (uint16_t) 0, 0);
tracepoint (bts_trace,trace_order,(char*) "onMulticastMessageBlockEnd.end", 0, (uint16_t) 0, 0);
}
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
@@ -217,7 +217,6 @@ unique_ptr<ICEListener> ICEMain::prepare_listener(onixs_ice::Handler& handler, c
producer_channels.push_back(_producer.get_channel(symbol.symbol().c_str(), symbol.type(), symbol.exchange()));
}
const auto& options = channel_settings_for(channel);
unique_ptr<ICEListener> listener(new ICEListener(_index, _producer, /*_settlement_recorder,*/ producer_channels));
for (auto& symbol : channel.symbols)
listener->add_filter_symbol(symbol.symbol());
@@ -1,15 +1,17 @@
#pragma once
#include "lib/md/SecurityIndex.h"
#include "lib/md/MBOProcessor.h"
#include <string>
namespace bts {
namespace md {
struct SecurityData {
SecurityData(uint32_t id, MDProducerHandle& h) : id(id), handle(h), electronic_volume(0) {}
SecurityData(uint32_t id, MDProducerHandle& h) : id(id), handle(h), mbo_state(h), electronic_volume(0) { }
uint32_t id; // exchange id
MDProducerHandle handle;
MBOWorkingState mbo_state;
uint32_t electronic_volume;
};
@@ -15,7 +15,7 @@ namespace md {
MDP3Listener::MDP3Listener(
CMESecurityIndex& index,
MDProducer& producer,
// co2::SettlementRecorder& ,
MBOWriter* mbo,
const std::vector<MDProducerChannel> channels) :
_index(index),
_producer(producer),
@@ -27,7 +27,8 @@ MDP3Listener::MDP3Listener(
_symbol_filter(),
_channels(channels),
_concurrency_monitor(0),
_book_updates()
_book_updates(),
_mbo(mbo)
{
_book_updates.reserve(32);
}
@@ -109,7 +110,7 @@ void MDP3Listener::onEndOfPacket(onixs::Handler&, const onixs::PacketArgs&) {
}
//
// Security discovery
// Book updates
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::DirectBook& book) {
onBookUpdate<COCONUT_MAX_DEPTH>(sec, book, book_type_direct);
@@ -119,6 +120,24 @@ void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, con
onBookUpdate<COCONUT_MAX_IMPLIED_DEPTH>(sec, book, book_type_implied);
}
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::MboBook& book) {
if (_mbo) {
auto data = _index.get_security(sec.id());
if (data == nullptr) return;
auto key = data->handle.key();
if (key == invalid_security_key) return;
auto* snapshot = _mbo->allocate_snapshot();
// copy from onixs book
for (const auto& bid : book.bids()) {
snapshot->bids.push_back({ ExchangeOrderKey(bid.id()), convert_price(bid.price(), 0), bid.quantity() });
}
for (const auto& ask : book.offers()) {
snapshot->asks.push_back({ ExchangeOrderKey(ask.id()), convert_price(ask.price(),0), ask.quantity() });
}
_mbo->validate_snapshot(sec.id(), SecurityKey(key), snapshot);
}
}
//
// Trades
@@ -2,6 +2,7 @@
#include "lib/utils/bts_no_copy.h"
#include "lib/md/coconut2.h"
#include "lib/md/MBOProcessor.h"
#include "CMESecurityIndex.h"
#include "lib/tracing/bts_trace.h"
@@ -13,11 +14,57 @@
#include <atomic>
#include <array>
#include <utility>
#include <optional>
namespace bts {
namespace md {
inline inc::UpdateType mbo_update_type(onixs::EntryTypeBook::Enum otype) {
switch(otype) {
case onixs::EntryTypeBook::Bid:
case onixs::EntryTypeBook::ImpliedBid:
return inc::UpdateType::Bid;
break;
case onixs::EntryTypeBook::Offer:
case onixs::EntryTypeBook::ImpliedOffer:
return inc::UpdateType::Ask;
break;
default:
bts_assert_msg(false, "Unexpected update type");
}
}
inline inc::UpdateAction mbo_update_action(onixs::OrderUpdateAction::Enum a) {
switch(a) {
case onixs::OrderUpdateAction::New:
return inc::UpdateAction::New;
case onixs::OrderUpdateAction::Update:
return inc::UpdateAction::Change;
case onixs::OrderUpdateAction::Delete:
return inc::UpdateAction::Delete;
default:
bts_assert_msg(false, "Unexpected update action");
}
}
inline inc::UpdateAction mbo_update_action(onixs::UpdateAction::Enum a) {
switch(a) {
case onixs::UpdateAction::New:
return inc::UpdateAction::New;
case onixs::UpdateAction::Change:
return inc::UpdateAction::Change;
case onixs::UpdateAction::Delete:
return inc::UpdateAction::Delete;
case onixs::UpdateAction::DeleteThru:
case onixs::UpdateAction::DeleteFrom:
case onixs::UpdateAction::Overlay:
return inc::UpdateAction::Delete;
default:
bts_assert_msg(false, "Unexpected update action");
}
}
class MDP3Listener :
public onixs::HandlerListener,
public onixs::MarketDataListener,
@@ -31,7 +78,7 @@ class MDP3Listener :
public:
MDP3Listener(CMESecurityIndex& index,
MDProducer&,
// co2::SettlementRecorder&,
MBOWriter* mbo,
const std::vector<MDProducerChannel>);
virtual ~MDP3Listener();
@@ -54,6 +101,139 @@ public:
void onPacket(onixs::Handler&, const onixs::PacketArgs&) override;
void onEndOfPacket(onixs::Handler&, const onixs::PacketArgs&) override;
//
// Handle MBO incremental updates directly
void onMessage(onixs::Handler&, const onixs::IncrementalRefreshBook32Args& args) override {
onIncrementalRefreshBook(args);
};
void onMessage(onixs::Handler&, const onixs::IncrementalRefreshBook46Args& args) override {
onIncrementalRefreshBook(args);
}
template <class Args>
void onIncrementalRefreshBook(const Args& args) {
if (!_mbo) return;
for(const auto& entry : args.message().orderIdEntries()) {
int qty;
entry.displayQty(qty);
uint8_t ref;
if (entry.referenceId(ref)) {
// entries is indexed by 0 but ref is indexed by 1 :facepalm:
const auto& ref_entry = args.message().entries()[(int)ref-1];
uint32_t secid = ref_entry.securityId();
auto data = _index.get_security(secid);
if (!data) continue;
auto order_id = ExchangeOrderKey(entry.orderId());
inc::Priority priority;
bts_assert(entry.orderPriority(priority));
onixs::Decimal oprice;
ref_entry.entryPx(oprice);
auto price = convert_price(oprice, data->handle.security().display_factor);
int32_t size;
bts_assert(entry.displayQty(size));
auto type = mbo_update_type(ref_entry.entryType());
auto action = mbo_update_action(entry.orderUpdateAction());
_mbo->enqueue_incremental(
data->mbo_state,
inc::Update {
order_id,
priority,
price,
size,
type,
action
});
}
}
}
void onMessage(onixs::Handler&, const onixs::IncrementalRefreshOrderBook43Args& args) override {
onIncrementalRefreshOrderBook(args);
}
void onMessage(onixs::Handler&, const onixs::IncrementalRefreshOrderBook47Args& args) override {
onIncrementalRefreshOrderBook(args);
}
template <class Args>
void onIncrementalRefreshOrderBook(const Args& args) {
if (!_mbo) return;
for (const auto& entry : args.message().entries()) {
auto data = _index.get_security(entry.securityId());
if (!data) continue;
// order id
uint64_t temp;
bts_assert(
entry.orderId(temp));
auto order_id = ExchangeOrderKey(temp);
inc::Priority priority;
bts_assert(
entry.orderPriority(priority));
onixs::Decimal onxprice;
bts_assert(
entry.entryPx(onxprice));
auto price = convert_price(onxprice, data->handle.security().display_factor);
int32_t size;
bts_assert(
entry.displayQty(size));
auto type = mbo_update_type(entry.entryType());
auto action = mbo_update_action(entry.updateAction());
_mbo->enqueue_incremental(
data->mbo_state,
inc:: Update {
order_id,
priority,
price,
size,
type,
action
});
}
}
void onMessage(onixs::Handler&, const onixs::SnapshotFullRefreshOrderBook44Args& args) override {
onSnapshotFullRefreshOrderBook(args);
}
void onMessage(onixs::Handler&, const onixs::SnapshotFullRefreshOrderBook53Args& args) override {
onSnapshotFullRefreshOrderBook(args);
}
template <class Args>
void onSnapshotFullRefreshOrderBook(const Args& args) {
if (!_mbo) return;
auto data = _index.get_security(args.message().securityId());
if (!data) return;
for (const auto& entry : args.message().entries()) {
auto order_id = ExchangeOrderKey(entry.orderId());
inc::Priority priority;
bts_assert(entry.orderPriority(priority));
auto price = convert_price(entry.entryPx(), data->handle.security().display_factor);
auto size = entry.displayQty();
inc::UpdateType type = mbo_update_type(entry.entryType());
_mbo->enqueue_incremental(
data->mbo_state,
inc::Update {
order_id,
priority,
price,
size,
type,
inc::UpdateAction::New
});
}
}
//
// Security discovery
template <typename InstrumentDefinitionFuture>
@@ -74,9 +254,9 @@ public:
//
// Book
// void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::MboBook&) override;
void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::DirectBook&) override;
void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::ImpliedBook&) override;
void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::MboBook&) override;
// void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::ConsolidatedBook&) override;
template <size_t MAX_DEPTH,class TBook>
@@ -199,6 +379,7 @@ private:
std::pair<std::array<PriceSize, COCONUT_MAX_DEPTH>, std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH>> asks;
};
std::vector<BookData> _book_updates;
MBOWriter* _mbo;
};
//////////////////////////////////////////
@@ -228,9 +409,7 @@ void MDP3Listener::onFuture(onixs::Handler&, const onixs::Security& osec, const
tracepoint (bts_trace,trace_order,(char*) "onDefinition.future", _latest_sequence, (uint16_t) _latest_sequence, 0);
const auto& sec = def.message();
auto exchange_id = sec.securityId();
// LOG(md, Info, "onDefinition future {} {}",
// std::string(sec.asset().items(), sec.asset().size()),
// std::string(sec.symbol().items(), sec.symbol().size()));
// LOG(md, Info, "onDefinition future exch:{}", exchange_id);
if (filter_security(exchange_id, sec) != ADD) return;
bts_assert(exchange_id == osec.id());
@@ -258,7 +437,7 @@ void MDP3Listener::onOption(onixs::Handler&, const onixs::Security&, const Instr
tracepoint (bts_trace,trace_order,(char*) "onDefinition.option", _latest_sequence, (uint16_t) _latest_sequence, 0);
const auto& sec = def.message();
auto exchange_id = sec.securityId();
// LOG(Info, MarketData) << "onDefinition option " << exchange_id;
// LOG(md, Info, "onDefinition option exch:{}", exchange_id);
if (filter_security(exchange_id, sec) != ADD) return;
FixedOptionSecurity fsec;
@@ -302,9 +481,7 @@ void MDP3Listener::onSpread(onixs::Handler&, const onixs::Security&, const Instr
tracepoint (bts_trace,trace_order,(char*) "onDefinition.spread", _latest_sequence, (uint16_t) _latest_sequence, 0);
const auto& sec = spread_args.message();
auto exchange_id = sec.securityId();
// LOG(md, Info, "onDefinition spread {} {}",
// std::string(sec.asset().items(), sec.asset().size()),
// std::string(sec.symbol().items(), sec.symbol().size()));
// LOG(md, Info, "onDefinition spread exch:{}", exchange_id);
if (filter_security(exchange_id, sec) != ADD) return;
// Filter out any already expired spreads
@@ -1,6 +1,7 @@
#include "MDP3Main.h"
#include "lib/md/cme/MDP3Utils.h"
#include "lib/md/MBOProcessor.h"
#include "lib/config/config.h"
#include "lib/md/coconut2.h"
#include "lib/os/os.h"
@@ -116,12 +117,23 @@ map<string, unique_ptr<onixs::FeedEngine>> create_feed_engines(const vector<CMEC
return result;
}
onixs::SessionSettings session_settings_for_channel(const string& channel_id) {
onixs::SessionSettings session_settings_for_channel(const string& channel_id, MBOWriter* mbo) {
const auto& options = channel_settings_for(channel_id);
onixs::SessionSettings settings;
settings.marketRecovery(onixs::MarketRecoveryOptions::MbpOnly);
if (mbo) {
settings.marketRecovery(onixs::MarketRecoveryOptions::BothMboAndMbp);
}
else {
settings.marketRecovery(onixs::MarketRecoveryOptions::MbpOnly);
}
if (!options.join_snapshot_on_gap()) {
#ifdef DEBUG
settings.gapAndErrorHandling(onixs::GapAndErrorHandlingOptions::RecoverMarketState);
#else
settings.gapAndErrorHandling(onixs::GapAndErrorHandlingOptions::ContinueProcessing);
#endif
}
if (options.use_natural_refresh()) {
settings.joinRecovery(onixs::JoinRecoveryOptions::Disabled);
@@ -130,14 +142,14 @@ onixs::SessionSettings session_settings_for_channel(const string& channel_id) {
return settings;
}
void MDP3Main::start(std::vector<UserSymbol>& symbols) {
void MDP3Main::start(std::vector<UserSymbol>& symbols, MBOWriter* mbo) {
await_cme_config();
initialize();
const auto& channels = parse_channels(symbols);
_engines = create_feed_engines(channels);
for (const auto& channel : channels) {
unique_ptr<onixs::Handler> phandler = prepare_handler(channel.id);
_listeners.push_back(prepare_listener(*phandler, channel));
unique_ptr<onixs::Handler> phandler = prepare_handler(channel.id, mbo);
_listeners.push_back(prepare_listener(*phandler, channel, mbo));
_handlers.insert(std::make_pair(channel.id, move(phandler)));
std::stringstream symstr;
for (const auto& sym : channel.symbols) { symstr << sym.symbol() << "-" << SecurityType_Name(sym.type()) << " "; }
@@ -145,7 +157,7 @@ void MDP3Main::start(std::vector<UserSymbol>& symbols) {
}
for (auto& pair : _handlers) {
onixs::SessionSettings settings = session_settings_for_channel(pair.first);
onixs::SessionSettings settings = session_settings_for_channel(pair.first, mbo);
pair.second->start(settings);
}
LOG(md,Info, "** CME MDP3 Publisher started **");
@@ -185,7 +197,7 @@ void MDP3Main::initialize() {
// //
// Initialize HandlerSettings and allocate the channel Handler
unique_ptr<onixs::Handler> MDP3Main::prepare_handler(const string& channel_id) {
unique_ptr<onixs::Handler> MDP3Main::prepare_handler(const string& channel_id, MBOWriter* mbo) {
const auto& pub_options = config::get<PublisherOptions>();
const auto& options = channel_settings_for(channel_id);
LOG(md,Info,"Loaded channel options for channel {}:\n{}",channel_id ,options.DebugString());
@@ -198,6 +210,9 @@ unique_ptr<onixs::Handler> MDP3Main::prepare_handler(const string& channel_id) {
settings.bookManagement().directBooks().maintain(true);
settings.bookManagement().impliedBooks().maintain(true);
settings.bookManagement().consolidatedBooks().maintain(false);
if (options.validate_mbo() && mbo != nullptr) {
settings.bookManagement().mboBooks().maintain(true);
}
// 10k default is *far* too low
settings.recoveryQueueMaxSize(options.recovery_queue_size());
@@ -320,14 +335,14 @@ unique_ptr<onixs::Handler> MDP3Main::prepare_handler(const string& channel_id) {
//
// Create a listener for this channel and register callbacks
unique_ptr<MDP3Listener> MDP3Main::prepare_listener(onixs::Handler& handler, const CMEChannel& channel) {
unique_ptr<MDP3Listener> MDP3Main::prepare_listener(onixs::Handler& handler, const CMEChannel& channel, MBOWriter* mbo) {
// We need to get a handler to the producer channels so we can produce heartbeats
vector<MDProducerChannel> producer_channels;
for (auto& symbol : channel.symbols) {
producer_channels.push_back(_producer.get_channel(symbol.symbol().c_str(), symbol.type(), symbol.exchange()));
}
unique_ptr<MDP3Listener> listener(new MDP3Listener(_index, _producer, /*_settlement_recorder,*/ producer_channels));
unique_ptr<MDP3Listener> listener(new MDP3Listener(_index, _producer, mbo, producer_channels));
for (auto& symbol : channel.symbols) {
listener->add_filter_symbol(symbol.symbol());
}
@@ -16,6 +16,8 @@ namespace md {
// void run_onixs_mdp3_server(UserSymbol* sym, size_t symcount);
class MBOWriter;
struct CMEChannel;
/*
@@ -30,12 +32,12 @@ public:
MDP3Main();
~MDP3Main();
void start(std::vector<UserSymbol>&);
void start(std::vector<UserSymbol>&, MBOWriter*);
void send_heartbeats() override;
void initialize();
std::unique_ptr<onixs::Handler> prepare_handler(const std::string& channel);
std::unique_ptr<MDP3Listener> prepare_listener(onixs::Handler& handler, const CMEChannel& channel);
std::unique_ptr<onixs::Handler> prepare_handler(const std::string& channel, MBOWriter*);
std::unique_ptr<MDP3Listener> prepare_listener(onixs::Handler& handler, const CMEChannel& channel, MBOWriter*);
private:
BTS_NO_COPY(MDP3Main);
+13 -13
View File
@@ -23,6 +23,7 @@
#include <thread>
#include <memory>
#include <set>
#include <optional>
using namespace bts;
using namespace std;
@@ -68,8 +69,7 @@ void run_replay() {
replay_recording(file, naggle_ms, fallback_path);
}
void run_onixs()
{
void run_onixs(MBOWriter* mbo) {
std::vector<std::shared_ptr<IMDPlatform>> platforms;
// get all glbx symbols
std::vector<UserSymbol> cme_symbols;
@@ -78,7 +78,7 @@ void run_onixs()
{
MDP3Main* main = new MDP3Main();
std::shared_ptr<IMDPlatform> ptr(main);
main->start(cme_symbols);
main->start(cme_symbols, mbo);
platforms.push_back(ptr);
}
@@ -110,15 +110,14 @@ void run_cleanup() {
}
std::thread* _md_thread;
void run_market_data_publishers_forever() {
os::set_thread_name("md publisher");
std::optional<std::thread> _md_thread;
void run_market_data_publishers_forever(MBOWriter* mbo) {
const auto& conf = config::get<MarketDataConfig>();
if (conf.use_replay()) {
run_replay();
}
else if (conf.use_onixs()) {
run_onixs();
run_onixs(mbo);
}
}
@@ -142,14 +141,15 @@ void boot_sleep() {
}
}
void run_market_datapublishers() {
bts_assert(_md_thread == nullptr);
void run_market_datapublishers(MBOWriter* mbo) {
bts_assert(_md_thread == std::nullopt);
run_cleanup();
if (!config::get<MarketDataConfig>().use_md()) return;
// start a thread for the md
_md_thread = new std::thread([]() {
run_market_data_publishers_forever();
});
_md_thread = std::optional(
threading::make_thread("md publisher", [mbo]() {
run_market_data_publishers_forever(mbo);
}));
if (config::get<MarketDataConfig>().use_sim()) {
threading::make_thread("ExchangeSim", []() { sim::start_exchange_sim(); })
@@ -359,7 +359,7 @@ void md_tool(std::vector<std::string> const &args) {
else if (cmd == "cleanup") run_cleanup();
else if (cmd == "dump") run_dump(vector<string>(args.begin()+1, args.end()));
else if (cmd == "replay") run_replay();
else if (cmd == "run") run_market_data_publishers_forever();
else if (cmd == "run") run_market_data_publishers_forever(nullptr);
else if (cmd == "relay") relay_everything();
else if (cmd == "test_relay") relay_everything();
else if (cmd == "dumpsecurity" || cmd == "dumpsecurities") dump_db(vector<string>(args.begin()+1, args.end()));
+3 -1
View File
@@ -5,7 +5,9 @@
namespace bts {
namespace md {
void run_market_datapublishers();
class MBOWriter;
void run_market_datapublishers(MBOWriter*);
void md_tool(std::vector<std::string> const& args);
}}
@@ -48,6 +48,10 @@ me(new PImpl(context,
me->publisher.add_or_update(_on_fill(*f,false));
for (const auto& f : update.added())
me->publisher.add_or_update(_on_fill(*f,true));
for (const auto& f : update.changed()) {
me->publisher.add_or_update(_on_fill(*f.previous(),false));
me->publisher.add_or_update(_on_fill(*f.current(),true));
}
me->publisher.flush();
});
}
@@ -66,6 +66,10 @@ AdminServiceServer::AdminServiceServer(bts::api::ServiceContext& context)
if(it != _order_map.end())
_order_map.erase(it);
}
for (const auto& ch : update.changed()) {
const auto& o = ch.current();
_order_map[o->key()] = o;
}
for (const auto& o : update.added()){
_order_map[o->key()]=o;
}
+1 -2
View File
@@ -3,9 +3,8 @@ Fills UI Test
Start the form (Trading->Fills)
Make sure the following columns have filtered when the window starts:
Make sure the following columns have filters when the window starts:
Fill Time: Since Open
Fill Kind: Standard, Leg
Cross an order, get a fill and make sure it shows up
Test sorting and filtering
+2 -4
View File
@@ -17,12 +17,10 @@ and you may also want to clear your orders for a clean slate:
Throughout the test we'll refer to accounts ACC A and ACC A1. These specific accounts aren't necessary, but rather that we have fills in multiple distinct accounts.
Bring up the pnl form (empty).
Bring up a SIM:SI ladder form, pick your favorite expiration. It should be completely empty since we're controlling the md, and the ACC A account should be selected in the account drop down list.
Bring up a SIM:SI ladder form, pick your favorite expiration. It should be completely empty (just prices, no sizes) since we're controlling the md, and the ACC A account should be selected in the account drop down list.
Bring up an order ticket for your corresponding ladder
place a buy order with price 14.000 and qty 10
your ladder should populate now
place a sell order with price 14.010 at qty 10
this will give you a valid Midpoint for your PnL Price in the PnL
place a sell order with price 14.010 at qty 10 this will give you a valid Midpoint for your PnL Price in the PnL
Sell a 1 lot at your bid (14.000)
The Start Net Pos should be 0
Start Pnl should be 0