Compare commits

..
132 Commits
Author SHA1 Message Date
John Colvin 5accd0e2b4 Merge branch 'master' into prod 2020-05-22 19:14:16 -05:00
John Colvin df5ad61cd2 release notes for synthetic gui integer tick sizes 2020-05-22 19:12:29 -05:00
John Colvin 4a7674eb29 synthetic gui supports integer tick sizes 2020-05-22 18:15:53 -05:00
Eric Harding 5a458287f4 update license search info in disable_onixs.py 2020-05-22 15:18:46 -04:00
John Colvin 5921f9f6b5 Merge branch 'master' into prod 2020-05-22 12:34:18 -05:00
John Colvin 4c73a63ae8 release notes for bug fix (unsorted custom state grid params in orderbook) 2020-05-22 12:30:53 -05:00
John Colvin 42b1c921a8 OrderBook custom state grid params are sorted alphabetically (again) 2020-05-22 12:24:42 -05:00
Pedro Pinto fab3334b1e Updated release notes 2020-05-22 11:23:03 -04:00
Pedro Pinto 7239dd3ade Merge branch 'master' into prod 2020-05-22 11:20:17 -04:00
John Colvin dd4c8eb649 Merged csharp-api-examples-sln into master 2020-05-22 10:19:13 -05:00
John Colvin 76a29bdc8a client ui supports all integer tick sizes (not just 1) 2020-05-21 15:26:50 -05:00
John Colvin 06d708f3ea new csharp api examples 2020-05-21 15:17:32 -05:00
John Colvin e2f70cbc19 minor config tweaks needed after upgrading VisualStudio 2020-05-21 13:04:00 -05:00
Pedro Pinto 9d54e47a03 Update CMakeDeps 2020-05-19 17:00:24 -04:00
John Colvin faccdf581e Merged in limit-algo-clean-history (pull request #413)
Limit algo clean history
2020-05-19 13:53:07 +00:00
John Colvin 1a64714a54 always log errors observed in LadderGridHost._volume_from_row (not just when it fails after max_num_tries) 2020-05-18 17:03:39 -05:00
John Colvin 3ba6792a85 wrap LadderGridHost._volume_from_row in try/catch (addresses Pure client crash) 2020-05-18 16:46:23 -05:00
Pedro Pinto c32a7f1d62 Made native orders calculate route when recovered from disk.
This is in response to the Pure debacle of May 17th. CME changed the
MSGW used for certain products. As we only route orders once, when
they are created, any orders that already existed never transitioned
to the new MSGW. This made it so that users could no longer control
these orders. With this change we recalculate routes when the orders
are first loaded from disk. Assuming we have up to date securities,
something that sometimes does not happen unfortunately, this should
result in the order being automatically relocated to its new MSGW. As
a bonus we also get the hability to change MSGW configuration even if
there are working for that MSGW.
2020-05-18 08:59:42 -04:00
John Colvin 10e75f30e6 fix algo params grid enum template for GenericOrderVMs 2020-05-15 13:16:25 -05:00
John Colvin 20eac19c4f GenericOrderVM - normalize white space 2020-05-15 13:16:25 -05:00
John Colvin 8c0765a2c7 fix build 2020-05-15 10:34:59 -05:00
Taha Afzal 7a1f88e813 Merge branch 'limit-algo-clean-history' of bitbucket.org:bluetradesys/spark into limit-algo-clean-history 2020-05-14 22:01:32 -04:00
Taha Afzal 3420f14c8a DeleteAndCover detached hedges by default: 2020-05-14 22:01:22 -04:00
John Colvin 86277dfbfe config toggle for new 'specific' CustomOrderDefinition params dialog (disabled by default) 2020-05-14 20:14:51 -05:00
John Colvin 4839e464c6 added dialog to edit CustomOrderDefinition params of a specific algo node 2020-05-14 20:02:14 -05:00
John Colvin dc2ad071a9 GenericOrderHelper should use 'FirstOrDefault' (not 'First') 2020-05-14 19:59:06 -05:00
John Colvin 65e49992f9 LimitCoverAlgo.do_update - remove redundant conversion from DynamicOrderDefinition to LimitCoverDefinition 2020-05-14 17:08:01 -05:00
John Colvin afbb9ae92d LimitCover algo - 'Hedge Ticks' and 'Top Ticks Offset' params actually assigned to DeleteAndCover child 2020-05-14 12:29:29 -05:00
John Colvin ff088142d9 fix custom state output grid 2020-05-14 12:29:29 -05:00
Pedro Pinto 93b7cf346b Added an option for the Cover algo to detach hedges. 2020-05-14 10:20:46 -04:00
Eric Harding f0edeadd50 Make shared mem size configurable 2020-05-13 20:29:38 -05:00
Taha Afzal 8ee9b84488 Merge branch 'limit-algo-clean-history' of bitbucket.org:bluetradesys/spark into limit-algo-clean-history 2020-05-13 20:39:13 -04:00
Taha Afzal e3648f5373 Better Logging 2020-05-13 20:39:07 -04:00
John Colvin facd3a9d81 LimitCoverAlgo - remove unnecessary else block in 'do_before_launch' 2020-05-13 17:26:13 -05:00
John Colvin 84969cb481 LimitCoverAlgo - dont update me->child_qty in the child_changed callback, since its async 2020-05-13 17:21:32 -05:00
John Colvin 6d521e77db LimitCoverAlgo - don't override do_launch_attached (it's not supported) 2020-05-13 17:15:30 -05:00
John Colvin 82c49f8f49 remove redundant methods: tick_up/less, payup/less 2020-05-13 17:15:30 -05:00
John Colvin e074309da1 typo broke build (missed constructor in 'TickOffset' -> 'PriceOffset' refactor) 2020-05-13 17:15:30 -05:00
John Colvin 2d622a5f39 LimitCover algo - convert 'ticks_offset' param be in price terms 2020-05-13 16:55:01 -05:00
John Colvin d51d20e84c LimitCover algo - remove cancel_on_blah params, add hedge_ticks 2020-05-13 16:18:46 -05:00
John Colvin 2ccdef9dc5 fix clang build errors 2020-05-13 15:59:45 -05:00
John Colvin 42e62a30bf to_string(OrderError) uses generated protobuf enum Name method 2020-05-13 15:32:27 -05:00
John Colvin c233c607fa Formatter.h - pass strings/templated args by const ref instead of value 2020-05-13 13:40:07 -05:00
John Colvin 462cb930f7 dont redefine ChildTemplate 2020-05-13 13:20:55 -05:00
Pedro Pinto 2c0f920422 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2020-05-13 08:54:14 -04:00
Pedro Pinto 5ee26065ca Updated .gitignore 2020-05-13 08:53:52 -04:00
John Colvin 550a4e8d50 add config toggle to enable/disable LimitCover algo 2020-05-12 19:11:16 -05:00
John Colvin 99b8221efc LimitCoverAlgo unit tests 2020-05-12 19:11:16 -05:00
John Colvin 19e74c30f8 AlgoTestDriverBase - AlgoState << operator 2020-05-12 19:03:53 -05:00
John Colvin 26693a4c98 AlgoTestDriverBase - after_child_changed callback, and find_child method 2020-05-12 19:03:53 -05:00
John Colvin d8bdb69a7c AlgoTestDriverBase - ChildTemplate impl, added OrderDefinition param to child_launching callback 2020-05-12 19:03:53 -05:00
John Colvin 6d357a74e1 AlgoTestDriverBase - expose cancel/cancel_child methods 2020-05-12 19:03:53 -05:00
John Colvin 73f4e4814c LimitCover algo for pure 2020-05-12 19:03:53 -05:00
John Colvin 312bc4be15 add Formatter.h (defines commonly needed << operators for algos, basic string formatting utils) 2020-05-12 18:55:37 -05:00
John Colvin a0c3ba449a add DynamicOrderAdapter (util to keep param proto conversion code out of the algo) 2020-05-12 18:55:37 -05:00
John Colvin d5a2a7f3f0 orderman_utils.h pragma once 2020-05-12 18:55:37 -05:00
John Colvin eda8c94def replicate_to_linux_vm.py - ignore some dirs generated by visual studio 2020-05-12 18:55:37 -05:00
John Colvin 9a8e80e149 add payup/less price calc utils (Security.h)' 2020-05-12 18:55:37 -05:00
John Colvin 6a7e3d739d fix typo 'epislon' -> 'epsilon' 2020-05-12 15:58:00 -05:00
Taha Afzal e6b8274813 DeletAndCoverOrderDefinition does not contain the nested enum anymore 2020-05-12 15:58:00 -05:00
Taha Afzal 9783520c62 DeleteAndCover completed 2020-05-12 15:58:00 -05:00
Taha Afzal 7e7f0bc37f Adding DeleteAndCover proto 2020-05-12 15:58:00 -05:00
John Colvin b9e2ecdffa Merged in transitive-security-compare (pull request #411)
Transitive security compare

Approved-by: Eric Harding
2020-05-11 19:42:50 +00:00
Eric Harding f1c021a474 Merged in security_preload (pull request #412)
Security preload
2020-05-11 19:41:00 +00:00
Eric Harding 6fda54ffa1 Merged master into security_preload 2020-05-11 15:44:50 +00:00
Taha Afzal bdb0adc16c TASCounter test fixed 2020-05-07 13:07:44 -04:00
Eric Harding 4b19b0ea14 update launch.json 2020-05-06 18:26:03 -05:00
Eric Harding fab1b4dd0f Add SecurityPreload key to pre-load securities from the security DB for sunday startup. 2020-05-06 18:25:29 -05:00
John Colvin f3ed3075cf fix Security comparison impl (its transitive now) 2020-05-06 17:35:46 -05:00
John Colvin 742af36b93 Security comparison unit test (addresses bug that caused Krandall OrderBook crash) 2020-05-06 17:35:46 -05:00
John Colvin c573b5fbb2 add ProtoBufCapture extension (writes TableUpdate state to file when subscription is disposed) 2020-05-06 17:30:00 -05:00
Eric Harding 2d6d4af594 add vscode spark local linux debugger settings 2020-05-06 12:27:11 -05:00
John Colvin 2dcfad979c move FileStreamHelper.cs to spark_core 2020-05-06 12:11:03 -05:00
Taha Afzal ba7e35649e Ignore Spread Pos Limits are defaulted to true 2020-05-06 10:36:40 -04:00
Pedro Pinto cda4a870b7 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2020-05-05 06:14:30 -04:00
Pedro Pinto b3a8564365 Fixed int32 params in DynamicOrders to be int32 rathern than uint32. 2020-05-05 06:13:40 -04:00
Pedro Pinto 1e0114a4cf Treated disconnected orders as working. 2020-05-05 06:13:06 -04:00
Eric Harding 3a6e95ef05 Downgrade security scanner message to info 2020-05-04 16:16:55 -04:00
Eric Harding 6875263743 Update 'update' instructions for spark_sdk 2020-05-01 14:17:07 -04:00
Taha Afzal eac828f323 Merge branch 'master' into TASCounter 2020-05-01 13:49:27 -04:00
Taha Afzal 1635d6f649 Added test for TASCounter 2020-05-01 13:48:50 -04:00
Eric Harding 2da535e9ff Update onixs license 2020-05-01 12:51:31 -04:00
Taha Afzal a98be44a40 Merge branch 'master' into TASCounter 2020-04-30 13:59:49 -04:00
John Colvin 6210ac2ee6 OrderBook custom state grid only shows data if the selected rows are of the same root/branch 2020-04-29 16:21:17 -05:00
Pedro Pinto cfebab0488 Merged in api_improvements (pull request #405)
Api improvements

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2020-04-28 20:06:53 +00:00
Pedro Pinto eacd766359 Updated release notes. 2020-04-28 16:05:21 -04:00
Pedro Pinto 8fb8f8ff58 Merge branch 'master' into api_improvements 2020-04-28 15:58:43 -04:00
Taha Afzal f4b22816a2 Putting config to hide TASCounter 2020-04-27 23:39:41 -04:00
Taha Afzal 1029a464d7 Fixing the bts::timestamp tp protobuf timestamp function (unused, but useful) 2020-04-27 09:57:21 -04:00
Taha Afzal fd8b8e03ea cleanup, comments 2020-04-26 23:10:16 -04:00
Pedro Pinto 41cf66a943 Added the hability to retrieve all securities in the API. 2020-04-24 08:00:13 -04:00
Pedro Pinto 1c57c9a012 Added position updates in the API. 2020-04-24 07:03:40 -04:00
Taha Afzal 8ccab66120 Merge branch 'add_custom_algo_state_reporting_to_api' into TASCounter 2020-04-23 18:14:28 -04:00
John Colvin e2b2f7ec9c better default widths for custom state grid columns 2020-04-23 16:11:35 -05:00
John Colvin b3c897bf0f custom state grid params are sorted alphabetically 2020-04-23 16:02:38 -05:00
John Colvin 87c9f54c8b custom state grid populates with first child row that has params (if selected row doesn't have any itself) 2020-04-23 15:59:49 -05:00
John Colvin df56b76ab6 OrderBookRowVM - remove dead property 'is_visible' 2020-04-23 15:54:30 -05:00
Taha Afzal 28ff13db11 Merge branch 'add_custom_algo_state_reporting_to_api' into TASCounter 2020-04-23 13:23:30 -04:00
Taha Afzal 2957075769 comments 2020-04-23 13:22:33 -04:00
John Colvin a6477824a8 OrderBookVM - clear the previous 'custom state' params before adding the new ones 2020-04-23 11:16:47 -05:00
Taha Afzal 17431f5ac0 Code complete, output is not showing in the UI which is making testing harder 2020-04-23 00:09:23 -04:00
Taha Afzal 8708f02fe0 Merge branch 'add_custom_algo_state_reporting_to_api' into TASCounter 2020-04-21 17:44:58 -04:00
Taha Afzal 752fb2bc4a Merge branch 'master' into TASCounter 2020-04-21 17:10:32 -04:00
Taha Afzal 6efb78ecf9 Code complete 2020-04-21 14:26:32 -04:00
John Colvin 75c1970aef remove useless IObservable 'Select' op in 'SubscribeEach' extension 2020-04-20 10:39:31 -05:00
John Colvin c2583518d7 OrderBook was only updating custom state grid when selection changed 2020-04-20 10:37:12 -05:00
John Colvin 8792871387 add algo custom state grid to orderbook (right panel) 2020-04-17 16:36:05 -05:00
Eric Harding 68a593af7b Add more info to system test 2020-04-17 16:48:10 -04:00
John Colvin 17c2321a82 Merged in SP-289-naudio-crash (pull request #407)
SP-289 naudio crash

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2020-04-16 18:11:18 +00:00
John Colvin 0affc8a942 SP-289 naudio crash 2020-04-16 12:07:44 -05:00
John Colvin ce36ea4109 move Delegates.cs to spark_core 2020-04-16 10:47:41 -05:00
Pedro Pinto 7c29a3d1d8 Added custom state reporting to API. 2020-04-13 11:30:32 -04:00
Taha Afzal 90bc20575a TimeStamps fixed 2020-04-13 08:56:49 -04:00
Pedro Pinto cb268f505a Added settlements to the new algo API. 2020-04-13 08:19:55 -04:00
Pedro Pinto dfd93b836b Updated externals 2020-04-13 07:44:52 -04:00
Taha Afzal 28e9c66675 Added all the state, order execution remains 2020-04-10 12:54:57 -04:00
Taha Afzal c0520d6a31 Initial Check-in, skeleton code 2020-04-09 09:30:00 -04:00
Pedro Pinto 1ba6e3280c Updated CMakeDeps 2020-04-06 09:38:19 -04:00
Eric Harding 3a6f50dfea Merge branch 'prod' of bitbucket.org:bluetradesys/spark 2020-04-03 15:40:30 -04:00
Eric Harding d711f99548 fix externals pointer 2020-04-03 15:40:15 -04:00
pfisher 07ace80871 fixed keycode.conf 2020-04-03 13:40:56 -05:00
John Colvin 93c6277bfc Merged prod into master 2020-04-03 13:20:33 -05:00
Jake Norbut 7ac9d05ae2 Merged in manual_fills_permissions (pull request #403)
add permission filter to manual fills

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2020-04-03 18:18:16 +00:00
Eric Harding 5a4b31ccc0 add cpp sample to service.md api page 2020-03-20 12:10:40 -04:00
John Colvin fb568aab39 SP-288 ladder crash (IndexOutOfRange ex accessing price array) 2020-03-19 15:28:27 -05:00
Eric Harding 64416de10d Fix the braces in my sample. 2020-03-16 20:41:23 -04:00
Eric Harding 46953cb639 add c# market data sample 2020-03-16 20:39:48 -04:00
Eric Harding 7e68a235ee Add securities sub to client_sdk_example 2020-03-16 15:18:39 -04:00
Jake Norbut 85f2285ac0 add permission filter to manual fills 2020-03-09 09:28:23 -05:00
Eric Harding 70e0979836 override sim port for sdk image 2020-03-06 12:45:09 -05:00
Kevin Darby 1f80c3366e Merged in master (pull request #401)
Master
2020-03-03 19:16:43 +00:00
Kevin Darby 19e66a1c32 add a new stunnel path for Pete 2020-02-25 11:16:40 -05:00
152 changed files with 5801 additions and 1340 deletions

No files matched your search

+1
View File
@@ -35,3 +35,4 @@ trace_data/
.ionide/*
/.ignore
/build
.clangd/
+24
View File
@@ -1,6 +1,30 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Spark Local",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd",
"args": [
"run",
// "test",
// "-a",
// "[ilink3]"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/server/",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "SparkVM Launch",
"type": "cppdbg",
@@ -124,4 +124,17 @@ service OrderService {
```
### C++ Example
```cpp
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/OrderService.client.pb.h"
using namespace bts::api;
ServiceContext ctx;
auto orders = ctx.locate<orderman::OrderService>();
auto unsub = orders.subscribe_fills().subscribe([](const auto& update) {
std::cout
<< "Added " << update.added().size() << " items, "
<< "Removed " << update.removed().size() << " items, "
<< "Updated " << update.updated().size() << " items, "
<< "Total items " << update.all().size() << std::endl;
});
std::this_thread::sleep_for(std::chrono::minutes(5));
```
+49 -1
View File
@@ -1,3 +1,51 @@
+++
title = "Market Data"
+++
+++
Use the MarketDataService to subscribe enumerate all available securities.
```cs
using System;
using System.Threading;
using bts.api;
using bts.api.core;
namespace sample {
class Program {
static void Main(string[] args) {
using var context = new ServiceContext();
var md = context.locate<MarketDataService>();
using var sub = md.subscribe_securities().Subscribe(update => {
foreach (var security in update.added) {
Console.WriteLine($"Added security {security.Id}: {security.Symbol}");
}
});
Thread.Sleep(TimeSpan.FromMinutes(5));
}
}
}
```
```cpp
#include "lib/api/public/ServiceContext.h"
#include "spark/lib/domain_model/Security.h"
#include "lib/api/services/MarketDataService.client.pb.h"
#include <thread>
using namespace bts::api;
int main(int argc, char** argv) {
ServiceContext ctx;
auto md_service = ctx.locate<bts::api::MarketDataService>();
auto sub = md_service.subscribe_securities().subscribe([](const auto& update) {
for (const auto& s : update.added()) {
std::cout << "Added security: " << s->id() << ": " << s->symbol() << std::endl;
}
});
std::this_thread::sleep_for(std::chrono::minutes(5));
return 0;
}
```
@@ -68,12 +68,21 @@ To update your Spark SDK simply download an unzip a new [spark_sdk.zip](https://
To preserve the old image so you can go back to it later run `docker tag spark_sdk spark_sdk:your_custom_tag` before loading the new SDK image.
<!--
Then stop, remove and restart spark_sdk.
### Re-build the Dev Container
If you are using Visual Studio Code you also need to rebuild the dev-container so it picks up the new image.
#### Inside VSCode
To re-build from inside Visual Studio Code. Click the 'Remote Explorer' icon on the left hand side.
You should see your spark_sdk project listed under the Containers section. Right click on it and select **rebuild-container**.
#### Command Line
To re-build from the command line run `docker ps -a` and find the row with the spark_sdk image.
```
c:\bts> docker stop spark_sdk
c:\bts> docker rm spark_sdk
c:\bts> docker run -d -v %cd%:/sdk --name spark_sdk bluetradingsytems/spark_sdk
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
54d96cfa3888 spark_sdk "/bin/sh -c 'echo Co…" About a minute ago Up About a minute 127.0.0.1:9000-9001->9000-9001/tcp quizzical_lichterman
```
Then run `docker rm 54d96cfa3888` (use your container id instead of 54d96cfa3888) or `docker rm quizzical_lichterman` (use your container name).
``` -->
+1 -710
View File
@@ -1,710 +1 @@
+++
title = "Release notes"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.15.3
### Bug Fixes
- Fixed a bug where some CME currency spreads exhibited incorrect tick values
## 2.15.2
### User Visible Changes
- Excel RTD plugin can now receive ICE market data
## 2.15.1
### Bug Fixes
- Fixed a FIX log parsing issue that resulted in exchange gateway logon problems
## 2.15.0
### User Visible Changes
- Added ability to hide Ladder status messages via Ladder Settings -> Left Panel tab -> Show/Hide groupbox -> Status Message
- Synthetic cloning now allows for any parameter to be changed, including tick size and leg contracts
- Added ability to import synthetics from a CSV file; see our manual for further details on this process
- Reduced Iceberg reloading latency
### Bug Fixes
- Fixed a bug where highlighted fills could lose their highlight status
- Fixed a bug where the (Mini) OrderBook PIQ could be displaying an old value
## 2.14.3
### Bug Fixes
- Fixed a bug regarding ICE logon messaging
## 2.14.2
### Bug Fixes
- Fixed a bug regarding ICE messaging
## 2.14.1
### Bug Fixes
- Fixed a bug regarding ICE logon authentication
## 2.14.0
### User Visible Changes
- All ladders can now be recentered by (shift or alt) + double clicking over the Price column in a ladder
- Added a Max Order Quantity setting to the Safety Checks tab on the Ladder settings. This setting allows for a user-defined maximum order quantity.
- Added a Price Display Format setting to the Market Price and Size Display tab on the Ladder settings. This setting allows for price levels in the ladder to be displayed without a decimal separator.
- Synthetic instrument creation with 3 legs no longer defaults to butterfly quantites
### Bug Fixes
- Fixed an issue where market transition were not observed by IAlgoOrder
- Fixed a bug in error reporting
- Fixed a bug in the PnL window where specifying a certain grouping order would lead to a UI failure
## 2.13.0
### User Visible Changes
- Added the name of the spreader to the parent algo badge (for spreader child orders), in the fills, orderbook and floating orderbook windows
- Added support for sounds for new orders
- Added a new quoting throttle parameter for the spreader, that throttles quotes that are moving toward the inside market but are still a configurable number of ticks away
- Enabled the ladder to remember the left panel scroll position across front end restarts
### Bug Fixes
- Fixed the local fills window so that it does not show the admin fills
- Removed the deleted accounts from the ladder accounts dropdown
- Fixed the orderbook price for iceberg of spreaders
## 2.12.0
### User Visible Changes
- Added support for ICE market data and order execution
### Internal Changes
- Lots of permission / admin changes to facilitate ICE
## 2.11.0
### User Visible Changes
- Fixed fills form time filter to also apply to SOD fills
- Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections
- Removed the option to enter manual spread fills
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
### Internal Changes
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
- To facilitate support, allowed the bts user to view and edit algos from all other users
- Improved system tests
- Improved log messages in UI to include dispatcher thread name
- Added support for Windows core dumps in the UI
## 2.10.0
### User Visible Changes
- Fills now include information about the algo that originated the fill
- A new, more powerful PnL form is now available
### API Changes
- Added security state information (pre open, halted, normal, closed, etc)
### Bug Fixes
- Brought back the "select all" check box in the mini order book
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
### Internal Changes
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
## 2.9.3
### Internal Changes
- Fixed system test scripts
## 2.9.2
### Bug Fixes
- Fixed an issue with the exchange simulator that could lead to crossed markets
## 2.9.1
### Bug Fixes
- Fixed an issue with trade sound alerts, where in some cases the use of a "In Between" condition could lead to a UI failure.
- Fixed an issue with the display of the new raw position column in the market view window.
- Fixed the popup label that describes the raw position information in the ladder
### Internal Changes
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
### Api
- C# API sample now uses synchronous calls.
## 2.9.0
### User Visible Changes
- Improved Workspace management User Interface
- Added the ability to change grid background colors
- The Spark UI Window group now has a title in the Windows task bar
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
- Added import facilities for GMI position files
- Added the ability for risk managers to enter manual fills
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
### Bug Fixes
- Fixed the settings action menu (Load/Save/Reset to Factory)
- Fixed the method used to determine if a security is expired to take into account the start of day time
### API Changes
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
- Added the ability for algo orders to distinguish fills existing prior to launch
### Internal Changes
- Changed how we packaged certain third-party components
- Added logging of entitlement status
- Tagged outgoing orders with sequence number of marketdata or FIX event that triggered them
## 2.8.3
### Bug Fixes
- Fixed a bug that could cause an OrderBook's Security column filter to be blank
- Fixed a bug that could cause a UI failure when clearing a filter selection on a filtering widget
## 2.8.2
### Bug Fixes
- Added proper display factors for SOM spreads
- Fixed a bug that could cause a UI failure when trying to change settings in a synthetic ladder
## 2.8.1
### Bug fixes
- Fixed an error in the UI grids filtering widget
## 2.8.0
### User Visible Changes
- Added ProRata and Scratch algos
- Ladder badge now grows/shrinks to fill the containing cell
- Attached algo parameters are now displayed in the update panel
- Fixed a bug where the update panel would show '-' values when only
1 order was selected
- Fixed a bug that cause cause a failure on startup if the user
loaded a workspace with a Ladder that had a dynamic order type
selected by default
- Increased width of login panel to better show login error messages
- Added optional 'Maker/Taker' pricing to Spreader algo
- Algo buttons in ladder can now be made to deactivate after launching an algo or remain 'sticky'
### Internal changes
- Made it easier to generate simple UI's for new order types
### API Changes
- Dynamic orders can now use QuantityMeasure parameter types
## 2.7.2
### User Visible Changes
- Changed the login dialog to avoid leaking workspace names from other users.
- Added new Splitter algo. From the manual: The splitter is useful
for pro-rata markets where you want to vary your exposure
independent of the order quantity that you want to fill. The
splitter will complete when the number of fills reaches or exceeds
the order quantity but it will put exposed_qty total size on the
market. Splitter favors order size over queue position.
### Internal Changes
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
## 2.7.1
### User Visible Changes
- Fixed some issues with the Cover algo, where in some circumstances
it would erroneously cancel launched hedges on completion.
## 2.7.0
### User Visible Changes
- Allowed algos to process "in-flight" fills while shutting down
- Changed the cover algo to continue to hedge while shutting down
### API Changes
- Changed the API so that algos do not fully terminate until all
their children are terminated. Algos that are in the process of
shutting down in response to a complete or cancel call will still
receive child updates, which they can use to calculate and report
an accurate fill count (during this shutdown stage algos cannot
create, cancel or update children). A new IAlgoServices method,
is_shutting_down(), can be used to determine if an algo is
shutting down.
## 2.6.2
### User Visible Changes
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
- Fixed a rare race condition that could lead to erroneous limit enforcement
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
## 2.6.1
### User Visible Changes
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
## 2.6.0
### User Visible Changes
- In the ladder made last trade more distinctive when overlayed on the volumn column
- In the ladder, corrected display of volume tool tip
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
- Added an option for the Top Algo to delete itself if within N ticks from top of book
- Address a rare issue that could lead to an error report on UI exit
- Added margin risk limits
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
- Avoided switching the current account and order type on open ladders when limits are changed
### Internal Changes
- Ensure that admin logins are always allowed to login, regardless of keycode status.
## 2.5.0
### User Visible Changes
- Added loss limit risk check
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
- Allowed the mini order book defaults for column widths to be changed from the mini order book
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
## 2.4.0
### User Visible Settings
- In the ladder, changed the last trade column to accumulate successive trades at the same price
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
- Added ladder settings for the mini order book
- Added a safety check to guard against unintentional market crosses
- Improved the Queue Holder mode in the spreader:
- Added a Show Quantity, allowing the spreader to keep queue position of the trailing orders as Fills (of size Show Qty) happen
- Improved spreader performance when in Queue Holder mode
### Internal changes
- Suppressed order callbacks for completed/canceled orders. This should eliminate some bogus error logs by spreader orders.
- Addressed a potential concurrency issues with service initialization.
## 2.3.0
### User Visible Changes
- Introduced multiple settings profiles for the ladder
- This allows multiple standard configuration for the ladder, optionally with security specific variations
- Made Cover order more independent from its hedging chidren:
- Cover continues to operate even if an hedging child is canceled
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
- Changed manual fills to appear in day PnL rather than in the SOD records
- Add buy/sell coloring to fill rows in fills window
- Streamlined the mini order book:
- Display of the mini order book popup is quicker
- Only exchange orders are displayed
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
- A checkbox column has been added to make clearer which rows are selected
### API Changes
- Enhanced the API to allow algos to implement custom cancel handling
- Allowed algos to control their visibility in the ladder through the API
### Internal Changes
- Added system requirements to Spark documentation
- Implemented keycodes
- Temporarily rejected stop and stop limit orders, pending bug fixes
## 2.2.0
### User Visible Changes
- Made our orders easier to spot in MBO display
- Changed middle-click on center column to recenter ladder and introduced Alt middle-click on center to bring up the MBO popup
- Fixed Net/Absolute radio button in risk admin UI
- Fixed double counting of spread legs in limits
- Removed expired fills from the system
- Improved handling of orders with undiscovered securities
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
- Fixed display of algo studio algo types in order book
- Hid net change setting in synthetic ladders
- Fixed a number of problems with filtering on the Fills form
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
- Added security symbol column to fills window
- Re-organized ladder settings dialog to make it clearer and easier to use
## 2.1.4
### User Visible Changes
- Fixed handling of cancel button in security picker
## 2.1.3
### User Visible Changes
- Fixed a simulator bug in the handling of 0 sized orders
## 2.2.1
### User Visible Changes
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
## 2.1.1
### User Visible Changes
- Added PIQ to GLBX Iceberg orders
- Corrected handling of stop limit orders
## 2.1.0
### User Visible Changes
- Added support for the GLBX Iceberg order type
- Allowed quick algo param edits by middle-clicking on either the bid/ask columns or a "favorite" button
- Added support for manual fills, either entered manually or imported from file. Manual fills are used only for P&L purposes, they have no impact on limits
- Allowed the LTQ and VAP values to be overlayed on the same ladder column
- Added day position information to the ladder
- Added PIQ to the mini order book
- Fixed a rare bug that could lead to server failure when requesting market data
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
## 2.0.5
### User Visible Changes
- Fixed a bug in the handling of rejected QHO orders
- Implemented stricter position limit enforcement
- Fixed a bug that could cause server failure when trying to attach to already attached orders
## 2.0.4
### User Visible Changes
- Fixed performance regression in order manager
### Internal changes
- Improved market data diagnostics for bad configurations
- Changed market data default buffer size
## 2.0.3
### Internal changes
- Improved backwards compatibility in UI settings
## 2.0.2
### User Visible Changes
- Middle click the ladder price column to show the orders for that price level
- Middle click the bid or ask column in the ladder no longer sends an order
### Internal changes
- Improved market by order performance under heavy load
- Fixed a concurrency bug in SIM
## 2.0.0
### User Visible Changes
- Support for attaching and detaching algos to existing orders
- Support for display and update of all parameters in running algos
- Streamlining of order badge decorations in ladder
- New order search/filtering algorithm for order book
- Simpler representation of complex algo chains in order book and mini order book
- Visual and usability improvements to mini order book
- Streamlining of algo parameter dialog in ladder
- New spreader queue holder behavior
- Visual improvements to the order book
- New server side API
- Improved admin UI performance
- Optimized PIQ rendering
### Internal changes
- Exchange simulator no longer requires daily sequence number reset
- Improved MD resilience to corrupted shared memory
- Native orders can now buffer updates/cancels when disconnected or pending first acknowledgment
- Improved settings manager tool output
- Added diagnostics in the event MD memory corruption is detected
- Added systems tests for join,lean, iceberg and spreader orders
- Made exchange order recovery more resilient in the presence of database loss
## 1.6.2
### User Visible Changes
### Exchange simulator
- Exchange simulator now now produce position in queue data
## 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)
- Fixed a bug that occasionally would cause windows to be position outside the visible screen on startup
- Fixed a bug in the order book form that would occasionally cause the Recreate Order option to be unavailable
- Added a confirmation step to Cancel All, Cancel Buys and Cancel Sells commands
- Fixed Aggressor coloring and ladder high/low coloring in the SIM execution gateway
- Added initial support for ICE market data
- In an effort to streamline the PnL report we've made the following changes:
- The PnL report will by default filter for Futures contracts only
- If you wish to see Spread contracts again you may change Security Type column filter
- The PnL report will now only show rows in which there is a position (net and/or day)
- The Exchange, Start PnL, Total Net Pos, and Total Net PnL columns are now hidden by default
- These columns can be restored by toggling their visibility within the PnL's Settings form
- A new Traded Today column has been added to the PnL
- Filtering on this column will allow a user to restrict the report to only show today's position
### Internal changes
- Improved organization of the shell notification architecture
- Added logging to help troubleshoot shell exit hang
## 1.5.6
### User Visible Changes
- Fixed a bug that could cause limits to be erroneously enforced
- Improved algo documentation
- Changed how the spreader uses net change
- Simplified trigger algo user interface
### Internal changes
- Defaulted to not recording sim market data
- Improved documentation build
- Added additional performance tracing
## 1.5.5
- Fixed Market View 'stale working orders' bug
## 1.5.4
- Avoided logging when warming up cache
## 1.5.3
- Fixed sounds startup issue
## 1.5.2
- Added better logging to AlgoDefinitionService
## 1.5.1
- Fixed missing securities in the Fills and P&L windows.
## 1.5.0
### User Visible Changes
#### New Features
- This release introduces a major new feature, the Algo Studio, a
set of tools allowing traders to create their own algos by
composing existing algos to express unique strategies.
#### User Interface Improvements
- Removed security key field from most grids
- Removed trade key field from time and sales grid
- Fixed the "Since Open" time filter to use the exchange open time rather than 8:30 AM
- Fixed the floating order book so that rows are no longer
highlighted after an order is modified
- Relocated quantity and price columns in floating order book immediately after the cancel column; users now can move the mouse a much shorter distance when modifying their orders
- The escape key now closes the floating order book
#### Bug Fixes
- Fixed the "Since Open" time filter to use the exchange open time rather than 8:30 AM
- Fixed a bug in the order book where sometimes parent orders would not update their last message column.
#### Algos
- Added OCO Algo
- 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
#### SDK
- Improved SDK documentation and usability
- Dynamic orders are now properly identified in the order book
#### Documentation
- Information on the Spark API has been added to the Spark manual
- The documentation has been updated and reorganized to make information easier to find
### Internal Changes
- Improved robustness of limit system test
- Fixed a bug in handling of built-in bts to user that could lead to failure to login as bts in old systems
- Improved market data subsystem to support security definition updates
- Fixed a startup race in market data that could lead to data corruption on startup
- Increased default order price precision to 8 digis as required by some CME products
## 1.4.11
### New Features
- Floating Order Book
- The floating order book provides a quick view of orders occupying a price level in the ladder
- Launch the Floating Order Book by middle-clicking a ladder badge
- Cancel orders by clicking on the red X in the Cancel column
- Quantity and Price modification
- Clicking on a row will select it, providing the option for changing your price and quantity
- Enter in a new value or use the up/down buttons to set a desired value
- Press the green checkbox to finalize your change and update the order
### SDK
- Improved SDK documentation and usability
### User Interface Improvements
- The following spreader quoting parameters have been deprecated:
- Hidden parameter
- Price Type setting of the Conditional parameter
### Bug Fixes
- Fixed a limit enforcement problem that could lead to 0 sized orders being sent to the exchange
## 1.4.10
### Bug Fixes
- Removed duplicate entries in performance log
## 1.4.9
### SDK
- Added ticker events to the order API
### Performance Improvements
- Significantly improved hedging speed
### Bug Fixes
- Fixed a visual oddity in the order book UI when using High DPI displays
## 1.4.8
### Marketdata
- Additional trade details for CME ticker
## 1.4.7
### Bug Fixes
- Corrected FIX tag 1028, ManualOrderIndicator, when orders are triggered by algos
## 1.4.6
### Ladder Usability Improvements
- The ladder can now be zoomed in/out according to a user's preference
- The zoom slider can be enabled from Ladder Form -> Settings button -> Left Pane tab -> check Zoom Slider checkbox
- The zoom functionality can also be performed via hotkey and mouse wheel combinations
- Zoom In = CTRL + Mouse wheel up
- Zoom Out = CTRL + Mouse wheel down
- Reset zoom = CTRL + Mouse wheel click or double click on the zoom slider
### Exchange simulator
- Improved the exchange simulator to track real market data more closely
### Bug Fixes
- Fixed a UI crash in some cases when attempting to sort the order book by time
## 1.4.5
- Fixed entitlement validation for ICS spreads
- Tuned performance logging
## 1.4.4
### Performance Improvements
- Reduced performance logging activity
## 1.4.3
### Ladder Usability Improvements
- Controls found in Left Pane of the ladder can now be hidden via the checkboxes found under Ladder Form -> Settings button -> Left Pane tab
- The Left Pane is now scrollable with the mouse wheel when there isn't enough vertical space to accomodate the entire form
- The Left Pane can now grown and shrunk horizontally via the splitter between the left pane and the main ladder area
### Performance Improvements
- Requoting and hedging latency has been reduced by up to 30%
### Software Development Kit (SDK) Improvements
- The API now enforces authentication
### Bug Fixes
- The Market View form now honors filters when new securities are added
## 1.4.2
### User Interface Improvements
- When creating a synthetic from the Market View window, securities are now added to the Create Synthetic dialog in the same sort order as they appear in grid
### Bug Fixes
- Shell no longer crashes when removing a security from the synthetics window
- It is now possible to create a new synthetic using the same name as a previously deleted synthetic
### SDK Improvements
- Improved Client and Server SDK clarity and documentation
### Performance Improvements
- Synthetic calculation speed was improved by 400%.
### Risk / Entitlement System
- Risk Admin UI
- Spark users now have separate Trading, RiskAdmin, and/or MDAdmin permissions
- Spark users can now only add other users or accounts if permissioned to do so.
- Market Data Entitlement
- Automated market data reporting
## 1.4.1
### Tabs
- Tabs now shrink and expand to make better use of horizontal space
### Bug Fixes
- Fixed a bug that could cause excessive CPU usage when a connection broke
- Fixed a layout bug in hedging/requoting performance reports
- Fixed account name mismatching bug for old fills
### Absolute Position limits
- Positions can now be netted or absolute for purposes of limit enforcing
## 1.4.0
### Speed Enhancements
- Improved requoting and hedging performance
- Improved order book form performance
### Server Side SDK
- Added a beta version of the server side SDK
### Front End
- Added status bars to all UI grids
### Reporting
- Added reporting of hedging and requoting performance
## 1.3.8
### Speed Enhancements
- Further speed enhancements
### Conditional (new quoting parameter)
- Reduces re-quoting updates by only quoting if N ticks away from the opposite inside market.
- Quotes are canceled when it gets further away.
- "Price Type" dictates if this logic applies to the quoting market or to the synthetic spread market.
## 1.3.7
### Net Change pricing
- The ability to price synthetics based off of net change
## 1.3.5
### Speed Enhancements
- Further speed enhancements
### Separation of implied and direct market data
- Viewing within a market view will now show 3 columns
- Direct
- Implied
- Merged
- Ability to configure the market data size in a ladder as the following
- Direct
- Implied
- Merged
### Synthetic creation option to choose Merged or Direct
- The ability to choose Merged or Direct per leg when creating a synthetic
### Volume at price
- Within a ladder, you can now see a volume at price graph
- This is configurable from a ladder's settings as well
### Customized settings in all windows
- All windows now contain a settings page where grid settings (font color, size, etc) are applied
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
## 1.3.1 Through 1.3.4
### Speed Enhancements
- Further speed enhancements
## 1.2
### Market View
- Synthetic future leg working orders are now shown
### Order Book
- Many visual facets of the Order Book grid can now be customized
- The Order Book settings page can be accessed from the gears icon in the Order Book toolbar or from the right-click menu
- The Expand/Collapse All button has been separated into two different buttons on the toolbar
### Sound Alerts
- Sounds alerts can now be configured for various order events
- Configure sounds under the Sound/Notification Triggers on the Order Book settings page
### Fills
- Newly launched fill windows will filter out start-of-day records by default
### PnL
- Prices are now formatted with penny precision
- Day PnL has been added to the Totals footer at the bottom of the window
### Ladder
- Order badges now have been improved to communicate a variety of order state
- Background color now indicates order side
- Child orders will display a one-character abbreviation to their parent order enclosed in a rectangle; the rectangle will have a fill color matching the parent order
- Border color around badge will match the associated algo color
- Exchange orders will present with a solid dot in the badge's top-right corner
- When multiple orders reside at the same level, a solid triangle will be present in badge's bottom-right corner; all other badge decorations will not be visible in this state
- Hedge orders will present with a vertical line inside the badge's left border
- Trigger orders, once triggered, will no longer be shown in the ladder
- Session High and Low visual indicators have been added to the Price column
- Session range is now indicated in the Price column
- A button reflecting position has been added above the quantity field
- This button reflects the position for the currently selected account
- Pressing the button will copy the position size to the quantity field
- Order quantity button size values are now customizable
- Default size preset values can now be changed, as well as added or removed
- This can be configured per ladder instance (i.e. per security) by making use of the Custom tab
- Synthetic ladders that were saved as part of a workspace are now restored on startup
### Synthetics
- Hedge Ratio parameter has been renamed to Lean Qty Factor
- Spreader now includes a Hidden mode parameter
- A quoting leg can be hidden, ie an order will not be sent out until the market crosses the computed quoting price. When that happens, the quote is sent out as an IOC order.
### Algos
- Trigger orders now can contain a Trigger Exit parameter based on time or price; once the exit condition has been met the Trigger order will cancel
- IOC/FAK order type added
- Cover order type added
- Canceling a synthetic order no longer cancels any working hedge orders
### General
- A manual page has been added to the launcher Help menu
- Various help buttons and tooltips were added throughout the application
- Various cash per point values updated
- Time-based grid filters now have tick up/down buttons
- Numerous performance improvements and stability enhancements
- Automated position and Order Book maintenance during daily recycle
- Individual fills will be compacted into the Start of Day position records
- Orders from the day before the recycle will be deleted
## 1.1
### 1.1.22
- Order Book: Orders in Error status can now be recreated
- Order Book: Orders containing child orders will now be collapsed by default.
- Order Book: Expand / Collapse All button added to the right-click menu and window action pane, denoted by ▲ icon
- Order Book: Symbol column added to allow for filtering based on group symbol
- Time filters now have a quick Set Last Hour hyperlink which will set the Period Begin filter to (Now - 1 hour)
### 1.1.20
- Update CME market data handler for price precision extension.
https://www.cmegroup.com/confluence/display/EPICSANDBOX/Price+Precision+Extension
### 1.1.8
- Enabled wild cards in risk management UI
- Fixed spreader order updates
### 1.1.7
- Fixed spread leg price fill
- Fixed HO cash per point value
### 1.1.6
- Fixed occasional crash when filtering fills
- Fixed several cash per point multipliers
- Colored time of sales row with aggressor side
- Iceberg orders now cancel (rather than an error) when a child is canceled
### 1.1.0
- Added per user self match protection, location and self match protection FIX tag settings
- Implemented a new "Trigger Order" algo
- Corrected tick formatting for a number of products
- Added the ability to recreate canceled orders
- Added leg fills to facilitate futures based PnL
- PnL report now has PnL columns calculated based on your Start of Day, current day, and total position
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
- Corrected cash per point values, used in pnl calculations, for a number of products
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
+++ title = "Release notes" weight = 3 sort_by = "weight" insert_anchor_links = "right" +++ ## 2.16.2 ### User Visible Change - Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units. ## 2.16.1 ### Bug Fixes - OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically ## 2.16.0 ### Bug Fixes - Solved an issue that could result in incorrect ladder levels - Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes ### API Changes - Add C# client side API samples for order entry, position monitoring and market data observation ## 2.15.10 ### Bug Fixes - Solved an issue that could cause the user interface to fail when playing some alert sounds ### API Changes - Added settlement and position information to the API - Added the ability for user algos to report custom state to the user interface - Added the ability for algos to discover all active securities ## 2.15.3 ### Bug Fixes - Fixed a bug where some CME currency spreads exhibited incorrect tick values ## 2.15.2 ### User Visible Changes - Excel RTD plugin can now receive ICE market data ## 2.15.1 ### Bug Fixes - Fixed a FIX log parsing issue that resulted in exchange gateway logon problems ## 2.15.0 ### User Visible Changes - Added ability to hide Ladder status messages via Ladder Settings -> Left Panel tab -> Show/Hide groupbox -> Status Message - Synthetic cloning now allows for any parameter to be changed, including tick size and leg contracts - Added ability to import synthetics from a CSV file; see our manual for further details on this process - Reduced Iceberg reloading latency ### Bug Fixes - Fixed a bug where highlighted fills could lose their highlight status - Fixed a bug where the (Mini) OrderBook PIQ could be displaying an old value ## 2.14.3 ### Bug Fixes - Fixed a bug regarding ICE logon messaging ## 2.14.2 ### Bug Fixes - Fixed a bug regarding ICE messaging ## 2.14.1 ### Bug Fixes - Fixed a bug regarding ICE logon authentication ## 2.14.0 ### User Visible Changes - All ladders can now be recentered by (shift or alt) + double clicking over the Price column in a ladder - Added a Max Order Quantity setting to the Safety Checks tab on the Ladder settings. This setting allows for a user-defined maximum order quantity. - Added a Price Display Format setting to the Market Price and Size Display tab on the Ladder settings. This setting allows for price levels in the ladder to be displayed without a decimal separator. - Synthetic instrument creation with 3 legs no longer defaults to butterfly quantites ### Bug Fixes - Fixed an issue where market transition were not observed by IAlgoOrder - Fixed a bug in error reporting - Fixed a bug in the PnL window where specifying a certain grouping order would lead to a UI failure ## 2.13.0 ### User Visible Changes - Added the name of the spreader to the parent algo badge (for spreader child orders), in the fills, orderbook and floating orderbook windows - Added support for sounds for new orders - Added a new quoting throttle parameter for the spreader, that throttles quotes that are moving toward the inside market but are still a configurable number of ticks away - Enabled the ladder to remember the left panel scroll position across front end restarts ### Bug Fixes - Fixed the local fills window so that it does not show the admin fills - Removed the deleted accounts from the ladder accounts dropdown - Fixed the orderbook price for iceberg of spreaders ## 2.12.0 ### User Visible Changes - Added support for ICE market data and order execution ### Internal Changes - Lots of permission / admin changes to facilitate ICE ## 2.11.0 ### User Visible Changes - Fixed fills form time filter to also apply to SOD fills - Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections - Removed the option to enter manual spread fills - Fixed an issue that could cause erroneous fill account attribution in the event of a crash ### Internal Changes - Added support for tag 1031. Currently defaults to Y, can it be override through config. - To facilitate support, allowed the bts user to view and edit algos from all other users - Improved system tests - Improved log messages in UI to include dispatcher thread name - Added support for Windows core dumps in the UI ## 2.10.0 ### User Visible Changes - Fills now include information about the algo that originated the fill - A new, more powerful PnL form is now available ### API Changes - Added security state information (pre open, halted, normal, closed, etc) ### Bug Fixes - Brought back the "select all" check box in the mini order book - Fixed an issue in the market data subsyLine truncated
+6
View File
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "2.1.508",
"rollForward": "latestMajor"
}
}
+4
View File
@@ -22,6 +22,10 @@
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
</PropertyGroup>
<PropertyGroup Condition="'$(VCTargetsPath)'==''">
<VCTargetsPath>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets</VCTargetsPath>
</PropertyGroup>
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
<ItemGroup>
+10 -1
View File
@@ -115,7 +115,7 @@ namespace mbts.ui.shell
{
try
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
string path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
if (File.Exists(path + "config\\stunnel.conf"))
{
uint job_error = BTSUINativeMethods.setup_process_job_object();
@@ -124,6 +124,15 @@ namespace mbts.ui.shell
kill_stunnel();
start_stunnel(path, job_error);
}
path = AppDomain.CurrentDomain.BaseDirectory + "\\..\\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)
{
@@ -1,7 +1,6 @@
using bts.api;
using bts.co2;
using bts.utils;
using bts.utils.csv;
using mbts;
using mbts.spark;
using mbts.ui.controls;
@@ -45,18 +44,6 @@ namespace bts.shell.time_and_sales {
public void raise_invalidated() =>
invalidated?.Invoke();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(TimeAndSalesRowVM),
new string[] {
nameof(security),
nameof(time),
nameof(price),
nameof(size),
nameof(aggressor_side),
nameof(security_key),
});
public bool Equals(TimeAndSalesRowVM other) =>
other != null &&
row_key == other.row_key;
@@ -2,7 +2,6 @@
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts;
using mbts.co2;
using mbts.persist;
@@ -115,8 +114,6 @@ namespace bts.shell.time_and_sales {
show_in_context_menu = _can_pause_updates
};
_commands = new SparkCommand[] {
// csv export
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
// ladder
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
@@ -549,24 +549,48 @@
</u:BindingUpdateGroupCollection>
</u:Behavior.BindingUpdateGroups>
</TextBox>
<TextBlock
<StackPanel
x:Name="PART_TickFactors_Label"
Grid.Column="2"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks"
IsEnabled="True"
Orientation="Horizontal"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:TickFactorsPicker
>
<TextBlock
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks ("
/>
<CheckBox
Margin="1,0,0,0"
IsChecked="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Padding="0"
>
fractional
</CheckBox>
<TextBlock VerticalAlignment="Center" Margin="1,0,0,0">)</TextBlock>
</StackPanel>
<Grid
x:Name="PART_TickFactors_Picker"
Grid.Column="3"
Margin="{StaticResource Margin.LHeaderPanelItem}"
HorizontalAlignment="Stretch"
IsEnabled="True"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
>
<c:TickFactorsPicker
HorizontalAlignment="Stretch"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static cv:BooleanToVisibilityConverter.HiddenInstance}}"
/>
<c:TickUpDownInt
HorizontalAlignment="Stretch"
Minimum="1"
Value="{Binding IntegerTickSize, RelativeSource={RelativeSource TemplatedParent}}"
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource FalseVisibilityConverter}}"
/>
</Grid>
<TextBlock
x:Name="PART_Pricing_Label"
Grid.Column="4"
@@ -52,6 +52,26 @@ namespace mbts.ui.spark {
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
#endregion
#region UseFractionalTicks
public static readonly DependencyProperty UseFractionalTicksProperty = WPFHelper.create_dp<bool, ExecutionStrategyEditor>(
nameof(UseFractionalTicks), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, true);
public bool UseFractionalTicks {
get => (bool)GetValue(UseFractionalTicksProperty);
set => SetValue(UseFractionalTicksProperty, value);
}
#endregion
#region IntegerTickSize
public static readonly DependencyProperty IntegerTickSizeProperty = WPFHelper.create_dp<int, ExecutionStrategyEditor>(
nameof(IntegerTickSize), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 1);
public int IntegerTickSize {
get => (int)GetValue(IntegerTickSizeProperty);
set => SetValue(IntegerTickSizeProperty, value);
}
#endregion
#region IsNew
public static readonly DependencyProperty IsNewProperty =
DependencyProperty.Register(
@@ -36,6 +36,8 @@
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
UseFractionalTicks="{Binding use_fractional_ticks}"
IntegerTickSize="{Binding integer_tick_size}"
/>
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
</Grid>
@@ -2,7 +2,6 @@
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
@@ -30,8 +29,16 @@ namespace mbts.spark {
readonly Property<bool> _has_working_orders, _can_add_leg;
Security _synthetic;
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
bool
_set_name_automatically,
_set_ticks_automatically,
_set_quantities_automatically,
_set_price_mults_automatically,
_is_setting_quantities,
_is_setting_price_mults,
_use_fractional_ticks = true;
TickFactors _tick_factors;
int _integer_tick_size = 1;
ExecutionStrategyImmutable _strategy;
HedgePricing _hedge_pricing;
@@ -44,6 +51,10 @@ namespace mbts.spark {
: this(synthetic, strategy, false) {
ExceptionAssert.InvalidOperation.That(synthetic.Id != 0, "This constructor may only be used for already-existing synthetic securities (id must be non-zero).");
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
if (TickUtils.is_integer_tick(synthetic.ContractSpec)) {
_use_fractional_ticks = false;
_integer_tick_size = DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0;
}
title = $"Edit '{synthetic.Synthetic.Name}'";
icon = Icons.ModifySynthetic;
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
@@ -131,14 +142,14 @@ namespace mbts.spark {
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
valid = false;
}
if (_tick_factors == null || !_tick_factors.is_valid()) {
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
if (use_fractional_ticks ? (_tick_factors == null || !_tick_factors.is_valid()) : integer_tick_size == 0) {
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, use_fractional_ticks ? nameof(tick_factors) : nameof(integer_tick_size)).ignore();
valid = false;
}
if (valid) {
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
_synthetic.Symbol = _deduce_symbol(_synthetic);
_synthetic.ContractSpec = _tick_factors.to_spec();
_synthetic.ContractSpec = use_fractional_ticks ? _tick_factors.to_spec() : TickUtils.create_integer_tick_spec(integer_tick_size);
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
if (id.HasValue && id != 0) {
if (await model.save_strategy(id.Value, get_strategy())) {
@@ -295,6 +306,20 @@ namespace mbts.spark {
public bool has_working_orders => _has_working_orders.Value;
public bool is_new { get; }
public bool is_new_leg_popup_open { get; set; }
public bool use_fractional_ticks {
get => _use_fractional_ticks;
set {
if (Set(ref _use_fractional_ticks, value))
_set_ticks_automatically = false;
}
}
public int integer_tick_size {
get => _integer_tick_size;
set {
if (Set(ref _integer_tick_size, value))
_set_ticks_automatically = false;
}
}
public bool can_add_leg => _can_add_leg.Value;
public SyntheticContract.Types.PriceCalc price_calc { get => _synthetic.Synthetic.PriceCalc; set => _synthetic.Synthetic.PriceCalc = value; }
public ObservableCollection<LegSpreaderParamsTuple> leg_tuples { get; } = new ObservableCollection<LegSpreaderParamsTuple>();
@@ -323,6 +348,16 @@ namespace mbts.spark {
_strategy = strategy;
hedge_pricing = strategy.HedgePricing;
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
if (synthetic.HasContractSpec) {
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
/// rather than the properties <see cref="use_fractional_ticks"/> and <see cref="integer_tick_size"/>
/// because the setters of the former flip <see cref="_set_ticks_automatically"/> to false if the value changes.
/// This behavior causes the ticks to be automatically deduced as legs are added/removed,
/// until the user explicitly enters a different value (as the properties are bound to tick input controls in the view).
Set(ref _use_fractional_ticks, !TickUtils.is_integer_tick(synthetic.ContractSpec), nameof(use_fractional_ticks));
if (!use_fractional_ticks)
Set(ref _integer_tick_size, DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0, nameof(integer_tick_size));
}
RaisePropertyChanged(nameof(name), nameof(tick_factors));
}
@@ -5,7 +5,6 @@ using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.collections;
using mbts.persist;
using mbts.spark.filters;
@@ -331,27 +330,6 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(typeof(Row),
new string[] {
nameof(security),
nameof(order_type),
nameof(root_algo_display_name),
nameof(side),
nameof(fill_time),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(size),
nameof(price),
nameof(fill_kind),
nameof(fill_status),
nameof(fill_key),
nameof(order_key),
nameof(security_key)
});
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
}
@@ -4,7 +4,6 @@ using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -62,7 +61,6 @@ namespace mbts.spark {
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
@@ -534,14 +534,44 @@ namespace mbts.spark {
_size_from_row(row, _price_list, _asks, _vm.security.ContractSpec);
uint _volume_from_row(int row) {
if (_price_list != null) {
var price = _price_list[row];
var vapls = _volume_container.read_new_value();
if (vapls != null) {
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
}
const int max_tries = 3;
var errors = new List<string>(0);
int num_tries = 0;
for (; num_tries < max_tries; num_tries++) {
try {
var rv = impl();
if (num_tries > 0) {
log_errors(true);
}
return rv;
} catch (Exception ex) {
errors.Add(ex.ToString());
}
}
log_errors(false);
return 0;
void log_errors(bool succeeded) {
// Only log distinct errors to avoid useless noise.
// It's likely that the exception will always be the same (IndexOutOfRange),
// however, this bug is not fully understood, so assume nothing.
var error_msg = string.Join("; ", errors.Distinct());
if (succeeded) {
Log.Ui.error($"Ladder '{security}' got volume for row at index '{row}' after {num_tries + 1} attempts (price_list array length is {_price_list.Length}). The following exceptions were observed: {error_msg}");
} else {
Log.Ui.error($"Ladder '{security}' failed to get volume for row at index '{row}' after {max_tries} attempts (price_list array length is {_price_list.Length}): {error_msg}");
}
}
uint impl() {
if (_price_list.SafeTryGetAt(row, out var price)) {
var vapls = _volume_container.read_new_value();
if (vapls != null) {
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
}
}
return 0u;
}
return 0u;
}
@@ -2,6 +2,7 @@ using bts.api;
using bts.api.core;
using bts.api.position;
using bts.collections;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
@@ -4,7 +4,6 @@ using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.ui;
using mbts.ui.controls;
using System;
@@ -12,7 +11,6 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
@@ -38,7 +36,6 @@ namespace mbts.spark {
public OrderBookRowVM(OrderBookVM owner, OrderSecurityRootPair order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
_owner = owner;
this.order_tuple = order_tuple;
is_visible = !is_child;
_is_expanded = is_expanded;
prospective_quantity = this.quantity;
prospective_price = this.price;
@@ -62,35 +59,6 @@ namespace mbts.spark {
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
public event PropertyChangedEventHandler PropertyChanged;
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(OrderBookRowVM),
new string[] {
nameof(security_text),
nameof(symbol),
nameof(time_created),
nameof(last_change),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(order_type),
nameof(root_algo_display_name),
nameof(order_sub_type),
nameof(side),
nameof(quantity),
nameof(filled_quantity),
nameof(price),
nameof(avg_price),
nameof(last_price),
nameof(piq),
nameof(status),
// last message (could contains commas, so skipping)
nameof(security_key),
nameof(order_key),
nameof(parent_order_key),
});
// Core
public OrderSecurityRootPair order_tuple { get; private set; }
public OrderRecordImmutable order => order_tuple.order.order;
@@ -144,7 +112,6 @@ namespace mbts.spark {
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
public bool is_child => order.has_parent();
public bool is_visible { get; set; }
public bool is_expanded {
get => _is_expanded;
set {
@@ -51,6 +51,7 @@ namespace mbts.spark {
readonly OrderParamsAggregator _selected_params_mon;
readonly CommonValueMonitor<SecurityImmutable?> _selected_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly RightPanelModel _right_panel = new RightPanelModel();
readonly CustomStateSynchronizer _custom_state_sync;
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
double
_raw_right_panel_width = OrderBookStartupData.Default.right_panel_width,
@@ -124,6 +125,8 @@ namespace mbts.spark {
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
_current_row = backing<OrderBookRowVM>(nameof(current_row));
_columns.filter_changed += _on_filter_changed;
_custom_state_sync = new CustomStateSynchronizer(this);
// commands
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
var modify_selected_orders_async_cmd = new AsyncCommand(_modify_selected_orders_async, () => _selected_params_mon.all_active && order_mod_vm.is_dirty);
@@ -188,6 +191,7 @@ namespace mbts.spark {
security_plurality = p;
RaisePropertyChanged(nameof(security_plurality));
}),
_current_row.Subscribe(_ => _custom_state_sync.sync()),
orders_source.OnDispatcher().Subscribe(t => {
if (_selected_rows.Count == 0) {
_cached_orders = t.state;
@@ -207,6 +211,8 @@ namespace mbts.spark {
}
}
}
_custom_state_sync.sync(true);
orders_changed?.Invoke();
}),
model.on_orders_can_attach.OnDispatcher().Subscribe(table => {
_cached_attachable = new MappedDictionary<uint, KeyValuePair<uint, bool>, bool>(table.state, kv => kv.Value);
@@ -375,7 +381,7 @@ namespace mbts.spark {
}
public event Action<OrderRecordImmutable?> selected_order_changed;
public event Action begin_attaching, end_attaching;
public event Action begin_attaching, end_attaching, orders_changed;
public event Action<(IEnumerable, bool)> request_selection;
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
@@ -395,6 +401,7 @@ namespace mbts.spark {
}
}
public OrderRecordImmutable? current_order => current_row?.order;
public ObservableCollection<NamedValueVM> current_algo_custom_state { get; } = new ObservableCollection<NamedValueVM>();
public AlgoDefinitionVM algo_vm => order_mod_vm.algo_vm;
public OrderParamsVM order_mod_vm { get; }
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
@@ -474,6 +481,28 @@ namespace mbts.spark {
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
public IObservable<bool> subscribe_show_order_params() => _show_order_params.observe_current(true);
public IObservable<OrderSecurityAlgo?> subscribe_single_selected_order() =>
Observable.Create<OrderSecurityAlgo?>(o => {
OrderSecurityAlgo? last_fired = null;
orders_changed += notify;
_current_row.Changed += on_current_row_changed;
notify();
return Disposable.Create(dispose);
// local methods
void notify() {
var next = current_row == null ? (OrderSecurityAlgo?)null : current_row.order_tuple.order;
if (next != last_fired)
o.OnNext(last_fired = next);
}
void on_current_row_changed(OrderBookRowVM oldval, OrderBookRowVM newval) =>
notify();
void dispose() {
orders_changed -= notify;
_current_row.Changed -= on_current_row_changed;
}
});
public void row_selection_changed(SelectionChangedEventArgs e) {
try {
@@ -503,6 +532,7 @@ namespace mbts.spark {
_can_attach_selected_orders.refresh();
_can_detach_selected_orders.refresh();
selected_order_changed?.Invoke(current_order);
_custom_state_sync.sync();
} catch (Exception ex) {
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
@@ -723,6 +753,45 @@ namespace mbts.spark {
}
}
class CustomStateSynchronizer {
readonly OrderBookVM _vm;
OrderBookRowVM _synced_row;
public CustomStateSynchronizer(OrderBookVM vm) =>
_vm = vm;
public void sync(bool force_refresh = false) {
var row = _vm.current_row != null &&
_vm.selected_rows.Select(r => r.order_tuple.root.order.Key).Distinct().TryGetSingle(out uint droot) &&
_vm.current_row.order_tuple.root.order.Key == droot ?
_vm.current_row : null;
if (force_refresh || row != _synced_row) {
_synced_row = row;
_refresh();
}
}
void _refresh() {
_vm.current_algo_custom_state.Clear();
if (_synced_row == null)
return;
var dcs = _synced_row.order.dynamic_custom_state();
// while dcs is not expected to be null, best to play it safe and double check.
if (dcs.is_null_or_empty()) {
var descendent_rows = _vm._row_collection.get_descendent_rows(_synced_row.order_key);
if (_vm._row_collection.filter != null)
descendent_rows = descendent_rows.Where(child => _vm._row_collection.filter(child.order_tuple));
foreach (var dr in descendent_rows) {
dcs = dr.order.dynamic_custom_state();
if (!dcs.is_null_or_empty())
break;
}
}
foreach (var p in dcs.empty_if_null().OrderBy(kv => kv.Key))
_vm.current_algo_custom_state.Add(new NamedValueVM(p.Key, DynamicOrderHelper.fmt_param(p.Value)));
}
}
public class OrderParamsVM : DisposableViewModelBase {
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly OrderBookVM _owner;
@@ -1,15 +1,17 @@
using bts.api;
using bts;
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.utils.csv;
using bts.view;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utilities;
using mbts.ui.utility;
using mbts.utils;
using System;
@@ -25,7 +27,10 @@ namespace mbts.spark {
const string _window_type = "OrderBook", _default_title = "Order Book";
readonly INotificationService _notification_service;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly bool _can_edit_specific_algo_params;
IDisposable _attachment_exit_sub;
// initialized in thread_init
List<IDisposable> _thread_init_resources;
OrderBookStartupData _state;
OrdersModel _model;
OrderBookVM _vm;
@@ -35,13 +40,14 @@ namespace mbts.spark {
SparkCommand[] _commands;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool can_edit_specific_algo_params)
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
_model = model;
_factory = factory;
_notification_service = notification_service;
_state = state;
_settings_manager = settings_manager;
_can_edit_specific_algo_params = can_edit_specific_algo_params;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
@@ -65,12 +71,14 @@ namespace mbts.spark {
public string thread_id { get; }
public override void Dispose() {
_commands.OfType<IDisposable>().DisposeAll();
Disposable.dispose(_thread_init_resources);
Disposable.dispose(ref _attachment_exit_sub);
Disposable.dispose(ref _vm);
}
public void thread_init() {
Log.assert(_thread_init_resources == null, $"{nameof(_thread_init_resources)} must be initialized in thread_init.");
_thread_init_resources = new List<IDisposable>();
_menu_display_vm = new SparkMenuDisplayVM();
if (_state != null)
_menu_display_vm.load_from(_state.menu_display);
@@ -148,6 +156,15 @@ namespace mbts.spark {
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
);
recreate_selected_orders_command.group = CommandGroups.Modify;
ICommand edit_algo_params_cmd;
if (_can_edit_specific_algo_params) {
_thread_init_resources.Add(CommandHelper.bind_param_source(
new DialogCommand<OrderSecurityAlgo?, Window>(mk_algo_params_dialog, has_algo_params),
_vm.subscribe_single_selected_order(),
out edit_algo_params_cmd, true));
} else {
edit_algo_params_cmd = DelegateCommand.empty;
}
var attach_selected_orders_command = new SparkCommandCommand(
"Attach To Selected",
@@ -195,15 +212,22 @@ namespace mbts.spark {
show_grid_settings_command.group = CommandGroups.Auxiliary;
_commands = new SparkCommand[] {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
// modify commands
toggle_show_right_panel_command,
attach_selected_orders_command,
detach_selected_orders_command,
recreate_selected_orders_command,
new SparkCommandCommand("Algo Params...", Icons.Algo, edit_algo_params_cmd) {
group = CommandGroups.Modify,
show_in_toolbar = false,
show_in_context_menu = _can_edit_specific_algo_params
},
// cancel commands
cancel_selected_orders_command,
cancel_buy_orders_command,
cancel_sell_orders_command,
cancel_all_orders_command,
toggle_collapse_all_command,
toggle_expand_all_command,
show_grid_settings_command,
@@ -212,9 +236,12 @@ namespace mbts.spark {
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
_menu_display_vm.spark_cmd
};
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
_thread_init_resources.AddRange(_commands.OfType<IDisposable>());
_thread_init_resources.Add(_vm.as_demuxer()
.subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title()));
_vm.selected_order_changed += vm_selected_order_changed;
_thread_init_resources.Add(Disposable.Create(() => _vm.selected_order_changed -= vm_selected_order_changed));
// local methods
string get_toggle_show_right_panel_cmd_title() =>
_vm.show_right_panel ? OrderBookView.CommandDescriptions.HideRightPanel : OrderBookView.CommandDescriptions.ShowRightPanel;
@@ -236,6 +263,36 @@ namespace mbts.spark {
attach_selected_orders_command.raiseCanExecuteChanged();
detach_selected_orders_command.raiseCanExecuteChanged();
}
bool has_algo_params(OrderSecurityAlgo? order) =>
order.HasValue && !OrderDataHelper.get_metadata(order.Value.algo.value.CustomCase).NoProps;
Window mk_algo_params_dialog(OrderSecurityAlgo? order) {
if (!has_algo_params(order))
return null;
var algo_def = AlgoDefinitionVM.create(_model, order.Value.algo, editing_permissions: AlgoEditingPermissions.AllowEditValues);
var algo_params_vm = new OrderBookAlgoParamsVM(algo_def, order.Value.order.is_non_terminal());
var dialog = new OrderBookAlgoParamsDialog {
Content = algo_params_vm
};
ui.WindowStyle.SetTitleContent(dialog, algo_def);
dialog.Closed += (s, e) => {
Disposable.dispose(ref algo_params_vm);
dialog = null;
};
algo_params_vm.RequestCommit += commit;
algo_params_vm.RequestClose += (s, e) => close_dialog();
return dialog;
void close_dialog() {
dialog?.Close();
dialog = null;
}
void commit(CustomOrderDefinitionImmutable value) {
_model.update_order(order.Value.order, new_algo: value).ignore();
close_dialog();
}
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) {
@@ -1,6 +1,7 @@
using bts.api;
using bts.shell.orderbook;
using bts.spark;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
@@ -15,8 +16,11 @@ namespace bts.shell {
readonly INotificationServiceDataCache _notification_service_data_cache;
readonly OrdersModel _model;
readonly UserSettingsHub _settings_hub;
readonly OrderBookConfig _config;
public OrderBookWindowFactory(IShellModule owner, IBus bus, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, OrdersModel model, UserSettingsHub settings_hub) : base(owner, "SparkOrderBookWindow") {
var raw_config = Configuration.get<bts.config.OrderBookOptions>();
_config = new OrderBookConfig(raw_config.can_edit_specific_algo_params);
_bus = bus;
_notification_service = notification_service;
_notification_service_data_cache = notification_service_data_cache;
@@ -29,7 +33,8 @@ namespace bts.shell {
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
(us, s) => us.with(order_book_grid: s));
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state,
SparkCommands.ShowOrders.icon, settings_manager.as_non_shared(), _config.can_edit_specific_algo_params);
return Task.FromResult<IShellWindow>(w);
}
@@ -37,5 +42,12 @@ namespace bts.shell {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
readonly struct OrderBookConfig {
public readonly bool can_edit_specific_algo_params;
public OrderBookConfig(bool can_edit_specific_algo_params) =>
this.can_edit_specific_algo_params = can_edit_specific_algo_params;
}
}
}
@@ -0,0 +1,40 @@
<Window
x:Class="bts.shell.orderbook.OrderBookAlgoParamsDialog"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:ob="clr-namespace:bts.shell.orderbook"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
u:WindowStyle.SizeToContent="WidthAndHeight"
Margin="0"
Padding="0"
Style="{StaticResource BTSWindowTitleTemplate}"
>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate DataType="{x:Type sp:AlgoDefinitionVM}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<usp:OrderTypeItem Margin="4,0" DisplayName="{Binding name, StringFormat='{}{0} Params'}" BadgeFill="{Binding badge_fill}"/>
</StackPanel>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
<Window.ContentTemplate>
<DataTemplate DataType="{x:Type ob:OrderBookAlgoParamsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<usp:AlgoEditorGridView
Width="390"
Margin="5"
AlgoParamRows="{Binding param_rows}"
CanUserChangeAlgoRoot="False"
/>
<c:DialogSkirt Grid.Row="1" RightItems="{Binding dialog_skirt}"/>
</Grid>
</DataTemplate>
</Window.ContentTemplate>
</Window>
@@ -0,0 +1,49 @@
using bts.api.orderman;
using bts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Windows;
namespace bts.shell.orderbook {
/// <summary>
/// Interaction logic for OrderBookAlgoParamsDialog.xaml
/// </summary>
partial class OrderBookAlgoParamsDialog : Window {
public OrderBookAlgoParamsDialog() =>
InitializeComponent();
}
class OrderBookAlgoParamsVM : DisposableViewModelBase, IRequestViewClose {
readonly ObservableAggregateCollection<AlgoParamRowVM> _param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
readonly DialogSkirtItem _commit, _cancel;
public OrderBookAlgoParamsVM(AlgoDefinitionVM algo_definition, bool can_user_edit) {
this.algo_definition = algo_definition;
_dispose = AlgoParamRowsHelper.connect(algo_definition, _param_rows, false);
_commit = new DialogSkirtItem("OK", new DelegateCommand(commit, () => can_user_edit)) { is_default = true };
_cancel = new DialogSkirtItem("Cancel", new DelegateCommand(close)) { is_cancel = true };
// local methods
void commit() {
RequestCommit?.Invoke(new CustomOrderDefinitionImmutable(algo_definition.algo_root.created_assigned()));
close();
}
void close() => RequestClose?.Invoke(this, EventArgs.Empty);
}
public event EventHandler RequestClose;
public event Action<CustomOrderDefinitionImmutable> RequestCommit;
public AlgoDefinitionVM algo_definition { get; }
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows => _param_rows;
public IEnumerable<DialogSkirtItem> dialog_skirt {
get {
yield return _commit;
yield return _cancel;
}
}
}
}
@@ -484,6 +484,7 @@
<Border Padding="12,12,12,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
@@ -495,7 +496,45 @@
AlgoParamRows="{Binding param_rows}"
IsReadOnly="{Binding DataContext.can_user_modify_selected_orders, ElementName=Root, Converter={StaticResource NotConverter}}"
/>
<StackPanel Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
<c:ExpanderToggleButton
x:Name="algo_state_toggle"
Grid.Row="1"
BorderThickness="0"
Content="Algo State"
HorizontalContentAlignment="Left"
Margin="0,4,0,0"
VerticalAlignment="Top"
Visibility="{Binding current_algo_custom_state.Count, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:DIYGrid Grid.Row="1" Rows="{Binding current_algo_custom_state}" Margin="0,28,0,4">
<c:DIYGrid.Visibility>
<MultiBinding>
<MultiBinding.Converter>
<cv:MultiValueConverterPipe>
<cv:BooleanAndConverter/>
<cv:BooleanToVisibilityConverter/>
</cv:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="current_algo_custom_state.Count" Mode="OneWay" Converter="{StaticResource IntNotZeroConverter}"/>
<Binding Path="IsChecked" ElementName="algo_state_toggle"/>
</MultiBinding>
</c:DIYGrid.Visibility>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection>
<c:DIYGridColumn id="Name" Header="Param" Width="176"/>
<c:DIYGridColumn id="Value" Header="Value" Width="176"/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
<c:TextCell c:DIYGrid.ColumnKey="Name" Text="{Binding name}"/>
<c:TextCell c:DIYGrid.ColumnKey="Value" Text="{Binding value}"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<StackPanel Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
<Button x:Name="modify_orders_button" Command="{Binding modify_selected_orders_cmd}">
<Button.Margin>
<Thickness Right="{StaticResource DialogSkirtButtonPadding.Right}"/>
+18 -5
View File
@@ -2,6 +2,7 @@
using bts.api;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.config;
using mbts.persist;
using mbts.spark;
@@ -114,6 +115,7 @@ namespace mbts.ui {
void dispose_core() {
Disposable.dispose(ref _settings_sub);
Disposable.dispose(ref _toast_controller);
Disposable.dispose(ref _sound_player);
if (_dispatcher != null && !_dispatcher.HasShutdownStarted) {
_dispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
}
@@ -158,15 +160,25 @@ namespace mbts.ui {
}
}
class SoundNotificationPlayer {
readonly IReadOnlyDictionary<SoundNotification.SystemSound, NAudioPlayer> _players;
public static SoundNotificationPlayer create() => new SoundNotificationPlayer(_all_players());
class SoundNotificationPlayer : IDisposable {
IReadOnlyDictionary<SoundNotification.SystemSound, NAudioPlayer> _players;
SoundNotificationPlayer(IReadOnlyDictionary<SoundNotification.SystemSound, NAudioPlayer> players) =>
_players = players;
public static SoundNotificationPlayer create() =>
new SoundNotificationPlayer(_all_players());
public void Dispose() {
if (_players != null) {
_players.Values.DisposeAll();
_players = null;
}
}
public void play_sounds(IEnumerable<SoundNotification> sounds) {
if (_players == null)
return;
foreach (var s in sounds) {
switch (s.sound_type_case) {
case SoundNotification.SoundTypeOneofCase.System: _players.GetValueOrDefault(s.system_sound.Value)?.play(); break;
@@ -177,7 +189,8 @@ namespace mbts.ui {
}
static IReadOnlyDictionary<SoundNotification.SystemSound, NAudioPlayer> _all_players() =>
EnumHelper.get_values<SoundNotification.SystemSound>().ToReadOnlyDictionary(s => s, NAudioPlayer.create);
EnumHelper.get_values<SoundNotification.SystemSound>()
.ToReadOnlyDictionaryWhere<SoundNotification.SystemSound, NAudioPlayer>(NAudioPlayer.try_create);
}
// This class adopts to role of view-controller in the mvc paradigm, rather than view-model in mvvm.
+75 -22
View File
@@ -1,4 +1,5 @@
using bts.shell;
using bts;
using bts.shell;
using mbts.ui.utility;
using NAudio.Wave;
using System;
@@ -6,29 +7,71 @@ using System.IO;
using System.Threading.Tasks;
namespace mbts.ui {
class NAudioPlayer {
class NAudioPlayer : IDisposable {
// number of milliseconds to delay playing sound,
// during which time all subsequent requests to play the same sound will be ignored.
const int _delay_ms = 75;
readonly int _start_position;
IDisposable _stream_in;
WaveOutEvent _wave_out = new WaveOutEvent();
WaveFileReader _wave_reader;
bool _is_playing, _is_play_requested;
public NAudioPlayer(Stream uwave_stream, int start_position = 0) {
var ms = new MemoryStream();
uwave_stream.CopyTo(ms);
ms.Position = 0;
_wave_reader = new WaveFileReader(ms);
/// <paramref name="stream_in"/> should not be disposed by the caller,
/// as NAudioPlayer handles that in its own Dispose method;
/// this constructor is private is to prevent such misuse.
NAudioPlayer(Stream stream_in, int start_position = 0) {
_stream_in = stream_in;
_wave_reader = new WaveFileReader(stream_in);
_wave_reader.Position = _start_position = (start_position < _wave_reader.Length ? start_position : 0);
_wave_out.Init(_wave_reader);
_wave_out.PlaybackStopped += _on_playback_stopped;
}
public static NAudioPlayer create(SoundNotification.SystemSound system_sound) {
var stream = _get_stream(system_sound);
var sb = EnumHelper.get_attribute<SoundNotification.SystemSound, SilenceBufferAttribute>(system_sound);
return sb != null ? new NAudioPlayer(stream, sb.start_position) : new NAudioPlayer(stream);
/// <see cref="_playback_stopped"/> is a proxy/buffer for the _wave_out.PlaybackStopped event.
/// It serves a couple purposes:
/// 1) The main callback (<see cref="_on_playback_stopped(object, StoppedEventArgs)"/>)
/// is guarenteed to be executed first (though it would be anyways, since that
/// callback is hooked up in the constructor, the proxy event makes it obvious).
/// 2) <see cref="_playback_stopped"/> is only raised after playback is truly complete,
/// meaning there are no pending requests to repeat the sound.
event Action _playback_stopped;
public static bool try_create(SoundNotification.SystemSound system_sound, out NAudioPlayer result) {
if (_try_get_stream(system_sound, out var stream)) {
var sb = EnumHelper.get_attribute<SoundNotification.SystemSound, SilenceBufferAttribute>(system_sound);
result = sb != null ? new NAudioPlayer(stream, sb.start_position) : new NAudioPlayer(stream);
return true;
}
result = null;
return false;
}
public static void play(SoundNotification.SystemSound system_sound) {
if (try_create(system_sound, out var player)) {
try {
var temp = player;
temp._playback_stopped += temp.Dispose;
player.play();
player = null;
} catch (Exception ex) {
Log.Ui.warn($"Encountered exception playing the system sound '{system_sound}': {ex}.");
} finally {
player?.Dispose();
}
} else {
Log.Ui.warn($"Unable to play the system sound '{system_sound}': unexpected sound type (did not find an audio stream that corresponds to this enum value).");
}
}
public void Dispose() {
if (_wave_out != null) {
_wave_out.PlaybackStopped -= _on_playback_stopped;
_wave_out.Dispose();
_wave_out = null;
}
Disposable.dispose(ref _wave_reader);
Disposable.dispose(ref _stream_in);
}
public void play() {
@@ -46,6 +89,8 @@ namespace mbts.ui {
if (_is_play_requested) {
_is_play_requested = false;
_play_core();
} else {
_playback_stopped?.Invoke();
}
}
@@ -56,18 +101,26 @@ namespace mbts.ui {
_is_playing = false;
}
static Stream _get_stream(SoundNotification.SystemSound system_sound) {
static bool _try_get_stream(SoundNotification.SystemSound system_sound, out Stream stream) {
UnmanagedMemoryStream um_stream;
switch (system_sound) {
case SoundNotification.SystemSound.GameCorrect: return bts.Properties.Resources.game_sound_correct;
case SoundNotification.SystemSound.GameIncorrect: return bts.Properties.Resources.game_sound_wrong;
case SoundNotification.SystemSound.GameOverArcade: return bts.Properties.Resources.game_over_arcade;
case SoundNotification.SystemSound.Whistle: return bts.Properties.Resources.whistle;
case SoundNotification.SystemSound.BellCounter: return bts.Properties.Resources.bell_counter;
case SoundNotification.SystemSound.Powerup: return bts.Properties.Resources.powerup;
case SoundNotification.SystemSound.CashRegister: return bts.Properties.Resources.cash_register_purchase;
case SoundNotification.SystemSound.Reload: return bts.Properties.Resources.reload_1911;
default: throw new NotImplementedException($"Unknown {nameof(SoundNotification.SystemSound)}: {system_sound}.");
case SoundNotification.SystemSound.GameCorrect: um_stream = bts.Properties.Resources.game_sound_correct; break;
case SoundNotification.SystemSound.GameIncorrect: um_stream = bts.Properties.Resources.game_sound_wrong; break;
case SoundNotification.SystemSound.GameOverArcade: um_stream = bts.Properties.Resources.game_over_arcade; break;
case SoundNotification.SystemSound.Whistle: um_stream = bts.Properties.Resources.whistle; break;
case SoundNotification.SystemSound.BellCounter: um_stream = bts.Properties.Resources.bell_counter; break;
case SoundNotification.SystemSound.Powerup: um_stream = bts.Properties.Resources.powerup; break;
case SoundNotification.SystemSound.CashRegister: um_stream = bts.Properties.Resources.cash_register_purchase; break;
case SoundNotification.SystemSound.Reload: um_stream = bts.Properties.Resources.reload_1911; break;
default:
stream = null;
return false;
}
stream = new MemoryStream();
um_stream.CopyTo(stream);
um_stream.Dispose();
stream.Position = 0;
return true;
}
}
}
}
@@ -23,6 +23,32 @@ namespace mbts.spark {
public IEnumerable<string> dynamic_types => _dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType);
public static string fmt_param(DynamicOrderParam dyn_param) {
if (dyn_param == null)
return null;
switch (dyn_param.ValueCase) {
case DynamicOrderParam.ValueOneofCase.AsArray:
return string.Join(", ", dyn_param.AsArray.Element.Select(fmt_param));
case DynamicOrderParam.ValueOneofCase.AsBool:
return dyn_param.AsBool.ToString();
case DynamicOrderParam.ValueOneofCase.AsDouble:
return dyn_param.AsDouble.ToString();
case DynamicOrderParam.ValueOneofCase.AsEnumerated:
return dyn_param.AsEnumerated.EnumValue;
case DynamicOrderParam.ValueOneofCase.AsInt32:
return dyn_param.AsInt32.ToString();
case DynamicOrderParam.ValueOneofCase.AsQuantity:
return dyn_param.AsQuantity.ToString();
case DynamicOrderParam.ValueOneofCase.AsString:
return dyn_param.AsString;
default:
// implement additional cases as needed
// (as of 4/17/20, this method is only used to format
// dynamic orders' CustomState params in the orderbook)
return $"({dyn_param.ValueCase})";
}
}
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs, Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory) {
foreach (var d in _dynamic_types.DynamicOrderDesc) {
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
@@ -33,7 +33,7 @@ namespace mbts.spark
{
return;
}
var ev = fd.EnumType.Values.First(x => x.Name == value)?.Number;
var ev = fd.EnumType.Values.FirstOrDefault(x => x.Name == value)?.Number;
if(ev.HasValue)
fd.Accessor.SetValue(message, ev.Value);
}
@@ -2,7 +2,6 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using mbts.collections;
@@ -15,21 +14,19 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
static partial class OrderTypeVMFactory
{
internal class GenericOrderFactory
{
static public IAlgoVM create_vm(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions)
{
static partial class OrderTypeVMFactory {
internal class GenericOrderFactory {
static public IAlgoVM create_vm(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions) {
var prop = typeof(CustomOrderDefinition).GetProperty(algo_type.ToString(),
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
BindingFlags.Instance | BindingFlags.Public);
var rt = prop.PropertyType;
var propi = typeof(CustomOrderDefinitionImmutable).GetProperty(algo_type.ToString(),
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
BindingFlags.Instance | BindingFlags.Public);
var rti = propi.PropertyType;
var vmt = typeof(GenericOrderVM<,>);
@@ -38,38 +35,33 @@ namespace mbts.spark {
object vm = Activator.CreateInstance(makevm, key, algo_type, display_name, editing_permissions);
return (IAlgoVM)vm;
}
static public void setup_generic_custom_order(CustomOrderDefinition custom, CustomOrderDefinition.CustomOneofCase type)
{
static public void setup_generic_custom_order(CustomOrderDefinition custom, CustomOrderDefinition.CustomOneofCase type) {
var prop = typeof(CustomOrderDefinition).GetProperty(type.ToString(),
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
BindingFlags.Instance | BindingFlags.Public);
var rt = prop.PropertyType;
var rtt = Activator.CreateInstance(rt);
var ci = rt.GetProperties().Where(x => x.PropertyType == typeof(CustomOrderDefinition));
foreach(var cid in ci)
foreach (var cid in ci)
cid.SetValue(rtt, new CustomOrderDefinition { Limit = new LimitOrderDefinition() });
prop.SetValue(custom, rtt);
}
}
}
class GenericOrderVM<TMutable, TImmutable> : OrderTypeVMBase<TMutable, TImmutable>
where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage
{
where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
readonly ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string> _parameters =
new ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string>(vm => vm.descriptor.Name);
readonly AlgoType _algo_type;
public GenericOrderVM(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions)
{
: base(key, display_name, editing_permissions) {
_algo_type = algo_type;
_parameters = new ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string>(vm => vm.descriptor.Name);
_resources.AddRange(
ObservableAutoWrapper.connect_item_hooks(_parameters, p =>
{
ObservableAutoWrapper.connect_item_hooks(_parameters, p => {
p.PropertyChanged += on_param_prop_changed;
return Disposable.Create(() =>
{
return Disposable.Create(() => {
p.PropertyChanged -= on_param_prop_changed;
remove_property(p.descriptor.Name);
p.Dispose();
@@ -80,84 +72,74 @@ namespace mbts.spark {
}
public string algo_type => _algo_type.ToString();
public override string abbreviation =>
public override string abbreviation =>
model == null ? display_name : model.factory.abbreviate_algo(new OneOf<CustomOrderDefinition.CustomOneofCase, string>(_algo_type));
public override AlgoDisplayGroup group => AlgoDisplayGroup.API;
public ObservableCollection<GenericOrderParamVM<TMutable>> parameters => _parameters;
protected override void on_model_internal()
{
protected override void on_model_internal() =>
_sync_generic_params();
}
void _sync_generic_params()
{
if (model != null)
{
Google.Protobuf.Reflection.MessageDescriptor desc = new TMutable().Descriptor;
void _sync_generic_params() {
if (model != null) {
MessageDescriptor desc = new TMutable().Descriptor;
_parameters.SyncWithDictionary(
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), _mk_param_vm);
}
}
static bool _is_value_param_desc(FieldDescriptor desc)
{
return (desc.FieldType != Google.Protobuf.Reflection.FieldType.Message ||
static bool _is_value_param_desc(FieldDescriptor desc) =>
(desc.FieldType != FieldType.Message ||
_is_quantity_measure_desc(desc));
}
static bool _is_quantity_measure_desc(FieldDescriptor desc)
{
return desc.FieldType == Google.Protobuf.Reflection.FieldType.Message &&
static bool _is_quantity_measure_desc(FieldDescriptor desc) =>
desc.FieldType == FieldType.Message &&
desc.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName;
}
protected override IEnumerable<ChildAlgoData> get_selector_data()
{
var v = OrderDataHelper.get_child_algo_data(_algo_type);
return v;
}
protected override IEnumerable<ChildAlgoData> get_selector_data() =>
OrderDataHelper.get_child_algo_data(_algo_type);
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor)
{
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor) {
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
if (descriptor.FieldType == FieldType.Message &&
descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName)
{
if (descriptor.FieldType == FieldType.Message &&
descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName) {
var qm = new GenericOrderQuantityVM<TMutable>(descriptor);
return attached(qm, GenericOrderHelper.get_quantity_value, GenericOrderHelper.set_quantity_value);
}
else if(descriptor.FieldType == FieldType.Enum)
{
var pvm = new GenericOrderEnumeratedVM<TMutable>(descriptor);
attached(pvm, GenericOrderHelper.get_enumerated_value, GenericOrderHelper.set_enumerated_value);
return pvm;
}
else if (descriptor.FieldType == FieldType.Bool)
} else if (descriptor.FieldType == FieldType.Enum) {
return EnumParamFactory.create(this, descriptor);
} else if (descriptor.FieldType == FieldType.Bool) {
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault());
else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float)
} else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float) {
return mk<double, double>(descriptor, v => v, v => v, double.NaN);
else if (descriptor.FieldType == FieldType.Int32)
} else if (descriptor.FieldType == FieldType.Int32) {
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault());
else if (descriptor.FieldType == FieldType.UInt32)
} else if (descriptor.FieldType == FieldType.UInt32) {
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault());
else if (descriptor.FieldType == FieldType.String)
} else if (descriptor.FieldType == FieldType.String) {
return mk<string, string>(descriptor, v => v, v => v);
else
return null;
} else return null;
}
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default)
{
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(
FieldDescriptor fdesc,
Func<TModel, TView> convert,
Func<TView, TModel> convert_back,
TView default_value = default) {
//
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
return attached(pvm, get_from_param, assign_to_param, default_value);
TView get_from_param(IMessage param, FieldDescriptor fd) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
void assign_to_param(IMessage param, FieldDescriptor fd, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
}
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
{
add_property<T>(
TParam attached<T, TParam>(
TParam pvm,
Func<IMessage, FieldDescriptor, T>
selector, Action<IMessage, FieldDescriptor, T> assign_to_param,
T default_value = default)
where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T> {
//
add_property(
pvm.descriptor.Name,
mdef => mdef != null ? selector(mdef, pvm.descriptor) : default_value,
(mdef, v) => assign_to_param(mdef, pvm.descriptor, v),
@@ -166,7 +148,37 @@ namespace mbts.spark {
pvm.subscribe_value(),
default_value);
return pvm;
}
}
static class EnumParamFactory {
[ThreadStatic]
static Dictionary<Type, IFactory> _factories_backing;
static Dictionary<Type, IFactory> _factories =>
_factories_backing ?? (_factories_backing = new Dictionary<Type, IFactory>());
public static GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor) {
if (descriptor.FieldType != FieldType.Enum)
throw ExceptionBuilder.Argument.unexpected_case(descriptor.FieldType, $"{nameof(descriptor)}.{nameof(FieldDescriptor.FieldType)}");
if (!_factories.TryGetValue(descriptor.EnumType.ClrType, out var f)) {
// Impl<T> is a nested type, so parent class's generic type args must be included.
_factories[descriptor.EnumType.ClrType] = f = (IFactory)Activator.CreateInstance(typeof(Impl<>)
.MakeGenericType(typeof(TMutable), typeof(TImmutable), descriptor.EnumType.ClrType));
}
return f.create(owner, descriptor);
}
interface IFactory {
GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor);
}
class Impl<T> : IFactory where T : struct {
static readonly Func<T, T?> _convert = v => v;
static readonly Func<T?, T> _convert_back = v => v ?? default;
public GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor) =>
owner.mk(descriptor, _convert, _convert_back, default);
}
}
}
}
}
}
+5
View File
@@ -63,6 +63,11 @@ namespace mbts.spark {
public static string last_msg(this OrderRecordImmutable order) =>
order.HasState ? order.State.LastMsg : string.Empty;
public static IReadOnlyDictionary<string, DynamicOrderParam> dynamic_custom_state(this OrderRecordImmutable order) =>
order.HasState && order.State.HasDynamic ?
(order.State.Dynamic.CustomState ?? DictionaryHelper.empty<string, DynamicOrderParam>()) :
DictionaryHelper.empty<string, DynamicOrderParam>();
public static DateTime created(this OrderRecordImmutable order) =>
order.HasState && order.State.HasCreated ? order.State.Created.ToDateTime().ToLocalTime() : DateTime.MinValue;
+5 -2
View File
@@ -215,8 +215,11 @@ namespace mbts.spark {
FillTuple.key_fn);
on_fill_tuples_with_root = fill_order_tuples_with_root_join.Cache();
// manual fills
on_manual_fills_securities = _position_service.subscribei_manual_fills()
.Naggle(interval, scheduler)
on_manual_fills_securities = admin_model
.permission_filter(
_position_service.subscribei_manual_fills()
.Naggle(interval, scheduler),
f => f.AccountId)
.Filter(f => f.FillType == ManualFill.Types.FillType.Local)
.join(sstore, mf => mf.SecurityKey)
.Table().Cache();
+1
View File
@@ -12,6 +12,7 @@
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
<PlatformTarget>AnyCPU</PlatformTarget>
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
<LanguageTargets Condition="'$(VisualStudioVersion)'=='16.0'">$(MSBuildToolsPath)\Microsoft.CSharp.targets</LanguageTargets>
<ApplicationIcon>logo.ico</ApplicationIcon>
<OutputType>WinExe</OutputType>
<StartupObject>mbts.ui.shell.App</StartupObject>
@@ -63,5 +63,20 @@ namespace mbts {
public static double? nan_nullable(double value) =>
double.IsNaN(value) ? (double?)null : value;
public static int? to_optional_int32(double value) {
if (double.IsNaN(value) || double.IsInfinity(value))
return null;
if (value > 0) {
value = Math.Floor(value);
if (value <= int.MaxValue)
return (int)value;
} else if (value < 0) {
value = Math.Ceiling(value);
if (value >= int.MinValue)
return (int)value;
}
return null;
}
}
}
@@ -0,0 +1,9 @@
namespace mbts.ui.controls {
class DIYGridColumn : DIYGrid.ColumnBase<string> {
public DIYGridColumn()
: base() {
is_visible = true;
Width = 60;
}
}
}
@@ -38,11 +38,7 @@ namespace mbts.ui.controls {
}
public static ICommand PreviewSoundCommand =>
new DelegateCommand<SoundNotification.SystemSound>(s => {
try {
NAudioPlayer.create(s).play();
} catch (NAudio.MmException) { }
});
new DelegateCommand<SoundNotification.SystemSound>(NAudioPlayer.play);
#region SoundTypes
public static readonly DependencyProperty SoundTypesProperty =
@@ -1005,6 +1005,8 @@
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
UseFractionalTicks="{Binding use_fractional_ticks}"
IntegerTickSize="{Binding integer_tick_size}"
/>
<c:DialogSkirt Grid.Row="1" Margin="-4" VerticalAlignment="Top" RightItems="{Binding skirt_items}"/>
</Grid>
@@ -12,6 +12,9 @@ namespace mbts.ui.converters
public Visibility VisibilityForFalse { get { return _visfalse; } set { _visfalse = value; } }
public static BooleanToVisibilityConverter Instance => new BooleanToVisibilityConverter();
public static BooleanToVisibilityConverter HiddenInstance => new BooleanToVisibilityConverter {
VisibilityForFalse = Visibility.Hidden
};
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
bool flag = false;
@@ -64,25 +64,6 @@ namespace mbts.ui {
}
}, can_execute == null ? null : new Func<object, bool>(_ => can_execute()));
public static IDialogCommand export_csv(
Func<string> get_file_content,
string source_window_name) =>
new AnonymousFileDialogCommand<object>(_ =>
_create_save_file_dialog(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
$"BTS_Spark_{source_window_name}_{DateTime.Now.ToString("yyyyMMdd_HHmm")}.csv",
new[] { ("CSV (comma delimited)", new string[] { "csv" }) }
),
(_, file) => {
try {
File.WriteAllText(file, get_file_content.Invoke());
} catch (Exception ex) {
BTSMessageBox.Show($"Error exporting data to csv: {ex}.", "CSV Export Error", BTSMessageBoxButtons.OK, System.Windows.MessageBoxImage.Error, true, false, true);
}
},
// we are always able to perform an export, even when no rows (it will just be a header)
(_) => true);
static TDialog _create_file_dialog<TDialog>(string initial_dir = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) where TDialog : FileDialog, new() {
var rv = new TDialog();
if (!string.IsNullOrEmpty(initial_dir))
+23 -1
View File
@@ -49,6 +49,28 @@ namespace mbts.spark {
public static double parse(this ContractSpecificationImmutable? spec, string text, double display_multiplier, string nan_text) =>
spec.spec_or_default().parse(text, display_multiplier, nan_text);
public static bool is_integer_tick(ContractSpecificationImmutable spec) =>
spec.MainFraction == 0 &&
spec.PriceDisplayFormat == 0 &&
spec.LowTick > 0 &&
MathUtils.is_int(spec.LowTick) &&
spec.HighTick > 0 &&
MathUtils.is_int(spec.HighTick);
public static ContractSpecification create_integer_tick_spec(int low_tick) =>
create_integer_tick_spec(low_tick, low_tick);
public static ContractSpecification create_integer_tick_spec(int low_tick, int high_tick) {
var rv = SecurityDataHelper.Defaults.ContractSpecification;
rv.MainFraction = 0;
rv.SubFraction = 0;
rv.PriceDisplayFormat = 0;
rv.PriceBreak = 0;
rv.LowTick = low_tick;
rv.HighTick = high_tick;
return rv;
}
public static double low_tick(NativePriceSize[] bids, NativePriceSize[] asks) {
double low = bids[bids.Length - 1].price;
if (double.IsNaN(low))
@@ -326,7 +348,7 @@ namespace mbts.spark {
}
static bool _check_tick_size(double tick_size) =>
(tick_size > 0 && tick_size <= 0.5) || tick_size == 1;
tick_size > 0 && (tick_size <= 0.5 || MathUtils.is_int(tick_size));
struct SpecKey : IEquatable<SpecKey> {
public readonly uint denominator, display_digits;
@@ -1,5 +1,7 @@
using mbts.ui.converters;
using System;
using System.Globalization;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
@@ -12,6 +14,31 @@ namespace mbts.ui.utilities {
public static string fmt(string title, KeyGesture key_gesture) =>
key_gesture == null ? title : $"{title} ({key_gesture.GetDisplayStringForCulture(CultureInfo.CurrentCulture)})";
public static IDisposable bind_param_source<T>(ICommand command, IObservable<T> param_source, out ICommand bound_cmd, bool dispose_command_on_dispose = false) {
bool initialized = false;
bool can_execute = false;
T param = default;
var bcmd = new DelegateCommand(() => command.Execute(param), () => can_execute);
bound_cmd = bcmd;
var sub = param_source.Subscribe(p => {
param = p;
if (!initialized) {
initialized = true;
can_execute = command.CanExecute(p);
bcmd.RaiseCanExecuteChanged();
} else if (can_execute != command.CanExecute(p)) {
can_execute = !can_execute;
bcmd.RaiseCanExecuteChanged();
}
});
return dispose_command_on_dispose ?
Disposable.Create(() => {
Disposable.dispose(ref sub);
(command as IDisposable)?.Dispose();
command = null;
}) : sub;
}
#region Paraphrased from https://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Commands/CommandHelpers.cs
public static bool CanExecuteCommandSource(ICommandSource commandSource) {
@@ -30,6 +30,21 @@ namespace mbts.ui {
public string name { get; }
}
class NamedValueVM : ViewModelBase {
string _name;
object _value;
public NamedValueVM() { }
public NamedValueVM(string name, object value) {
_name = name;
_value = value;
}
public string name { get => _name; set => Set(ref _name, value); }
public object value { get => _value; set => Set(ref _value, value); }
}
static class EnabledValueVMExtensions {
public static T? get_nullable<T>(this EnabledValueVM<T> vm) where T : struct =>
vm.enabled ? vm.value : (T?)null;
+13 -11
View File
@@ -83,22 +83,24 @@ namespace bts.api {
}
public static int Compare(SecurityImmutable x, SecurityImmutable y) {
if (x.ContractCase == ContractOneofCase.Future) return y.ContractCase == ContractOneofCase.Future ? Compare(x.Symbol, x.Future, y.Symbol, y.Future) : -1;
else if (y.ContractCase == ContractOneofCase.Future) return 1;
if (x.ContractCase == ContractOneofCase.Future) {
return y.ContractCase == ContractOneofCase.Future ? Compare(x.Symbol, x.Future, y.Symbol, y.Future) : -1;
} else if (y.ContractCase == ContractOneofCase.Future) {
return 1;
}
var xlegs = get_flat_future_legs(x).ToList();
var ylegs = get_flat_future_legs(y).ToList();
int max = Math.Min(xlegs.Count, ylegs.Count);
for (int i = 0; i < max; i++) {
int rv = Compare(xlegs[i].symbol, xlegs[i].future, ylegs[i].symbol, ylegs[i].future);
if (rv != 0) return rv;
}
for (int i = 0; i < max; i++) {
int rv = xlegs[i].quantity.CompareTo(ylegs[i].quantity);
if (rv != 0) return rv;
}
for (int i = 0; i < max; i++) {
int rv = xlegs[i].exchange.CompareTo(ylegs[i].exchange);
if (rv != 0) return rv;
if (rv != 0)
return rv;
rv = xlegs[i].quantity.CompareTo(ylegs[i].quantity);
if (rv != 0)
return rv;
rv = xlegs[i].exchange.CompareTo(ylegs[i].exchange);
if (rv != 0)
return rv;
}
return xlegs.Count.CompareTo(ylegs.Count);
}
@@ -390,6 +390,15 @@ namespace System.Collections.Generic {
public static ReadOnlyDictionary<TKey, TElement> ToReadOnlyDictionary<T, TKey, TElement>(this IEnumerable<T> enumerable, Func<T, TKey> key_selector, Func<T, TElement> element_selector, IEqualityComparer<TKey> comparer) =>
new ReadOnlyDictionary<TKey, TElement>(enumerable.ToDictionary(key_selector, element_selector, comparer));
public static ReadOnlyDictionary<TKey, TElement> ToReadOnlyDictionaryWhere<TKey, TElement>(this IEnumerable<TKey> keys, TryGetValue<TKey, TElement> value_selector) {
var mdict = new Dictionary<TKey, TElement>();
foreach (var k in keys) {
if (value_selector(k, out var val))
mdict[k] = val;
}
return new ReadOnlyDictionary<TKey, TElement>(mdict);
}
public static ILookup<TKey, TElement> ToFlatLookup<T, TKey, TElement>(this IEnumerable<T> source, Func<T, TKey> key_selector, Func<T, IEnumerable<TElement>> selector) =>
source.SelectMany(v => {
var k = key_selector(v);
@@ -103,6 +103,15 @@ namespace bts.collections {
return array;
}
public static bool SafeTryGetAt<T>(this IList<T> list, int index, out T value) {
if (list != null && index >= 0 && index < list.Count) {
value = list[index];
return true;
}
value = default;
return false;
}
public static bool TryGetAt<T>(this IList<T> list, int index, out T value) {
if (index >= 0 && index < list.Count) {
value = list[index];
-52
View File
@@ -1,52 +0,0 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
namespace bts.utils.csv {
public static class CsvHelper {
public static string ToCsv<T>(this IEnumerable<T> items, OrderedDictionary properties = null) where T : class {
var output = "";
var delimiter = ",";
if (properties == null) {
properties = new OrderedDictionary();
foreach (var pi in typeof(T).GetProperties()) {
properties.Add(pi.Name, pi);
}
}
using (var sw = new StringWriter()) {
var dict_values = properties.Values.OfType<PropertyInfo>();
var header = dict_values.Select(n => n.Name).Aggregate((first, second) => first + delimiter + second);
sw.WriteLine(header);
foreach (var i in items) {
var row = dict_values.Select(x => x.GetValue(i, null))
.Select(x => x == null ? "null" : x.ToString())
.Aggregate((first, second) => first + delimiter + second);
sw.WriteLine(row);
}
output = sw.ToString();
}
return output;
}
public static OrderedDictionary create_ordering_maintainer(Type t, string[] property_names_in_order) {
OrderedDictionary od = new OrderedDictionary();
var props = t.GetProperties();
foreach (var n in property_names_in_order) {
var query = props.Where(pi => pi.Name == n);
if (query.Any()) {
var property_info = query.First();
if (!od.Contains(property_info)) {
od.Add(n, property_info);
}
}
}
return od;
}
}
}
@@ -1,11 +1,11 @@
using System;
using System.Globalization;
namespace mbts.utils {
delegate bool TryGetValue<T>(out T value);
delegate bool TryGetValue<TKey, TValue>(TKey key, out TValue value);
namespace bts.utils {
public delegate bool TryGetValue<T>(out T value);
public delegate bool TryGetValue<TKey, TValue>(TKey key, out TValue value);
static class LogicalDelegates {
public static class LogicalDelegates {
public static readonly Func<bool, bool, bool>
And = (a, b) => a && b,
Or = (a, b) => a || b,
@@ -13,7 +13,7 @@ namespace mbts.utils {
None = (a, b) => !a && !b;
}
static class FormattingDelegates {
public static class FormattingDelegates {
public static class Price {
public static readonly Func<double, string> Value =
p => double.IsNaN(p) ? "" : p.ToString("#,0.00");
@@ -26,4 +26,4 @@ namespace mbts.utils {
p => p.ToString("#,0.00", CultureInfo.CurrentCulture);
}
}
}
}
@@ -0,0 +1,21 @@
using System;
using System.IO;
namespace bts.utils {
public static class FilePathHelper {
public static string GetUniquePathWithDate(string path_root, string ext) =>
GetUniquePath($"{path_root}_{DateTime.Now:MM - dd - yyyy_hh - mm}", ext);
public static string GetUniquePath(string path_root, string ext) {
var path = $"{path_root}.{ext}";
const int max_copies = 2 ^ 14;
int i = 1;
while (File.Exists(path)) {
path = $"{path_root}({i++}).{ext}";
if (i == max_copies)
throw new InvalidOperationException("Unable to make path unique by concating a number.");
}
return path;
}
}
}
@@ -209,7 +209,7 @@ namespace System.Reactive.Linq {
/// <returns>An IDisposable object used to unsubscribe from the source sequence, and dispose the latest value subscription.</returns>
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
IDisposable value_sub = null;
IDisposable source_sub = source.Select(v => v).Subscribe(on_source);
IDisposable source_sub = source.Subscribe(on_source);
return Disposable.Create(dispose);
// local methods
void on_source(T value) {
+253 -208
View File
@@ -1,208 +1,253 @@
using bts.api.core;
using bts.collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace bts.utils {
public static class UpdateExtensions {
public static TableUpdate<T, TKey> with_added<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> added) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, added, Enumerable.Empty<Change<T>>(), Enumerable.Empty<T>(), table.key_extractor);
public static TableUpdate<T, TKey> with_removed<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> removed) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), Enumerable.Empty<Change<T>>(), removed, table.key_extractor);
public static TableUpdate<T, TKey> with_changed<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> changed) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changed.Select(v => new Change<T>(table.state[table.key_extractor(v)], v)), Enumerable.Empty<T>(), table.key_extractor);
public static TableUpdate<T, TKey> with_changes<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<Change<T>> changes) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changes, Enumerable.Empty<T>(), table.key_extractor);
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TResult, TKey>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> with_root,
Func<T, TResult> without_root,
Func<TResult, TKey> result_key)
where TKey : IEquatable<TKey> =>
RootDependent(source, key, parent_key, with_root, without_root, result_key, default);
// TODO: unit tests
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TKey, TResult>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> get_result,
Func<T, TResult> get_result_no_parent,
Func<TResult, TKey> result_key,
TKey no_parent_key)
where TKey : IEquatable<TKey> =>
Observable.Create<TableUpdate<TResult, TKey>>(observer => {
var state = DictionaryHelper.empty<TKey, T>();
var output = TableUpdate<TResult, TKey>.mk_empty(result_key);
var parent_to_children = new SetMap<TKey, T>();
return source.Subscribe(update);
//
void update(TableUpdate<T, TKey> input) {
state = input.state;
var builder = output.to_builder();
// execute all parent_to_children changes first, so descendent-propagation will be efficient (otherwise propagation might be needlessly performed on values that are yet to be removed, or performed with the wrong root, if the real root is yet to be added).
foreach (var v in input.removed.Concat(input.changes.Select(ch => ch.Old))) {
var k = input.key_extractor(v);
parent_to_children.remove(parent_key(v), v);
}
foreach (var v in input.added.Concat(input.changed)) {
var pk = parent_key(v);
if (!pk.Equals(no_parent_key))
parent_to_children.add(pk, v);
}
foreach (var v in input.removed) {
var k = input.key_extractor(v);
builder.remove(k);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children) {
var ck = key(child);
builder.add_or_update(ck, get_result_no_parent(child));
foreach (var d in descendents(ck))
builder.add_or_update(key(d), get_result(d, child));
}
}
}
foreach (var v in input.changed) {
var k = input.key_extractor(v);
var root = get_root(v);
TResult result;
if (key(root).Equals(k)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(key(d), get_result(d, v));
} else {
result = get_result(v, root);
}
builder.add_or_update(k, result);
}
foreach (var v in input.added) {
var k = input.key_extractor(v);
var pk = parent_key(v);
TResult result;
if (pk.Equals(no_parent_key)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(key(d), get_result(d, v));
} else {
parent_to_children.add(pk, v);
var root = get_root(v);
if (key(root).Equals(k)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(key(d), get_result(d, v));
} else {
result = get_result(v, root);
}
}
builder.add_or_update(k, result);
}
observer.OnNext(output = builder.to_table());
}
T get_root(T value) {
var pk = parent_key(value);
while (!pk.Equals(no_parent_key) && state.TryGetValue(pk, out var parent)) {
value = parent;
pk = parent_key(value);
}
return value;
}
IEnumerable<T> descendents(TKey k) {
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children) {
yield return child;
foreach (var d in descendents(key(child)))
yield return d;
}
}
}
});
public static IObservable<TableUpdate<T, TKey>> MergeTable<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, IObservable<TableUpdate<T, TKey>> other) where TKey : IEquatable<TKey> =>
source.AggregateLatest(other, _Merge<T, TKey>.source_change_del, _Merge<T, TKey>.other_change_del, () => TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty);
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> get_result,
Func<T, TResult> get_result_no_parent)
where TResult : IEquatable<TResult>
where TKey : IEquatable<TKey> =>
ParentDependent(source, key, parent_key, get_result, get_result_no_parent, EqualityComparer<TResult>.Default, default);
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> get_result,
Func<T, TResult> get_result_no_parent,
IEqualityComparer<TResult> result_comparer,
TKey no_parent_key)
where TKey : IEquatable<TKey> =>
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
var state = DictionaryHelper.empty<TKey, T>();
var output = TableUpdate<KeyValuePair<TKey, TResult>, TKey>.mk_empty(kv => kv.Key);
var parent_to_children = new SetMap<TKey, T>();
return source.Subscribe(update);
//
void update(TableUpdate<T, TKey> input) {
state = input.state;
var builder = output.to_builder();
foreach (var v in input.removed) {
var k = input.key_extractor(v);
builder.remove(k);
parent_to_children.remove(parent_key(v), v);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children)
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result_no_parent(child)));
}
}
foreach (var v in input.changed) {
var k = key(v);
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, lookup_result(v)));
parent_to_children.set(parent_key(v), v);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children)
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result(child, v)));
}
}
foreach (var v in input.added) {
var k = key(v);
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, lookup_result(v)));
parent_to_children.add(parent_key(v), v);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children)
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result(child, v)));
}
}
var result = builder.to_table_checked(value_comparer);
if (result.has_changes())
observer.OnNext(output = result);
}
TResult lookup_result(T child) {
var pk = parent_key(child);
return !pk.Equals(no_parent_key) && state.TryGetValue(pk, out T parent) ? get_result(child, parent) : get_result_no_parent(child);
}
});
static class _Merge<T, TKey> where TKey : IEquatable<TKey> {
public static readonly Func<TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>, TableUpdate<T, TKey>> source_change_del = _source_changed_impl;
public static readonly Func<TableUpdate<T, TKey>, TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>> other_change_del = _other_changed_impl;
static TableUpdate<T, TKey> _source_changed_impl(TableUpdate<T, TKey> target, Change<TableUpdate<T, TKey>> source_change, TableUpdate<T, TKey> other_table) =>
TableUpdate<T, TKey>.update(target, source_change.New.added, source_change.New.changes, source_change.New.removed, source_change.New.key_extractor);
static TableUpdate<T, TKey> _other_changed_impl(TableUpdate<T, TKey> target, TableUpdate<T, TKey> source_table, Change<TableUpdate<T, TKey>> other_change) =>
TableUpdate<T, TKey>.update(target, other_change.New.added, other_change.New.changes, other_change.New.removed, other_change.New.key_extractor);
}
}
}
using bts.api.core;
using bts.collections;
using bts.utils.proto;
using Google.Protobuf;
using mbts;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
namespace bts.utils {
public static class UpdateExtensions {
public static TableUpdate<T, TKey> with_added<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> added) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, added, Enumerable.Empty<Change<T>>(), Enumerable.Empty<T>(), table.key_extractor);
public static TableUpdate<T, TKey> with_removed<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> removed) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), Enumerable.Empty<Change<T>>(), removed, table.key_extractor);
public static TableUpdate<T, TKey> with_changed<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> changed) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changed.Select(v => new Change<T>(table.state[table.key_extractor(v)], v)), Enumerable.Empty<T>(), table.key_extractor);
public static TableUpdate<T, TKey> with_changes<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<Change<T>> changes) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changes, Enumerable.Empty<T>(), table.key_extractor);
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TResult, TKey>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> with_root,
Func<T, TResult> without_root,
Func<TResult, TKey> result_key)
where TKey : IEquatable<TKey> =>
RootDependent(source, key, parent_key, with_root, without_root, result_key, default);
// TODO: unit tests
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TKey, TResult>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> get_result,
Func<T, TResult> get_result_no_parent,
Func<TResult, TKey> result_key,
TKey no_parent_key)
where TKey : IEquatable<TKey> =>
Observable.Create<TableUpdate<TResult, TKey>>(observer => {
var state = DictionaryHelper.empty<TKey, T>();
var output = TableUpdate<TResult, TKey>.mk_empty(result_key);
var parent_to_children = new SetMap<TKey, T>();
return source.Subscribe(update);
//
void update(TableUpdate<T, TKey> input) {
state = input.state;
var builder = output.to_builder();
// execute all parent_to_children changes first, so descendent-propagation will be efficient (otherwise propagation might be needlessly performed on values that are yet to be removed, or performed with the wrong root, if the real root is yet to be added).
foreach (var v in input.removed.Concat(input.changes.Select(ch => ch.Old))) {
var k = input.key_extractor(v);
parent_to_children.remove(parent_key(v), v);
}
foreach (var v in input.added.Concat(input.changed)) {
var pk = parent_key(v);
if (!pk.Equals(no_parent_key))
parent_to_children.add(pk, v);
}
foreach (var v in input.removed) {
var k = input.key_extractor(v);
builder.remove(k);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children) {
var ck = key(child);
builder.add_or_update(ck, get_result_no_parent(child));
foreach (var d in descendents(ck))
builder.add_or_update(key(d), get_result(d, child));
}
}
}
foreach (var v in input.changed) {
var k = input.key_extractor(v);
var root = get_root(v);
TResult result;
if (key(root).Equals(k)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(key(d), get_result(d, v));
} else {
result = get_result(v, root);
}
builder.add_or_update(k, result);
}
foreach (var v in input.added) {
var k = input.key_extractor(v);
var pk = parent_key(v);
TResult result;
if (pk.Equals(no_parent_key)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(key(d), get_result(d, v));
} else {
parent_to_children.add(pk, v);
var root = get_root(v);
if (key(root).Equals(k)) {
result = get_result_no_parent(v);
foreach (var d in descendents(k))
builder.add_or_update(key(d), get_result(d, v));
} else {
result = get_result(v, root);
}
}
builder.add_or_update(k, result);
}
observer.OnNext(output = builder.to_table());
}
T get_root(T value) {
var pk = parent_key(value);
while (!pk.Equals(no_parent_key) && state.TryGetValue(pk, out var parent)) {
value = parent;
pk = parent_key(value);
}
return value;
}
IEnumerable<T> descendents(TKey k) {
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children) {
yield return child;
foreach (var d in descendents(key(child)))
yield return d;
}
}
}
});
public static IObservable<TableUpdate<T, TKey>> MergeTable<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, IObservable<TableUpdate<T, TKey>> other) where TKey : IEquatable<TKey> =>
source.AggregateLatest(other, _Merge<T, TKey>.source_change_del, _Merge<T, TKey>.other_change_del, () => TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty);
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> get_result,
Func<T, TResult> get_result_no_parent)
where TResult : IEquatable<TResult>
where TKey : IEquatable<TKey> =>
ParentDependent(source, key, parent_key, get_result, get_result_no_parent, EqualityComparer<TResult>.Default, default);
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
this IObservable<TableUpdate<T, TKey>> source,
Func<T, TKey> key,
Func<T, TKey> parent_key,
Func<T, T, TResult> get_result,
Func<T, TResult> get_result_no_parent,
IEqualityComparer<TResult> result_comparer,
TKey no_parent_key)
where TKey : IEquatable<TKey> =>
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
var state = DictionaryHelper.empty<TKey, T>();
var output = TableUpdate<KeyValuePair<TKey, TResult>, TKey>.mk_empty(kv => kv.Key);
var parent_to_children = new SetMap<TKey, T>();
return source.Subscribe(update);
//
void update(TableUpdate<T, TKey> input) {
state = input.state;
var builder = output.to_builder();
foreach (var v in input.removed) {
var k = input.key_extractor(v);
builder.remove(k);
parent_to_children.remove(parent_key(v), v);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children)
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result_no_parent(child)));
}
}
foreach (var v in input.changed) {
var k = key(v);
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, lookup_result(v)));
parent_to_children.set(parent_key(v), v);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children)
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result(child, v)));
}
}
foreach (var v in input.added) {
var k = key(v);
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, lookup_result(v)));
parent_to_children.add(parent_key(v), v);
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
foreach (var child in children)
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result(child, v)));
}
}
var result = builder.to_table_checked(value_comparer);
if (result.has_changes())
observer.OnNext(output = result);
}
TResult lookup_result(T child) {
var pk = parent_key(child);
return !pk.Equals(no_parent_key) && state.TryGetValue(pk, out T parent) ? get_result(child, parent) : get_result_no_parent(child);
}
});
static class _Merge<T, TKey> where TKey : IEquatable<TKey> {
public static readonly Func<TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>, TableUpdate<T, TKey>> source_change_del = _source_changed_impl;
public static readonly Func<TableUpdate<T, TKey>, TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>> other_change_del = _other_changed_impl;
static TableUpdate<T, TKey> _source_changed_impl(TableUpdate<T, TKey> target, Change<TableUpdate<T, TKey>> source_change, TableUpdate<T, TKey> other_table) =>
TableUpdate<T, TKey>.update(target, source_change.New.added, source_change.New.changes, source_change.New.removed, source_change.New.key_extractor);
static TableUpdate<T, TKey> _other_changed_impl(TableUpdate<T, TKey> target, TableUpdate<T, TKey> source_table, Change<TableUpdate<T, TKey>> other_change) =>
TableUpdate<T, TKey>.update(target, other_change.New.added, other_change.New.changes, other_change.New.removed, other_change.New.key_extractor);
}
}
}
namespace bts.utils.diagnostics {
public static class DiagnosticUpdateExtensions {
// Hard to imagine why this util would be needed outside of diagnosig bugs.
// The #if DEBUG directive is an extra precaution to avoid accidentally writing
// files like this on a client's machine.
#if DEBUG
static readonly string _proto_capture_ext = "proto_capture";
public static IObservable<TableUpdate<T, TKey>> ProtoBufCapture<T, TKey, TProto>(this IObservable<TableUpdate<T, TKey>> source, Func<T, TProto> to_message)
where TKey : IEquatable<TKey>
where TProto : class, IMessage<TProto> =>
ProtoBufCapture(source,
FilePathHelper.GetUniquePathWithDate(Path.Combine(Directory.GetCurrentDirectory(), typeof(TProto).Name), _proto_capture_ext),
to_message);
public static IObservable<TableUpdate<T, TKey>> ProtoBufCapture<T, TKey, TProto>(this IObservable<TableUpdate<T, TKey>> source, string path, Func<T, TProto> to_message)
where TKey : IEquatable<TKey>
where TProto : class, IMessage<TProto> =>
Observable.Create<TableUpdate<T, TKey>>(o => {
// write the state on dispose, and/or on error
IReadOnlyDictionary<TKey, T> state = DictionaryHelper.empty<TKey, T>();
var sub = source.Subscribe(table => {
state = table.state;
o.OnNext(table);
}, ex => {
save(true);
o.OnError(ex);
},
o.OnCompleted);
return Disposable.Create(() => {
save(false);
Disposable.dispose(ref sub);
});
void save(bool has_error) {
if (state.Count > 0)
FileStreamHelper.WriteProtos(has_error ? $"{path}(error)" : path, state.Values.Select(to_message).ExceptNull());
}
});
#endif
}
}
@@ -0,0 +1,13 @@
using System.IO;
using System.Reflection;
namespace bts.resources {
class ResourceHelper {
const string _default_namespace = "spark_test.Resources";
public static Stream LoadResource(string name) {
var ass = Assembly.GetExecutingAssembly();
return ass.GetManifestResourceStream(name) ?? ass.GetManifestResourceStream($"{_default_namespace}.{name}");
}
}
}
@@ -1,13 +1,11 @@
using System;
using bts.api;
using bts.resources;
using bts.test;
using bts.utils.proto;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using bts.api;
using bts.test;
namespace bts.mcore
{
namespace bts.mcore {
[TestGroup]
class SecurityTests {
[Test]
@@ -28,5 +26,23 @@ namespace bts.mcore
Assert.That(dict[b] == 1);
Assert.That(dict[c] == 2);
}
[Test]
public void test_security_compare() {
const string securities_resource = "KrandallOrderBookCrash.security_proto";
// verify that the sample size doesn't shrink for some reason.
// if it grows a bit that's fine; the generic comparison test
// asserts against collections that are too large.
const int min_sample_size = 91;
var securities = StreamHelper.ReadProtos<Security>(
ResourceHelper.LoadResource(securities_resource))
.Select(s => new SecurityImmutable(s))
.ToList();
Assert.IsTrue(securities.Count >= min_sample_size);
ComparisonTests.transitive_comparison(securities, SecurityHelper.Comparer.InstanceImmutable);
ComparisonTests.identity_comparison(securities, SecurityHelper.Comparer.InstanceImmutable);
}
}
}
@@ -0,0 +1,34 @@
using bts.test;
using System.Collections.Generic;
using System.Linq;
namespace bts.mcore {
static class ComparisonTests {
public static void transitive_comparison<T>(IEnumerable<T> items, IComparer<T> comparer) {
// Verify that the sample size isn't too big.
// This test compares each item to every other item
// (technically twice; lhs/rhs is flipped the second time),
// so performance is O(n^2).
const int max_sample_size = 4092;
var sorted_items = items.ToList();
Assert.That(sorted_items.Count <= max_sample_size);
sorted_items.Sort(comparer);
for (int i = 0; i < sorted_items.Count - 1; i++) {
for (int j = i + 1; j < sorted_items.Count; j++) {
assert_comp(i, j);
}
}
void assert_comp(int lhs, int rhs) {
int pos_comp = lhs.CompareTo(rhs);
// hit both sides for good measure
Assert.That(comparer.Compare(sorted_items[lhs], sorted_items[rhs]) == pos_comp);
Assert.That(comparer.Compare(sorted_items[rhs], sorted_items[lhs]) == -pos_comp);
}
}
public static void identity_comparison<T>(IEnumerable<T> items, IComparer<T> comparer) {
foreach (var v in items)
Assert.That(comparer.Compare(v, v) == 0);
}
}
}
@@ -61,7 +61,7 @@ public static async Task<IEnumerable<SecurityImmutable>> get_multiple_securities
return Enumerable.Empty<SecurityImmutable>();
}
public static async Task<IEnumerable<FutureContract>> get_expirations(MarketDataRelay relay, string symbol, SecurityType security_type, MDRequest.Types.Flags flags = MDRequest.Types.Flags.Bbo, Venue v = Venue.Sim) {
public static async Task<(bool, List<FutureContract>)> get_expirations(MarketDataRelay relay, string symbol, SecurityType security_type, MDRequest.Types.Flags flags = MDRequest.Types.Flags.Bbo, Venue v = Venue.Sim) {
var request = new MDRequest() {
Host = _MD_HOST,
Channels = {
@@ -89,7 +89,7 @@ public static async Task<IEnumerable<SecurityImmutable>> get_multiple_securities
}
}
return (IEnumerable<FutureContract>) futures;
return (result.success, futures);
}
}
@@ -22,8 +22,9 @@ namespace bts.tests
public class SpreaderUtilities {
public static async Task<(SecurityImmutable s1, SecurityImmutable s2)> get_securities_for_common_setup_one(MarketDataRelay relay, string symbol) {
var exps = (await MarketDataUtilities.get_expirations(relay, symbol, SecurityType.Future)).ToList();
var (success, exps) = await MarketDataUtilities.get_expirations(relay, symbol, SecurityType.Future);
Assert.IsTrue(success, "Could not reach market data service");
Assert.IsNotNull(exps);
Assert.IsTrue(exps.Count() > 2, "Need at least two expirations");
+3 -1
View File
@@ -14,5 +14,7 @@
<ItemGroup>
<Content Include="../spark_test/system_test_scripts/*.sh" CopyToOutputDirectory="PreserveNewest"></Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\KrandallOrderBookCrash.security_proto" />
</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/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metricsLine truncated
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metricsLine truncated
@@ -685,6 +685,7 @@ bts::api::admin::BTSAccount* AdminServiceUtils::create_account(bts::api::admin::
new_account_ptr->set_name(account_name);
new_account_ptr->set_description(description);
new_account_ptr->set_is_active(is_active);
new_account_ptr->mutable_risk_limit_enforcement()->set_ignore_spread_position_limits(true);
return new_account_ptr;
}
void AdminServiceUtils::update_account(bts::api::admin::AdminData& admin_data,
+27 -2
View File
@@ -104,6 +104,7 @@ message CustomOrderDefinition
SplitterOrderDefinition splitter = 29;
ScratchOrderDefinition scratch = 31;
ProRataOrderDefinition prorata = 32;
DeleteAndCoverDefinition delete_and_cover = 34;
DynamicOrderDefinition dynamic = 1000;
}
@@ -144,6 +145,13 @@ message TickOrderDefinition {
bool detach_on_toward = 5;
CustomOrderDefinition child = 6;
}
enum LeanOnToward
{
NO_CHANGE = 0; //continue to lean on initial inside market
CANCEL = 1; //cancel algo and order
DETACH = 2; //cancel algo but leave order working
REPRICE = 3; //lean on new inside market
}
message LeanOrderDefinition {
spark.QuantityMeasure cross_threshold = 1;
uint32 max_chase_ticks = 2;
@@ -179,6 +187,21 @@ message CoverOrderDefinition {
CustomOrderDefinition child = 3;
bool cancel_on_external_hedge_cancel = 4;
bool cancel_hedges_on_cancel = 5;
bool detach_hedges = 6;
}
message DeleteAndCoverDefinition{
// cover
uint32 hedge_ticks = 1;
bool cancel_on_external_hedge_cancel = 2;
bool cancel_hedges_on_cancel = 3;
bool detach_hedges = 4;
// main child = top
spark.QuantityMeasure top_threshold = 10;
uint32 ticks_off = 11;
// hedge child = Lean
spark.QuantityMeasure cross_threshold = 20;
uint32 max_chase_ticks = 21;
LeanOnToward trade_toward_behavior = 22;
}
message OffsetOrderDefinition {
uint32 tick_offset_on_deploy = 1;
@@ -383,7 +406,9 @@ message OCOOrderState {}
message PricerOrderState {}
message ScratchOrderState {}
message ProRataOrderState {}
message DynamicOrderState {}
message DynamicOrderState {
map<string,DynamicOrderParam> custom_state = 1;
}
message QueueHolderState {}
message SplitterOrderState {}
@@ -644,7 +669,7 @@ message RegisteredDynamicOrderTypes {
message DynamicOrderParam {
oneof value {
uint32 as_int32 = 1;
int32 as_int32 = 1;
uint32 as_uint32 = 2;
double as_double = 3;
string as_string = 4;
+3 -6
View File
@@ -1,13 +1,10 @@
#nusers: 1
#allow concurrent : true
#expires: 2020-Mar-31 00:00:00 637212096000000000
#generation_time: 2020-Jan-06 18:56:23.406931 5248825356261457214
#expires: 2021-Jan-01 00:00:00 637450560000000000
#generation_time: 2020-Apr-03 18:39:31.010863 5248901378137496534
EncryptedKeyCode {
value {
keycode: "Ixb/z6hwFQmojpm4GwNEpQmsqpYT9OTHbcKtROIqj38GwGqyVn54JMDB04V048quO+NAI78DKJXsUWgjDH0y5HQlwN6Lq48s/bdwKqR1OTBPr6xoTImtCb+l5b+8fUGlg7t3X8gfMdxohHgcUi9k9n7ZrnNvqcjdYKoP0CaTZT8zHuBQx3f4Lqrz32gq9rJ5skRjH6ZqNblNdlsF+kOAqXCkvI5p0QfyKgtCodr33qsW2cbqY5gUD9KwsnmFVa8/zuFBucsoM7dHB47yRaIeOFFu7xGayAdAHA8SwLyoxE5y+adIX1SYnAiqAu1raZpeXgRE1LNQk3c7uhUjt0coag=="
keycode: "BJBQWsbdTXWa/iLmOf3ekE8vP/NYkf0Q/nUsp/rJ78Ze3W9pNXICCMabmmsr1xOM7aqRIclPgZd955uSSfvFkdNWFTxwPQ5IfnDGopj3QdhUVxhzgVLaXHN+vC0H/MWrDQ5K4WY5J/6VXgUn1LSN/2HNb5iR2sKXeh3Alamg87YtCWEi+scelffE7HK5ERT/3A3Y/e0pqUFhP6xouIjHRhC2FRBb6OoJQVvFwVDADSuKZNo9B6GhC3H0YdHqjFhGDoky5ecgzeKIX8uWJEQNnQXQJ0iTgQ95+YR5IMU+XWdlVfSrc0fuTmj3YWcBQ8Kl2ItEcobyZzJYega3tu6Ftw=="
}
}
+10
View File
@@ -52,6 +52,11 @@ message PublisherOptions
optional uint32 heartbeat_boot_seconds = 12 [default = 60]; // 1 minute grace period from shm creation
optional bool skip_options = 13 [default = true];
optional uint32 hot_initial = 20 [default = 1024];
optional uint32 hot_growth = 21 [default = 1024];
optional uint32 cold_initial = 22 [default = 8192];
optional uint32 cold_growth = 23 [default = 2048];
}
message ICEPublisherOptions
@@ -193,6 +198,11 @@ message RecordOptions {
optional bool record_sim = 5 [default=false];
}
// If starting on Sunday pre-load the selected symbols from disk
message SecurityPreload {
repeated string symbol = 1;
}
//
// Replay options
//
+1
View File
@@ -76,6 +76,7 @@ message LadderKnobs {
message OrderBookOptions {
optional bool animate_child_order_rows = 1 [default = false];
optional double status_color_opacity_when_selected = 2 [default = 0.35];
optional bool can_edit_specific_algo_params = 3 [default = false];
}
message OrderTicketOptions {
+2
View File
@@ -67,4 +67,6 @@ message DynamicAlgos{
optional bool enable_dorman = 1 [default = false];
optional bool enable_pyramid = 2 [default = false];
optional bool enable_tas = 3 [default = false];
optional bool enable_tascounter = 4 [default = false];
optional bool enable_limit_cover = 5 [default = false];
}
+6 -6
View File
@@ -41,16 +41,16 @@ inline api::Side other_side(api::Side side) {
}
}
inline double market_epislon() { return 1e-10; }
inline double market_epsilon() { return 1e-10; }
// better == p1 more aggressive than p1 ie for a buy order p1 is higher
// than p2, and for a sell order p1 is lower than p2
inline bool is_better(api::Side side, Price p1, Price p2) {
switch (side) {
case api::SIDE_BUY:
return p1.value() - p2.value() > market_epislon();
return p1.value() - p2.value() > market_epsilon();
case api::SIDE_SELL:
return p2.value() - p1.value() > market_epislon();
return p2.value() - p1.value() > market_epsilon();
default:
return false;
}
@@ -76,7 +76,7 @@ inline Price worsen_price(api::Side side, Price p, Price offset) {
}
inline bool is_same(Price p1, Price p2) {
return std::abs(p1.value() - p2.value()) < market_epislon();
return std::abs(p1.value() - p2.value()) < market_epsilon();
}
inline bool is_equal_or_better(api::Side side, Price p1, Price p2) {
@@ -212,5 +212,5 @@ inline bool gr_than(double p1, double p2, double tick_size) {
!test_price_equality(p1, p2, tick_size);
}
}}
} // namespace securities
} // namespace bts
+6 -1
View File
@@ -792,7 +792,7 @@ public:
// We can safely send a security key to every _instance because by definition
// a security cannot appear in 2 instances
if (unresolved_securities.size() > 250) {
LOG(md,Warn,"Failed to resolve legs for {}"
LOG(md,Info,"Failed to resolve legs for {}"
" spreads. This can happen when a spread is subscribed without "
"it's underliers and may lead to higher than expected CPU use.",
unresolved_securities.size() );
@@ -901,6 +901,10 @@ public:
return _cache.get_security(key);
}
std::vector<api::Security const *> get_securities() const {
return _cache.get_securities();
}
MDConsumerChannel get_channel(const std::string& symbol, SecurityType type, Venue exchange) const {
for (auto& inst : _instances) {
if ((inst->symbol() == symbol && inst->type() == type && inst->exchange() == exchange) ||
@@ -976,6 +980,7 @@ size_t MDConsumer::walk_security_list(SecurityCallback cb, const string& symbol,
size_t MDConsumer::scan_securities(bool resolve_legs) { return _pimpl->scan_securities(resolve_legs); }
void MDConsumer::on_security(SecurityCallback cb, bool resolve_legs) { _pimpl->on_security(cb, resolve_legs); }
const api::Security* MDConsumer::get_security(SecurityKey key) const { return _pimpl->get_security(key).get(); }
std::vector<api::Security const*> MDConsumer::get_securities() const {return _pimpl->get_securities();}
MDConsumerChannel MDConsumer::get_channel(const std::string& symbol, SecurityType type, Venue exchange) const {
return _pimpl->get_channel(symbol, type, exchange);
+3
View File
@@ -59,6 +59,9 @@ public:
// You still need to call scan_securities() periodically to discover new securities
const api::Security* get_security(SecurityKey key) const;
// Retrieve all cached securities
std::vector<api::Security const*> get_securities() const;
MDConsumerChannel get_channel(const std::string& symbol, SecurityType type, Venue exchange) const;
MDConsumerChannel get_channel(const UserSymbol&) const;
+8 -2
View File
@@ -20,8 +20,14 @@ public:
_symbol(symbol),
_type(type),
_exchange(exchange),
_hot(hot_segment_name(symbol, type, exchange, platform), 1024, 1024, flags & MDProducerFlags::Shared),
_cold(cold_segment_name(symbol, type, exchange, platform), 8192, 2048, flags & MDProducerFlags::Shared),
_hot(hot_segment_name(symbol, type, exchange, platform),
config::get<PublisherOptions>().hot_initial(),
config::get<PublisherOptions>().hot_growth(),
flags & MDProducerFlags::Shared),
_cold(cold_segment_name(symbol, type, exchange, platform),
config::get<PublisherOptions>().cold_initial(),
config::get<PublisherOptions>().cold_growth(),
flags & MDProducerFlags::Shared),
_trades(_cold.header().trade_buffer, BTS_ARRAYSIZE(_cold.header().trade_buffer), true),
_rfqs(_cold.header().rfq_buffer, BTS_ARRAYSIZE(_cold.header().rfq_buffer), true)
{
+12 -1
View File
@@ -69,6 +69,17 @@ SecurityPtr SecurityCache::get_security(SecurityKey key) {
}
}
std::vector<api::Security const*> SecurityCache::get_securities()
{
std::vector<api::Security const*> result;
result.reserve(100000);
std::unique_lock<std::mutex> lock(_me->cache_mutex);
for (auto const&p : _me->cache)
result.push_back(p.second.get());
return result;
}
SecurityPtr SecurityCache::add_db_security(const SecurityPtr& security) {
bts_assert(security);
std::lock_guard<std::mutex> lock(_me->cache_mutex);
@@ -90,4 +101,4 @@ bool SecurityCache::add_live_security(const SecurityPtr& security) {
void SecurityCache::flush_to_disk() { _me->flush(); }
}}//ns
}}//ns
+4 -1
View File
@@ -22,6 +22,9 @@ public:
//Load security from cache with fallback to database
SecurityPtr get_security(SecurityKey key);
// Load all securities from cache, no fallback to database
std::vector<api::Security const*> get_securities();
// Get the security if it's in the cache otherwise return nullptr. Does not query security database.
SecurityPtr try_get_security(SecurityKey key) const;
@@ -33,4 +36,4 @@ public:
};
}}//ns
}}//ns
+5 -1
View File
@@ -10,4 +10,8 @@ const api::Security* SecurityOracle::get_security(SecurityKey key) const {
return _client.current_consumer().get_security(key);
}
}}//namespace
std::vector<api::Security const*> SecurityOracle::get_securities() const {
return _client.current_consumer().get_securities();
}
}}//namespace
+4 -1
View File
@@ -1,6 +1,8 @@
#pragma once
#include "lib/domain_model/SecurityKey.h"
#include <memory>
#include <vector>
namespace bts {
namespace api {class Security;}
@@ -12,6 +14,7 @@ public:
SecurityOracle(const MDClient&);
~SecurityOracle();
const api::Security* get_security(SecurityKey key) const;
std::vector<api::Security const*> get_securities() const;
};
}}//namespace
}}//namespace
+4 -4
View File
@@ -18,16 +18,16 @@ const char * onixs_license[] = {
" <name>Blue Trading Systems OF 9420-2</name>",
" </customer>",
" <period>",
" <since>2019-05-02</since>",
" <till>2020-05-13</till>",
" <since>2020-05-01</since>",
" <till>2021-05-12</till>",
" </period>",
" <products>",
" <product id=\"4\" name=\"CME Market Data Handler for C++\" />",
" <product id=\"7\" name=\"ICE iMpact Multicast Price Feed Handler for C++\" />",
" <product id=\"67\" name=\"Eurex Market and Reference Data Interfaces (EMDI, RDI) Handler for C++\" />",
" </products>",
" <checksum>5E50A1DACD94D7C1BECD316EEAE5AB02</checksum>",
"</license>",
" <checksum>1C9210495AA3C9C884B7A37A4C7AD8F9</checksum>",
"</license>"
};
OnixsTemplLicense onixs_create_temporary_license() {
+7 -4
View File
@@ -154,10 +154,13 @@ void OrderStore::store_and_publish_if_complete(
bool OrderStore::_order_cutoff(const api::orderman::OrderRecord &rec) {
// TODO: move this to orderutils
bool working = (rec.state().status() ==
api::orderman::OrderStatus::ORDER_STATUS_WORKING) ||
(rec.state().status() ==
api::orderman::OrderStatus::ORDER_STATUS_PENDING_WORKING);
bool working =
(rec.state().status() ==
api::orderman::OrderStatus::ORDER_STATUS_WORKING) ||
(rec.state().status() ==
api::orderman::OrderStatus::ORDER_STATUS_DISCONNECTED) ||
(rec.state().status() ==
api::orderman::OrderStatus::ORDER_STATUS_PENDING_WORKING);
return me->soft_cleanup_enabled && !working &&
rec.state().created().seconds() < me->cutoff_time.tv_sec;
}
+44 -1
View File
@@ -7,6 +7,7 @@
#include "lib/utils/PBUtils.h"
#include "lib/utils/bts_assert.h"
#include <google/protobuf/timestamp.pb.h>
#include <google/protobuf/util/time_util.h>
#include <sstream>
#include <sys/time.h>
@@ -53,7 +54,7 @@ timeval SODPosition::sod_time() {
// if the server started before the sod for today
if (rv.tv_sec >= now_epoch)
rv = subtract_days (rv,1);
return rv;
return rv;
}
timeval SODPosition::get_order_cutoff_time(
@@ -98,6 +99,48 @@ timeval SODPosition::now(){
return tv;
}
google::protobuf::Timestamp SODPosition::today_local_time_to_utc_timestamp (
uint16_t hours,
uint16_t mins,
uint16_t secs,
bool add_day_if_less,
bool subtract_day_if_more){
time_t now_epoch;
time(&now_epoch); // seconds since epoch (utc) (independent of machine timezone)
auto now_ts = google::protobuf::util::TimeUtil::TimeTToTimestamp(now_epoch);
struct tm *ptm_local = localtime(&now_epoch); // in local timezone
ptm_local->tm_hour = hours;
ptm_local->tm_min = mins;
ptm_local->tm_sec = secs;
ptm_local->tm_isdst = -1;
time_t ovrd_epoch = mktime(ptm_local); // converts local time to UTC epoch time
if(ovrd_epoch < now_epoch){
if (add_day_if_less)
ovrd_epoch += 24*60*60;
}
else{
if(subtract_day_if_more)
ovrd_epoch -= 24*60*60;
}
auto ovrd_ts = google::protobuf::util::TimeUtil::TimeTToTimestamp(ovrd_epoch);
return ovrd_ts;
}
google::protobuf::Timestamp SODPosition::add_seconds (
const google::protobuf::Timestamp& ts,
int secs_diff){
auto secs = google::protobuf::util::TimeUtil::TimestampToSeconds(ts);
secs += secs_diff;
return google::protobuf::util::TimeUtil::SecondsToTimestamp(secs);
}
google::protobuf::Timestamp SODPosition::add_nanoseconds (
const google::protobuf::Timestamp& ts,
long diff){
return google::protobuf::util::TimeUtil::NanosecondsToTimestamp(
google::protobuf::util::TimeUtil::TimestampToNanoseconds(ts) + diff);
}
google::protobuf::Timestamp SODPosition::to_timestamp (timeval tv){
google::protobuf::Timestamp rv;
rv.set_seconds(tv.tv_sec);
+12
View File
@@ -31,6 +31,18 @@ public:
const std::map<std::string,std::string>& ordermap);
static timeval now();
static google::protobuf::Timestamp to_timestamp (timeval);
static google::protobuf::Timestamp today_local_time_to_utc_timestamp (
uint16_t hours,
uint16_t mins,
uint16_t secs,
bool add_day_if_less,
bool subtract_day_if_more);
static google::protobuf::Timestamp add_seconds (
const google::protobuf::Timestamp& ts,
int secs);
static google::protobuf::Timestamp add_nanoseconds (
const google::protobuf::Timestamp& ts,
long diff);
bool is_sod (const google::protobuf::Timestamp& fill_time) const;
};
@@ -157,6 +157,7 @@ void OrderManager::on_filled_position(
}
for (auto const &ev : *(update.events)) {
me->services_data.position.update(ev);
me->services_data.limiter.set_filled_position(
*ev.security, ev.account_id, ev.long_quantity, ev.short_quantity);
}
@@ -550,6 +551,8 @@ void OrderManager::init_order_services() {
std::make_unique<ContentSubscription<TimerEvent, NoKey>>(*this, log);
me->services_data.order_creation_subs =
std::make_unique<ContentSubscription<CreateOrderReplyEvent>>(*this, log);
me->services_data.position_subs =
std::make_unique<ContentSubscription<FilledPositionEvent,NoKey>>(*this, log);
}
void OrderManager::after_event() {
@@ -32,6 +32,7 @@ void OrderServices::init(OrderServicesData &data) {
_status_updates.init(*data.status_subs);
_settlement_updates.init(*data.settlement_subs);
_time_change.init(*data.time_subs);
_position_update.init(*data.position_subs);
if (_sessions.empty())
for (auto &sd : data.sessions)
_sessions.emplace_back(*sd);
@@ -8,6 +8,7 @@
#include "lib/orderman/actors/order_manager/SessionServices.h"
#include "lib/orderman/events/TickerUpdateEvent.h"
#include "lib/orderman/events/StatusUpdateEvent.h"
#include "lib/orderman/events/FilledPositionEvent.h"
#include "lib/orderman/governor/Governor.h"
#include "lib/orderman/orderman_utils/EventStream.h"
#include "lib/orderman/orders/Order.h"
@@ -62,6 +63,10 @@ class OrderServices {
_data->log.log(corid(), entry_kind, uint64_1, txt_1, uint32_1);
}
std::vector<PositionUpdate> const& current_positions() {
return _data->position.current_positions();
}
trace::ScopedPerfLogEntry log_scope(const char *entry_kind,
uint64_t uint64_1 = 0,
const char *txt_1 = "",
@@ -123,6 +128,15 @@ class OrderServices {
void unsubscribe_from_time() { _time_change.unsub(_time_change_sub); }
// Time
// Position Updates
template <class CB> void subscribe_to_position_updates(CB &&cb) {
_position_update_sub = _position_update.sub(
[this, cb](auto const &ev) { this->on_external_event(ev, cb); });
}
void unsubscribe_from_position_updates() { _position_update.unsub(_position_update_sub); }
// Position Updates
// Execution Reports
using ExeReportSubID = EventStream<NewExecutionReportEvent>::SubID;
template <class CB>
@@ -298,8 +312,10 @@ class OrderServices {
EventStream<StatusUpdateEvent> _status_updates;
EventStream<SettlementUpdateEvent> _settlement_updates;
EventStream<mercury::TimerEvent, mercury::NoKey> _time_change;
EventStream<orderman::FilledPositionEvent, mercury::NoKey> _position_update;
boost::container::small_vector<SessionServices, 20> _sessions;
EventStream<mercury::TimerEvent, mercury::NoKey>::SubID _time_change_sub;
EventStream<orderman::FilledPositionEvent, mercury::NoKey>::SubID _position_update_sub;
EventStream<CreateOrderReplyEvent> _create_order_reply;
}; // namespace orderman
} // namespace orderman
@@ -35,6 +35,7 @@
#include "lib/orderman/order_router/OrderRouter.h"
#include "lib/utils/datetime.h"
#include "lib/utils/Puid.h"
#include "lib/orderman/actors/order_manager/PositionIndex.h"
#include <boost/date_time/posix_time/posix_time.hpp>
@@ -87,6 +88,7 @@ class OrderServicesData {
OrderManager &owner;
trace::PerfLog &log;
securities::MapSecurityDB sec_db;
PositionIndex position;
OrderRouter order_router;
Limiter limiter;
OrderFactory order_factory;
@@ -101,6 +103,8 @@ class OrderServicesData {
time_subs;
std::unique_ptr<mercury::ContentSubscription<CreateOrderReplyEvent>>
order_creation_subs;
std::unique_ptr<mercury::ContentSubscription<FilledPositionEvent,mercury::NoKey>>
position_subs;
mercury::Stream<CreateOrderRequestEvent> create_order_req_pub;
mercury::Stream<CreateFIXBufferRequestEvent> create_fix_req_pub;
@@ -0,0 +1,31 @@
#include "PositionIndex.h"
#include "lib/orderman/events/FilledPositionEvent.h"
#include "lib/orderman/orders/algo/PositionUpdate.h"
namespace bts {
namespace orderman {
struct PositionIndex::PImpl {
PImpl() : position_idx(), current_positions() {}
std::map<std::pair<SecurityKey,AccountID>,size_t> position_idx;
std::vector<PositionUpdate> current_positions;
};
void PositionIndex::update(FilledPositionEvent const&ev) {
auto &idx = me->position_idx[{SecurityKey(ev.security->id()),ev.account_id}];
if (idx == 0) {
me->current_positions.push_back({});
idx = me->current_positions.size();
}
me->current_positions[idx-1] = {ev.security,ev.account_id,ev.long_quantity, ev.short_quantity};
}
std::vector<PositionUpdate> const& PositionIndex::current_positions() const {
return me->current_positions;
}
PositionIndex::PositionIndex() : me (new PImpl()) {}
PositionIndex::~PositionIndex() {}
}}
@@ -0,0 +1,23 @@
#pragma once
#include <memory>
#include <vector>
namespace bts {
namespace orderman {
class FilledPositionEvent;
struct PositionUpdate;
class PositionIndex {
public:
PositionIndex();
~PositionIndex();
void update(FilledPositionEvent const&);
std::vector<PositionUpdate> const &current_positions() const;
private:
struct PImpl;
std::unique_ptr<PImpl> me;
};
}}
+27 -22
View File
@@ -11,16 +11,16 @@ namespace bts {
namespace orderman {
class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
public:
public:
using SynopsisPolicy = mercury::CacheAll<OrderStateUpdateEvent>;
OrderStateUpdateEvent()
: key(null_order_key()), parent_key(null_order_key()) {}
OrderStateUpdateEvent(
OrderKey k, OrderKey pk,
std::shared_ptr<const api::orderman::OrderDefinition> const &def,
api::orderman::OrderState const &state) {
OrderKey k, OrderKey pk,
std::shared_ptr<const api::orderman::OrderDefinition> const &def,
api::orderman::OrderState const &state) {
init(k, pk, def, state);
}
@@ -41,18 +41,21 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
display_status = state.display_status();
custom = state.custom_case();
auto native_state = get_native_state(state);
if (native_state) {
exchange_order_id = native_state->exchange_order_id();
last_msg_seq_num = native_state->last_msg_seq_num();
sub_error_code = native_state->sub_error_code();
native_status = native_state->native_status();
routing_record = native_state->routing_record();
native_type = native_state->native_type();
orderman::str_to(native_state->last_transact_time(),
&last_transact_time);
stop_px = native_state->stop_px();
if (custom == api::orderman::OrderState::kDynamic) {
dynamic_order_state = state.dynamic();
} else {
auto native_state = get_native_state(state);
if (native_state) {
exchange_order_id = native_state->exchange_order_id();
last_msg_seq_num = native_state->last_msg_seq_num();
sub_error_code = native_state->sub_error_code();
native_status = native_state->native_status();
routing_record = native_state->routing_record();
native_type = native_state->native_type();
orderman::str_to(native_state->last_transact_time(),
&last_transact_time);
stop_px = native_state->stop_px();
}
}
}
@@ -70,7 +73,6 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
uint32_t filled_at_attach_time_quantity;
std::string last_msg;
api::orderman::OrderState::DisplayStatus display_status;
api::orderman::OrderState::CustomCase custom;
// native order state
@@ -82,7 +84,10 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
api::orderman::NativeOrderType native_type;
mercury::TimeStampStr last_transact_time;
double stop_px;
// dynamic order state
std::optional<api::orderman::DynamicOrderState> dynamic_order_state;
api::orderman::OrderState quick_rebuild_state() const {
api::orderman::OrderState result;
result.set_status(status);
@@ -111,11 +116,11 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
break;
case api::orderman::OrderState::kStopLimit:
rebuild_native_state(
result.mutable_stop_limit()->mutable_native_state());
result.mutable_stop_limit()->mutable_native_state());
break;
case api::orderman::OrderState::kGlbxIceberg:
rebuild_native_state(
result.mutable_glbx_iceberg()->mutable_native_state());
result.mutable_glbx_iceberg()->mutable_native_state());
break;
case api::orderman::OrderState::kIceberg:
result.mutable_iceberg();
@@ -163,7 +168,7 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
result.mutable_prorata();
break;
case api::orderman::OrderState::kDynamic:
result.mutable_dynamic();
*result.mutable_dynamic() = dynamic_order_state.value();
break;
case api::orderman::OrderState::kSplitter:
result.mutable_splitter();
@@ -175,7 +180,7 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
return result;
}
private:
private:
void rebuild_native_state(api::orderman::NativeOrderState *st) const {
st->set_stop_px(stop_px);
st->set_exchange_order_id(exchange_order_id);
@@ -24,6 +24,7 @@
#include "lib/orderman/orders/algo/algo_orders/OffsetAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/ScratchAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/ProRataAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/DeleteAndCoverAlgo.h"
#include "lib/orderman/orders/book_algos/OCOOrder.h"
#include "lib/orderman/orders/book_algos/PricerOrder.h"
#include "lib/orderman/orders/spreader_orders/SpreaderOrder.h"
@@ -242,6 +243,10 @@ Order &OrderFactory::allocate(OrderKey k, OrderKey pk,
order_ptr.reset(
new orderman::AlgoWrapperOrder(std::make_unique<splitter::SplitterAlgo>()));
break;
case CustomOrderDefinition::kDeleteAndCover:
order_ptr.reset(
new orderman::AlgoWrapperOrder(std::make_unique<DeleteAndCoverAlgo>()));
break;
case CustomOrderDefinition::CUSTOM_NOT_SET:
bts_assert_msg(false, "Cannot create order with unknown type");
break;
@@ -0,0 +1,26 @@
#include "BookUtils.h"
namespace bts {
namespace orderman {
using Size = bts::md::Size;
using Side = bts::api::Side;
Size BookUtils::volume_at_or_better(const Book& book, Side side, Price price) {
return volume_at_or_better(book, side, price.value());
}
Size BookUtils::volume_at_or_better(const Book& book, Side side, double price) {
Size volume = 0;
SidePriceComp spc(side);
for (const auto& plevel : book.side(side)) {
if (spc(price, plevel.price()))
break;
volume += plevel.size();
}
return volume;
}
SidePriceComp BookUtils::price_comp(Side side) {
return SidePriceComp(side);
}
} // namespace orderman
} // namespace bts
@@ -0,0 +1,18 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/config/protos/MarketData.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/common.h"
#include "lib/orderman/orderman_utils/Book.h"
#include "lib/orderman/orderman_utils/PriceComp.h"
namespace bts {
namespace orderman {
struct BookUtils {
static md::Size volume_at_or_better(const Book& book, api::Side side, Price price);
static md::Size volume_at_or_better(const Book& book, api::Side side, double price);
static SidePriceComp price_comp(api::Side side);
};
} // namespace orderman
} // namespace bts
+283
View File
@@ -0,0 +1,283 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/config/protos/MarketData.pb.h"
#include "lib/domain_model/common.h"
#include "lib/md/PriceSize.h"
#include "lib/orderman/orderman_utils/Book.h"
#include "lib/orderman/orders/algo/IAlgoOrder.h"
#include "lib/utils/PBUtils.h"
#include <functional>
#include <iostream>
#include <map>
#include <sstream>
#include <fmt/format.h>
namespace bts {
namespace orderman {
struct Formatter {
using Side = bts::api::Side;
// this is a placeholder for other book formats down the road.
enum class BookFormat { BBO = 1 };
enum class Tense {
Present,
Preterite
};
template<typename T>
inline static void concat(std::ostream& ostr, const T& v) {
ostr << v;
}
template<typename T, typename... Args>
inline static void concat(std::ostream& ostr, const T& first, Args... args) {
ostr << first;
(concat(ostr, args), ...);
}
template<typename T>
inline static std::string fmt(const T& curr) {
std::stringstream str;
str << curr;
return str.str();
}
template<typename T, typename... Args>
inline static std::string fmt(const T& curr, Args... next) {
std::stringstream str;
concat(str, curr, next...);
return str.str();
}
template <class TKey, class TValue>
static std::string fmt_values(const std::map<TKey, TValue>& map) {
if (map.empty())
return "None";
std::stringstream str;
auto it = map.begin();
str << it->second;
while (++it != map.end()) {
str << ", " << it->second;
}
return str.str();
}
template <class T>
static std::string fmt_values(const std::vector<T>& values) {
if (values.empty())
return "None";
std::stringstream str;
auto it = values.begin();
str << *it;
while (++it != values.end()) {
str << ", " << *it;
}
return str.str();
}
static std::string fmt_fill(Side side, uint32_t qty, double price) {
std::stringstream ostr;
ostr << to_string(side, Tense::Preterite) << " " << qty << " @ " << price;
return ostr.str();
}
static std::string fmt_order(Side side, uint32_t qty, double price) {
std::stringstream ostr;
ostr << to_string(side) << " " << qty << " @ " << price;
return ostr.str();
}
static std::string fmt_order(OrderKey key, uint32_t qty, double price, const api::orderman::OrderDefinition& def) {
std::stringstream ostr;
ostr << key << " " << to_string(def.side()) << " " << qty << " @ "
<< price << std::endl << "[Def]" << utils::pb::to_string(def);
return ostr.str();
}
static std::string fmt_order(OrderKey key, Side side, uint32_t qty, double price, ChildState state) {
std::stringstream ostr;
ostr << key << " " << to_string(side) << " " << qty << " @ " << price
<< " " << to_string(state);
return ostr.str();
}
private:
template<typename T, typename... Args>
inline static void join_next(std::stringstream&, const std::string&) { }
template<typename T, typename... Args>
inline static void join_next(std::stringstream&, std::function<std::string()>) { }
template<typename T, typename... Args>
inline static void join_next(std::stringstream& str, const std::string& separator, const T& curr) {
str << separator << curr;
}
template<typename T, typename... Args>
inline static void join_next(std::stringstream& str, std::function<std::string()> get_separator, const T& curr) {
str << get_separator() << curr;
}
template<typename T, typename... Args>
inline static void join_next(std::stringstream& str, const std::string& separator, const T& curr, Args... next) {
str << separator << curr;
(join_next(str, separator, next), ...);
}
template<typename T, typename... Args>
inline static void join_next(std::stringstream& str, std::function<std::string()> get_separator, const T& curr, Args... next) {
str << get_separator() << curr;
(join_next(str, get_separator, next), ...);
}
public:
template<typename T, typename... Next>
inline static std::string join(const std::string&, const T& curr) {
std::stringstream str;
str << curr;
return str.str();
}
template<typename T, typename... Next>
inline static std::string join(const std::string& separator, const T& curr, Next... next) {
std::stringstream str;
str << curr;
join_next(str, separator, next...);
return str.str();
}
template<typename T, typename... Next>
inline static std::string join(std::function<std::string()> get_separator, const T& curr, Next... next) {
std::stringstream str;
str << curr;
join_next(str, get_separator, next...);
return str.str();
}
template <class T>
static std::string to_string(const T& value) {
std::stringstream str;
str << value;
return str.str();
}
static std::string to_string(bool value) {
return value ? "true" : "false";
}
static std::string to_string(Side side, Tense tense = Tense::Present) {
switch (side) {
case bts::api::SIDE_BUY:
switch (tense) {
case Tense::Preterite:
return "Bought";
case Tense::Present:
default:
return "Buy";
}
case bts::api::SIDE_SELL:
switch (tense) {
case Tense::Preterite:
return "Sold";
case Tense::Present:
default:
return "Sell";
}
default:
return fmt("Side_", static_cast<int>(side));
}
}
static std::string to_string(ChildStatus value) {
switch (value) {
case ChildStatus::WORKING: return "Working";
case ChildStatus::COMPLETED: return "Completed";
case ChildStatus::CANCELED: return "Canceled";
default: return fmt("ChildStatus_", static_cast<int>(value));
}
}
static std::string to_string(ChildState value) {
std::stringstream ostr;
ostr << "[" << to_string(value.status) << "] "
<< value.quantity.value() << " @ " << value.price.value()
<< "(total filled " << value.total_fills.value() << " @[avg] "
<< value.avg_price.value() << ", last filled "
<< value.last_fills.value() << " @ "
<< value.last_price.value() << ")";
return ostr.str();
}
static std::string to_string(const Book &book) {
return to_string(book, BookFormat::BBO);
}
static std::string to_string(const Book &book, BookFormat format) {
// add other impls as needed
std::stringstream ostr;
switch (format) {
case BookFormat::BBO:
if (book.bid().size() > 0 && !book.bid().at(0).is_nan()) {
auto best_bid = book.bid().at(0);
ostr << best_bid.size() << "@" << best_bid.price();
} else {
ostr << "N/A";
}
ostr << " <> ";
if (book.ask().size() > 0 && !book.ask().at(0).is_nan()) {
auto best_ask = book.ask().at(0);
ostr << best_ask.size() << "@" << best_ask.price();
} else {
ostr << "N/A";
}
break;
default:
break;
}
return ostr.str();
}
static std::string to_string(api::orderman::OrderError error) {
return api::orderman::OrderError_Name(error);
}
static std::string to_string(api::orderman::CustomOrderDefinition::CustomCase algo_type) {
switch (algo_type) {
case api::orderman::CustomOrderDefinition::CustomCase::kLimit: return "Limit";
case api::orderman::CustomOrderDefinition::CustomCase::kOffset: return "Offset";
case api::orderman::CustomOrderDefinition::CustomCase::kCover: return "Cover";
case api::orderman::CustomOrderDefinition::CustomCase::kIceberg: return "Iceberg";
default: return fmt("AlgoType_", static_cast<int>(algo_type));
}
}
};
// << operators
inline std::ostream &operator<<(std::ostream &ostr, const api::Side &side) {
ostr << Formatter::to_string(side);
return ostr;
}
inline std::ostream &operator<<(std::ostream &ostr, const ChildStatus &child_status) {
ostr << Formatter::to_string(child_status);
return ostr;
}
inline std::ostream &operator<<(std::ostream &ostr, const ChildState &child_state) {
ostr << "[" << child_state.status << "] " << child_state.quantity.value()
<< " @ " << child_state.price.value() << "(filled "
<< child_state.total_fills.value() << " @avg "
<< child_state.avg_price.value() << ")";
return ostr;
}
inline std::ostream& operator<<(std::ostream& ostr, const bts::Price& price) {
ostr << price.value();
return ostr;
}
inline std::ostream& operator<<(std::ostream& ostr, const bts::Quantity& quantity) {
ostr << quantity.value();
return ostr;
}
} // namespace orderman
} // namespace bts
@@ -0,0 +1,32 @@
#pragma once
#include "lib/domain_model/Security.h"
namespace bts {
namespace orderman {
struct PriceComp {
bool operator()(const Price& lhs, const Price& rhs) { return lhs.value() > rhs.value(); }
bool operator()(const double& lhs, const double& rhs) { return lhs > rhs; }
};
struct ReversePriceComp {
bool operator()(const Price& lhs, const Price& rhs) { return lhs.value() < rhs.value(); }
bool operator()(const double& lhs, const double& rhs) { return lhs < rhs; }
};
struct SidePriceComp {
SidePriceComp(api::Side side) : _side(side) {}
api::Side side() const { return _side; }
bool operator()(const Price& lhs, const Price& rhs) {
return _side == api::SIDE_SELL ? lhs.value() < rhs.value() : lhs.value() > rhs.value();
}
bool operator()(const double& lhs, const double& rhs) {
return _side == api::SIDE_SELL ? lhs < rhs : lhs > rhs;
}
private:
api::Side _side;
};
} // namespace orderman
} // namespace bts
@@ -1,3 +1,4 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include <optional>
#include <variant>
+8 -11
View File
@@ -190,21 +190,18 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
return;
}
// bail if the session we used for the order is no longer valid
auto const &session = native_state.routing_record().session();
if (!serv.order_router.is_valid_session(session)) {
LOG(OrderMan, Error,
"Unknown JetFIX session {} referenced by recovered "
"order {}. This is a serious "
"problem which has to be corrected manually. ",
session, rec.key());
auto rq = serv.order_router.get_routing_record(
UserID(def.user_id()), AccountID(def.account_id()), *me->sec);
if (!rq.has_route) {
auto msg = fmt::format("Could not route order: {}", rq.error_msg);
LOG(OrderMan, Error, "Unable to route order {} recovered from disk. Message: {}",
rec.key(), msg);
transition_to_error(NativeOrderState::SUB_ERROR_UNKNOWN_FIX_SESSION,
fmt::format("Unknown FIX session: {}", session));
"Could not route order");
return;
}
// if we made it this far we are good.
me->routing_record = native_state.routing_record();
me->routing_record = rq.routing_record;
if (me->state.status() != ORDER_STATUS_PENDING_WORKING)
me->exchange_order_id = native_state.exchange_order_id();
init_fix();
@@ -56,11 +56,12 @@ struct AlgoWrapperOrder::PImpl {
OrderState const &state() const { return _state; }
Map<SecurityKey, OrderServices::BookSubID> book_subs;
Map<SecurityKey, OrderServices::SettlementSubID> settlement_subs;
Map<SecurityKey, OrderServices::TickerSubID> ticker_subs;
Map<SecurityKey, OrderServices::StatusSubID> status_subs;
Map<OrderKey, ChildRecord> children;
bool has_timer_sub = false;
bool has_position_sub = false;
bool is_state_dirty = false;
Map<Edge, Action> transition_actions;
@@ -118,6 +119,13 @@ struct AlgoWrapperOrder::PImpl {
: OrderState::HIDDEN);
}
void update_custom_state(api::orderman::DynamicOrderState const&cstate) {
is_state_dirty = true;
bts_assert(definition.has_custom_def());
bts_assert(definition.custom_def().has_dynamic());
*_state.mutable_dynamic() = cstate;
}
void set_key(OrderKey k) { _my_key = k; }
private:
@@ -382,6 +390,10 @@ api::Security const *AlgoWrapperOrder::lookup_security(SecurityKey k) const {
return me->security_db->resolve(k);
}
std::vector<api::Security const *> AlgoWrapperOrder::known_securities() const {
return me->security_db->get_securities();
}
Price AlgoWrapperOrder::price() const { return Price(me->state().price()); }
Quantity AlgoWrapperOrder::quantity() const {
@@ -396,6 +408,10 @@ bool AlgoWrapperOrder::is_shutting_down() const {
return me->status == Status::SHUTTING_DOWN;
}
std::vector<PositionUpdate> const&AlgoWrapperOrder::current_positions() const {
return me->services->current_positions();
}
void AlgoWrapperOrder::subscribe_book(SecurityKey k) {
if (!is_working())
return;
@@ -416,6 +432,26 @@ void AlgoWrapperOrder::unsubscribe_book(SecurityKey k) {
me->services->unsubscribe_from_book(it->second);
}
void AlgoWrapperOrder::subscribe_settlement(SecurityKey k) {
if (!is_working())
return;
bts_assert(me->settlement_subs.find(k) == me->settlement_subs.end());
me->settlement_subs[k] =
me->services->subscribe_to_settlements(k, [this, k](auto const &ev) {
ReportStateGuard _(*this);
me->algo->on_settlement(k, Price(ev.settlement_price.price()));
});
}
void AlgoWrapperOrder::unsubscribe_settlement(SecurityKey k) {
if (!is_working())
return;
bts_assert(me->settlement_subs.find(k) != me->settlement_subs.end());
if (auto it = me->settlement_subs.find(k); it != me->settlement_subs.end())
me->services->unsubscribe_from_settlements(it->second);
}
void AlgoWrapperOrder::subscribe_ticker(SecurityKey k) {
if (!is_working())
return;
@@ -497,6 +533,25 @@ void AlgoWrapperOrder::unsubscribe_timer() {
me->has_timer_sub = false;
}
void AlgoWrapperOrder::subscribe_position() {
if (!is_working())
return;
bts_assert(!me->has_position_sub);
me->has_position_sub = true;
me->services->subscribe_to_position_updates([this](auto const &ev) {
ReportStateGuard _(*this);
me->algo->on_position({ev.security,ev.account_id,ev.long_quantity, ev.short_quantity});
});
}
void AlgoWrapperOrder::unsubscribe_position() {
if (!is_working())
return;
bts_assert(me->has_position_sub);
me->services->unsubscribe_from_position_updates();
me->has_position_sub = false;
}
bool AlgoWrapperOrder::is_attaching() const {
return me->attach_target != null_order_key();
}
@@ -610,6 +665,12 @@ void AlgoWrapperOrder::set_ladder_visibility(bool is_visible) {
me->set_ladder_visibility(is_visible);
}
void AlgoWrapperOrder::update_custom_state(api::orderman::DynamicOrderState const&cstate) {
if (!is_working())
return;
me->update_custom_state(cstate);
}
void AlgoWrapperOrder::cancel(std::string const &msg) {
if (!is_working())
return;
@@ -841,6 +902,8 @@ void AlgoWrapperOrder::check_invariants() {
bts_assert(is_attaching());
bts_assert(me->external_status() == ORDER_STATUS_WORKING);
bts_assert(me->book_subs.empty());
bts_assert(me->settlement_subs.empty());
bts_assert(me->ticker_subs.empty());
bts_assert(me->children.empty());
bts_assert(!me->has_timer_sub);
bts_assert(!me->pending_terminate.has_value());
@@ -860,6 +923,8 @@ void AlgoWrapperOrder::check_invariants() {
bts_assert(me->security);
bts_assert(me->external_status() == ORDER_STATUS_WORKING);
bts_assert(me->book_subs.empty());
bts_assert(me->settlement_subs.empty());
bts_assert(me->ticker_subs.empty());
bts_assert(!me->has_timer_sub);
bts_assert(!me->pending_update.has_value());
bts_assert(me->pending_terminate.has_value());
@@ -873,6 +938,8 @@ void AlgoWrapperOrder::check_invariants() {
me->external_status() == ORDER_STATUS_ERROR ||
me->external_status() == ORDER_STATUS_REJECTED_BY_SPARK);
bts_assert(me->book_subs.empty());
bts_assert(me->settlement_subs.empty());
bts_assert(me->ticker_subs.empty());
bts_assert(!me->has_timer_sub);
bts_assert(!me->pending_update.has_value());
bts_assert(!me->pending_terminate.has_value());
@@ -919,10 +986,18 @@ void AlgoWrapperOrder::shutdown(api::orderman::OrderStatus st,
}
void AlgoWrapperOrder::cleanup() {
for (auto const &s : me->book_subs) {
for (auto const &s : me->book_subs)
me->services->unsubscribe_from_book(s.second);
me->book_subs.clear();
}
me->book_subs.clear();
for (auto const &s : me->settlement_subs)
me->services->unsubscribe_from_settlements(s.second);
me->settlement_subs.clear();
for (auto const &s : me->ticker_subs)
me->services->unsubscribe_from_ticker(s.second);
me->ticker_subs.clear();
for (auto &c : me->children)
c.second.order.cancel();
if (me->has_timer_sub)
@@ -33,6 +33,8 @@ class AlgoWrapperOrder : public Order,
OrderKey order_key() const override;
OrderKey parent_order_key() const override;
api::Security const *lookup_security(SecurityKey k) const override;
std::vector<api::Security const *> known_securities() const override;
bool is_attaching() const override;
ChildTemplate &make_child_template(api::orderman::OrderDefinition const &,
size_t) override;
@@ -41,8 +43,12 @@ class AlgoWrapperOrder : public Order,
Quantity quantity() const override;
Quantity total_fills() const override;
bool is_shutting_down() const override;
std::vector<PositionUpdate> const&current_positions() const override;
void subscribe_book(SecurityKey) override;
void unsubscribe_book(SecurityKey) override;
void subscribe_settlement(SecurityKey) override;
void unsubscribe_settlement(SecurityKey) override;
void subscribe_child_change(OrderKey) override;
void unsubscribe_child_change(OrderKey) override;
void subscribe_timer() override;
@@ -51,6 +57,8 @@ class AlgoWrapperOrder : public Order,
void unsubscribe_ticker(SecurityKey) override;
void subscribe_status(SecurityKey) override;
void unsubscribe_status(SecurityKey) override;
void subscribe_position() override;
void unsubscribe_position() override;
OrderKey launch_child(ChildTemplate &, Price, Quantity,
bool subscribe = true) override;
@@ -64,6 +72,7 @@ class AlgoWrapperOrder : public Order,
void terminate_error(std::string const &msg) override;
void complete() override;
void update_fill_state(Quantity total_fills, Price last_price) override;
void update_custom_state(api::orderman::DynamicOrderState const&) override;
void set_ladder_visibility (bool is_visibile) override;
// Private
@@ -3,6 +3,7 @@
namespace bts {
namespace orderman {
#include "lib/orderman/orders/algo/IAlgoOrder.h"
IAlgoOrder::~IAlgoOrder() {}
OrderOpResult IAlgoOrder::do_before_launch(IBeforeLaunchAlgoServices &) {
@@ -2,6 +2,8 @@
#include "lib/orderman/orders/Order.h"
#include "lib/orderman/events/TickerUpdateEvent.h"
#include "lib/orderman/orders/algo/PositionUpdate.h"
#include <memory>
namespace bts {
@@ -44,6 +46,12 @@ class IAlgoOrder {
// The trading status for the specified security has changed
virtual void on_status(SecurityKey, co2::TradingStatus) {};
// The settlement price for the specified security has changed
virtual void on_settlement(SecurityKey, Price) {};
// The current position for the security, account combination
virtual void on_position(PositionUpdate const&) {};
// A periodic timer has fired
virtual void on_timer() {}
@@ -1,10 +1,15 @@
#pragma once
#include "lib/orderman/orders/algo/IBeforeLaunchAlgoServices.h"
#include "lib/orderman/orders/algo/PositionUpdate.h"
namespace bts {
namespace orderman {
// Services to an algo *after* it has been launched. See
// IBeforeLaunchAlgoServices for services available before launch,
// that is outside of the latency sensitive path.
class IAlgoServices : public IBeforeLaunchAlgoServices {
public:
// --------------------------------------------------------------
@@ -26,6 +31,13 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
// cancel child orders will be ignored.
virtual bool is_shutting_down() const = 0;
// Returns the current position per security and account. The
// returned reference is only valid immediately after the
// call. Callers should make a copy if they wish to use it beyond
// that. Callers can keep track of changes to the position by
// subscribing to the position update events below.
virtual std::vector<PositionUpdate> const&current_positions() const = 0;
// --------------------------------------------------------------
// Event Subscriptions
// --------------------------------------------------------------
@@ -34,6 +46,10 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
virtual void subscribe_book(SecurityKey) = 0;
virtual void unsubscribe_book(SecurityKey) = 0;
// Subscribe to settlement prices for the specified security
virtual void subscribe_settlement(SecurityKey) = 0;
virtual void unsubscribe_settlement(SecurityKey) = 0;
// Subscribe to child order updates for the specified child
// order. This is rarely needed since launch_child() by default
// automatically subscribes to new orders.
@@ -52,6 +68,10 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
virtual void subscribe_status(SecurityKey) = 0;
virtual void unsubscribe_status(SecurityKey) = 0;
// Subscribe to position updates
virtual void subscribe_position() = 0;
virtual void unsubscribe_position() = 0;
// --------------------------------------------------------------
// Child management
// --------------------------------------------------------------
@@ -102,6 +122,9 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
// Set wheter this algo instance should be displayed in the Spark UI ladder
virtual void set_ladder_visibility(bool is_visible) = 0;
// Report custom algo state for display in the Spark UI
virtual void update_custom_state(api::orderman::DynamicOrderState const&) = 0;
// Update this algos's filled quantity and last price
virtual void update_fill_state(Quantity total_fills, Price last_price) = 0;
};
@@ -31,6 +31,9 @@ class IBeforeLaunchAlgoServices {
// The security identified by k or nullptr if no such security exists
virtual api::Security const *lookup_security(SecurityKey k) const = 0;
// Known active securities
virtual std::vector<api::Security const*> known_securities() const = 0;
// Whether this algo is being attached to an existing order
// If an algo does not support attaching it can return an error
// if is_attaching() == true from do_before_launch.
Loaded 100 of 152 files, more files were not shown because too many files have changed in this diff. Show more