Compare commits
109
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d5b72001a | ||
|
|
cb0dfa50b3 | ||
|
|
2429e6019c | ||
|
|
e0bb48b0c7 | ||
|
|
a38b0dbc79 | ||
|
|
73065ab48a | ||
|
|
bd07c96504 | ||
|
|
dc4033875a | ||
|
|
130794c538 | ||
|
|
b1743f0b1c | ||
|
|
33492c9131 | ||
|
|
d434778a82 | ||
|
|
4c6cc9b3e0 | ||
|
|
6aaeddb7e9 | ||
|
|
1685a13994 | ||
|
|
54633dd6db | ||
|
|
510f163e5b | ||
|
|
ee4779da73 | ||
|
|
28e98259f6 | ||
|
|
82bc402216 | ||
|
|
ff70e9694b | ||
|
|
525d0eda30 | ||
|
|
e9e85b44c9 | ||
|
|
2654b2e446 | ||
|
|
be45938b51 | ||
|
|
663f9ecc52 | ||
|
|
a6edbbe7b5 | ||
|
|
51276d161c | ||
|
|
3fa699b4c9 | ||
|
|
c9396bd6af | ||
|
|
b80f96fa5e | ||
|
|
b7dea6817c | ||
|
|
f4dc550f1a | ||
|
|
bc978ed065 | ||
|
|
202c5faffd | ||
|
|
a565dcbab7 | ||
|
|
65d55a7618 | ||
|
|
57c3ec34c2 | ||
|
|
4e85218d02 | ||
|
|
f6784f1196 | ||
|
|
7f05a2c2af | ||
|
|
c3d2101dcd | ||
|
|
8855ea44b0 | ||
|
|
51f70ad5ed | ||
|
|
5aa3f1a684 | ||
|
|
5cd3a710bd | ||
|
|
6d8b4636ab | ||
|
|
64a75c4b48 | ||
|
|
143b6ce5d8 | ||
|
|
aa5c0a5613 | ||
|
|
d5fc0c5a56 | ||
|
|
7c4daa0f4b | ||
|
|
ab430e4afb | ||
|
|
29f6ea6d48 | ||
|
|
e46372d08e | ||
|
|
1d2faeba3e | ||
|
|
23cffc0630 | ||
|
|
90b597fc70 | ||
|
|
5ea2a8d6b6 | ||
|
|
6e64f7c6df | ||
|
|
8a210f39b6 | ||
|
|
2aacd141ad | ||
|
|
5c01e6a99a | ||
|
|
2688281a10 | ||
|
|
c6cd5fcf93 | ||
|
|
177b96b453 | ||
|
|
292f66a774 | ||
|
|
7ccd9134c4 | ||
|
|
54d82e1773 | ||
|
|
9483962e7b | ||
|
|
f94c5bc542 | ||
|
|
ce985f4b44 | ||
|
|
032f2253a7 | ||
|
|
4b4eb66ef0 | ||
|
|
8c837f9f64 | ||
|
|
a71914d566 | ||
|
|
bd00183f7a | ||
|
|
2674e526be | ||
|
|
4ea2c08cc3 | ||
|
|
99432ad7ac | ||
|
|
8c210bff34 | ||
|
|
e566a94380 | ||
|
|
85f97ae552 | ||
|
|
7ca0d10eb0 | ||
|
|
07da9fd296 | ||
|
|
436dbd217c | ||
|
|
0937381a3f | ||
|
|
d42f6b6fff | ||
|
|
03940b291b | ||
|
|
2a72b03afc | ||
|
|
98caeb5410 | ||
|
|
73cb56a661 | ||
|
|
88932081b7 | ||
|
|
ca8aed1f2b | ||
|
|
b62d9751cf | ||
|
|
b32d63fcda | ||
|
|
26b5fe5699 | ||
|
|
2fccceb91d | ||
|
|
fa49a0de07 | ||
|
|
bd28a0fa54 | ||
|
|
14ebe5ef51 | ||
|
|
a9b4049307 | ||
|
|
54d9d5e2f3 | ||
|
|
234d499731 | ||
|
|
324b9c1cc7 | ||
|
|
dbe2194ed3 | ||
|
|
45e10ca377 | ||
|
|
4de77ce339 | ||
|
|
96f71290a4 |
No files matched your search
Vendored
+8
-3
@@ -5,7 +5,9 @@
|
||||
"includePath": [
|
||||
"/usr/include",
|
||||
"/usr/local/include",
|
||||
"${workspaceRoot}"
|
||||
"${workspaceRoot}",
|
||||
"${workspaceFolder}/src",
|
||||
"${workspaceRoot}/externals/linux/include"
|
||||
],
|
||||
"defines": [],
|
||||
"intelliSenseMode": "clang-x64",
|
||||
@@ -13,13 +15,16 @@
|
||||
"path": [
|
||||
"/usr/include",
|
||||
"/usr/local/include",
|
||||
"${workspaceRoot}"
|
||||
"${workspaceRoot}",
|
||||
"${workspaceFolder}/src",
|
||||
"${workspaceRoot}/externals/linux/include"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true,
|
||||
"databaseFilename": ""
|
||||
},
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17"
|
||||
"cppStandard": "c++17",
|
||||
"compileCommands": "${workspaceFolder}/build/ninja_debug/compile_commands.json"
|
||||
},
|
||||
{
|
||||
"name": "Windows",
|
||||
|
||||
Vendored
+19
-10
@@ -14,24 +14,21 @@
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "server_build",
|
||||
"label": "server_build",
|
||||
"type": "shell",
|
||||
"command": ".\\tools\\buildsparkd.cmd",
|
||||
"problemMatcher": {
|
||||
"base": "$gcc",
|
||||
"problemMatcher": {
|
||||
"base": "$gcc",
|
||||
"owner": "cpp",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
"${workspaceFolder}"
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"pattern": {
|
||||
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
|
||||
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
|
||||
"file": 8,
|
||||
"line": 9,
|
||||
"column": 10,
|
||||
@@ -49,6 +46,18 @@
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "linux build (Debug)",
|
||||
"type": "shell",
|
||||
"command": "cd build/ninja_debug; time ninja",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
+++
|
||||
title="Client API"
|
||||
template="client.html"
|
||||
redirect_to = "client/getting-started/overview/"
|
||||
sort_by="weight"
|
||||
+++
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Getting Started"
|
||||
template="client.html"
|
||||
sort_by="weight"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
+++
|
||||
title = "Install Prerequisites"
|
||||
template="client.html"
|
||||
weight = 1
|
||||
weight = 10
|
||||
+++
|
||||
|
||||
## Coming Soon
|
||||
# C#/.NET
|
||||
|
||||
We target [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) so you can use .NET Framework 4.6.1+ or .NET Core 2.0+. Simply add a reference to the [Spark Nuget package](https://nuget.org) to get started.
|
||||
|
||||
```
|
||||
dotnet new console -lang c#
|
||||
dotnet add package spark_sdk
|
||||
dotnet build
|
||||
```
|
||||
|
||||
# C++
|
||||
|
||||
The easiest way to get started with C++ is to use the Spark Server SDK to develop your application. Follow the instructions to [download and install the Spark Server SDK](/server/getting-started/install) and open the client_sdk sample project. If you want to run your C++ Client SDK application on Windows [contact us](mailto://api@bluetradesys.com) to get the required headers and binaries.
|
||||
|
||||
# JavaScript
|
||||
|
||||
We use JavaScript internally with the Spark Client API over a websocket connection but the documentation and packaging isn't ready yet. If this is something you're interested in get in touch with us at [api@bluetradesys.com](mailto:api@bluetradesys.com).
|
||||
|
||||
|
||||
|
||||
* Install .net core
|
||||
@@ -0,0 +1,31 @@
|
||||
+++
|
||||
title = "Overview"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
The Spark Client SDK is a high level command and control API which can be used to build scripts or integrate Spark into your existing ecosystem. The API is divided into several independent **"services"** responsible for different aspects of the system. A Service can define both [remote procedure call (RPC)](/client/getting-started/services#rpc) and incremental [streaming](/client/getting-started/services#streams) data.
|
||||
|
||||
The [Spark Desktop](https://www.bluetradingsystems.com/bts-spark/) is built on top of this API so you can be assured that it is complete and well tested.
|
||||
|
||||
Spark uses [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) as an interface definition language similar to [GRPC](https://grpc.io) but backed by our custom high-performance TCP protocol. We currently support C#/.NET and C++ <!--and Javascript--> but the architecture is designed for easy extension so contact us about your specific business needs.
|
||||
|
||||
## Orders
|
||||
|
||||
Use the [OrderService](/client/services/orderservice) to send and manage orders including native exchange orders, spreader orders, and custom Algo orders created using the [Spark Server SDK](https://api.bluetradesys.com/server/).
|
||||
|
||||
## Spreader
|
||||
|
||||
Use the [SyntheticSecurityService](/client/services/syntheticsecurityservice) to create and manage synthetic spreads. Once created these spreads show up in the security and market data feeds just like exchange listed spreads.
|
||||
|
||||
## Position
|
||||
|
||||
Use the [PositionService](/client/services/positionservice) to get your position and P&L reports or add fills manually to keep the Spark system up to date with an external system.
|
||||
|
||||
## Administration
|
||||
|
||||
Use the [AdminService](/client/services/adminservice) to manage users and control limits.
|
||||
|
||||
## Market Data
|
||||
|
||||
For .NET subscribe to market data though the high level MarketDataClient interface. From C++ or JavaScript use the MarketDataRelay service directly.
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
+++
|
||||
title = "Services"
|
||||
weight = 20
|
||||
+++
|
||||
|
||||
The Spark Client SDK uses [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) to define the interface for each service in a language independent manner. Services have both functions which return a single result and streams of data which return values continuously while subscribed. This page describes the mapping from the Protocol Buffer definition to the concrete definition in each supported language.
|
||||
|
||||
# ServiceContext
|
||||
|
||||
To connect your client to the Spark Server you need to get or create a `ServiceContext` instance. A `ServiceContext` represents a connection to the Spark Server and a background thread for handling messages on that connection. Generally there should be a single `ServiceContext` per process. All of the methods on the `ServiceContext` are safe thread safe.
|
||||
|
||||
Construct a ServiceContext by calling the default constructor. The ServiceContext will find the Spark Server by loading the server configuration over an HTTP connection. To determine the URL it will search the following locations and use the first one that is present.
|
||||
|
||||
* A command line argument `-configserver <url>`
|
||||
* An environment variable `BTSCONFIG`
|
||||
* `%APPDATA%/bts/conf/spark.conf` (Windows)
|
||||
* /sparkdata/conf/spark.conf (Linux)
|
||||
* $HOME/sparkdata/conf/spark.conf (Linux)
|
||||
|
||||
If your application is running on a system with the BTS Client installed this will probably work without any further configuration. If you plan to run your client application on a separate machine we recommend setting the `BTSCONFIG` environment variable.
|
||||
|
||||
<div class="info">
|
||||
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
|
||||
If you are using the Spark Server SDK to run a local copy of the Spark server set <p><strong>BTSCONFIG = http://localhost:9000/config/</strong></p>
|
||||
</div>
|
||||
|
||||
To get a proxy for a given service call ServiceContext.locate<T>("qualifier") where T is the type of the service and "qualifier" is an optional qualifier used to disambiguate between 2 instances of the same service. The locate call cannot fail but it may return a disconnected proxy. When you make a call on the proxy you should check the call result to see if the method call succeeded.
|
||||
|
||||
# RPC
|
||||
|
||||
Here is the OrderService reduced to a single method definition. A RPC method always takes exactly 1 argument and returns 1 value. In this example the `create_order` call takes a `Create` instance and returns a `Response`.
|
||||
|
||||
```C
|
||||
service OrderService {
|
||||
rpc create_order(Create) returns (Response);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The `Create` and `Response` types are also defined as protocol buffer messages. Each field can either be a primitive type another message or a list or map.
|
||||
|
||||
```C
|
||||
message Create {
|
||||
OrderDefinition new_order = 1;
|
||||
double price = 2;
|
||||
uint32 quantity = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
uint32 order_key = 1;
|
||||
OrderError error_code = 2;
|
||||
string msg = 3;
|
||||
}
|
||||
```
|
||||
|
||||
Each RPC call generates both synchronous (blocking) and asynchronous variants. In C# the asynchronous calls have the `_async` suffix and return a `Task`. In C++ the call is overloaded to either return a value synchronously or accept a callback function to be called on the ServiceContext thread when the call completes. <!-- In Javascript only async methods returning a Promise are generated because it never makes sense to make a blocking call in the browser. -->
|
||||
|
||||
To call the resulting service locate a proxy using the [ServiceContext](#servicecontext) and then call `cancel_order` or `cancel_order_async`. If you are calling the Spark API from a GUI application you should always prefer the `_async` version of the calls.
|
||||
|
||||
|
||||
### C# example
|
||||
```c#
|
||||
var ctx = new ServiceContext();
|
||||
var orders = ctx.locate<OrderService>();
|
||||
var response =
|
||||
await orders.create_order_async(
|
||||
new Create { price = 2.25, quantity = 42 });
|
||||
if (response.success) {
|
||||
Console.WriteLine($"success: {response.value}");
|
||||
}
|
||||
else {
|
||||
Console.WriteLine($"Error: {response.error_message}");
|
||||
}
|
||||
```
|
||||
### C++ example
|
||||
```cpp
|
||||
auto ctx = ServiceContext{};
|
||||
auto orders = ctx.locate<OrderService>();
|
||||
Create c;
|
||||
c.set_price(2.25);
|
||||
c.set_quantity(42);
|
||||
// Instead of providing an _async method C++ uses overloading.
|
||||
// To make a syncronous call simply omit the callback
|
||||
orders.create_order(c, [=](const Result<Response>& response) {
|
||||
if(response.success()) {
|
||||
std::cout << "success.";
|
||||
}
|
||||
else {
|
||||
std::cout << "error: " << response.error_message();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
# Streams
|
||||
|
||||
Streams allow you to subscribe to live updating data. Each stream represents a changing collection of protocol buffer objects. Similar to a database row each item has an `id` which uniquely identifies it within the collection. Updates from the Spark server can add, remove or replace entries in the collection. Since only the changed items are sent over the wire the stream can remain responsive even when it represents a large collection.
|
||||
|
||||
Streams are implemented using the language bindings for [ReativeX](http://reactivex.io/) which provide utility functions to drill down to the specific data your application needs. In C# we provide both mutable and immutable versions of the generated data types (C++ users can rely on `const`).
|
||||
|
||||
When defined in a proto file streams use the `stream` keyword on the result type.
|
||||
```C
|
||||
service OrderService {
|
||||
...
|
||||
rpc fills (FillsSub) returns (stream Fill);
|
||||
}
|
||||
```
|
||||
|
||||
### C# example
|
||||
```c#
|
||||
var sc = new ServiceContext();
|
||||
var orders = sc.locate<OrderService>();
|
||||
var fills_observable = orders.subscribei_fills();
|
||||
var unsub = fills_observable.Subscribe(update => {
|
||||
Console.WriteLine(
|
||||
"Added {0} items, Removed {1} items, Updated {2} items. Total items {3}",
|
||||
update.added.Count(),
|
||||
update.removed.Count(),
|
||||
update.changes.Count(),
|
||||
update.all.Count());
|
||||
});
|
||||
|
||||
Thread.Sleep(TimeSpan.FromMinutes(5));
|
||||
unsub.Dispose();
|
||||
```
|
||||
### C++ Example
|
||||
```cpp
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title="Samples"
|
||||
sort_by="weight"
|
||||
weight = 30
|
||||
+++
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "AdminService"
|
||||
+++
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "OrderService"
|
||||
+++
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "PositionService"
|
||||
+++
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "SyntheticService"
|
||||
+++
|
||||
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title="Services"
|
||||
#sort_by="weight"
|
||||
weight = 10
|
||||
+++
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "Market Data"
|
||||
+++
|
||||
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Algo API"
|
||||
template="documentation.html"
|
||||
redirect_to = "server/getting-started/overview/"
|
||||
sort_by="weight"
|
||||
+++
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Getting Started"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
@@ -71,12 +71,12 @@ The first step is to start the spark_sdk Docker container with the examples dire
|
||||
|
||||
Windows Command Prompt:
|
||||
{% clipable() %}
|
||||
docker run --rm -it -v %CD%:/examples -u bts -w /examples --shm-size 1024m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
docker run --rm -it -v %CD%:/examples -u bts -w /examples --shm-size 2048m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
{% end %}
|
||||
|
||||
**Powershell** / Linux / Mac:
|
||||
{% clipable() %}
|
||||
docker run --rm -it -v ${PWD}:/examples -u bts -w /examples --shm-size 1024m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
docker run --rm -it -v ${PWD}:/examples -u bts -w /examples --shm-size 2048m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
{% end %}
|
||||
|
||||
### Attach
|
||||
|
||||
@@ -5,7 +5,7 @@ weight = 2
|
||||
|
||||
## Request Access 🔑
|
||||
|
||||
To start using the Server SDK you will need to request access though our sales department. Please email <a href="mailto:info@bluetradesys.com">info@bluetradesys.com</a> or call <a href="tel:919-913-0850">(919) 913-0850</a> and we will provide download credentials.
|
||||
To start using the Server SDK you will need to request access though our sales department. Please email <a href="mailto:api@bluetradesys.com">api@bluetradesys.com</a> or call <a href="tel:919-913-0850">(919) 913-0850</a> and we will provide download credentials.
|
||||
|
||||
## Download
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Reference"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 2
|
||||
+++
|
||||
|
||||
@@ -6,3 +6,9 @@ weight = 40
|
||||
## I need to make a call that might take a few seconds. What is the best way to do this?
|
||||
|
||||
Create a background thread in [do_before_launch](/server/ref/initialization#before-launch) or [do_launch](/server/ref/initialization#launch-launch-attached) to do the work. Use [on_timer](/server/ref/subscriptions#timer) to poll until the result is available.
|
||||
|
||||
## I don't see the header files in the .zip file. Where are they?
|
||||
|
||||
The SDK headers are baked into the docker image along with the build tools. Once you've loaded the image with `docker load < spark_sdk.img` you can run this command to copy them out to your local file system `docker run --rm -it -v $PWD:/out spark_sdk /bin/tar -cvf /out/headers.tar /sdk/include` This will copy the headers to the current directory in headers.tar. We still recommend doing the actual build inside the docker environment but having a copy of the headers can be useful for setting up your development environment.
|
||||
|
||||
|
||||
File renamed without changes.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Samples"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block extra_nav_class %}{% endblock extra_nav_class %}
|
||||
{% block extra_content_class %}content--reversed{% endblock extra_content_class %}
|
||||
|
||||
{% block content %}
|
||||
{% set section = get_section(path="client/_index.md") %}
|
||||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
{% for page in subsection.pages %}
|
||||
<li class="{% if current_path == page.path %}active{% endif %}">
|
||||
<a href="{{page.permalink}}">{{page.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="documentation__content">
|
||||
{% block doc_content %}
|
||||
{% endblock doc_content %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -1,36 +0,0 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block extra_nav_class %}{% endblock extra_nav_class %}
|
||||
{% block extra_content_class %}content--reversed{% endblock extra_content_class %}
|
||||
|
||||
{% block content %}
|
||||
{%if page.extra.section %}
|
||||
{% set section = get_section(path="{{page.extra.section}}/_index.md") %}
|
||||
{% else %}
|
||||
{% set section = get_section(path="server/_index.md") %}
|
||||
{% endif %}
|
||||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
{% for page in subsection.pages %}
|
||||
<li class="{% if current_path == page.path %}active{% endif %}">
|
||||
<a href="{{page.permalink}}">{{page.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="documentation__content">
|
||||
{% block doc_content %}
|
||||
{% endblock doc_content %}
|
||||
{% block next_link %} {% endblock next_link %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-gb">
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
@@ -14,6 +14,7 @@
|
||||
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/>
|
||||
<link rel="icon" href="{{ get_url(path="favicon.ico") }}">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<script> dataLayer = [{ 'visitorType': 'geek' }]; </script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -36,13 +37,13 @@
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="content {% block extra_content_class %}{% endblock extra_content_class %}">
|
||||
<div class="content content--reversed">
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<h1>Spark SDK</h1>
|
||||
<p class="hero__tagline">
|
||||
Spark consists of a client UI
|
||||
(<a class="white" href="https://www.bluetradingsystems.com/bts-spark#ladder-algo-screen">Spark Client</a>),
|
||||
(<a href="https://www.bluetradingsystems.com/bts-spark#ladder-algo-screen">Spark Client</a>),
|
||||
a high-performance professional futures front end, connected via our proprietary protocol over TCP to our Spark Server, which in turn connects to execution venues via their native protocols.
|
||||
Use the Spark SDK to extend Spark Server with your custom Algos, then use the Client SDK to launch and control them.
|
||||
</p>
|
||||
@@ -64,7 +65,7 @@
|
||||
<a href="{{ get_url(path="@/server/_index.md") }}" class="button">Get started</a>
|
||||
</div>
|
||||
|
||||
<div class="selling-point disabled">
|
||||
<div class="selling-point">
|
||||
<h2>
|
||||
Client SDK
|
||||
<i class="fa fa-gamepad" aria-hidden="true"></i>
|
||||
@@ -72,19 +73,9 @@
|
||||
<p>
|
||||
The Spark client SDK allows you to integrate spark with your existing ecosystem. The Spark Client is built entirely on this simple, powerful API.
|
||||
</p>
|
||||
<a href="" class="button" disabled>Get started</a>
|
||||
<a href="" class="button">Get started</a>
|
||||
</div>
|
||||
|
||||
<!-- <div class="selling-point disabled">
|
||||
<h2>
|
||||
Microsoft Excel
|
||||
<i class="fa fa-table" aria-hidden="true"></i>
|
||||
</h2>
|
||||
<p>
|
||||
More of your business is built on excel than probably should be. Continue this worrying trend with the Spark Excel API.
|
||||
</p>
|
||||
<a href="" class="button" disabled>Get started</a>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,6 +85,15 @@
|
||||
© Blue Trading Systems
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
if (location.hostname == "api.bluetradesys.com") {
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-56FHG8R');
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="{{ get_url(path="elasticlunr.min.js") }}"></script>
|
||||
<script type="text/javascript" src="{{ get_url(path="search_index.en.js") }}"></script>
|
||||
<script type="text/javascript" src="{{ get_url(path="search.js") }}"></script>
|
||||
|
||||
@@ -1,13 +1,39 @@
|
||||
{% extends "documentation.html" %}
|
||||
{% extends "index.html" %}
|
||||
{# this has access to the "page" variable but documentation.html does not #}
|
||||
|
||||
{% block title %}{{ page.title }} | {{ super() }} {% endblock title %}
|
||||
{% block doc_content %}
|
||||
<h1>{{page.title}}</h1>
|
||||
{{page.content | safe}}
|
||||
{% endblock doc_content %}
|
||||
|
||||
{% block next_link %}
|
||||
<p>
|
||||
{% endblock next_link %}
|
||||
|
||||
{% block content %}
|
||||
{% set section = get_section(path=page.ancestors[1]) %}
|
||||
{#
|
||||
{#<div>{{ section.title }} </div>#}
|
||||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
{% for page in subsection.pages %}
|
||||
<li class="{% if current_path == page.path %}active{% endif %}">
|
||||
<a href="{{page.permalink}}">{{page.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="documentation__content">
|
||||
<h1>{{page.title}}</h1>
|
||||
{{page.content | safe}}
|
||||
|
||||
{# next/pre links #}
|
||||
<p>
|
||||
{% if page.lighter %}
|
||||
<a href="{{ page.lighter.permalink }}">
|
||||
<i class="fa fa-chevron-left fa-fw" aria-hidden="true"></i>
|
||||
@@ -21,5 +47,7 @@
|
||||
<i class="fa fa-chevron-right fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endblock next_link %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -1 +1 @@
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
set(sources /spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
@@ -4,11 +4,47 @@ 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
|
||||
|
||||
@@ -4,43 +4,112 @@ using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts {
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
class DeleteSyntheticDaemon : IShellDaemon, IDisposable {
|
||||
// request_id is noted in the series of log messages that occur during a particular delete op.
|
||||
static int _request_id = 1;
|
||||
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
IReadOnlyDictionary<uint, ExecutionStrategyImmutable> _strategy_map =
|
||||
DictionaryHelper.empty<uint, ExecutionStrategyImmutable>();
|
||||
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model, ErrorReporter error_reporter) {
|
||||
_orders_model = orders_model;
|
||||
_error_reporter = error_reporter;
|
||||
_dispose = bus.subscribe(SparkTags.SyntheticDeleteTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(_delete_synthetic);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SyntheticDeleteTag).Subscribe(_delete_synthetic),
|
||||
orders_model.spreader.strategy.Subscribe(map => _strategy_map = map));
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async void _delete_synthetic(uint id) {
|
||||
var (orders, orders_success) = await _orders_model.on_orders.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
async void _delete_synthetic(IImmutableSet<uint> keys) {
|
||||
var log = new LoggerContext(Log.Ui, $"Synthetic Delete Req {unchecked(Interlocked.Increment(ref _request_id))} - ", null);
|
||||
if (keys.is_null_or_empty()) {
|
||||
log.warn("received empty delete request (no securities specified).");
|
||||
return;
|
||||
}
|
||||
log.info($"received request to delete the following {keys.Count} securities: {string.Join(", ", keys)}");
|
||||
var (orders, orders_success) = await _orders_model.security_order_groups.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
if (!orders_success) {
|
||||
_error_reporter.report_error($"Unable to delete synthetic security ({id}): unable to determine whether or not security ({id}) has working orders (deletion is not permitted while there are working orders).");
|
||||
Log.Ui.warn($"Ignoring request to delete synthetic security ({id}) because the orders lookup timed out (must confirm no working orders).");
|
||||
} else if (orders.state.Values.Any(o => o.order.Def.SecurityKey == id && o.order.is_non_terminal())) {
|
||||
Log.Ui.warn($"Ignoring request to delete synthetic security ({id}) because non-terminal-orders were detected.");
|
||||
var msg = "Unable to delete synthetic securities: " +
|
||||
$"timed out waiting for orders table (must verify that a synthetic security does not have any working orders before it can be deleted).";
|
||||
_error_reporter.report_error(msg);
|
||||
log.warn(msg);
|
||||
} else {
|
||||
ExecutionStrategyImmutable? strategy = await _orders_model.spreader.lookup_strategy_async(id);
|
||||
if (strategy.HasValue) {
|
||||
ExecutionStrategyImmutable deleted_strategy = strategy.Value.with(deleted: true);
|
||||
await _orders_model.spreader.save_strategy(id, deleted_strategy);
|
||||
var working_orders = new List<uint>();
|
||||
var missing_strategies = new List<uint>();
|
||||
var items = new List<(uint key, ExecutionStrategyImmutable strategy)>();
|
||||
var temp_strats = _strategy_map;
|
||||
foreach (var k in keys) {
|
||||
if (orders.state.TryGetValue(k, out var sec_group) && sec_group.table.state.Values.Any(tup => tup.order.is_active())) {
|
||||
working_orders.Add(k);
|
||||
} else if (!temp_strats.TryGetValue(k, out var strat)) {
|
||||
missing_strategies.Add(k);
|
||||
} else {
|
||||
items.Add((k, strat.with(deleted: true)));
|
||||
}
|
||||
}
|
||||
var lines = new List<string>();
|
||||
if (working_orders.Count > 0) {
|
||||
lines.Add($"skipping {working_orders.Count} securities because they have working orders.");
|
||||
}
|
||||
if (missing_strategies.Count > 0) {
|
||||
lines.Add($"skipping {missing_strategies.Count} securities because their execution strategies were not discovered.");
|
||||
}
|
||||
if (lines.Count > 0)
|
||||
log.warn(string.Join("; ", lines));
|
||||
var errors = await _orders_model.spreader.save_strategies(items);
|
||||
int error_count = errors.Count(e => !string.IsNullOrEmpty(e));
|
||||
log.info($"{items.Count - error_count}/{items.Count} attempted deletes were successful (skipped {working_orders.Count + missing_strategies.Count}).");
|
||||
// display results to user
|
||||
const string dialog_title = "Synthetic Deletion";
|
||||
string display_msg;
|
||||
MessageBoxImage icon = MessageBoxImage.None;
|
||||
if (keys.Count > 1) {
|
||||
if (error_count == 0 && working_orders.Count == 0 && missing_strategies.Count == 0) {
|
||||
display_msg = $"Synthetic securities have been deleted.";
|
||||
icon = MessageBoxImage.Information;
|
||||
} else {
|
||||
display_msg = $"{keys.Count - error_count - working_orders.Count - missing_strategies.Count}/{keys.Count} securities were successfully deleted.";
|
||||
icon = MessageBoxImage.Error;
|
||||
}
|
||||
} else {
|
||||
if (error_count == 0 && working_orders.Count == 0 && missing_strategies.Count == 0) {
|
||||
display_msg = "Synthetic security has been deleted.";
|
||||
icon = MessageBoxImage.Information;
|
||||
} else {
|
||||
string reason;
|
||||
if (error_count > 0) {
|
||||
reason = errors[0];
|
||||
} else if (missing_strategies.Count > 0) {
|
||||
reason = "did not find execution strategy.";
|
||||
} else if (working_orders.Count > 0) {
|
||||
reason = "working orders detected.";
|
||||
} else {
|
||||
// unexpected code path
|
||||
reason = "unknown error.";
|
||||
}
|
||||
icon = MessageBoxImage.Error;
|
||||
display_msg = $"Unable to delete synthetic security: " + reason;
|
||||
}
|
||||
}
|
||||
BTSMessageBox.Show(display_msg, dialog_title, BTSMessageBoxButtons.OK, icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.support;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
@@ -164,5 +167,45 @@ namespace bts.spark {
|
||||
void _report_error(string msg) =>
|
||||
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
public static async Task<(TResult result, string error_msg)[]> safe_execute_batch<T, TResult>(
|
||||
this ErrorReporter error_reporter,
|
||||
IEnumerable<Task<T>> execute,
|
||||
Func<T, (TResult result, string error_msg)> result_selector,
|
||||
string desc,
|
||||
string title) {
|
||||
//
|
||||
try {
|
||||
var results = await Task.WhenAll(execute as Task<T>[] ?? ArrayHelper.clone_optimized(execute));
|
||||
var result_values = new (TResult, string)[results.Length];
|
||||
StringBuilder error_bd = null;
|
||||
for (int i = 0; i < results.Length; i++) {
|
||||
var r = results[i];
|
||||
var (result, error) = result_selector(r);
|
||||
if (!string.IsNullOrEmpty(error)) {
|
||||
if (error_bd == null) {
|
||||
error_bd = new StringBuilder();
|
||||
} else {
|
||||
error_bd.AppendLine();
|
||||
}
|
||||
error_bd.Append(error);
|
||||
}
|
||||
result_values[i] = (result, error);
|
||||
}
|
||||
if (error_bd != null) {
|
||||
var error_msg = error_bd.ToString();
|
||||
if (!string.IsNullOrEmpty(error_msg))
|
||||
_report_error(error_msg);
|
||||
}
|
||||
return result_values;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
_report_error(ex.ToString());
|
||||
}
|
||||
return null;
|
||||
//
|
||||
void _report_error(string msg) =>
|
||||
error_reporter.report_error($"Error {desc}: {msg}", title, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace bts {
|
||||
_relay.set_token(_token.Value);
|
||||
_dispose.Add(
|
||||
_ss.Value.subscribei_metadata()
|
||||
.Select(upd => upd.all.ToDictionary(i => i.SecurityKey, i => i.Strategy))
|
||||
.Select(upd => upd.all.Where(i => i.HasStrategy).ToDictionary(i => i.SecurityKey, i => i.Strategy))
|
||||
.Subscribe(t => {
|
||||
var deleted_synthetic_keys = new HashSet<uint>(t.Where(kv => kv.Value.Deleted).Select(kv => kv.Key));
|
||||
// add back 'undeleted' synthetics
|
||||
@@ -64,4 +64,4 @@ namespace bts {
|
||||
Task.Run(run);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts;
|
||||
using mbts.ui.dpi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -40,7 +41,6 @@ namespace mbts.ui {
|
||||
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
|
||||
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
|
||||
GearGeometryKey = nameof(GearGeometryKey),
|
||||
HamburgerMenuGeometryKey = nameof(HamburgerMenuGeometryKey),
|
||||
HornGeometryKey = nameof(HornGeometryKey),
|
||||
KeyGeometryKey = nameof(KeyGeometryKey),
|
||||
LetterAKey = nameof(LetterAKey),
|
||||
@@ -56,7 +56,9 @@ namespace mbts.ui {
|
||||
StarGeometryKey = nameof(StarGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
TrashGeometryKey = nameof(TrashGeometryKey),
|
||||
UndoCircularGeometryKey = nameof(UndoCircularGeometryKey);
|
||||
UndoCircularGeometryKey = nameof(UndoCircularGeometryKey),
|
||||
WarningExclamationGeometryKey = nameof(WarningExclamationGeometryKey),
|
||||
WarningGeometryKey = nameof(WarningGeometryKey);
|
||||
|
||||
public static Geometry PlayGeometry => Geometry.Parse(get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
|
||||
|
||||
@@ -173,7 +175,6 @@ namespace mbts.ui {
|
||||
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
|
||||
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
|
||||
public static ImageSource Gear => create_icon(GearGeometryKey);
|
||||
public static ImageSource HamburgerMenu => create_icon(HamburgerMenuGeometryKey);
|
||||
public static ImageSource Horn => create_icon(HornGeometryKey);
|
||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||
public static ImageSource Key => create_icon(KeyGeometryKey);
|
||||
@@ -192,6 +193,8 @@ namespace mbts.ui {
|
||||
public static ImageSource Tetris { get { return load_resource_and_freeze("Tetris"); } }
|
||||
public static ImageSource Tools => create_icon(ToolsGeometryKey);
|
||||
public static ImageSource Trash => create_icon(TrashGeometryKey);
|
||||
public static ImageSource Warning => create_icon(WarningGeometryKey, Brushes.IndianRed);
|
||||
public static ImageSource WarningExclamation => create_icon(WarningExclamationGeometryKey, Brushes.IndianRed);
|
||||
public static ImageSource UndoCircular => create_icon(UndoCircularGeometryKey);
|
||||
public static ImageSource ViewSettings => load_resource_and_freeze("ViewSettings");
|
||||
public static ImageSource Wrench { get { return load_resource_and_freeze("Wrench"); } }
|
||||
|
||||
@@ -272,10 +272,6 @@
|
||||
M16.000015,9.6000061C12.500012,9.6000061 9.5999844,12.5 9.5999844,16 9.5999844,19.5 12.500012,22.399994 16.000015,22.399994 19.500019,22.399994 22.400015,19.5 22.400015,16 22.400015,12.5 19.500019,9.6000061 16.000015,9.6000061z M14.199995,0L17.800005,0C18.199999,0,18.599993,0.29998779,18.599993,0.79998779L18.599993,4.7999878C19.900013,5.1000061,21.000019,5.6000061,22.099997,6.2999878L25.000023,3.3999939C25.099999,3.2999878 25.300011,3.1999817 25.500025,3.1999817 25.700007,3.1999817 25.900019,3.2999878 26.000025,3.3999939L28.600033,6C28.900021,6.2999878,28.900021,6.7999878,28.600033,7.1000061L25.700007,10C26.400019,11.100006,26.900019,12.299988,27.200007,13.5L31.20001,13.5C31.600037,13.5,31.999998,13.799988,31.999998,14.299988L31.999998,17.899994C31.999998,18.299988,31.700012,18.699982,31.20001,18.699982L27.200007,18.699982C26.900019,20,26.400019,21.100006,25.700007,22.199982L28.600033,25.100006C28.700009,25.199982 28.799984,25.399994 28.799984,25.600006 28.799984,25.799988 28.700009,26 28.600033,26.100006L26.000025,28.600006C25.900019,28.699982 25.700007,28.799988 25.500025,28.799988 25.300011,28.799988 25.099999,28.699982 25.000023,28.600006L22.099997,25.699982C21.000019,26.399994,19.800007,26.899994,18.599993,27.199982L18.599993,31.199982C18.599993,31.600006,18.300005,32,17.800005,32L14.199995,32C13.8,32,13.400006,31.699982,13.400006,31.199982L13.400006,27.199982C12.099987,26.899994,11.000011,26.399994,9.9000027,25.699982L7.0000064,28.600006C6.9000003,28.699982 6.6999881,28.799988 6.5000059,28.799988 6.2999937,28.799988 6.099981,28.699982 6.0000054,28.600006L3.3999967,26C3.0999784,25.699982,3.0999784,25.199982,3.3999967,24.899994L6.2999937,22C5.5999805,20.899994,5.0999801,19.699982,4.7999923,18.5L0.79998828,18.5C0.39999409,18.5,-1.933513E-07,18.199982,0,17.699982L0,14.100006C-1.933513E-07,13.699982,0.2999879,13.299988,0.79998828,13.299988L4.7999923,13.299988C5.0999801,12,5.5999805,10.899994,6.2999937,9.7999878L3.3999967,7C3.2999908,6.8999939 3.1999845,6.6999817 3.1999845,6.5 3.1999845,6.2999878 3.2999908,6.1000061 3.3999967,6L6.0000054,3.3999939C6.099981,3.2999878 6.2999937,3.1999817 6.5000059,3.1999817 6.6999881,3.1999817 6.9000003,3.2999878 7.0000064,3.3999939L9.9000027,6.2999878C11.000011,5.6000061,12.199993,5.1000061,13.400006,4.7999878L13.400006,0.79998779C13.400006,0.29998779,13.8,0,14.199995,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.HamburgerMenuGeometryKey}">
|
||||
M2.384186E-06,24L32.000004,24 32.000004,26 2.384186E-06,26z M0,11.999998L32,11.999998 32,13.999998 0,13.999998z M2.384186E-06,0L32.000004,0 32.000004,2 2.384186E-06,2z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.HornGeometryKey}">
|
||||
M4.0499969,7.0870913C2.9519978,7.0870913 2.0249984,8.4780906 2.0249987,10.125091 2.0249984,11.771091 2.9519978,13.162091 4.0499969,13.162091 5.1479961,13.162091 6.0749954,11.771091 6.0749955,10.125091 6.0749954,8.4780906 5.1479961,7.0870913 4.0499969,7.0870913z M29.975016,3.316155C23.883831,8.2016557,11.829888,8.9691842,8.1184387,9.0896781L8.0151367,9.0928462 8.0175586,9.1059306C8.0716033,9.4352315 8.0999939,9.7760909 8.0999942,10.125091 8.0999939,10.473966 8.0716033,10.814716 8.0175586,11.143921L8.0098858,11.185364 8.193316,11.195448C12.163719,11.420399,23.994581,12.458921,29.975016,17.005057z M31.176758,0.00019051835C31.280359,-0.0022594665 31.391857,0.01889403 31.514975,0.065146674 31.908983,0.21413228 31.999985,0.59114302 31.999985,1.0121596L31.999985,19.236969C31.999985,19.653958 31.913988,20.028956 31.525991,20.179955 31.405998,20.226952 31.450981,20.250023 31.32699,20.250023 31.050991,20.250023 30.778992,20.13601 30.583986,19.925928 25.229074,14.140572 9.6001122,13.269665 7.2865356,13.172559L7.2813506,13.172345 7.1739591,13.343056C6.4304952,14.468533 5.3060585,15.187091 4.0499969,15.187091 1.8169985,15.187091 0,12.916091 0,10.125091 0,7.3330911 1.8169985,5.0620912 4.0499969,5.0620912 5.3060585,5.0620912 6.4304952,5.78065 7.1739591,6.9063042L7.2852713,7.0832756 7.3087114,7.0828188C9.6077337,7.036146 25.141474,6.4939421 30.567997,0.34315929 30.75913,0.12521249 30.948835,0.005580858 31.176758,0.00019051835z
|
||||
</sys:String>
|
||||
@@ -367,6 +363,18 @@
|
||||
c-84.15,0-153-68.85-153-153h-51c0,112.2,91.8,204,204,204s204-91.8,204-204S360.825,89.25,248.625,89.25z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.WarningExclamationGeometryKey}">
|
||||
M15.998999,17.988002C16.551,17.988002 16.999,18.437009 16.999,18.990018 16.999,19.543027 16.551,19.992034 15.998999,19.992034 15.446999,19.992034 14.998999,19.543027 14.998999,18.990018 14.998999,18.437009 15.446999,17.988002 15.998999,17.988002z
|
||||
M15.998999,8.0630169C16.552,8.0630169,16.999,8.5110159,16.999,9.0650149L16.999,16.167001C16.999,16.721 16.552,17.168998 15.998999,17.168998 15.446,17.168998 14.998999,16.721 14.998999,16.167001L14.998999,9.0650149C14.998999,8.5110159,15.446,8.0630169,15.998999,8.0630169z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.WarningGeometryKey}">
|
||||
M15.998999,17.988002C16.551,17.988002 16.999,18.437009 16.999,18.990018 16.999,19.543027 16.551,19.992034 15.998999,19.992034 15.446999,19.992034 14.998999,19.543027 14.998999,18.990018 14.998999,18.437009 15.446999,17.988002 15.998999,17.988002z
|
||||
M15.998999,8.0630169C16.552,8.0630169,16.999,8.5110159,16.999,9.0650149L16.999,16.167001C16.999,16.721 16.552,17.168998 15.998999,17.168998 15.446,17.168998 14.998999,16.721 14.998999,16.167001L14.998999,9.0650149C14.998999,8.5110159,15.446,8.0630169,15.998999,8.0630169z
|
||||
M15.999277,2.3337629L2.3830507,22.996989 29.615442,22.996989z
|
||||
M15.999513,0C16.50052,-3.8678991E-07,17.001274,0.21826346,17.288297,0.65479128L31.74151,22.585975C32.057495,23.064982 32.084472,23.677966 31.812494,24.184989 31.541494,24.687009 31.02049,24.999999 30.450475,24.999999L1.5480773,24.999999C0.97903845,24.999999 0.45705819,24.687009 0.18703459,24.184989 -0.085979476,23.677966 -0.057963934,23.064982 0.25704309,22.585975L14.709219,0.65479128C14.997247,0.21826346,15.498506,-3.8678991E-07,15.999513,0z
|
||||
</sys:String>
|
||||
|
||||
<!--
|
||||
'y' for equilateral triangle of side length x:
|
||||
(x/2)^2 + Y^2 = x^2
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using mbts.spark;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
readonly struct MarketViewCell {
|
||||
public readonly MarketViewVM.Row row;
|
||||
public readonly MarketViewView.Columns column;
|
||||
|
||||
public MarketViewCell(MarketViewVM.Row row, MarketViewView.Columns column) {
|
||||
this.row = row;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -19,7 +18,7 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : DisposableViewModelBase, ISecurityMDRow, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AnimationManager _animation = new AnimationManager();
|
||||
@@ -28,6 +27,7 @@ namespace mbts.spark {
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
readonly CellProperty<bool> _inside_bids, _inside_asks;
|
||||
@@ -37,18 +37,17 @@ namespace mbts.spark {
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
double _day_position, _raw_day_position;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
bool _is_visible;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) {
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
_bus = bus;
|
||||
security_md = smd;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
string format_price(double price) => security.ContractSpec.format(price);
|
||||
_position = new CellProperty<int>(this, nameof(position), 0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
@@ -92,17 +91,9 @@ namespace mbts.spark {
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
|
||||
bool day_pos_dirty = false;
|
||||
var new_day_pos = t.state.Values.Sum(p => p.Item2.day_pos());
|
||||
if (new_day_pos != _day_position) {
|
||||
if (!double.IsNaN(new_day_pos) && !double.IsNaN(_day_position))
|
||||
_animation.animate_position(new_day_pos > _day_position);
|
||||
_day_position = new_day_pos;
|
||||
RaisePropertyChanged(nameof(day_position));
|
||||
day_pos_dirty = true;
|
||||
}
|
||||
if (day_pos_dirty | Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
raise_invalidated();
|
||||
}),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
@@ -114,20 +105,10 @@ namespace mbts.spark {
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public SecurityMD security_md { get; }
|
||||
public SecurityImmutable security => security_md.security;
|
||||
public uint security_key => security_md.security.Id;
|
||||
SecurityMD ISecurityMDRow.md => security_md;
|
||||
|
||||
public bool is_visible {
|
||||
get => _is_visible;
|
||||
set => Set(ref _is_visible, value);
|
||||
}
|
||||
|
||||
#region Bound Properties
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public double day_position => _day_position;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
@@ -177,30 +158,29 @@ namespace mbts.spark {
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
MDConsumerHandle _md => security_md.md;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (_md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_refresh_inside_working_orers(ref dirty);
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
if (_md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
dirty |=
|
||||
_settlement.set_new(_md.settlement_price) |
|
||||
_session_low.set_new(_md.session_low) |
|
||||
_session_high.set_new(_md.session_high) |
|
||||
_day_change.set_new(last_price - settlement);
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
@@ -226,7 +206,7 @@ namespace mbts.spark {
|
||||
security_key.GetHashCode();
|
||||
|
||||
#region Show Windows
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, security_md);
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book(MarketViewView.Columns col) {
|
||||
switch (col) {
|
||||
@@ -295,11 +275,14 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _refresh_orders(ref bool dirty) {
|
||||
_position.on_next_distinct(_order_summarizer.position, ref dirty);
|
||||
_working_bid_volume.on_next_distinct(_order_summarizer.working_bid_qty, ref dirty);
|
||||
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
|
||||
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_refresh_inside_working_orers(ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
@@ -309,11 +292,6 @@ namespace mbts.spark {
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
|
||||
void _refresh_inside_working_orers(ref bool dirty) {
|
||||
_inside_bids.on_next_distinct(!double.IsNaN(working_best_bid) && OrderDataHelper.PriceComparer.Compare(working_best_bid, bid_price) >= 0, ref dirty);
|
||||
_inside_asks.on_next_distinct(!double.IsNaN(working_best_ask) && OrderDataHelper.PriceComparer.Compare(working_best_ask, ask_price) <= 0, ref dirty);
|
||||
}
|
||||
#endregion
|
||||
|
||||
class MyOrderSummarizer {
|
||||
@@ -389,4 +367,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,15 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
@@ -32,61 +17,21 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// Viewmodel class designed for <see cref="ui.spark.MarketViewView"/>
|
||||
/// </summary>
|
||||
partial class MarketViewVM : DisposableViewModelBase, IHandleRowSelection {
|
||||
static readonly IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>().ToReadOnlyList();
|
||||
|
||||
static readonly IComparer<SecurityMD> _sec_md_key_comparer = Comparer<uint>.Default.wrap(SecurityMD.key_fn);
|
||||
static readonly IComparer<Row> _row_key_comparer = _sec_md_key_comparer.wrap<SecurityMD, Row>(r => r.security_md);
|
||||
static readonly string _security_delimiter = $" {SpecialCharacters.Dot} ";
|
||||
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ObservableCollection<Row> _rows = new ObservableCollection<Row>();
|
||||
readonly IList<SecurityMD> _wrapped_rows;
|
||||
readonly ListCollectionView _rows_view;
|
||||
readonly Dictionary<uint, Row> _cache = new Dictionary<uint, Row>();
|
||||
readonly Property<Row> _current_row;
|
||||
readonly DeferrableActionWrapper _refresh_filter;
|
||||
Func<Row, bool> _filter;
|
||||
IComparer<Row> _comparer;
|
||||
Color _selection_highlight;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of the <see cref="ui.spark.MarketViewView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="bus"></param>
|
||||
/// <param name="orders_model"></param>
|
||||
/// <param name="security_mds_source"></param>
|
||||
/// <param name="use_mouse_state_transitions">Indicates whether or not hyperlink cells should use visual states to animate mouse over (seems to be slower than style triggers, should investigate to be certain).</param>
|
||||
public MarketViewVM(
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
IObservable<TableUpdate<SecurityMD, uint>> security_mds_source,
|
||||
bool use_mouse_state_transitions = false) {
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
// rows
|
||||
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
|
||||
var r = new Row(_bus, s, _orders_model);
|
||||
r.is_visible = _filter == null || _filter(r);
|
||||
return r;
|
||||
});
|
||||
_current_row = backing<Row>(nameof(current_row));
|
||||
_rows_view = new ListCollectionView(_rows) {
|
||||
Filter = obj => obj is Row r && r.is_visible,
|
||||
IsLiveFiltering = true
|
||||
};
|
||||
_rows_view.LiveFilteringProperties.Add(nameof(Row.is_visible));
|
||||
// Important: these selected_security/md wrappers should be instantiated in the constructor (rather than lazily),
|
||||
// to prevent situations where callers subscribe up to the selected_rows.CollectionChanged event before the wrappers do.
|
||||
// This way, the wrappers are protected from unintended side effects of the foreign callbacks, as they will always receive CollectionChange notifications first.
|
||||
selected_securities = selected_rows.SelectObservable(r => r.security);
|
||||
selected_security_mds = selected_rows.SelectObservable(r => r.security_md);
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
|
||||
const double working_orders_col_width = 105;
|
||||
@@ -94,55 +39,24 @@ namespace mbts.spark {
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax);
|
||||
add_hl_col(
|
||||
Columns.Position,
|
||||
"Day Pos",
|
||||
new Binding(nameof(Row.day_position)),
|
||||
nameof(Row.day_position),
|
||||
day_position_filter,
|
||||
DataGridFilterKnownTemplates.IntMinMax,
|
||||
DefaultColumnWidths.Size,
|
||||
tooltip: $"Show {_fills_title(Columns.Position).desc}");
|
||||
add_hl_col(
|
||||
Columns.RawDayPosition,
|
||||
"Raw Day Pos",
|
||||
new Binding(nameof(Row.raw_day_position)) {
|
||||
Converter = ValueConverter.create<double, string>(SparkUtils.fmt_position)
|
||||
},
|
||||
nameof(Row.raw_day_position),
|
||||
raw_day_position_filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
DefaultColumnWidths.Size,
|
||||
tooltip: $"Show {_fills_title(Columns.RawDayPosition).desc}");
|
||||
add_hl_working_orders_col(
|
||||
Columns.WorkingBids,
|
||||
working_col_header,
|
||||
nameof(Row.working_bids_text),
|
||||
nameof(Row.working_best_bid),
|
||||
working_bid_volume_filter,
|
||||
BTSColors.MarketData.bid);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid);
|
||||
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), bid_merged_size_filter, BTSColors.MarketData.bid);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), ask_merged_size_filter, BTSColors.MarketData.ask);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask);
|
||||
add_hl_working_orders_col(
|
||||
Columns.WorkingAsks,
|
||||
working_col_header,
|
||||
nameof(Row.working_asks_text),
|
||||
nameof(Row.working_best_ask),
|
||||
working_ask_volume_filter,
|
||||
BTSColors.MarketData.ask);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
@@ -151,32 +65,9 @@ namespace mbts.spark {
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
// row vm filter
|
||||
// Althoguh securities are filtered at the row 'model' level (SecurityMD), the column base filters have to be applied at the row vm level,
|
||||
// because they access market data, which is polled periodically and cached in the row vm.
|
||||
_refresh_filter = new DeferrableActionWrapper(() => _filter_changed?.Invoke());
|
||||
var row_filter_source = Observable.Create<Func<Row, bool>>(o => {
|
||||
notify();
|
||||
_filter_changed += notify;
|
||||
return Disposable.Create(() => _filter_changed -= notify);
|
||||
void notify() =>
|
||||
o.OnNext(get_filter_states().build_predicate());
|
||||
});
|
||||
foreach (var f in _all_filters) {
|
||||
f.filter_changed += _refresh_filter.execute;
|
||||
}
|
||||
_dispose = Disposable.Create(
|
||||
row_filter_source.Subscribe(f => {
|
||||
// the rows are polled every 100 ms, just set the filter and let the refresh take care of the rest.
|
||||
_filter = f;
|
||||
}),
|
||||
security_mds_source.Subscribe(_on_securities),
|
||||
ObservableAutoWrapper.connect_item_hooks(_rows, r => r, true),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
// local methods
|
||||
RenderTextColumn<Row> add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, HorizontalAlignment h_alignment = HorizontalAlignment.Right) {
|
||||
var col = new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
@@ -185,91 +76,31 @@ namespace mbts.spark {
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
};
|
||||
columns.Add(col);
|
||||
return col;
|
||||
}
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, IDataGridColumnFilter filter, Color foreground, Color? background) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
header,
|
||||
price_binding(prop),
|
||||
prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
DefaultColumnWidths.MViewPrice,
|
||||
foreground,
|
||||
background,
|
||||
tooltip: _order_ticket_tooltip(col_key));
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string sort_prop, string text_prop, IDataGridColumnFilter filter, Color foreground) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
merged_size_col_header,
|
||||
new Binding(text_prop),
|
||||
sort_prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax,
|
||||
DefaultColumnWidths.ImpliedDirectMergedSize,
|
||||
foreground,
|
||||
tooltip: _order_ticket_tooltip(col_key));
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
|
||||
HyperlinkElementColumn<Columns> add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, IDataGridColumnFilter filter, Color foreground) =>
|
||||
add_hl_col(
|
||||
col_key,
|
||||
header,
|
||||
new Binding(text_prop),
|
||||
sort_prop,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.IntMinMax,
|
||||
working_orders_col_width,
|
||||
foreground,
|
||||
tooltip: $"Show {_orderbook_title(col_key).desc}");
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
|
||||
HyperlinkElementColumn<Columns> add_hl_col(
|
||||
Columns col_key,
|
||||
string header,
|
||||
BindingBase binding,
|
||||
string prop,
|
||||
IDataGridColumnFilter filter,
|
||||
string filter_template,
|
||||
double width,
|
||||
Color? foreground = null,
|
||||
Color? background = null,
|
||||
HorizontalAlignment h_alignment = HorizontalAlignment.Right,
|
||||
string tooltip = null,
|
||||
BindingBase tooltip_binding = null) {
|
||||
|
||||
var col = new HyperlinkElementColumn<Columns>(
|
||||
col_key, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
TextBinding = binding,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
};
|
||||
col.view_generated += on_view_generated;
|
||||
columns.Add(col);
|
||||
return col;
|
||||
//
|
||||
void on_view_generated(HyperlinkCell cell_view) {
|
||||
cell_view.SetValue(ColumnKeyProperty, col_key);
|
||||
cell_view.SetValue(ColumnProperty, col);
|
||||
cell_view.SetBinding(GridProperty, new Binding { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DIYGrid), 1) });
|
||||
if (tooltip != null) {
|
||||
cell_view.ToolTip = tooltip;
|
||||
} else if (tooltip_binding != null) {
|
||||
cell_view.SetBinding(FrameworkElement.ToolTipProperty, tooltip_binding);
|
||||
}
|
||||
if (use_mouse_state_transitions)
|
||||
init_hyperlink_hover_animation(cell_view, col);
|
||||
}
|
||||
}
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
@@ -277,95 +108,72 @@ namespace mbts.spark {
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
|
||||
void init_hyperlink_hover_animation(HyperlinkCell cell_view, ColumnBase col) {
|
||||
// background timeline
|
||||
var hover_bg_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view,
|
||||
new PropertyPath("(0).(1)", HyperlinkCell.BackgroundProperty, SolidColorBrush.ColorProperty),
|
||||
new Binding(nameof(ColumnBase.background_color)) {
|
||||
Source = col,
|
||||
Converter = new ColorBlendConverter { Overlay = Colors.Black.with(0x1A) }
|
||||
});
|
||||
// background pressed timeline
|
||||
var pressed_bg_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view,
|
||||
new PropertyPath("(0).(1)", HyperlinkCell.BackgroundProperty, SolidColorBrush.ColorProperty),
|
||||
new Binding(nameof(ColumnBase.background_color)) {
|
||||
Source = col,
|
||||
Converter = new ColorBlendConverter { Overlay = Colors.Black.with(0x25) }
|
||||
});
|
||||
// border timeline
|
||||
var bd_brush = new SolidColorBrush();
|
||||
BindingOperations.SetBinding(bd_brush, SolidColorBrush.ColorProperty, new Binding(nameof(ColumnBase.background_color)) { Source = col });
|
||||
cell_view.BorderBrush = bd_brush;
|
||||
cell_view.BorderThickness = new Thickness(1);
|
||||
var hover_bd_timeline = VisualStateHelper.create_color_animation(
|
||||
cell_view, new PropertyPath("(0).(1)", HyperlinkCell.BorderBrushProperty, SolidColorBrush.ColorProperty), Colors.Black.with(0x60));
|
||||
// mouse states
|
||||
VisualStateManager.GetVisualStateGroups(cell_view).Add(
|
||||
VisualStateHelper.create_group("MouseStates",
|
||||
new VisualState { Name = "Normal" },
|
||||
VisualStateHelper.create_state("MouseOver", hover_bg_timeline, hover_bd_timeline),
|
||||
VisualStateHelper.create_state("Pressed", pressed_bg_timeline, hover_bd_timeline)));
|
||||
// state groups manager
|
||||
StateManager.SetGroups(cell_view, new StateGroupsManager {
|
||||
StateDefinitions = new StateDefinitionCollection {
|
||||
new MouseStateDefinition { MouseSource = cell_view }
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
|
||||
public event PropertyChange<Row> current_row_changed { add => _current_row.Changed += value; remove => _current_row.Changed -= value; }
|
||||
event Action _filter_changed;
|
||||
|
||||
public ICollectionView rows_view => _rows_view;
|
||||
public ObservableCollection<Row> rows => _rows;
|
||||
public ObservableCollection<Row> selected_rows { get; } = new ObservableCollection<Row>();
|
||||
public IReadOnlyObservableCollection<SecurityImmutable> selected_securities { get; }
|
||||
public IReadOnlyObservableCollection<SecurityMD> selected_security_mds { get; }
|
||||
public Color selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public Row current_row { get => _current_row.Value; set => _current_row.set(value); }
|
||||
public SecurityImmutable? current_security => current_row?.security;
|
||||
public SecurityMD? current_security_md => current_row?.security_md;
|
||||
|
||||
internal IBus bus => _bus;
|
||||
internal OrdersModel orders_model => _orders_model;
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> day_position_filter { get; } = new IntMinMax<Row>(r => unchecked((int)r.day_position), nameof(Row.day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<Row>> _all_filters =>
|
||||
_filter_getters.Select(g => g(this));
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
@@ -373,9 +181,8 @@ namespace mbts.spark {
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSpd: return wrap(r => r.last_price_from_spread);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.day_position);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
|
||||
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
|
||||
@@ -402,60 +209,37 @@ namespace mbts.spark {
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
public IComparer<Row> comparer {
|
||||
get => _comparer;
|
||||
set => _rows_view.CustomSort = (_comparer = value)?.to_non_generic();
|
||||
}
|
||||
|
||||
public (FillsVM vm, string context, string title, Color fg) fills_params(IEnumerable<SecurityImmutable> securities, Columns column) {
|
||||
var (title_desc, fg) = _fills_title(column);
|
||||
var state = StartupData.DefaultAuxiliaryFills;
|
||||
if (state.filters == null)
|
||||
state.filters = new bts.shell.fills.FillsFilterStates();
|
||||
switch (column) {
|
||||
case Columns.RawDayPosition:
|
||||
state.filters.fill_kind = new[] {
|
||||
Fill.Types.FillKind.Standard
|
||||
};
|
||||
break;
|
||||
case Columns.Position:
|
||||
state.filters.fill_kind = new[] {
|
||||
Fill.Types.FillKind.Standard,
|
||||
Fill.Types.FillKind.Sod
|
||||
};
|
||||
break;
|
||||
}
|
||||
state.grid = GridSettingsHelper.hide_cols(state.grid, new[] { ui.spark.FillsView.Columns.Security });
|
||||
var sec_keys = securities.Select(SecurityDataHelper.key_fn);
|
||||
if (securities.All(s => s.ContractCase == Security.ContractOneofCase.Synthetic)) {
|
||||
state.root_securities = sec_keys.ToArray();
|
||||
} else {
|
||||
state.filters.security = SecurityCriteria.from_keys(sec_keys);
|
||||
}
|
||||
var vm = new FillsVM(
|
||||
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
return (vm, $"{string.Join(_security_delimiter, securities)}", title_desc, fg);
|
||||
}
|
||||
|
||||
public (OrderBookVM vm, string context, string title, Color fg) orderbook_params(IEnumerable<SecurityImmutable> securities, Columns column) {
|
||||
var (title_desc, fg) = _orderbook_title(column);
|
||||
var filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = SecurityCriteria.from_keys(securities.Select(SecurityDataHelper.key_fn)),
|
||||
order_types = securities.All(s => s.ContractCase == Security.ContractOneofCase.Synthetic) ?
|
||||
new[] { new AlgoID(CustomOrderDefinition.CustomOneofCase.Spreader) } :
|
||||
AlgoID.exchange_algo_keys,
|
||||
statuses = new[] { OrderStatus.Working }
|
||||
};
|
||||
switch (column) {
|
||||
case Columns.WorkingBids: filters.sides = new[] { Side.Buy }; break;
|
||||
case Columns.WorkingAsks: filters.sides = new[] { Side.Sell }; break;
|
||||
}
|
||||
var vm = _mk_orderbook_vm_common(filters, new[] {
|
||||
OrderBookGridView.ColumnKeys.Security,
|
||||
OrderBookGridView.ColumnKeys.Side
|
||||
});
|
||||
return (vm, $"{string.Join(_security_delimiter, securities)}", title_desc, fg);
|
||||
}
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
@@ -475,7 +259,7 @@ namespace mbts.spark {
|
||||
settlement = settlement_filter.get_state(),
|
||||
session_low = session_low_filter.get_state(),
|
||||
session_high = session_high_filter.get_state(),
|
||||
day_position = day_position_filter.get_state(),
|
||||
position = position_filter.get_state(),
|
||||
raw_day_position = raw_day_position_filter.get_state(),
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
@@ -489,109 +273,14 @@ namespace mbts.spark {
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void load_filters(FilterStates filters) {
|
||||
if (filters != null) {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
day_position_filter.load_state(filters.day_position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _fills_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.Position: return ("Day Fills", Colors.Black);
|
||||
case Columns.RawDayPosition: return ("Raw Day Fills", Colors.Black);
|
||||
default: return ("Fills", Colors.Black);
|
||||
}
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _orderbook_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.WorkingBids: return ("Working Buy Orders", BTSColors.MarketData.bid);
|
||||
case Columns.WorkingAsks: return ("Working Sell Orders", BTSColors.MarketData.ask);
|
||||
default: return ("Working Orders", Colors.Black);
|
||||
}
|
||||
}
|
||||
|
||||
static GridSettings _hide_cols(GridSettings grid, IEnumerable<OrderBookGridView.ColumnKeys> columns) =>
|
||||
grid.with(_hide_cols(grid.columns, columns));
|
||||
|
||||
static IEnumerable<GridColumnSettings> _hide_cols(IEnumerable<GridColumnSettings> cols, IEnumerable<OrderBookGridView.ColumnKeys> columns) {
|
||||
// hide columns who's data is implied by the grouping
|
||||
var col_hash = new HashSet<string>(columns.Select(k => k.ToString()));
|
||||
foreach (var c in cols)
|
||||
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
|
||||
}
|
||||
|
||||
static string _order_ticket_tooltip(Columns key) {
|
||||
var bid_or_ask = Regex.Match(key.ToString(), "(Bid|Ask)");
|
||||
return bid_or_ask.Success ? $"Order Ticket @ {bid_or_ask.Value}" : "Order Ticket";
|
||||
}
|
||||
|
||||
OrderBookVM _mk_orderbook_vm_common(bts.shell.orderbook.FilterStates filters, IEnumerable<OrderBookGridView.ColumnKeys> hide_cols, IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source = null) {
|
||||
var state = new bts.shell.orderbook.OrderBookStartupData(filters, bts.shell.orderbook.OrderBookStartupData.default_mini_grid, use_defaults: false);
|
||||
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, hide_cols);
|
||||
var settings_man = new WindowSettingsManager<bts.shell.orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
|
||||
var obvm = orders_source == null ?
|
||||
new OrderBookVM(_bus, null, _orders_model, settings_man, true) :
|
||||
new OrderBookVM(_bus, null, _orders_model, orders_source, settings_man, true);
|
||||
foreach (var c in OrderBookVM.OrderBookColumnCollection.editable_columns)
|
||||
obvm.columns.Insert(0, c);
|
||||
obvm.load_from(state);
|
||||
return obvm;
|
||||
}
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _rows) {
|
||||
r.refresh();
|
||||
r.is_visible = _filter == null || _filter(r);
|
||||
}
|
||||
}
|
||||
|
||||
Row _row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
|
||||
void IHandleRowSelection.row_selection_changed(SelectionChangedEventArgs e) {
|
||||
selected_rows.RemoveSorted(e.RemovedItems.OfType<Row>(), _row_key_comparer);
|
||||
selected_rows.InsertSorted(e.AddedItems.OfType<Row>(), _row_key_comparer);
|
||||
}
|
||||
|
||||
void _on_securities(TableUpdate<SecurityMD, uint> table) {
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
foreach (var smd in table.removed) {
|
||||
int i = CollectionHelper.binary_search(_wrapped_rows, smd, _sec_md_key_comparer);
|
||||
Log.assert(i >= 0);
|
||||
_wrapped_rows.RemoveAt(i);
|
||||
}
|
||||
foreach (var smd in table.added) {
|
||||
int i = CollectionHelper.binary_search(_wrapped_rows, smd, _sec_md_key_comparer);
|
||||
Log.assert(i < 0);
|
||||
_wrapped_rows.Insert(~i, smd);
|
||||
}
|
||||
sw.Stop();
|
||||
Log.Ui.info($"MarketView update took {sw.ElapsedMilliseconds:0.00}ms.");
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,8 @@
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:bv="clr-namespace:bts.view"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
u:WindowStyle.UnsafeWindow="{Binding Path='(u:WindowStyle.UnsafeWindow)', RelativeSource={RelativeSource AncestorType=bv:SparkWindowView}}"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
@@ -17,56 +13,8 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="c:HyperlinkCell">
|
||||
<Setter Property="Background" Value="{Binding Path='(c:DIYGrid.Column).background_brush', RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Command" Value="{x:Static usp:MarketViewView.CellClickCommand}"/>
|
||||
<Setter Property="CommandParameter">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static usp:MarketViewView.CellConverter}">
|
||||
<Binding Path=""/>
|
||||
<Binding Path="(c:DIYGrid.ColumnKey)" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=usp:MarketViewView}}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOverClickArea" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<cv:MultiValueConverterPipe>
|
||||
<cv:ColorBlendMultiConverter/>
|
||||
<cv:ColorToBrushConverter/>
|
||||
</cv:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="(c:DIYGrid.Grid).SelectionHighlight" RelativeSource="{RelativeSource Self}"/>
|
||||
<Binding Path="(c:DIYGrid.Column).background_color" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<Binding Path="SelectionHighlight" RelativeSource="{RelativeSource AncestorType=c:DIYGrid}">
|
||||
<Binding.Converter>
|
||||
<cv:ValueConverterPipe>
|
||||
<cv:ColorWithConverter A="255"/>
|
||||
<cv:ColorToBrushConverter/>
|
||||
</cv:ValueConverterPipe>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<UserControl.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:MarketViewView.CellClickCommand}" Executed="{x:Static usp:MarketViewView.CellClickExecuted}"/>
|
||||
</UserControl.CommandBindings>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -74,14 +22,13 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
SelectionHighlight="{Binding selection_highlight, Mode=OneWay}"
|
||||
SyncColumnSortDirectionWithSortDescriptions="False"
|
||||
>
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
|
||||
@@ -1,38 +1,14 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for MarketSheetsView.xaml
|
||||
/// </summary>
|
||||
partial class MarketViewView : UserControl {
|
||||
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
|
||||
|
||||
MarketViewVM _vm;
|
||||
DIYGridPopupManager _popup_man_backing;
|
||||
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
this.ConnectDataContext<MarketViewVM>(vm => _vm = vm);
|
||||
AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(
|
||||
_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
}
|
||||
|
||||
public enum Columns {
|
||||
None,
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
@@ -63,116 +39,14 @@ namespace mbts.ui.spark {
|
||||
MicCode
|
||||
}
|
||||
|
||||
public static ExecutedRoutedEventHandler
|
||||
CellClickExecuted => CommandImpl.cell_click;
|
||||
|
||||
public static IMultiValueConverter CellConverter =>
|
||||
ValueConverter.create<MarketViewVM.Row, Columns, MarketViewCell>((r, c) => new MarketViewCell(r, c));
|
||||
|
||||
IBus _bus => _vm.bus;
|
||||
OrdersModel _model => _vm.orders_model;
|
||||
DIYGridPopupManager _popup_man =>
|
||||
_popup_man_backing ?? (_popup_man_backing = DIYGridPopupManager.create(this));
|
||||
|
||||
public void show_working_orders(IReadOnlyList<SecurityImmutable> securities) =>
|
||||
_show_mini_orderbook(securities, Columns.None);
|
||||
|
||||
public void show_mini_fills(IReadOnlyList<SecurityImmutable> securities) =>
|
||||
_show_mini_fills(securities, Columns.None);
|
||||
|
||||
public void show_order_ticket(SecurityMD security_md) =>
|
||||
_show_order_ticket(security_md);
|
||||
|
||||
void _on_cell_click(MarketViewVM.Row row, Columns col) {
|
||||
switch (col) {
|
||||
case Columns.WorkingAsks:
|
||||
case Columns.WorkingBids:
|
||||
_show_mini_orderbook(row.security, col);
|
||||
break;
|
||||
case Columns.Bid:
|
||||
_show_order_ticket(row.security_md, row.bid_price);
|
||||
break;
|
||||
case Columns.BidMergedSize: _show_order_ticket(row.security_md, row.bid_price, row.bid_merged_size); break;
|
||||
case Columns.BidDirectSize: _show_order_ticket(row.security_md, row.bid_price, row.bid_direct_size); break;
|
||||
case Columns.BidImpliedSize: _show_order_ticket(row.security_md, row.bid_price, row.bid_implied_size); break;
|
||||
case Columns.Ask:
|
||||
_show_order_ticket(row.security_md, row.ask_price);
|
||||
break;
|
||||
case Columns.AskMergedSize: _show_order_ticket(row.security_md, row.ask_price, row.ask_merged_size); break;
|
||||
case Columns.AskDirectSize: _show_order_ticket(row.security_md, row.ask_price, row.ask_direct_size); break;
|
||||
case Columns.AskImpliedSize: _show_order_ticket(row.security_md, row.ask_price, row.ask_implied_size); break;
|
||||
case Columns.Last:
|
||||
_show_order_ticket(row.security_md, row.last_price);
|
||||
break;
|
||||
case Columns.LastSize:
|
||||
_show_order_ticket(row.security_md, row.last_price, row.last_size);
|
||||
break;
|
||||
case Columns.Position:
|
||||
case Columns.RawDayPosition:
|
||||
_show_mini_fills(row.security, col);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_mini_orderbook(SecurityImmutable security, Columns col) =>
|
||||
_show_mini_orderbook(new[] { security }, col);
|
||||
|
||||
void _show_mini_orderbook(IReadOnlyList<SecurityImmutable> securities, Columns col) {
|
||||
var (vm, context, title, fg) = _vm.orderbook_params(securities, col);
|
||||
_show_popup(vm, new AuxiliaryDataHeader {
|
||||
context = context,
|
||||
title = title,
|
||||
title_foreground = fg
|
||||
});
|
||||
}
|
||||
|
||||
void _show_mini_fills(SecurityImmutable security, Columns col) =>
|
||||
_show_mini_fills(new[] { security }, col);
|
||||
|
||||
void _show_mini_fills(IEnumerable<SecurityImmutable> securities, Columns col) {
|
||||
var (vm, context, title, fg) = _vm.fills_params(securities, col);
|
||||
_show_popup(vm, new AuxiliaryDataHeader {
|
||||
context = context,
|
||||
title = title,
|
||||
title_foreground = fg
|
||||
});
|
||||
}
|
||||
|
||||
void _show_order_ticket(SecurityMD security_md, double price = double.NaN, uint size = 0) {
|
||||
var resources = new OrderTicketResources();
|
||||
resources.MergedDictionaries.Add(new AuxiliaryDataResources());
|
||||
var ticket_vm = new NewOrderTicketVM(
|
||||
_model, security_md, _model.factory.order(security_md.security), price, size, Observable.Return(mbts.spark.LadderSettings.Default.calculator_buttons));
|
||||
_show_popup(ticket_vm, new OrderTicketHeader { bbo = ticket_vm.bbo }, resources);
|
||||
}
|
||||
|
||||
void _show_popup(object content, object header) {
|
||||
var resources = new AuxiliaryDataResources();
|
||||
resources.MergedDictionaries.Add(new ScrollBarResources());
|
||||
_show_popup(content, header, resources);
|
||||
}
|
||||
|
||||
void _show_popup(object content, object header, ResourceDictionary resources) {
|
||||
var view = new HeaderedContentControl {
|
||||
DataContext = content,
|
||||
Content = content,
|
||||
Header = header,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Resources = resources ?? new AuxiliaryDataResources()
|
||||
};
|
||||
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
|
||||
_popup_man.show(view);
|
||||
}
|
||||
|
||||
static class CommandImpl {
|
||||
public static void cell_click(object sender, ExecutedRoutedEventArgs e) {
|
||||
if (sender is MarketViewView mview && e.Parameter is MarketViewCell cell)
|
||||
mview._on_cell_click(cell.row, cell.column);
|
||||
e.Handled = true;
|
||||
}
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(
|
||||
_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
}
|
||||
}
|
||||
|
||||
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row> { }
|
||||
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row, MarketViewView.Columns> { }
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
@@ -24,18 +21,9 @@ using System.Windows;
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ MarketViewView.Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ MarketViewView.Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ MarketViewView.Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ MarketViewView.Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
List<SortingCriteria> _current_sorting_criterion;
|
||||
MarketViewVM _vm;
|
||||
MarketViewView _view;
|
||||
MarketViewWindowFactory.StartupData _state;
|
||||
@@ -44,7 +32,6 @@ namespace mbts.spark {
|
||||
SecurityCriteria
|
||||
_selected_security_criteria_backing,
|
||||
_sorted_selected_security_criteria_backing;
|
||||
SimpleGridSettingsManager<MarketViewVM.Row> _grid_settings_manager;
|
||||
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
@@ -55,17 +42,13 @@ namespace mbts.spark {
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
|
||||
}
|
||||
|
||||
event Action<SecurityCriteria> _security_criteria_changed;
|
||||
event Action<MarketViewWindowFactory.StartupData> _state_changed;
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override MarketViewWindowFactory.StartupData SaveState =>
|
||||
_state = new MarketViewWindowFactory.StartupData(
|
||||
_state.security_criteria,
|
||||
_vm.get_filter_states(),
|
||||
_settings_manager.instance_settings,
|
||||
_current_sorting_criterion);
|
||||
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
|
||||
|
||||
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
@@ -85,47 +68,43 @@ namespace mbts.spark {
|
||||
return _selected_security_criteria_backing;
|
||||
}
|
||||
}
|
||||
|
||||
SecurityCriteria _sorted_selected_security_criteria => _sorted_selected_security_criteria_backing ?? (
|
||||
_sorted_selected_security_criteria_backing = _vm != null ?
|
||||
new SecurityCriteria(_vm.selected_rows.Ordered(_vm.comparer).Select(r => r.security)) :
|
||||
null);
|
||||
SecurityCriteria _sorted_selected_security_criteria {
|
||||
get {
|
||||
if (_sorted_selected_security_criteria_backing == null) {
|
||||
if (_vm != null) {
|
||||
var sorting_criteria = _vm.rows_view.get_sorting_criteria(_vm.columns);
|
||||
var comp_builder = new ComparerBuilder<MarketViewVM.Row>();
|
||||
foreach (var sc in sorting_criteria) {
|
||||
if (_vm.columns.TryGetFirst(col => string.Equals(col.id, sc.col_name, StringComparison.CurrentCultureIgnoreCase), out var match)) {
|
||||
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
|
||||
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
|
||||
if (col_comp != null) {
|
||||
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
|
||||
} else {
|
||||
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
|
||||
}
|
||||
} else {
|
||||
Log.Ui.warn($"{_window_type} is unable find a column with id '{sc.col_name}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
var comp = comp_builder.to_comparer();
|
||||
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
|
||||
}
|
||||
}
|
||||
return _sorted_selected_security_criteria_backing;
|
||||
}
|
||||
}
|
||||
|
||||
bool _has_selected_rows() => _vm != null && _vm.selected_rows.Count > 0;
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
var security_md_map_src = Observable.Create<IReadOnlyDictionary<uint, SecurityMD>>(o => {
|
||||
IDisposable sub = null;
|
||||
_security_criteria_changed += on_security_criteria_changed;
|
||||
on_security_criteria_changed(_state.security_criteria);
|
||||
return Disposable.Create(dispose);
|
||||
void on_security_criteria_changed(SecurityCriteria security_criteria) {
|
||||
sub?.Dispose();
|
||||
sub = _state.security_criteria.subscribe_md_map(_bus).Subscribe(o.OnNext);
|
||||
}
|
||||
void dispose() =>
|
||||
Disposable.dispose(ref sub);
|
||||
});
|
||||
_vm = new MarketViewVM(
|
||||
_bus,
|
||||
_orders_model,
|
||||
security_md_map_src
|
||||
.BufferLatest(TimeSpan.FromSeconds(1))
|
||||
.Table(SecurityMD.key_fn)
|
||||
.Where(t => t.has_changes())
|
||||
.OnDispatcher());
|
||||
var sort_man = new ColumnSortManager(_vm.columns, MarketViewView.Columns.Security.ToString());
|
||||
_current_sorting_criterion = _state.sorting_criterion.ToList();
|
||||
sort_man.load(_state.sorting_criterion);
|
||||
_vm.load_filters(_state.filters);
|
||||
_vm.comparer = sort_man.build_comparer<MarketViewView.Columns, MarketViewVM.Row>(MarketViewVM.get_column_comparer);
|
||||
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new MarketViewView { DataContext = _vm };
|
||||
_view._grid.Sorting += on_grid_sorting;
|
||||
_grid_settings_manager = new SimpleGridSettingsManager<MarketViewVM.Row>(_vm.columns, _col_settings_flags, _vm.rows);
|
||||
_grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
|
||||
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
|
||||
/// For these commands, the objective is to evaluate 'CanExecute' without instantiating the SecurityCriteria (or more importantly, without creating a new List).
|
||||
@@ -147,13 +126,6 @@ namespace mbts.spark {
|
||||
});
|
||||
var show_fills_actual_cmd = SparkCommands.ShowFills();
|
||||
var show_fills_cmd_wrapper = new SparkCommandCommand(show_fills_actual_cmd.title, show_fills_actual_cmd.icon, show_fills_cmd, SparkCommands.KeyGestures.ShowFills);
|
||||
var show_mini_fills_cmd = new DelegateCommand(() => _view.show_mini_fills(_vm.selected_securities), () => _vm.selected_securities.Count > 0);
|
||||
var show_ticket_cmd = new DelegateCommand(() => {
|
||||
var smd = _vm.current_security_md;
|
||||
if (smd.HasValue)
|
||||
_view.show_order_ticket(smd.Value);
|
||||
}, () => _vm.current_security_md.HasValue);
|
||||
var show_ticket_actual_cmd = SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket);
|
||||
// show synthetic create
|
||||
var show_synth_create_cmd = new DelegateCommand(() => {
|
||||
var sc = _sorted_selected_security_criteria;
|
||||
@@ -162,29 +134,15 @@ namespace mbts.spark {
|
||||
}, () => _vm.selected_rows.Count > 0 && _vm.selected_rows.Count <= 24);
|
||||
var show_synth_create_actual_cmd = SparkCommands.ShowSyntheticCreate();
|
||||
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
|
||||
var show_working_orders_cmd = new DelegateCommand(() => _view.show_working_orders(_vm.selected_securities), () => _vm.selected_securities.Count > 0);
|
||||
|
||||
var current_sec_md_src = Observable.Create<SecurityMD?>(o => {
|
||||
notify(null, null);
|
||||
_vm.current_row_changed += notify;
|
||||
return Disposable.Create(() => _vm.current_row_changed -= notify);
|
||||
void notify(MarketViewVM.Row oldval, MarketViewVM.Row newval) {
|
||||
if (newval != null)
|
||||
o.OnNext(newval.security_md);
|
||||
else o.OnNext(null);
|
||||
}
|
||||
});
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
// commands
|
||||
_commands = new List<SparkCommand> {
|
||||
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_rows.Observe().Select(_ => _vm.selected_rows.WrapSelectorRO(r => r.security_md)), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_sec_md_src, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
show_fills_cmd_wrapper,
|
||||
show_tas_cmd_wrapper,
|
||||
show_synth_create_cmd_wrapper,
|
||||
// Note: the commands commented out below open the mini popup versions of their respective windows
|
||||
// TODO: determine if/how to integrate the mini popups into the toolbar.
|
||||
//new SparkCommandCommand("Working Orders", Icons.Orders, show_working_orders_cmd),
|
||||
//new SparkCommandCommand("Fills", show_fills_actual_cmd.icon, show_mini_fills_cmd),
|
||||
//new SparkCommandCommand(show_ticket_actual_cmd.title, show_ticket_actual_cmd.icon, show_ticket_cmd),
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
// 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(() => {
|
||||
@@ -192,7 +150,7 @@ namespace mbts.spark {
|
||||
_settings_manager,
|
||||
us => us.market_view_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _grid_settings_manager.create_vm(
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
@@ -205,40 +163,26 @@ namespace mbts.spark {
|
||||
}))
|
||||
};
|
||||
//
|
||||
_vm.selected_rows.CollectionChanged += on_selection_changed;
|
||||
_subscriptions = new CompositeDisposable(
|
||||
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c => {
|
||||
_state = new MarketViewWindowFactory.StartupData(c, _state.filters, _state.grid, _state.sorting_criterion.ToList());
|
||||
_security_criteria_changed?.Invoke(c);
|
||||
}),
|
||||
_settings_manager.subscribe_current().OnDispatcher().Subscribe(new CompositeObserver<GridSettings>(_grid_settings_manager) {
|
||||
s => _vm.selection_highlight = s.selection_highlight
|
||||
})) {
|
||||
_vm.selection_changed += on_selection_changed;
|
||||
_subscriptions = new CompositeDisposable(_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())))) {
|
||||
() => {
|
||||
_vm.selected_rows.CollectionChanged -= on_selection_changed;
|
||||
_vm.selection_changed -= on_selection_changed;
|
||||
_view._grid.Sorting -= on_grid_sorting;
|
||||
}
|
||||
};
|
||||
|
||||
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
void on_selection_changed((IEnumerable<MarketViewVM.Row> removed, IEnumerable<MarketViewVM.Row> added) change) {
|
||||
// invalidate selected security criteria
|
||||
_selected_security_criteria_backing = null;
|
||||
_sorted_selected_security_criteria_backing = null;
|
||||
show_tas_cmd.RaiseCanExecuteChanged();
|
||||
show_fills_cmd.RaiseCanExecuteChanged();
|
||||
show_synth_create_cmd.RaiseCanExecuteChanged();
|
||||
show_working_orders_cmd.RaiseCanExecuteChanged();
|
||||
show_mini_fills_cmd.RaiseCanExecuteChanged();
|
||||
show_ticket_cmd.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
void on_grid_sorting(object sender, ui.controls.DIYGridSortingEventArgs e) {
|
||||
void on_grid_sorting(object sender, ui.controls.DIYGridSortingEventArgs e) =>
|
||||
_sorted_selected_security_criteria_backing = null;
|
||||
sort_man.on_column_click(e.Column, KeyboardExtensions.IsShiftDown());
|
||||
_vm.comparer = sort_man.build_comparer<MarketViewView.Columns, MarketViewVM.Row>(MarketViewVM.get_column_comparer);
|
||||
_current_sorting_criterion = sort_man.create().ToList();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.proto;
|
||||
using bts.settings;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -89,22 +88,7 @@ namespace bts.shell {
|
||||
}
|
||||
|
||||
|
||||
public static GridSettings DefaultGridSettings =>
|
||||
GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
|
||||
|
||||
public static fills.FillsSettings DefaultAuxiliaryFills {
|
||||
get {
|
||||
var grid = fills.FillsSettings.MiniGridSettings;
|
||||
//if (grid.columns.TryGetFirst(c => c.id == nameof(mbts.ui.spark.FillsView.Columns.Side), out var side_col, out int side_col_pos)) {
|
||||
// var cols = grid.columns.ToList();
|
||||
// cols[side_col_pos] = side_col.with(is_visible: false);
|
||||
// grid = grid.with(cols);
|
||||
//}
|
||||
var rv = fills.FillsSettings.Default();
|
||||
rv.grid = grid;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
@@ -212,7 +196,7 @@ namespace bts.shell {
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState session_high;
|
||||
[ProtoMember(12)]
|
||||
public IntMinMaxState day_position;
|
||||
public IntMinMaxState position;
|
||||
[ProtoMember(27)]
|
||||
public DoubleMinMaxState raw_day_position;
|
||||
[ProtoMember(13)]
|
||||
@@ -245,47 +229,6 @@ namespace bts.shell {
|
||||
public string last_price_from_spread;
|
||||
[ProtoMember(28)]
|
||||
public string mic_code;
|
||||
|
||||
public Func<MarketViewVM.Row, bool> build_predicate() {
|
||||
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
|
||||
// security
|
||||
pb.add_regex(security, r => r.security.ToString());
|
||||
pb.add_regex(security_key, r => r.security_key.ToString());
|
||||
// bid
|
||||
pb.add_numeric(bid_price, r => r.bid_price);
|
||||
pb.add_numeric(bid_merged_size, r => r.bid_merged_size);
|
||||
pb.add_numeric(bid_direct_size, r => r.bid_direct_size);
|
||||
pb.add_numeric(bid_implied_size, r => r.bid_implied_size);
|
||||
pb.add_numeric(working_bid_volume, r => r.working_bid_volume);
|
||||
// ask
|
||||
pb.add_numeric(ask_price, r => r.ask_price);
|
||||
pb.add_numeric(ask_implied_size, r => r.ask_implied_size);
|
||||
pb.add_numeric(ask_direct_size, r => r.ask_direct_size);
|
||||
pb.add_numeric(ask_merged_size, r => r.ask_merged_size);
|
||||
pb.add_numeric(working_ask_volume, r => r.working_ask_volume);
|
||||
// last
|
||||
pb.add_numeric(last_price, r => r.last_price);
|
||||
pb.add_regex(last_price_from_spread, r => r.last_price_from_spread.ToString());
|
||||
pb.add_numeric(last_size, r => r.last_size);
|
||||
// position
|
||||
// TODO: convert day_position to DoubleMinMaxState
|
||||
pb.add_numeric(day_position, r => unchecked((int)r.day_position));
|
||||
pb.add_numeric(raw_day_position, r => r.raw_day_position);
|
||||
// market
|
||||
pb.add_numeric(day_change, r => r.day_change);
|
||||
pb.add_numeric(settlement, r => r.settlement);
|
||||
pb.add_numeric(session_low, r => r.session_low);
|
||||
pb.add_numeric(session_high, r => r.session_high);
|
||||
pb.add_numeric(volume, r => r.volume);
|
||||
// ice
|
||||
pb.add_regex(market_description, r => r.market_description);
|
||||
pb.add_regex(cleared_alias, r => r.cleared_alias);
|
||||
pb.add_regex(product_description, r => r.product_description);
|
||||
pb.add_regex(hub_alias, r => r.hub_alias);
|
||||
pb.add_regex(strip_name, r => r.strip_name);
|
||||
pb.add_regex(mic_code, r => r.mic_code);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
|
||||
// migration rules
|
||||
@@ -328,11 +271,5 @@ namespace bts.shell {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
|
||||
class EnsureSelectionHighlightTranslucency : IMarketViewGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.apply(settings, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,5 @@ namespace mbts.ui.spark {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row> { }
|
||||
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row, TimeAndSalesView.Columns> { }
|
||||
}
|
||||
@@ -140,7 +140,7 @@
|
||||
<DataTemplate>
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:ConditionalQuoting.Types.PriceType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.ConditionalPriceTypeTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.ConditionalPriceTypeTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<Grid IsEnabled="{Binding enabled}">
|
||||
@@ -333,13 +333,61 @@
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ContractPickerButton" TargetType="Button">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="ToolTip" Value="Change Security"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Bd"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="Bd" Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="#24FFFFFF"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="Bd" Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="#40FFFFFF"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Border.Background>
|
||||
<SolidColorBrush/>
|
||||
</Border.Background>
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<DataGridTextColumn x:Key="{x:Static usp:ExecutionStrategyEditor.AddColumnPlaceholderKey}" x:Shared="False" IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<c:PopupControl
|
||||
ShowExpanderArrow="False" PopupPlacement="Right" Header="Add Leg" Padding="6,0"
|
||||
ShowExpanderArrow="False"
|
||||
PopupPlacement="Right"
|
||||
Header="Add Leg"
|
||||
Padding="6,0"
|
||||
Background="{StaticResource Button.Static.BackgroundBrush}"
|
||||
IsOpen="{Binding IsNewLegPopupOpen, RelativeSource={RelativeSource AncestorType=usp:ExecutionStrategyEditor}, Mode=TwoWay}"
|
||||
IsEnabled="{Binding CanUserAddNewLeg, RelativeSource={RelativeSource AncestorType=usp:ExecutionStrategyEditor}}"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<msp:ContractPickerView
|
||||
CloseOnOK="False" CloseOnCancel="False"
|
||||
@@ -348,7 +396,7 @@
|
||||
</c:PopupControl>
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.RemovableColumnHeaderTemplateKey}">
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.RemovableColumnHeaderTemplateKey}" DataType="{x:Type usp:LegSpreaderParamsTuple}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -356,13 +404,40 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Command="{Binding RemoveLegCommand, RelativeSource={RelativeSource AncestorType=usp:ExecutionStrategyEditor}}"
|
||||
CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource RoundCloseButton}"
|
||||
ToolTip="Remove Leg"
|
||||
/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,0,0" Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
|
||||
<c:ContractPickerPopup
|
||||
Grid.Column="1"
|
||||
BorderThickness="0"
|
||||
ButtonStyle="{StaticResource ContractPickerButton}"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding ContractPickerModel.all_securities, RelativeSource={RelativeSource AncestorType=usp:ExecutionStrategyEditor}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security}"
|
||||
ShowDialogSkirt="True"
|
||||
>
|
||||
<c:ContractPickerPopup.Background>
|
||||
<SolidColorBrush/>
|
||||
</c:ContractPickerPopup.Background>
|
||||
<c:ContractPickerPopup.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Foreground="{x:Static u:BTSColors.header_text_brush}" Text="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</c:ContractPickerPopup.HeaderTemplate>
|
||||
</c:ContractPickerPopup>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.NonRemovableColumnHeaderTemplateKey}">
|
||||
<TextBlock Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
|
||||
<DataTemplate x:Key="ReadOnlyColumnHeaderTemplate" DataType="{x:Type usp:LegSpreaderParamsTuple}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding security}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
<ControlTemplate x:Key="ExecutionStrategyEditorTemplate" TargetType="usp:ExecutionStrategyEditor">
|
||||
<Border
|
||||
@@ -560,6 +635,14 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.ColumnHeaderStyle>
|
||||
<Style TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Padding" Value="5,0"/>
|
||||
<Setter Property="MinHeight" Value="26"/>
|
||||
</Style>
|
||||
</DataGrid.ColumnHeaderStyle>
|
||||
<DataGrid.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource GroupItemStyle}"/>
|
||||
</DataGrid.GroupStyle>
|
||||
|
||||
@@ -227,8 +227,8 @@ namespace mbts.ui.spark {
|
||||
}
|
||||
|
||||
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
|
||||
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter);
|
||||
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
|
||||
var col = new DataGridColumnInternal(l, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter);
|
||||
col.HeaderTemplate = TryFindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey) as DataTemplate;
|
||||
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
|
||||
return col;
|
||||
}
|
||||
@@ -237,10 +237,12 @@ namespace mbts.ui.spark {
|
||||
|
||||
private class DataGridColumnInternal : DataGridDynamicColumn {
|
||||
readonly IValueConverter _cell_template_converter, _cell_editing_template_converter;
|
||||
readonly LegSpreaderParamsTuple _leg_tup;
|
||||
|
||||
public DataGridColumnInternal(Security security, IValueConverter cell_template_converter, IValueConverter cell_editing_template_converter) {
|
||||
Header = security;
|
||||
SetValue(DataGridEx.CellKeyProperty, security);
|
||||
public DataGridColumnInternal(LegSpreaderParamsTuple leg_tup, IValueConverter cell_template_converter, IValueConverter cell_editing_template_converter) {
|
||||
_leg_tup = leg_tup;
|
||||
Header = leg_tup;
|
||||
SetValue(DataGridEx.CellKeyProperty, leg_tup);
|
||||
_cell_template_converter = cell_template_converter;
|
||||
_cell_editing_template_converter = cell_editing_template_converter;
|
||||
}
|
||||
@@ -248,8 +250,8 @@ namespace mbts.ui.spark {
|
||||
// I decided this was an easy and simple enough solution to justify breaking the mvvm paradigm
|
||||
// The alternative - within strict bounds of the paradigm - is to change the DataGridParameterCellTemplateConverter into an IMultiValueConverter, bound to data_type AND read_only
|
||||
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) {
|
||||
if (dataItem is SyntheticEditorVM.RowVM row_vm) {
|
||||
if (row_vm.read_only || row_vm.get_cell(Header) is SyntheticEditorVM.ICell cell_vm && !cell_vm.enabled)
|
||||
if (dataItem is RowVM row_vm) {
|
||||
if (row_vm.read_only || row_vm.get_cell(_leg_tup) is ICell cell_vm && !cell_vm.enabled)
|
||||
return base.GenerateElement(cell, dataItem);
|
||||
}
|
||||
return base.GenerateEditingElement(cell, dataItem);
|
||||
@@ -352,4 +354,4 @@ namespace mbts.ui.spark {
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<UserControl
|
||||
x:Class="bts.shell.spreader.ContractLegTypeView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
>
|
||||
<ItemsControl ItemsSource="{Binding Controller.flags, RelativeSource={RelativeSource AncestorType=spr:ContractLegTypeView}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Rows="1"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox
|
||||
Margin="2"
|
||||
IsChecked="{Binding is_selected}"
|
||||
Content="{Binding value, Converter={x:Static spr:ContractLegTypeView.LegTypeShortDescConverter}}"
|
||||
ToolTip="{Binding value, Converter={x:Static spr:ContractLegTypeView.LegTypeLongDescConverter}}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,104 @@
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
static class EnumFlagItemsVM {
|
||||
public static EnumFlagItemsVM<T> create<T>(IEnumerable<T> flags) where T : struct =>
|
||||
new EnumFlagItemsVM<T>(flags);
|
||||
}
|
||||
|
||||
class EnumFlagItemsVM<T> : ViewModelBase where T : struct {
|
||||
readonly ObservableCollection<SelectableItemVM<T>> _flags = new ObservableCollection<SelectableItemVM<T>>();
|
||||
bool _is_busy;
|
||||
T _result;
|
||||
|
||||
public EnumFlagItemsVM()
|
||||
: this(EnumHelper.get_values<T>()) { }
|
||||
|
||||
public EnumFlagItemsVM(IEnumerable<T> flags) {
|
||||
this.flags = ObservableAutoWrapper.create_read_only_wrapper(_flags);
|
||||
_flags.AddRange(flags.Select(f => new SelectableItemVM<T>(f)));
|
||||
foreach (var vm in _flags) {
|
||||
vm.is_selected_changed += on_is_selected_changed;
|
||||
void on_is_selected_changed(bool value) {
|
||||
if (!_is_busy) {
|
||||
_is_busy = true;
|
||||
result = value ?
|
||||
EnumHelper.add_flags(result, vm.value) :
|
||||
EnumHelper.remove_flags(result, vm.value);
|
||||
_is_busy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
public IReadOnlyObservableCollection<SelectableItemVM<T>> flags { get; }
|
||||
|
||||
public T result {
|
||||
get => _result;
|
||||
set {
|
||||
if (!EqualityComparer<T>.Default.Equals(_result, value)) {
|
||||
_result = value;
|
||||
if (!_is_busy) {
|
||||
_is_busy = true;
|
||||
for (int i = 0; i < flags.Count; i++)
|
||||
flags[i].is_selected = EnumHelper.has_flags(value, flags[i].value);
|
||||
_is_busy = false;
|
||||
}
|
||||
result_changed?.Invoke();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Interaction logic for ContractLegTypeView.xaml
|
||||
/// </summary>
|
||||
partial class ContractLegTypeView : UserControl {
|
||||
public static IValueConverter LegTypeShortDescConverter =>
|
||||
ValueConverter.create<ContractLegType, string>(SyntheticSecurityParser.display_name);
|
||||
|
||||
public static IValueConverter LegTypeLongDescConverter =>
|
||||
ValueConverter.create<ContractLegType, string>(lt =>
|
||||
string.Join(" & ", EnumHelper.split_flags(lt).Select(f => EnumHelper.get_display_name(f))));
|
||||
|
||||
public ContractLegTypeView() {
|
||||
InitializeComponent();
|
||||
Controller.result = Value;
|
||||
Controller.result_changed += _on_controller_result_changed;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
WPFHelper.create_dp<ContractLegType, ContractLegTypeView>(
|
||||
nameof(Value),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => {
|
||||
o.Controller.result_changed -= o._on_controller_result_changed;
|
||||
o.Controller.result = v;
|
||||
o.Controller.result_changed += o._on_controller_result_changed;
|
||||
});
|
||||
|
||||
public ContractLegType Value {
|
||||
get => (ContractLegType)GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public EnumFlagItemsVM<ContractLegType> Controller { get; } = EnumFlagItemsVM.create(EnumHelper.get_values_except(ContractLegType.None));
|
||||
|
||||
void _on_controller_result_changed() =>
|
||||
SetCurrentValue(ValueProperty, Controller.result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<Window
|
||||
x:Class="bts.shell.spreader.SyntheticImportConflictsDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="Import Synthetics Conflicts"
|
||||
Style="{StaticResource BTSDialogTitleTemplate}"
|
||||
>
|
||||
<Window.MaxHeight>
|
||||
<Binding Path="SizeToContent" RelativeSource="{RelativeSource Self}">
|
||||
<Binding.Converter>
|
||||
<cv:WindowMaxHeightConverter SizeToContentThreshold="{u:DPIDouble 700}"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Window.MaxHeight>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<spr:SyntheticImportConflictsView Margin="12,10,12,8"/>
|
||||
<c:DialogSkirt Grid.Row="1">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Command="{Binding ok_cmd}">Continue</Button>
|
||||
<Button Command="{Binding cancel_cmd}" IsCancel="True">Cancel</Button>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticImportConflictsDialog.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticImportConflictsDialog : Window {
|
||||
public SyntheticImportConflictsDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.user.settings;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
readonly struct SyntheticComponent {
|
||||
public readonly SyntheticContractComponent type;
|
||||
public readonly int leg_index;
|
||||
|
||||
public SyntheticComponent(SyntheticContractComponent type, int leg_index) {
|
||||
this.type = type;
|
||||
this.leg_index = leg_index;
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticImportConflictsVM : ImportConflictsVM<(SyntheticImportRowVM synth, ContractLegVM leg), SyntheticImportRowVM, SyntheticContractComponent>, IRequestViewClose {
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public SyntheticImportConflictsVM(int num_synthetics)
|
||||
: base(num_synthetics, tup => tup.synth) { }
|
||||
|
||||
// misc items sources
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set {
|
||||
if (!ReferenceEquals(_security_map, value)) {
|
||||
_security_map = value;
|
||||
_resolve_securities();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _resolve_securities() {
|
||||
if (_security_map == null)
|
||||
return;
|
||||
var reverse_lookup = SyntheticSecurityParser.reverse_lookup(MappedDictionary.create(_security_map, v => v.security.ToString()));
|
||||
foreach (var r in conflicts) {
|
||||
if (r.component == SyntheticContractComponent.Security && r.has_error) {
|
||||
var id = reverse_lookup(r.text);
|
||||
if (id != 0 && _security_map.TryGetValue(id, out var smd))
|
||||
r.value = smd.security;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticImportConflictRowVM : ImportConflictRowVM<(SyntheticImportRowVM synth, ContractLegVM leg), SyntheticContractComponent>, ITemplateSelectorKey {
|
||||
public SyntheticImportConflictRowVM(
|
||||
SyntheticContractComponent component,
|
||||
string text,
|
||||
IReadOnlyCollection<(SyntheticImportRowVM synth, ContractLegVM leg)> affected_parts,
|
||||
object error_value,
|
||||
object value)
|
||||
: base(component, text, affected_parts, error_value, value, affected_parts.DistinctOn(tup => tup.synth).Count()) { }
|
||||
|
||||
public override void on_conflicts_resolved() {
|
||||
if (has_error)
|
||||
return;
|
||||
foreach (var (synth, leg) in affected_parts) {
|
||||
switch (component) {
|
||||
case SyntheticContractComponent.Name:
|
||||
if (value is string str)
|
||||
synth.name = str;
|
||||
break;
|
||||
case SyntheticContractComponent.TickSize:
|
||||
if (value is double ts && ts.IsNonZero()) {
|
||||
synth.tick_factors = TickUtils.get_regional_ticks(new ContractSpecificationImmutable(new ContractSpecification {
|
||||
LowTick = ts,
|
||||
HighTick = ts
|
||||
})).low_tick;
|
||||
} else if (value is TickFactors tf && tf.is_valid())
|
||||
synth.tick_factors = tf;
|
||||
break;
|
||||
case SyntheticContractComponent.Security:
|
||||
if (value is SecurityImmutable sec && sec.is_exchange_security())
|
||||
leg.security = sec;
|
||||
break;
|
||||
case SyntheticContractComponent.PriceMultiplier:
|
||||
if (value is double pm && pm.IsNonZero())
|
||||
leg.price_multiplier = pm;
|
||||
break;
|
||||
case SyntheticContractComponent.Quantity:
|
||||
if (value is double q && q.IsNonZero())
|
||||
leg.quantity = q;
|
||||
break;
|
||||
case SyntheticContractComponent.LegType:
|
||||
if (value is ContractLegType lt)
|
||||
leg.leg_type = lt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<UserControl
|
||||
x:Class="bts.shell.spreader.SyntheticImportConflictsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
MinWidth="400"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="VerticallyCenteredText" TargetType="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<!--Value Cell Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<u:TemplateSelectorKeyValue Key="PriceMultiplier">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="Quantity">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="Security">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="LegType">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={x:Static spr:ContractLegTypeView.LegTypeShortDescConverter}}">
|
||||
<TextBlock.ToolTip>
|
||||
<TextBlock>
|
||||
<Run Text="{Binding value, Converter={x:Static spr:ContractLegTypeView.LegTypeLongDescConverter}}"/>
|
||||
<LineBreak/>
|
||||
<Run Text="Q = Quote"/>
|
||||
<LineBreak/>
|
||||
<Run Text="H = Hedge"/>
|
||||
<LineBreak/>
|
||||
<Run Text="N = None"/>
|
||||
</TextBlock>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
<!--Value Cell Editing Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellEditingTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="Security">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding value, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="Name">
|
||||
<DataTemplate>
|
||||
<TextBox u:Behavior.FocusOnLoaded="True" Text="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="TickSize">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownDouble Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="PriceMultiplier">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownDouble Value="{Binding value}" DisplayConverter="{StaticResource DoubleTextConverter}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="Quantity">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownDouble Value="{Binding value}" DisplayConverter="{StaticResource DoubleTextConverter}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="LegType">
|
||||
<DataTemplate>
|
||||
<spr:ContractLegTypeView Value="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<!--Content-->
|
||||
<Grid x:Name="content_root">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image VerticalAlignment="Center" Margin="0,0,8,0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Canvas x:Name="text_canvas" Grid.Column="1" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Grid.Column="1" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=text_canvas}">
|
||||
<Run Text="{Binding num_successfully_parsed_rows, Mode=OneWay}"/>
|
||||
<Run Text="/"/>
|
||||
<Run Text="{Binding num_rows, Mode=OneWay}"/>
|
||||
<Run Text=" synthetics were successfully converted."/>
|
||||
<Run Text="Resolve conflicts below by selecting a value to assign each affected synthetic,"/>
|
||||
<Run Text="or modify individual synthetics in the main window."/>
|
||||
<LineBreak/>
|
||||
<LineBreak/>
|
||||
<Run Text="Click 'Continue' to proceed to the main window."/>
|
||||
</TextBlock>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding conflicts_view}"
|
||||
Margin="0,8,0,0"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
SelectionMode="Single"
|
||||
>
|
||||
<DataGrid.RowValidationErrorTemplate>
|
||||
<ControlTemplate>
|
||||
<Grid Background="Green">
|
||||
<Border x:Name="exclamation" HorizontalAlignment="Center">
|
||||
<Image Height="12" VerticalAlignment="Center" Source="{x:Static u:Icons.WarningExclamation}"/>
|
||||
</Border>
|
||||
<ContentControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" ToolTip="{Binding (Validation.Errors), RelativeSource={RelativeSource AncestorType=DataGridRow}}">
|
||||
<ContentControl.Template>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Content="{Binding ToolTip, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<ToolTip.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}" DisplayMemberPath="ErrorContent"/>
|
||||
</DataTemplate>
|
||||
</ToolTip.ContentTemplate>
|
||||
</ToolTip>
|
||||
</Grid.ToolTip>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</ContentControl.Template>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</DataGrid.RowValidationErrorTemplate>
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="ValidationErrorTemplate" Value="{Binding RowValidationErrorTemplate, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Component" Binding="{Binding component, Converter={StaticResource EnumDescriptionConverter}}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Text" Binding="{Binding text}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Affected Synthetics" Binding="{Binding num_affected_rows}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ValueCellTemplateSelector}" CellEditingTemplateSelector="{StaticResource ValueCellEditingTemplateSelector}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
enum SyntheticContractComponent {
|
||||
Name,
|
||||
TickSize,
|
||||
Security,
|
||||
PriceMultiplier,
|
||||
Quantity,
|
||||
LegType
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticImportConflictsView.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticImportConflictsView : UserControl {
|
||||
public SyntheticImportConflictsView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<Window
|
||||
x:Class="bts.shell.spreader.SyntheticImportDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="Import Synthetics"
|
||||
Style="{StaticResource BTSDialogTitleTemplate}"
|
||||
Content="{Binding}"
|
||||
>
|
||||
<Window.MaxHeight>
|
||||
<Binding Path="SizeToContent" RelativeSource="{RelativeSource Self}">
|
||||
<Binding.Converter>
|
||||
<cv:WindowMaxHeightConverter SizeToContentThreshold="{u:DPIDouble 700}"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Window.MaxHeight>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type spr:SyntheticImportVM}">
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<spr:SyntheticImportView/>
|
||||
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding ok_cmd}" CancelCommand="{Binding cancel_cmd}"/>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</DataTemplate>
|
||||
</Window.ContentTemplate>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticImportDialog.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticImportDialog : Window {
|
||||
public SyntheticImportDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
class SyntheticImportRowCollectionView : ListCollectionView {
|
||||
ReadOnlyObservableCollection<object> _base_groups, _groups_wrapper;
|
||||
ObservableAutoWrapper<object, object> _groups;
|
||||
|
||||
public SyntheticImportRowCollectionView(IList list)
|
||||
: base(list) { }
|
||||
|
||||
public override bool CanGroup => true;
|
||||
|
||||
// Must return null if there are no GroupDescriptions (i.e., instead of an empty collection).
|
||||
// If an empty collection is returned, the view assumes grouping is active and there are no items.
|
||||
public override ReadOnlyObservableCollection<object> Groups {
|
||||
get {
|
||||
if ((GroupDescriptions?.Count ?? 0) == 0)
|
||||
return null;
|
||||
if (_groups_wrapper == null) {
|
||||
_base_groups = base.Groups;
|
||||
if (_base_groups == null)
|
||||
return null;
|
||||
_groups = new ObservableAutoWrapper<object, object>(_base_groups, obj => new SyntheticImportRowCollectionViewGroup((CollectionViewGroup)obj, this));
|
||||
_groups_wrapper = new ReadOnlyObservableCollection<object>(_groups);
|
||||
}
|
||||
return _groups_wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
public void set_all_expanded(bool is_expanded) {
|
||||
foreach (var g in Groups.OfTypeSafe<SyntheticImportRowCollectionViewGroup>())
|
||||
g.set_all_expanded(is_expanded);
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticImportRowCollectionViewGroup : CollectionViewGroup, INotifyDataErrorInfo, IRaiseNotifyPropertyChanged, IEquatable<SyntheticImportRowCollectionViewGroup> {
|
||||
static readonly string _desc_delim = ", ";
|
||||
readonly SyntheticImportRowCollectionView _root;
|
||||
readonly CollectionViewGroup _from;
|
||||
readonly Dictionary<object, InnerGroupSubscription> _inner_groups = new Dictionary<object, InnerGroupSubscription>();
|
||||
readonly Dictionary<int, IDisposable> _rows = new Dictionary<int, IDisposable>();
|
||||
string
|
||||
_leg_type_desc,
|
||||
_security_desc,
|
||||
_quantity_desc,
|
||||
_price_multiplier_desc;
|
||||
|
||||
IDisposable _dispose;
|
||||
bool _is_expanded;
|
||||
|
||||
public SyntheticImportRowCollectionViewGroup(CollectionViewGroup from, SyntheticImportRowCollectionView root) : base(from.Name) {
|
||||
_root = root;
|
||||
_from = from;
|
||||
((INotifyCollectionChanged)_from.Items).CollectionChanged += _from_items_CollectionChanged;
|
||||
_from_items_CollectionChanged(_from.Items, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _from.Items, 0));
|
||||
}
|
||||
|
||||
public event Action ProtectedItemCountChanged;
|
||||
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
|
||||
|
||||
public override bool IsBottomLevel => _from.IsBottomLevel;
|
||||
public bool is_expanded { get => _is_expanded; set => _set(ref _is_expanded, value); }
|
||||
public int level => parent == null ? 1 : parent.level + 1;
|
||||
public SyntheticImportRowCollectionViewGroup parent { get; private set; }
|
||||
// leg type desc
|
||||
public string leg_type_desc =>
|
||||
_leg_type_desc ?? (_leg_type_desc = string.Join(_desc_delim, ProtectedItems.OfType<ContractLegVM>().Select(l => l.leg_type.display_name())));
|
||||
// price multiplier desc
|
||||
public string price_multiplier_desc =>
|
||||
_price_multiplier_desc ?? (_price_multiplier_desc = string.Join(_desc_delim, ProtectedItems.OfType<ContractLegVM>().Select(l => l.price_multiplier)));
|
||||
// quantity desc
|
||||
public string quantity_desc =>
|
||||
_quantity_desc ?? (_quantity_desc = string.Join(_desc_delim, ProtectedItems.OfType<ContractLegVM>().Select(l => l.quantity)));
|
||||
// security desc
|
||||
public string security_desc =>
|
||||
_security_desc ?? (_security_desc = string.Join(_desc_delim, ProtectedItems.OfType<ContractLegVM>().Select(l => l.security?.ToString() ?? "?").ExceptNull()).if_null_or_empty("(No Legs)"));
|
||||
|
||||
new int ProtectedItemCount {
|
||||
get => base.ProtectedItemCount;
|
||||
set {
|
||||
if (base.ProtectedItemCount != value) {
|
||||
base.ProtectedItemCount = value;
|
||||
ProtectedItemCountChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasErrors => Items.OfType<ContractLegVM>().Any(l => l.HasErrors);
|
||||
public IEnumerable GetErrors(string propertyName) =>
|
||||
propertyName == nameof(Items) ?
|
||||
Items.OfType<ContractLegVM>().SelectMany(l => l.AllErrors) :
|
||||
Enumerable.Empty<string>();
|
||||
|
||||
public void set_all_expanded(bool is_expanded) {
|
||||
this.is_expanded = is_expanded;
|
||||
foreach (var g in Items.OfTypeSafe<SyntheticImportRowCollectionViewGroup>())
|
||||
g.set_all_expanded(is_expanded);
|
||||
}
|
||||
|
||||
public bool Equals(SyntheticImportRowCollectionViewGroup other) =>
|
||||
other != null &&
|
||||
level == other.level &&
|
||||
Equals(Name, other.Name);
|
||||
|
||||
void IRaiseNotifyPropertyChanged.raise_notify_property_changed(string prop) =>
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(prop));
|
||||
|
||||
void _add_item(int index, object item) {
|
||||
Log.assert(index >= 0, "Index is less than 0.");
|
||||
if (item is CollectionViewGroup group) {
|
||||
var inner_group = new SyntheticImportRowCollectionViewGroup(group, _root);
|
||||
_inner_groups.Add(group.Name, new InnerGroupSubscription(this, inner_group));
|
||||
ProtectedItems.Insert(index, inner_group);
|
||||
} else {
|
||||
var row = (ContractLegVM)item;
|
||||
if (_rows.TryPopValue(row.id, out var sub))
|
||||
sub.Dispose();
|
||||
_rows[row.id] = _connect_row(row);
|
||||
ProtectedItems.Insert(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
void _remove_item(int index, object item) {
|
||||
if (item is SyntheticImportRowCollectionViewGroup inner_group) {
|
||||
if (!_inner_groups.TryPopValue(inner_group.Name, out var ig_sub))
|
||||
throw new InvalidOperationException($"Inner groups cache does not have an entry for the specified name '{inner_group.Name}' (entire tree structure may be compromised).");
|
||||
ig_sub.Dispose();
|
||||
Disposable.dispose(ref inner_group._dispose);
|
||||
}
|
||||
ProtectedItems.RemoveAt(index);
|
||||
}
|
||||
|
||||
void _from_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
for (int i = 0; i < e.OldItems.Count; i++)
|
||||
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
for (int i = 0; i < e.OldItems.Count; i++)
|
||||
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
while (ProtectedItems.Count > 0)
|
||||
_remove_item(0, ProtectedItems[0]);
|
||||
for (int i = 0; i < _from.Items.Count; i++)
|
||||
_add_item(i, _from.Items[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _invalidate_props(params string[] props) {
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(props[i]));
|
||||
}
|
||||
|
||||
void _set<T>(ref T backing, T new_value, [CallerMemberName]string prop = "") =>
|
||||
_set(ref backing, new_value, EqualityComparer<T>.Default, prop);
|
||||
|
||||
void _set<T>(ref T backing, T new_value, IEqualityComparer<T> comparer, [CallerMemberName]string prop = "") {
|
||||
if (!comparer.Equals(backing, new_value)) {
|
||||
backing = new_value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(prop));
|
||||
}
|
||||
}
|
||||
|
||||
IDisposable _connect_row(ContractLegVM row) {
|
||||
row.result_changed += on_result_changed;
|
||||
row.ErrorsChanged += on_errors_changed;
|
||||
return Disposable.Create(() => {
|
||||
row.result_changed -= on_result_changed;
|
||||
row.ErrorsChanged -= on_errors_changed;
|
||||
});
|
||||
void on_result_changed() {
|
||||
_security_desc = null;
|
||||
_price_multiplier_desc = null;
|
||||
_quantity_desc = null;
|
||||
_leg_type_desc = null;
|
||||
_invalidate_props(nameof(security_desc), nameof(price_multiplier_desc), nameof(quantity_desc), nameof(leg_type_desc));
|
||||
}
|
||||
void on_errors_changed(object sender, DataErrorsChangedEventArgs e) =>
|
||||
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Items)));
|
||||
}
|
||||
|
||||
class InnerGroupSubscription : IDisposable {
|
||||
public readonly SyntheticImportRowCollectionViewGroup _inner_group;
|
||||
readonly SyntheticImportRowCollectionViewGroup _parent;
|
||||
int _last_count;
|
||||
|
||||
public InnerGroupSubscription(SyntheticImportRowCollectionViewGroup parent, SyntheticImportRowCollectionViewGroup inner_group) {
|
||||
_parent = parent;
|
||||
_inner_group = inner_group;
|
||||
_inner_group.parent = parent;
|
||||
// init item count
|
||||
_on_item_count_changed();
|
||||
// listen to item count changes
|
||||
_inner_group.ProtectedItemCountChanged += _on_item_count_changed;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_inner_group.ProtectedItemCountChanged -= _on_item_count_changed;
|
||||
_inner_group.parent = null;
|
||||
// make sure the state is valid (if a bad state goes by undetected here, nefarious/difficult-to-track bugs could arise)
|
||||
if (_last_count != _inner_group.ProtectedItemCount) {
|
||||
Log.Ui.warn("Cached inner count is not equal to the inner group's ItemCount (entire tree structure may be compromised).");
|
||||
}
|
||||
_parent.ProtectedItemCount -= _last_count;
|
||||
}
|
||||
|
||||
void _on_item_count_changed() {
|
||||
// add the delta
|
||||
_parent.ProtectedItemCount += (_inner_group.ProtectedItemCount - _last_count);
|
||||
// reset the local cache
|
||||
_last_count = _inner_group.ProtectedItemCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Disposable = mbts.Disposable;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
class SyntheticImportVM : DisposableViewModelBase, IRequestViewClose {
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public SyntheticImportVM() {
|
||||
var rows_view = new SyntheticImportRowCollectionView(flat_legs);
|
||||
rows_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ContractLegVM.owner)));
|
||||
this.rows_view = rows_view;
|
||||
ok_cmd = new DelegateCommand(() => import_request?.Invoke());
|
||||
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()));
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(
|
||||
rows,
|
||||
r => {
|
||||
flat_legs.AddRange(r.legs);
|
||||
return Disposable.Create(() => flat_legs.removeRange(r.legs));
|
||||
});
|
||||
}
|
||||
|
||||
public event Action import_request;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public ObservableCollection<SyntheticImportRowVM> rows { get; } = new ObservableCollection<SyntheticImportRowVM>();
|
||||
public ObservableCollection<ContractLegVM> flat_legs { get; } = new ObservableCollection<ContractLegVM>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map { get => _security_map; set => Set(ref _security_map, value); }
|
||||
public ICollectionView rows_view { get; }
|
||||
// commands
|
||||
public ICommand ok_cmd { get; }
|
||||
public ICommand cancel_cmd { get; }
|
||||
}
|
||||
|
||||
class SyntheticImportRowVM : DisposableViewModelBase, IEquatable<SyntheticImportRowVM> {
|
||||
readonly ReactiveThresholdMonitor<ContractLegVM> _leg_errors_mon = new ReactiveThresholdMonitor<ContractLegVM>(l => l.subscribe_has_errors(true));
|
||||
readonly Func<string, bool> _get_name_exists;
|
||||
SecurityImmutable? _security;
|
||||
ExecutionStrategyImmutable? _strategy;
|
||||
TickFactors _tick_factors;
|
||||
string _name;
|
||||
bool _is_selected;
|
||||
|
||||
public SyntheticImportRowVM(uint user_id, Func<string, bool> get_name_exists) {
|
||||
this.user_id = user_id;
|
||||
_get_name_exists = get_name_exists;
|
||||
_validate();
|
||||
add_leg_cmd = new DelegateCommand<SyntheticImportRowVM>(r => {
|
||||
if (r != null) {
|
||||
r.legs.Add(new ContractLegVM {
|
||||
price_multiplier = 1,
|
||||
quantity = 1,
|
||||
owner = r
|
||||
});
|
||||
}
|
||||
});
|
||||
remove_leg_cmd = new DelegateCommand<ContractLegVM>(l => l?.owner?.legs?.Remove(l));
|
||||
PropertyChanged += on_property_changed;
|
||||
var disp = new DisposableCombo(
|
||||
_leg_errors_mon.OnDispatcher().Subscribe(v => _validate()),
|
||||
ObservableAutoWrapper.connect_item_hooks(legs,
|
||||
l => {
|
||||
l.result_changed += invalidate;
|
||||
return new DisposableCombo(_leg_errors_mon.join(l), l) +
|
||||
new Action(() => l.result_changed -= invalidate);
|
||||
})) +
|
||||
new Action(() => PropertyChanged -= on_property_changed);
|
||||
|
||||
void invalidate() {
|
||||
_security = null;
|
||||
_strategy = null;
|
||||
_validate();
|
||||
result_changed?.Invoke();
|
||||
}
|
||||
|
||||
void on_property_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
public SecurityImmutable result =>
|
||||
_security ?? (_security = new SecurityImmutable(to_security())).Value;
|
||||
|
||||
public ExecutionStrategyImmutable strategy =>
|
||||
_strategy ?? (_strategy = new ExecutionStrategyImmutable(to_strategy())).Value;
|
||||
|
||||
public (SecurityImmutable security, ExecutionStrategyImmutable strategy) result_tuple =>
|
||||
(result, strategy);
|
||||
|
||||
public ParsedSyntheticContract parsed_synth { get; set; }
|
||||
|
||||
public string name { get => _name; set => Set(ref _name, value); }
|
||||
public uint user_id { get; }
|
||||
public TickFactors tick_factors { get => _tick_factors; set => Set(ref _tick_factors, value); }
|
||||
public ObservableCollection<ContractLegVM> legs { get; } = new ObservableCollection<ContractLegVM>();
|
||||
public bool is_selected { get => _is_selected; set => Set(ref _is_selected, value); }
|
||||
public ICommand add_leg_cmd { get; }
|
||||
public ICommand remove_leg_cmd { get; }
|
||||
|
||||
public Security to_security() {
|
||||
var sc = new SyntheticContract {
|
||||
Name = name,
|
||||
UserId = user_id
|
||||
};
|
||||
sc.Legs.AddRange(legs.Select(l => l.to_contract_leg()));
|
||||
return new Security {
|
||||
Synthetic = sc,
|
||||
Symbol = sc.Legs.FirstOrDefault()?.Security?.Symbol ?? string.Empty,
|
||||
Exchange = Venue.Bts,
|
||||
ContractSpec = tick_factors.to_spec()
|
||||
};
|
||||
}
|
||||
|
||||
public ExecutionStrategy to_strategy() {
|
||||
var strat = new ExecutionStrategy();
|
||||
foreach (var l in legs) {
|
||||
if (l.security.HasValue)
|
||||
strat.LegParamsMap[l.security.Value.Id] = l.to_leg_params();
|
||||
}
|
||||
return strat;
|
||||
}
|
||||
|
||||
void _validate() {
|
||||
using (validation()) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
validation_assert(false, "Name is not specified.", nameof(name));
|
||||
} else if (_get_name_exists(name)) {
|
||||
validation_assert(false, "Name already exists.", nameof(name));
|
||||
}
|
||||
validation_assert(tick_factors != null && tick_factors.is_valid(), "Invalid tick", nameof(tick_factors));
|
||||
validation_assert(legs.Count > 0 && !_leg_errors_mon.is_above_threshold, "Invalid legs.", nameof(legs));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(SyntheticImportRowVM other) =>
|
||||
other != null &&
|
||||
parsed_synth.unpersisted_id == other.parsed_synth.unpersisted_id;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as SyntheticImportRowVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
parsed_synth.unpersisted_id;
|
||||
}
|
||||
|
||||
class ContractLegVM : DisposableViewModelBase, IEquatable<ContractLegVM> {
|
||||
ContractLegImmutable? _leg;
|
||||
LegSpreaderParamsImmutable? _leg_params;
|
||||
SecurityImmutable? _security;
|
||||
double _price_multiplier, _quantity;
|
||||
ContractLegType _leg_type;
|
||||
bool _is_selected;
|
||||
|
||||
public ContractLegVM() {
|
||||
PropertyChanged += on_property_changed;
|
||||
_dispose = Disposable.Create(() => PropertyChanged -= on_property_changed);
|
||||
_validate();
|
||||
void on_property_changed(object sender, PropertyChangedEventArgs e) {
|
||||
switch (e.PropertyName) {
|
||||
case nameof(security):
|
||||
case nameof(price_multiplier):
|
||||
case nameof(quantity):
|
||||
_leg = null;
|
||||
break;
|
||||
case nameof(leg_type):
|
||||
_leg_params = null;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_validate();
|
||||
result_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
public ParsedContractLeg parsed_leg { get; set; }
|
||||
public SyntheticImportRowVM owner { get; set; }
|
||||
public int owner_id => owner.parsed_synth.unpersisted_id;
|
||||
public int id => parsed_leg.id;
|
||||
|
||||
public ContractLegImmutable leg =>
|
||||
_leg ?? (_leg = new ContractLegImmutable(to_contract_leg())).Value;
|
||||
|
||||
public LegSpreaderParamsImmutable leg_params =>
|
||||
_leg_params ?? (_leg_params = new LegSpreaderParamsImmutable(to_leg_params())).Value;
|
||||
|
||||
public (ContractLegImmutable leg, LegSpreaderParamsImmutable leg_params) result_tuple =>
|
||||
(leg, leg_params);
|
||||
|
||||
public SecurityImmutable? security { get => _security; set => Set(ref _security, value); }
|
||||
public double price_multiplier { get => _price_multiplier; set => Set(ref _price_multiplier, value); }
|
||||
public double quantity { get => _quantity; set => Set(ref _quantity, value); }
|
||||
public ContractLegType leg_type { get => _leg_type; set => Set(ref _leg_type, value); }
|
||||
public bool is_selected { get => _is_selected; set => Set(ref _is_selected, value); }
|
||||
|
||||
public void load(ContractLegImmutable leg) {
|
||||
security = leg.Security;
|
||||
price_multiplier = leg.PriceMultiplier;
|
||||
quantity = leg.Quantity;
|
||||
}
|
||||
|
||||
public ContractLeg to_contract_leg() =>
|
||||
new ContractLeg {
|
||||
Security = security?.CloneToMutable(),
|
||||
PriceMultiplier = price_multiplier,
|
||||
Side = quantity < 0 ? Side.Sell : Side.Buy,
|
||||
Quantity = Math.Abs(quantity)
|
||||
};
|
||||
|
||||
public LegSpreaderParams to_leg_params() {
|
||||
var lps = SyntheticDataHelper.Defaults.LegSpreaderParams;
|
||||
lps.EnableHedging = (leg_type & ContractLegType.Hedge) != 0;
|
||||
lps.IsActiveQuoting = (leg_type & ContractLegType.Quote) != 0;
|
||||
return lps;
|
||||
}
|
||||
|
||||
void _validate() {
|
||||
using (validation()) {
|
||||
validation_assert(security.is_exchange_security(), "Invalid security", nameof(security));
|
||||
validation_assert(price_multiplier.IsNonZero(), "Invalid price multiplier", nameof(price_multiplier));
|
||||
validation_assert(quantity.IsNonZero(), "Invalid quantity", nameof(quantity));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(ContractLegVM other) =>
|
||||
other != null && id == other.id;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as ContractLegVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<UserControl
|
||||
x:Class="bts.shell.spreader.SyntheticImportView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
xmlns:pnl="clr-namespace:bts.shell.pnl"
|
||||
MinWidth="400"
|
||||
Content="{Binding}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
|
||||
<Style x:Key="GroupDesc" TargetType="TextBlock">
|
||||
<Setter Property="Margin" Value="{x:Static spr:SyntheticImportView.CellPadding}"/>
|
||||
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="#80000000"/>
|
||||
<Setter Property="FontStyle" Value="Italic"/>
|
||||
</Style>
|
||||
<DataTemplate DataType="{x:Type spr:ContractLegVM}">
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type spr:SyntheticImportVM}">
|
||||
<c:DIYGrid
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Rows="{Binding rows_view}"
|
||||
RowHeight="{x:Static spr:SyntheticImportView.RowHeight}"
|
||||
RowsHorizontalScrollBarVisibility="Collapsed"
|
||||
SelectionHighlight="#30000000"
|
||||
SelectionMode="Single"
|
||||
SupportGroupItems="True"
|
||||
>
|
||||
<c:DIYGrid.Columns>
|
||||
<c:DIYGridColumnCollection>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.Name}" Width="106"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.Ticks}" Width="128"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.Security}" Header="Securities" Width="120"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.Quantity}" Header="Quantities" Width="70"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.PriceMultiplier}" Header="Price Multipliers" Width="80"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.LegType}" Header="Leg Types" Width="83"/>
|
||||
</c:DIYGridColumnCollection>
|
||||
</c:DIYGrid.Columns>
|
||||
<c:DIYGrid.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.ContainerStyle>
|
||||
<Style TargetType="GroupItem">
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<EventSetter Event="MouseDown" Handler="_on_group_item_mouse_down"/>
|
||||
<!--Note: Tag is bound to 'Items' so errors on the 'Items' prop will be included in Validation.Errors.-->
|
||||
<Setter Property="Tag">
|
||||
<Setter.Value>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<cv:MultiValueConverterPipe/>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="Name.legs"/>
|
||||
<Binding Path="Name.name"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupItem">
|
||||
<Grid
|
||||
c:DIYGrid.SyncWithColumnGeometry="True"
|
||||
u:StateManager.VisualState="{Binding IsChecked, ElementName=expander, Converter={x:Static pnl:PNLView.ExpandedVisualStateConverter}}"
|
||||
Background="{TemplateBinding Background}"
|
||||
>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ExpansionStates">
|
||||
<VisualState Name="Expanded"/>
|
||||
<VisualState Name="Collapsed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="presenter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GridLengthPixelConverter}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Fill="Gray" UseLayoutRounding="True"/>
|
||||
<!--Expander Toggle-->
|
||||
<c:ExpanderToggleButton
|
||||
x:Name="expander"
|
||||
Grid.ColumnSpan="99"
|
||||
Validation.ValidationAdornerSiteFor="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ArrowPadding="14,2,0,0"
|
||||
Background="{u:Brush Transparent}"
|
||||
BorderThickness="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
IsChecked="{Binding is_expanded}"
|
||||
>
|
||||
<u:StateManager.VisualState>
|
||||
<Binding Path="TemplatedParent.(Validation.Errors).Count" RelativeSource="{RelativeSource Self}">
|
||||
<Binding.Converter>
|
||||
<cv:ValueConverterPipe>
|
||||
<cv:IntGreaterThanConverter Value="0"/>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="NoErrors" ValueForTrue="Errors"/>
|
||||
</cv:ValueConverterPipe>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</u:StateManager.VisualState>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="expander" Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="#10000000"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="expander" Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="#10000000"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Validation.ErrorTemplate>
|
||||
<ControlTemplate TargetType="Control">
|
||||
<Grid>
|
||||
<AdornedElementPlaceholder x:Name="adorned_element_pl"/>
|
||||
<Border x:Name="exclamation" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="6,0,0,0" VerticalAlignment="Center" Source="{x:Static u:Icons.WarningExclamation}"/>
|
||||
</Border>
|
||||
<ContentControl u:Behavior.MirrorLayoutFor="{Binding ElementName=exclamation}" ToolTip="{Binding AdornedElement.(Validation.ValidationAdornerSiteFor).(Validation.Errors), ElementName=adorned_element_pl}">
|
||||
<ContentControl.Template>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Content="{Binding ToolTip, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<ToolTip.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}" DisplayMemberPath="ErrorContent"/>
|
||||
</DataTemplate>
|
||||
</ToolTip.ContentTemplate>
|
||||
</ToolTip>
|
||||
</Grid.ToolTip>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</ContentControl.Template>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Validation.ErrorTemplate>
|
||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True" Margin="-22,0,0,0">
|
||||
<c:EditableTextBlock
|
||||
x:Name="PART_Name_EditableTextBlock"
|
||||
Grid.Column="0"
|
||||
Margin="22,0,0,0"
|
||||
EndEditOnLostKeyboardFocus="True"
|
||||
Foreground="{u:Brush Black}"
|
||||
Text="{Binding Name.name}"
|
||||
ToolTip="Click to Edit Name"
|
||||
UseLayoutRounding="True"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<c:EditableTextBlock.CommandBindings>
|
||||
<CommandBinding Command="{x:Static c:EditableTextBlock.BeginEditCommand}" Executed="{x:Static c:EditableTextBlock.BeginEditExecuted}"/>
|
||||
<CommandBinding Command="{x:Static c:EditableTextBlock.EndEditCommand}" Executed="{x:Static c:EditableTextBlock.EndEditExecuted}"/>
|
||||
<CommandBinding Command="{x:Static c:EditableTextBlock.CancelEditCommand}" Executed="{x:Static c:EditableTextBlock.CancelEditExecuted}"/>
|
||||
</c:EditableTextBlock.CommandBindings>
|
||||
<c:EditableTextBlock.InputBindings>
|
||||
<MouseBinding MouseAction="LeftClick" Command="{x:Static c:EditableTextBlock.BeginEditCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:EditableTextBlock.EndEditCommand}"/>
|
||||
<KeyBinding Key="Esc" Command="{x:Static c:EditableTextBlock.CancelEditCommand}"/>
|
||||
</c:EditableTextBlock.InputBindings>
|
||||
</c:EditableTextBlock>
|
||||
<c:TickFactorsPicker
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Ticks}"
|
||||
Margin="{x:Static spr:SyntheticImportView.CellPadding}"
|
||||
SelectedTickFactors="{Binding Name.tick_factors}"
|
||||
UseLayoutRounding="True"
|
||||
/>
|
||||
<TextBlock c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Security}" Text="{Binding security_desc}" Style="{StaticResource GroupDesc}"/>
|
||||
<TextBlock c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Quantity}" Text="{Binding quantity_desc}" Style="{StaticResource GroupDesc}"/>
|
||||
<TextBlock c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.PriceMultiplier}" Text="{Binding price_multiplier_desc}" Style="{StaticResource GroupDesc}"/>
|
||||
<TextBlock c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.LegType}" Text="{Binding leg_type_desc}" Style="{StaticResource GroupDesc}">
|
||||
<TextBlock.ToolTip>
|
||||
<TextBlock>
|
||||
<Run Text="{Binding leg_type_desc, Mode=OneWay}"/>
|
||||
<LineBreak/>
|
||||
<Run Text="Q = Quote"/>
|
||||
<LineBreak/>
|
||||
<Run Text="H = Hedge"/>
|
||||
<LineBreak/>
|
||||
<Run Text="N = None"/>
|
||||
</TextBlock>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</c:ExpanderToggleButton>
|
||||
<ItemsPresenter x:Name="presenter" Grid.Row="1" Grid.ColumnSpan="99"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</GroupStyle.ContainerStyle>
|
||||
</GroupStyle>
|
||||
</c:DIYGrid.GroupStyle>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate DataType="{x:Type spr:ContractLegVM}">
|
||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
|
||||
<c:ContractPickerPopup
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Security}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="{x:Static spr:SyntheticImportView.CellPadding}"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}}"
|
||||
>
|
||||
</c:ContractPickerPopup>
|
||||
<c:TickUpDownDouble
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Quantity}"
|
||||
Margin="{x:Static spr:SyntheticImportView.CellPadding}"
|
||||
Value="{Binding quantity}"
|
||||
/>
|
||||
<c:TickUpDownDouble
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.PriceMultiplier}"
|
||||
Margin="{x:Static spr:SyntheticImportView.CellPadding}"
|
||||
Value="{Binding price_multiplier}"
|
||||
/>
|
||||
<spr:ContractLegTypeView
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.LegType}"
|
||||
Value="{Binding leg_type}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
</DataTemplate>
|
||||
</UserControl.Resources>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,50 @@
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.utility;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
enum SyntheticImportColumns {
|
||||
Name = 1,
|
||||
Security,
|
||||
Ticks,
|
||||
PriceMultiplier,
|
||||
Quantity,
|
||||
LegType
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticImportView.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticImportView : UserControl {
|
||||
public static double RowHeight =>
|
||||
27.0.RoundLayoutValue();
|
||||
|
||||
public static Thickness CellPadding =>
|
||||
new Thickness(6, 3, 6, 3).RoundToLayout();
|
||||
|
||||
public SyntheticImportView() =>
|
||||
InitializeComponent();
|
||||
|
||||
void _on_group_item_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
if (!e.Handled && e.ClickCount == 2 && sender is FrameworkElement el && el.DataContext is SyntheticImportRowCollectionViewGroup group) {
|
||||
group.is_expanded = !group.is_expanded;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticImportColumn : DIYGrid.ColumnBase<SyntheticImportColumns> {
|
||||
public SyntheticImportColumn() =>
|
||||
is_visible = true;
|
||||
|
||||
protected override void on_key_initialized(SyntheticImportColumns key) {
|
||||
if (Header == null)
|
||||
Header = EnumHelper.get_display_name(key);
|
||||
if (id == null)
|
||||
id = key.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.shell.spreader;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class SyntheticImporter {
|
||||
const string _import_title = "Import Synthetics";
|
||||
static readonly TimeSpan _import_op_sec_map_timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
public static IDisposable import_from_file(string path, IBus bus, OrdersModel orders_model) {
|
||||
bool is_disposed = false;
|
||||
IDisposable dispose_op = null;
|
||||
dispose_op = Disposable.Create(() => is_disposed = true);
|
||||
impl();
|
||||
return Disposable.Create(() => Disposable.dispose(ref dispose_op));
|
||||
async void impl() {
|
||||
IReadOnlyDictionary<uint, SecurityMD> sec_md_map = null;
|
||||
while (true) {
|
||||
await Task.WhenAny(init_sec_md_map(), Task.Delay(_import_op_sec_map_timeout));
|
||||
if (is_disposed)
|
||||
return;
|
||||
if (sec_md_map != null)
|
||||
break;
|
||||
if (!retry_on_error($"Synthetic import op timed out waiting for security map after {_import_op_sec_map_timeout.TotalSeconds:0.00} seconds."))
|
||||
return;
|
||||
}
|
||||
// The following conditions should break the while loop:
|
||||
// 1) the import succeeds
|
||||
// 2) the import fails, and the user does not click 'Yes' in the 'try again...' confirmation dialog
|
||||
while (true) {
|
||||
string error_msg = null;
|
||||
try {
|
||||
var raw_lines = File.ReadAllLines(path);
|
||||
var used_names = new HashSet<string>(sec_md_map.Values.Where(smd => smd.security.ContractCase == Security.ContractOneofCase.Synthetic)
|
||||
.Select(smd => smd.security.Synthetic.Name));
|
||||
var parsed_synths = SyntheticSecurityParser.parse_rows(raw_lines, MappedDictionary.create(sec_md_map, smd => smd.security), used_names).ToList();
|
||||
var main_vm = new SyntheticImportVM() {
|
||||
security_map = sec_md_map
|
||||
};
|
||||
// add rows/conflicts
|
||||
main_vm.rows.AddRange(parsed_synths.Select(ps => {
|
||||
var sec = ps.result;
|
||||
var r = new SyntheticImportRowVM(orders_model.admin_model.user.id, used_names.Contains) {
|
||||
tick_factors = TickUtils.get_regional_ticks(sec.ContractSpec).low_tick,
|
||||
name = sec.Synthetic.Name,
|
||||
parsed_synth = ps
|
||||
};
|
||||
r.legs.AddRange(ps.legs.Zip(sec.Synthetic.Legs).Select(tup => {
|
||||
var (pl, l) = tup;
|
||||
var leg_vm = new ContractLegVM {
|
||||
price_multiplier = l.PriceMultiplier,
|
||||
quantity = l.Quantity,
|
||||
parsed_leg = pl,
|
||||
owner = r,
|
||||
leg_type =
|
||||
(pl.result_tuple.leg_params.EnableHedging ? ContractLegType.Hedge : ContractLegType.None) |
|
||||
(pl.result_tuple.leg_params.IsActiveQuoting ? ContractLegType.Quote : ContractLegType.None)
|
||||
};
|
||||
if (l.Security != null)
|
||||
leg_vm.security = new SecurityImmutable(l.Security);
|
||||
return leg_vm;
|
||||
}));
|
||||
return r;
|
||||
}));
|
||||
var main_win = new SyntheticImportDialog { DataContext = main_vm };
|
||||
ui.WindowStyle.SetSizeToContent(main_win, SizeToContent.WidthAndHeight);
|
||||
main_vm.import_request += on_main_import_request;
|
||||
main_win.move_to_mouse(-200, -75);
|
||||
dispose_op = WindowUtilities.connect_vm(main_win, main_vm, main_vm);
|
||||
main_win.Show();
|
||||
// conflicts
|
||||
var conflicts = SyntheticSecurityParser.get_conflicts(main_vm.rows);
|
||||
if (conflicts.Values.Any(c => c.Count > 0)) {
|
||||
var conflicts_vm = new SyntheticImportConflictsVM(parsed_synths.Count) {
|
||||
security_map = sec_md_map
|
||||
};
|
||||
foreach (var kv in conflicts) {
|
||||
var assign_del = SyntheticSecurityParser.assign_component(kv.Key);
|
||||
foreach (var text_groups in kv.Value) {
|
||||
var eval = get_error_value(kv.Key);
|
||||
var row = new SyntheticImportConflictRowVM(kv.Key, text_groups.Key, text_groups.Value, eval, eval);
|
||||
conflicts_vm.conflicts.Add(row);
|
||||
conflicts_vm.committed += () => {
|
||||
foreach (var (synth, leg_vm) in text_groups.Value) {
|
||||
var leg_tup = leg_vm?.parsed_leg?.result_tuple;
|
||||
assign_del(synth.parsed_synth.result, leg_tup?.leg, leg_tup?.leg_params, row.value);
|
||||
}
|
||||
row.on_conflicts_resolved();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var conflicts_win = new SyntheticImportConflictsDialog {
|
||||
DataContext = conflicts_vm,
|
||||
Owner = main_win,
|
||||
Title = $"{_import_title} Conflicts",
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||
};
|
||||
dispose_op = new DisposableCombo(WindowUtilities.connect_vm(conflicts_win, conflicts_vm, conflicts_vm)) + dispose_op;
|
||||
|
||||
conflicts_win.ShowDialog();
|
||||
}
|
||||
|
||||
object get_error_value(SyntheticContractComponent component) {
|
||||
switch (component) {
|
||||
case SyntheticContractComponent.PriceMultiplier:
|
||||
case SyntheticContractComponent.Quantity: return double.NaN;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
void on_main_import_request() {
|
||||
if (!is_disposed)
|
||||
import_synths(parsed_synths, main_vm.rows);
|
||||
Disposable.dispose(ref dispose_op);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
error_msg = $"Error parsing synthetics from file '{path}': {ex}.";
|
||||
}
|
||||
if (error_msg == null) {
|
||||
// Exit loop: the import succeeded.
|
||||
break;
|
||||
}
|
||||
if (!retry_on_error(error_msg)) {
|
||||
// Exit loop: the import failed, and the user did not click 'Yes' in the 'try again...' confirmation dialog.
|
||||
break;
|
||||
}
|
||||
// Continue loop: the import failed, and the user wants to try again.
|
||||
error_msg = null;
|
||||
}
|
||||
async void import_synths(IReadOnlyList<ParsedSyntheticContract> parsed_synths, IEnumerable<SyntheticImportRowVM> rows) {
|
||||
(uint? key, string error_msg)[] new_ids = null;
|
||||
string error_msg = null;
|
||||
try {
|
||||
new_ids = await orders_model.spreader.create_synthetics(
|
||||
rows.Where(r => !r.HasErrors)
|
||||
.Select(r => r.result_tuple));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.Ui.error($"Failed to save all synthetics during import op (one or more may have been successfully saved): {ex}.");
|
||||
error_msg = $"Import was unsuccessful: {ex}.";
|
||||
}
|
||||
if (new_ids == null)
|
||||
error_msg = "An unknown error occurred (check the synthetic management window to see if any synthetics were successfully added).";
|
||||
if (error_msg != null) {
|
||||
BTSMessageBox.Show(error_msg, "Import Synthetics", BTSMessageBoxButtons.OK);
|
||||
} else {
|
||||
Log.assert(new_ids != null);
|
||||
var failed = new_ids
|
||||
.Where(r => !r.error_msg.is_null_or_empty())
|
||||
.ToList();
|
||||
int num_succeeded = new_ids.Length - failed.Count;
|
||||
if (num_succeeded < parsed_synths.Count) {
|
||||
if (!BTSMessageBox.Confirm(
|
||||
$"{num_succeeded}/{parsed_synths.Count} synthetics were successfully imported.\n" +
|
||||
"Would you like to keep the imports that were successful?",
|
||||
_import_title)) {
|
||||
bus.post(SparkTags.SyntheticDeleteTag,
|
||||
ImmutableHashSet.CreateRange(new_ids.Where(r => r.key.HasValue).Select(r => r.key.Value)));
|
||||
}
|
||||
} else {
|
||||
BTSMessageBox.Show(
|
||||
$"All {num_succeeded} synthetics were successfully imported.",
|
||||
_import_title,
|
||||
BTSMessageBoxButtons.OK,
|
||||
MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task init_sec_md_map() =>
|
||||
sec_md_map = await bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().FirstOrDefaultAsync();
|
||||
|
||||
bool retry_on_error(string error_msg) {
|
||||
Log.Ui.warn(error_msg);
|
||||
return BTSMessageBox.Confirm(error_msg + "\nTry again?", _import_title, MessageBoxImage.Exclamation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
[Flags]
|
||||
enum ContractLegType {
|
||||
None = 0,
|
||||
Quote = 1,
|
||||
Hedge = 2
|
||||
}
|
||||
|
||||
class ParsedSyntheticContract : IDisposable {
|
||||
[ThreadStatic]
|
||||
static int _id_counter;
|
||||
|
||||
public readonly int unpersisted_id = unchecked(_id_counter++);
|
||||
public readonly Security result = new Security {
|
||||
Synthetic = new SyntheticContract(),
|
||||
ContractSpec = new ContractSpecification(),
|
||||
Exchange = Venue.Bts
|
||||
};
|
||||
|
||||
readonly ReactiveThresholdMonitor<ParsedContractLeg> _legs_valid_mon =
|
||||
new ReactiveThresholdMonitor<ParsedContractLeg>(l => l.subscribe_is_valid());
|
||||
IDisposable _dispose;
|
||||
|
||||
public ParsedSyntheticContract() {
|
||||
_dispose = Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(legs, _legs_valid_mon.join),
|
||||
_legs_valid_mon.Subscribe(_ => _is_dirty = true));
|
||||
}
|
||||
|
||||
bool
|
||||
_is_dirty,
|
||||
_is_valid;
|
||||
|
||||
(string text, bool success)
|
||||
_name,
|
||||
_tick_size;
|
||||
|
||||
public (string text, bool success) name { get => _name; set => _set(ref _name, value); }
|
||||
public (string text, bool success) tick_size { get => _tick_size; set => _set(ref _tick_size, value); }
|
||||
public ObservableCollection<ParsedContractLeg> legs { get; } = new ObservableCollection<ParsedContractLeg>();
|
||||
|
||||
public bool is_valid {
|
||||
get {
|
||||
if (_is_dirty) {
|
||||
_is_valid = tick_size.success && name.success && !_legs_valid_mon.is_above_threshold;
|
||||
_is_dirty = false;
|
||||
}
|
||||
return _is_valid;
|
||||
}
|
||||
}
|
||||
|
||||
public ParsedContractLeg current_leg =>
|
||||
legs.LastOrDefault() ?? add_new_leg();
|
||||
|
||||
public ParsedContractLeg add_new_leg() {
|
||||
var leg = new ParsedContractLeg();
|
||||
legs.Add(leg);
|
||||
result.Synthetic.Legs.Add(leg.result);
|
||||
return leg;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
public bool Equals(ParsedSyntheticContract other) => other != null && unpersisted_id == other.unpersisted_id;
|
||||
public override bool Equals(object obj) => Equals(obj as ParsedSyntheticContract);
|
||||
public override int GetHashCode() => unpersisted_id.GetHashCode();
|
||||
|
||||
void _set<T>(ref T backing, T value) {
|
||||
backing = value;
|
||||
_is_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
class ParsedContractLeg {
|
||||
[ThreadStatic]
|
||||
static int _id_counter;
|
||||
|
||||
public readonly ContractLeg result = new ContractLeg { };
|
||||
public readonly LegSpreaderParams leg_params = new LegSpreaderParams();
|
||||
bool
|
||||
_is_dirty,
|
||||
_is_valid;
|
||||
|
||||
(string text, bool success)
|
||||
_security,
|
||||
_quantity,
|
||||
_price_multiplier,
|
||||
_leg_type;
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public int id { get; } = unchecked(_id_counter++);
|
||||
public (string text, bool success) security { get => _security; set => _set(ref _security, value); }
|
||||
public (string text, bool success) quantity { get => _quantity; set => _set(ref _quantity, value); }
|
||||
public (string text, bool success) price_multiplier { get => _price_multiplier; set => _set(ref _price_multiplier, value); }
|
||||
public (string text, bool success) leg_type { get => _leg_type; set => _set(ref _leg_type, value); }
|
||||
|
||||
public (ContractLeg leg, LegSpreaderParams leg_params) result_tuple => (result, leg_params);
|
||||
|
||||
public bool is_valid {
|
||||
get {
|
||||
if (_is_dirty) {
|
||||
if (_is_valid != (security.success && quantity.success && price_multiplier.success && leg_type.success)) {
|
||||
_is_valid = !_is_valid;
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
_is_dirty = false;
|
||||
}
|
||||
return _is_valid;
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<bool> subscribe_is_valid() =>
|
||||
Observable.Create<bool>(o => {
|
||||
o.OnNext(is_valid);
|
||||
invalidated += on_invalidated;
|
||||
return Disposable.Create(() => invalidated -= on_invalidated);
|
||||
void on_invalidated() =>
|
||||
o.OnNext(is_valid);
|
||||
});
|
||||
|
||||
void _set<T>(ref T backing, T value) {
|
||||
backing = value;
|
||||
_is_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
static class SyntheticSecurityParser {
|
||||
public const string file_extension = "csv";
|
||||
|
||||
public static string display_name(this ContractLegType leg_type) {
|
||||
if (leg_type == ContractLegType.None)
|
||||
return "N";
|
||||
var flags = EnumHelper.split_flags(leg_type);
|
||||
return string.Join("", flags.Select(f => f.ToString())
|
||||
.Where(str => str.Length > 0)
|
||||
.Select(str => str.Substring(0, 1)));
|
||||
}
|
||||
|
||||
public static Action<Security, ContractLeg, LegSpreaderParams, object> assign_component(SyntheticContractComponent component) {
|
||||
switch (component) {
|
||||
case SyntheticContractComponent.Name: return (sec, leg, leg_params, val) => sec.Synthetic.Name = val?.ToString() ?? string.Empty;
|
||||
case SyntheticContractComponent.TickSize: return (sec, leg, leg_params, val) => sec.ContractSpec.LowTick = sec.ContractSpec.HighTick = ConvertUtils.try_convert<double?>(val) ?? 0.0;
|
||||
case SyntheticContractComponent.PriceMultiplier: return (sec, leg, leg_params, val) => leg.PriceMultiplier = ConvertUtils.try_convert<double?>(val) ?? double.NaN;
|
||||
case SyntheticContractComponent.Quantity: return (sec, leg, leg_params, val) => leg.Quantity = ConvertUtils.try_convert<double?>(val) ?? double.NaN;
|
||||
case SyntheticContractComponent.Security: return (sec, leg, leg_params, val) => leg.Security = val as Security ?? (val is SecurityImmutable si ? si.CloneToMutable() : null);
|
||||
case SyntheticContractComponent.LegType: return (sec, leg, leg_params, val) => assign_leg_type(leg_params, ConvertUtils.try_convert(val, out ContractLegType lt) ? lt : ContractLegType.None);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: the returned dictionary will have entries for each <see cref="SyntheticContractComponent"/>, regardless of whether or not that component has any conflicts.
|
||||
public static Dictionary<SyntheticContractComponent, Dictionary<string, List<(ParsedSyntheticContract synth, ParsedContractLeg leg)>>> get_conflicts(IEnumerable<ParsedSyntheticContract> parsed_fills) {
|
||||
var missing_components = EnumHelper.get_values<SyntheticContractComponent>().ToDictionary(c => c, c => new Dictionary<string, List<(ParsedSyntheticContract synth, ParsedContractLeg leg)>>());
|
||||
var mk_list = new Func<string, List<(ParsedSyntheticContract synth, ParsedContractLeg leg)>>(_ => new List<(ParsedSyntheticContract, ParsedContractLeg)>());
|
||||
foreach (var psc in parsed_fills) {
|
||||
if (!psc.name.success) {
|
||||
add_missing(SyntheticContractComponent.Name, psc.name.text, psc);
|
||||
}
|
||||
if (!psc.tick_size.success) {
|
||||
add_missing(SyntheticContractComponent.TickSize, psc.tick_size.text, psc);
|
||||
}
|
||||
foreach (var leg in psc.legs) {
|
||||
if (!leg.price_multiplier.success)
|
||||
add_missing(SyntheticContractComponent.PriceMultiplier, leg.price_multiplier.text, psc, leg);
|
||||
if (!leg.quantity.success)
|
||||
add_missing(SyntheticContractComponent.Quantity, leg.quantity.text, psc, leg);
|
||||
if (!leg.security.success)
|
||||
add_missing(SyntheticContractComponent.Security, leg.security.text, psc, leg);
|
||||
if (!leg.leg_type.success)
|
||||
add_missing(SyntheticContractComponent.LegType, leg.leg_type.text, psc, leg);
|
||||
}
|
||||
}
|
||||
return missing_components;
|
||||
void add_missing(SyntheticContractComponent component, string text, ParsedSyntheticContract result, ParsedContractLeg leg = null) =>
|
||||
missing_components[component].GetOrAdd(text ?? string.Empty, mk_list).Add((result, leg));
|
||||
}
|
||||
|
||||
public static Dictionary<SyntheticContractComponent, Dictionary<string, List<(SyntheticImportRowVM row, ContractLegVM leg)>>> get_conflicts(IEnumerable<SyntheticImportRowVM> import_rows) {
|
||||
var missing_components = EnumHelper.get_values<SyntheticContractComponent>().ToDictionary(c => c, c => new Dictionary<string, List<(SyntheticImportRowVM row, ContractLegVM leg)>>());
|
||||
var mk_list = new Func<string, List<(SyntheticImportRowVM row, ContractLegVM leg)>>(_ => new List<(SyntheticImportRowVM, ContractLegVM)>());
|
||||
foreach (var ir in import_rows) {
|
||||
var psc = ir.parsed_synth;
|
||||
if (!psc.name.success) {
|
||||
add_missing(SyntheticContractComponent.Name, psc.name.text, ir);
|
||||
}
|
||||
if (!psc.tick_size.success) {
|
||||
add_missing(SyntheticContractComponent.TickSize, psc.tick_size.text, ir);
|
||||
}
|
||||
foreach (var leg in ir.legs) {
|
||||
if (!leg.parsed_leg.price_multiplier.success)
|
||||
add_missing(SyntheticContractComponent.PriceMultiplier, leg.parsed_leg.price_multiplier.text, ir, leg);
|
||||
if (!leg.parsed_leg.quantity.success)
|
||||
add_missing(SyntheticContractComponent.Quantity, leg.parsed_leg.quantity.text, ir, leg);
|
||||
if (!leg.parsed_leg.security.success)
|
||||
add_missing(SyntheticContractComponent.Security, leg.parsed_leg.security.text, ir, leg);
|
||||
if (!leg.parsed_leg.leg_type.success)
|
||||
add_missing(SyntheticContractComponent.LegType, leg.parsed_leg.leg_type.text, ir, leg);
|
||||
}
|
||||
}
|
||||
return missing_components;
|
||||
void add_missing(SyntheticContractComponent component, string text, SyntheticImportRowVM row, ContractLegVM leg = null) =>
|
||||
missing_components[component].GetOrAdd(text ?? string.Empty, mk_list).Add((row, leg));
|
||||
}
|
||||
// This implementation prioritizes simplicity over speed.
|
||||
// The security desc lookup could be better optimized, but the speed at which fills are imported is probably not a paramount concern.
|
||||
public static IEnumerable<ParsedSyntheticContract> parse_rows(IEnumerable<string> rows, IReadOnlyDictionary<uint, SecurityImmutable> security_map, ISet<string> used_names) {
|
||||
var sec_lookup = security_map.ToLookup(kv => kv.Value.ToString());
|
||||
var name_col = Cells.name(used_names.Contains);
|
||||
var sec_col = Cells.contract_security(lookup_security);
|
||||
return RowParser.parse_rows(rows, columns(), Cells.delimiter);
|
||||
|
||||
Security lookup_security(string sec_text) =>
|
||||
sec_lookup.Contains(sec_text) ? sec_lookup[sec_text].First().Value.CloneToMutable() : null;
|
||||
|
||||
IEnumerable<RowParser.AssignColumnValue<ParsedSyntheticContract>> columns() {
|
||||
yield return name_col;
|
||||
yield return Cells.tick_size;
|
||||
// if an imported synth has > 255 legs, assume it is an error.
|
||||
const int max_legs = 255;
|
||||
int leg_count = 0;
|
||||
do {
|
||||
yield return sec_col;
|
||||
yield return Cells.contract_quantity;
|
||||
yield return Cells.contract_price_multiplier;
|
||||
yield return Cells.leg_type;
|
||||
} while (leg_count++ < max_legs);
|
||||
}
|
||||
}
|
||||
|
||||
public static Func<string, TKey> reverse_lookup<TKey>(IReadOnlyDictionary<TKey, string> map) {
|
||||
var lookup = map.ToLookup(kv => kv.Value, kv => kv.Key);
|
||||
return cell_text => lookup.Contains(cell_text) ? lookup[cell_text].First() : default;
|
||||
}
|
||||
|
||||
public static void assign_leg_type(LegSpreaderParams leg_params, ContractLegType leg_type) {
|
||||
leg_params.IsActiveQuoting = (leg_type & ContractLegType.Quote) != 0;
|
||||
leg_params.EnableHedging = (leg_type & ContractLegType.Hedge) != 0;
|
||||
}
|
||||
|
||||
static class Cells {
|
||||
public const char delimiter = ',';
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedSyntheticContract> name(Func<string, bool> name_exists) =>
|
||||
(ref ParsedSyntheticContract psc, string cell_text) => {
|
||||
psc.name = (cell_text, !name_exists(cell_text));
|
||||
psc.result.Synthetic.Name = cell_text;
|
||||
};
|
||||
|
||||
public static void tick_size(ref ParsedSyntheticContract psc, string cell_text) {
|
||||
psc.tick_size = (cell_text, double.TryParse(cell_text.Trim(), out var val));
|
||||
if (psc.tick_size.success) {
|
||||
psc.tick_size = (cell_text, val.IsNonZero());
|
||||
psc.result.ContractSpec.LowTick = psc.result.ContractSpec.HighTick = val;
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedSyntheticContract> contract_security(Func<string, Security> reverse_security_lookup) =>
|
||||
(ref ParsedSyntheticContract psc, string cell_text) => {
|
||||
var leg = psc.add_new_leg();
|
||||
leg.result.Security = reverse_security_lookup(cell_text.Trim());
|
||||
leg.security = (cell_text, leg.result.Security != null);
|
||||
};
|
||||
|
||||
public static void contract_quantity(ref ParsedSyntheticContract psc, string cell_text) {
|
||||
psc.current_leg.quantity = (cell_text, double.TryParse(cell_text.Trim(), out var val) && val.IsNonZero());
|
||||
psc.current_leg.result.Quantity = val;
|
||||
}
|
||||
|
||||
public static void contract_price_multiplier(ref ParsedSyntheticContract psc, string cell_text) {
|
||||
psc.current_leg.price_multiplier = (cell_text, double.TryParse(cell_text.Trim(), out var val) && val.IsNonZero());
|
||||
psc.current_leg.result.PriceMultiplier = val;
|
||||
}
|
||||
|
||||
public static void leg_type(ref ParsedSyntheticContract psc, string cell_text) {
|
||||
psc.current_leg.leg_type = (cell_text, _parse_leg_type(cell_text.Trim(), out var leg_type));
|
||||
assign_leg_type(psc.current_leg.leg_params, leg_type);
|
||||
}
|
||||
|
||||
static bool _parse_leg_type(string cell_text, out ContractLegType result) {
|
||||
result = ContractLegType.None;
|
||||
if (cell_text.IsNullOrEmpty())
|
||||
return false;
|
||||
switch (char.ToUpper(cell_text[0])) {
|
||||
case 'Q': result = ContractLegType.Quote; break;
|
||||
case 'H': result = ContractLegType.Hedge; break;
|
||||
case 'N': break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,52 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
class LegSpreaderParamsTuple : ViewModelBase, IEquatable<LegSpreaderParamsTuple> {
|
||||
[ThreadStatic]
|
||||
static int _id_counter;
|
||||
|
||||
SecurityImmutable? _security;
|
||||
|
||||
namespace mbts.ui.spark
|
||||
{
|
||||
internal class LegSpreaderParamsTuple
|
||||
{
|
||||
public LegSpreaderParamsTuple(ContractLeg leg, LegSpreaderParams leg_params) {
|
||||
id = unchecked(_id_counter++);
|
||||
this.leg = leg;
|
||||
this.leg_params = leg_params;
|
||||
if (leg.Security != null)
|
||||
_security = new SecurityImmutable(leg.Security);
|
||||
}
|
||||
|
||||
public event Action security_changed;
|
||||
|
||||
public int id { get; }
|
||||
/// <summary>
|
||||
/// Important: do NOT modify security via <see cref="ContractLeg.Security"/>,
|
||||
/// use <see cref="security"/> instead ([only]the latter raises the PropertyChanged event).
|
||||
/// </summary>
|
||||
public ContractLeg leg { get; }
|
||||
public LegSpreaderParams leg_params { get; }
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (!_security.Equals(value)) {
|
||||
_security = value;
|
||||
leg.Security = value?.CloneToMutable();
|
||||
security_changed?.Invoke();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public uint SecurityKey => leg.Security.Id;
|
||||
|
||||
public bool Equals(LegSpreaderParamsTuple other) =>
|
||||
ReferenceEquals(this, other);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as LegSpreaderParamsTuple);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ using bts.api.core;
|
||||
using bts.api.synthetic;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -82,6 +84,61 @@ namespace mbts.spark {
|
||||
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
|
||||
"lookup strategy");
|
||||
|
||||
public Task<(uint? key, string error_msg)[]> create_synthetics(IEnumerable<(SecurityImmutable security, ExecutionStrategyImmutable strategy)> synthetics) {
|
||||
var tasks = synthetics.Select(tup => {
|
||||
var (sec, strat) = tup;
|
||||
if (sec.ContractCase != Security.ContractOneofCase.Synthetic)
|
||||
return Task.FromException<(Result<CreateResult>, Result<UpdateResult>?)>(ExceptionBuilder.Argument.unexpected_case(sec.ContractCase, nameof(sec)));
|
||||
if (sec.Synthetic.UserId != _user_id) {
|
||||
var msec = sec.CloneToMutable();
|
||||
msec.Synthetic.UserId = _user_id;
|
||||
sec = new SecurityImmutable(msec);
|
||||
}
|
||||
return impl();
|
||||
async Task<(Result<CreateResult> sec, Result<UpdateResult>? strat)> impl() {
|
||||
var result = await _service.create_synthetic_async(sec);
|
||||
Result<UpdateResult>? strategy;
|
||||
if (result.success && result.value.Error == CreateResult.Types.Error.Ok) {
|
||||
strategy = await _service.update_security_metadata_async(new Metadata {
|
||||
SecurityKey = result.value.Key,
|
||||
Strategy = tup.strategy.CloneToMutable()
|
||||
});
|
||||
} else {
|
||||
strategy = null;
|
||||
}
|
||||
return (result, strategy);
|
||||
}
|
||||
});
|
||||
return _error.safe_execute_batch(tasks,
|
||||
tup => {
|
||||
var (sec, strat) = tup;
|
||||
// security
|
||||
string sec_error_msg = null;
|
||||
uint? key = null;
|
||||
if (!sec.success) {
|
||||
sec_error_msg = sec.error_message;
|
||||
} else if (sec.value.Error != CreateResult.Types.Error.Ok) {
|
||||
sec_error_msg = sec.value.Error.ToString();
|
||||
} else {
|
||||
key = sec.value.Key;
|
||||
}
|
||||
// strategy
|
||||
string strat_error_msg = null;
|
||||
if (!strat.HasValue) {
|
||||
strat_error_msg = "Strategy was not successfully saved";
|
||||
} else if (!strat.Value.success) {
|
||||
strat_error_msg = strat.Value.error_message;
|
||||
} else if (strat.Value.value.Error != UpdateResult.Types.Error.Ok) {
|
||||
strat_error_msg = strat.Value.value.Error.ToString();
|
||||
}
|
||||
// merge error msgs (result should be null if both errors are empty)
|
||||
var error_msg = string.Join(Environment.NewLine, new[] { sec_error_msg, strat_error_msg }.ExceptNull());
|
||||
return (key, error_msg.NullIfEmpty());
|
||||
},
|
||||
"Synthetic",
|
||||
"creating synthetics");
|
||||
}
|
||||
|
||||
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
|
||||
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
|
||||
return null;
|
||||
@@ -106,10 +163,32 @@ namespace mbts.spark {
|
||||
(r, success) => success,
|
||||
"saving strategy");
|
||||
|
||||
public async Task<string[]> save_strategies(IEnumerable<(uint key, ExecutionStrategyImmutable strategy)> items) {
|
||||
// Copy everything to a new list, to guarentee that it won't be altered during the op.
|
||||
// The performance impact should be insignificant, as this should be a rare call.
|
||||
var item_list = new List<(uint key, ExecutionStrategyImmutable strategy, int index)>(items.Select((item, index) => (item.key, item.strategy, index)));
|
||||
string[] errors = new string[item_list.Count];
|
||||
await Task.WhenAll(item_list.Select(impl));
|
||||
return errors;
|
||||
async Task impl((uint key, ExecutionStrategyImmutable strategy, int index) tup) {
|
||||
var (key, strategy, index) = tup;
|
||||
try {
|
||||
var result = await _service.update_security_metadata_async(new Metadata {
|
||||
SecurityKey = key,
|
||||
Strategy = strategy.CloneToMutable()
|
||||
});
|
||||
if (!result.success)
|
||||
errors[index] = result.error_message;
|
||||
} catch (Exception ex) {
|
||||
errors[index] = ex.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> verify_name_is_unique(string name) {
|
||||
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
name = name.Trim().ToLower();
|
||||
return success && !result.Values.Any(st => st.md.security.Synthetic.Name.Trim().ToLower() == name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,5 @@ namespace mbts.ui.spark {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row> { }
|
||||
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.spreader;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
@@ -18,7 +19,10 @@ using static bts.shell.SyntheticManagementWindowFactory;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "SyntheticManagement";
|
||||
const string
|
||||
_window_type = "SyntheticManagement",
|
||||
_import_title = "Import Synthetics";
|
||||
static readonly TimeSpan _import_op_sec_map_timeout = TimeSpan.FromSeconds(30);
|
||||
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
|
||||
StartupData _state;
|
||||
OrdersModel _orders_model;
|
||||
@@ -27,7 +31,6 @@ namespace mbts.spark {
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
|
||||
_orders_model = orders_model;
|
||||
@@ -90,7 +93,11 @@ namespace mbts.spark {
|
||||
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
})),
|
||||
new SparkCommandCommand(
|
||||
"Import Synthetics...", Icons.Import, FileDialogCommand.open_file(import, extensions: SyntheticSecurityParser.file_extension)) {
|
||||
show_in_toolbar = false
|
||||
}
|
||||
};
|
||||
|
||||
_subscriptions = Disposable.Create(
|
||||
@@ -105,13 +112,16 @@ namespace mbts.spark {
|
||||
r == null ? Observable.Return(new RowStrategy?()) :
|
||||
_orders_model.spreader.security_strategy(r.security.Id).Select(strategy => RowStrategy.create(r.security, strategy));
|
||||
|
||||
uint select_security_id(RowModificationParams? rmp) => rmp.HasValue ? rmp.Value.security.Id : 0;
|
||||
IImmutableSet<uint> select_security_id(RowModificationParams? rmp) => ImmutableHashSet.Create(rmp.HasValue ? rmp.Value.security.Id : 0);
|
||||
(SecurityImmutable, ExecutionStrategyImmutable) select_row_strategy_tuple(RowStrategy? rs) => rs.HasValue ? (rs.Value.security, rs.Value.strategy) : default;
|
||||
bool can_execute(RowModificationParams? rmp) => rmp.HasValue && !rmp.Value.has_working_orders;
|
||||
|
||||
_vm.value = new SyntheticManagementDialogVM(_bus, _orders_model, _settings_manager, _state.sorting_criteria, _state.filters ?? new FilterStates());
|
||||
if (_view == null) _view = new SyntheticManagementView { DataContext = _vm.value };
|
||||
else _view.DataContext = _vm.value;
|
||||
|
||||
void import(string path) =>
|
||||
SyntheticImporter.import_from_file(path, _bus, _orders_model);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
@@ -145,4 +155,4 @@ namespace mbts.spark {
|
||||
new RowStrategy(security, strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PostHedgeOppositeMarketVolumeBasedCrossCrossTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Cross Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_omv_cross(leg_tup) is PostHedgeOppositeMarketVolumeBasedForcedCross omv_cross)
|
||||
omv_cross.CrossTicks = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PostHedgeParams.OppositeMarketVolumeBasedForcedCross;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PostHedgeOppositeMarketVolumeBasedForcedCross.CrossTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PostHedgeOppositeMarketVolumeBasedForcedCross.CrossTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PostHedgeOppositeMarketVolumeBasedCrossThresholdModel : RowModelBase<QuantityMeasure> {
|
||||
IDictionary<uint, QuantityMeasure> _value_store = new Dictionary<uint, QuantityMeasure>();
|
||||
IDictionary<int, QuantityMeasure> _value_store = new Dictionary<int, QuantityMeasure>();
|
||||
|
||||
public override string display_name { get; } = "Threshold";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, QuantityMeasure value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_omv_cross(leg_tup) is PostHedgeOppositeMarketVolumeBasedForcedCross omv_cross)
|
||||
omv_cross.QuantityThreshold = value;
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PostHedgeParams.OppositeMarketVolumeBasedForcedCross;
|
||||
|
||||
QuantityMeasure _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PostHedgeOppositeMarketVolumeBasedForcedCross.QuantityThreshold);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PostHedgeOppositeMarketVolumeBasedForcedCross.QuantityThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PostHedgePriceBasedCrossMarketTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Cross Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_price_based_cross(leg_tup) is PostHedgePriceBasedForcedCross price_based_cross)
|
||||
price_based_cross.MarketCrossTicks = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PostHedgeParams.PriceBasedCross;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PostHedgePriceBasedForcedCross.MarketCrossTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PostHedgePriceBasedForcedCross.MarketCrossTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PostHedgePriceBasedCrossTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_price_based_cross(leg_tup) is PostHedgePriceBasedForcedCross price_based_cross)
|
||||
price_based_cross.TicksThreshold = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PostHedgeParams.PriceBasedCross;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PostHedgePriceBasedForcedCross.TicksThreshold);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PostHedgePriceBasedForcedCross.TicksThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PostHedgeTimeBasedCrossMarketTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Cross Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_time_based_cross(leg_tup) is PostHedgeTimeBasedForcedCross time_based_cross)
|
||||
time_based_cross.MarketCrossTicks = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PostHedgeParams.TimeBasedForcedCross;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PostHedgeTimeBasedForcedCross.MarketCrossTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PostHedgeTimeBasedForcedCross.MarketCrossTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PostHedgeTimeBasedCrossTimeModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Time(ms)";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_time_based_cross(leg_tup) is PostHedgeTimeBasedForcedCross time_based_cross)
|
||||
time_based_cross.TimeInMsThreshold = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PostHedgeParams.TimeBasedForcedCross;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PostHedgeTimeBasedForcedCross.TimeInMsThreshold);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PostHedgeTimeBasedForcedCross.TimeInMsThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PreHedgePaylessThresholdModel : RowModelBase<QuantityMeasure> {
|
||||
IDictionary<uint, QuantityMeasure> _value_store = new Dictionary<uint, QuantityMeasure>();
|
||||
IDictionary<int, QuantityMeasure> _value_store = new Dictionary<int, QuantityMeasure>();
|
||||
|
||||
public override string display_name { get; } = "Threshold";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, QuantityMeasure value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_payless_ticks(leg_tup) is PreHedgeQuantityBasedPayLessTicks payless_ticks)
|
||||
payless_ticks.QuantityThreshold = value;
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PreHedgeParams.QuantityBasedPaylessTicks;
|
||||
|
||||
QuantityMeasure _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PreHedgeQuantityBasedPayLessTicks.QuantityThreshold);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PreHedgeQuantityBasedPayLessTicks.QuantityThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PreHedgePaylessTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Payless Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -23,7 +23,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_payless_ticks(leg_tup) is PreHedgeQuantityBasedPayLessTicks payless_ticks)
|
||||
payless_ticks.PaylessTicks = value;
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PreHedgeParams.QuantityBasedPaylessTicks;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PreHedgeQuantityBasedPayLessTicks.PaylessTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PreHedgeQuantityBasedPayLessTicks.PaylessTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PreHedgePayupThresholdModel : RowModelBase<QuantityMeasure> {
|
||||
IDictionary<uint, QuantityMeasure> _value_store = new Dictionary<uint, QuantityMeasure>();
|
||||
IDictionary<int, QuantityMeasure> _value_store = new Dictionary<int, QuantityMeasure>();
|
||||
|
||||
public override string display_name { get; } = "Threshold";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, QuantityMeasure value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_payup_ticks(leg_tup) is PreHedgeQuantityBasedPayupTicks payup_ticks)
|
||||
payup_ticks.QuantityThreshold = value;
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PreHedgeParams.QuantityBasedPayupTicks;
|
||||
|
||||
QuantityMeasure _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PreHedgeQuantityBasedPayUpTicks.QuantityThreshold);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PreHedgeQuantityBasedPayUpTicks.QuantityThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PreHedgePayupTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Payup Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_payup_ticks(leg_tup) is PreHedgeQuantityBasedPayupTicks payup_ticks)
|
||||
payup_ticks.PayupTicks = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PreHedgeParams.QuantityBasedPayupTicks;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PreHedgeQuantityBasedPayUpTicks.PayupTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PreHedgeQuantityBasedPayUpTicks.PayupTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -9,7 +9,7 @@ namespace mbts.spark {
|
||||
{
|
||||
class ConditionalQuotingPriceTypeModel : RowModelBase<PriceType>
|
||||
{
|
||||
IDictionary<uint, PriceType> _value_store = new Dictionary<uint, PriceType>();
|
||||
IDictionary<int, PriceType> _value_store = new Dictionary<int, PriceType>();
|
||||
|
||||
public override string display_name { get; } = "Price Type";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -26,7 +26,7 @@ namespace mbts.spark {
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, PriceType value)
|
||||
{
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_value(leg_tup) is ConditionalQuoting cond)
|
||||
cond.PriceType = value;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.ConditionalQuoting;
|
||||
|
||||
PriceType _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ConditionalQuoting.PriceType);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.ConditionalQuoting.PriceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class ConditionalQuotingTicksModel : RowModelBase<int> {
|
||||
IDictionary<uint, int> _value_store = new Dictionary<uint, int>();
|
||||
IDictionary<int, int> _value_store = new Dictionary<int, int>();
|
||||
|
||||
public override string display_name { get; } = "Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_value(leg_tup) is ConditionalQuoting cond)
|
||||
cond.NTicks = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.ConditionalQuoting;
|
||||
|
||||
int _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ConditionalQuoting.NTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.ConditionalQuoting.NTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Windows;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteHiddenModel : RowModelBase<bool>, IHaveFontParams {
|
||||
IDictionary<uint, bool> _value_store = new Dictionary<uint, bool>();
|
||||
IDictionary<int, bool> _value_store = new Dictionary<int, bool>();
|
||||
|
||||
public override string display_name { get; } = "Hidden";
|
||||
public override string help_text { get; } = "A hidden quote is not sent out until the market crosses the computed quoting price. When that happens, the quote is sent out as an IOC order. This continues until the synthetic spreader order is fully filled, or cancelled.";
|
||||
@@ -21,7 +21,7 @@ namespace mbts.spark {
|
||||
_get_throttle(leg_tup) != null;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
leg_tup.leg_params.QuoteParams.QuoteHidden = value ? new QuoteHidden() : null;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.QuoteHidden;
|
||||
|
||||
bool _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => false);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteInsideMarketDistanceTowardModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Toward Distance";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -21,7 +21,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance)
|
||||
inside_market_distance.OnTowardDistance = value;
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey);
|
||||
_value_store.GetValueOrDefault(leg_tup.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteInsideMarketThrottleMinMoveModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Min Move";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_inside_market_throttle(leg_tup) is QuoteInsideMarketDistanceWithTravel inside_market_throttle)
|
||||
inside_market_throttle.Limit = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceWithTravel;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.QuoteInsideMarketDistanceWithTravel.Limit);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.QuoteInsideMarketDistanceWithTravel.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteInsideMarketThrottleTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_inside_market_throttle(leg_tup) is QuoteInsideMarketDistanceWithTravel inside_market_throttle)
|
||||
inside_market_throttle.TicksFromInside = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceWithTravel;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.QuoteInsideMarketDistanceWithTravel.TicksFromInside);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.QuoteInsideMarketDistanceWithTravel.TicksFromInside);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace mbts.spark
|
||||
{
|
||||
class QuoteQueueHolderModel : RowModelBase<uint>, IHaveFontParams
|
||||
{
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
public QuoteQueueHolderModel()
|
||||
{
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace mbts.spark
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value)
|
||||
{
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
leg_tup.leg_params.QuoteParams.NQueueHolderOrders = value ;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace mbts.spark
|
||||
leg_tup.leg_params.QuoteParams;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => 0u);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
internal class ShowQuantityQuantityModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name => "Quantity";
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace mbts.spark {
|
||||
_get_value(leg_tup) is NullableQuantity show_quantity ? show_quantity.Quantity : _value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_value(leg_tup) is NullableQuantity show_quantity) {
|
||||
show_quantity.Quantity = value;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ShowQuantity.Quantity);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.ShowQuantity.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteThrottleInsideTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Inside Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_throttle(leg_tup) is QuoteMuteZone throttle)
|
||||
throttle.InsideTicks = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.QuoteMuteZone;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.QuoteMuteZone.InsideTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.QuoteMuteZone.InsideTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteThrottleOutsideTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Outside Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_throttle(leg_tup) is QuoteMuteZone throttle)
|
||||
throttle.OutsideTicks = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.QuoteParams.QuoteMuteZone;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.QuoteMuteZone.OutsideTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.QuoteMuteZone.OutsideTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
internal abstract class RowModelBase<T> : ViewModelBase, IRowModel {
|
||||
readonly Dictionary<uint, ReplaySubject<bool>> _enabled_store = new Dictionary<uint, ReplaySubject<bool>>();
|
||||
readonly Dictionary<uint, Subject<T>> _override_value_store = new Dictionary<uint, Subject<T>>();
|
||||
readonly Dictionary<LegSpreaderParamsTuple, ReplaySubject<bool>> _enabled_store = new Dictionary<LegSpreaderParamsTuple, ReplaySubject<bool>>();
|
||||
readonly Dictionary<LegSpreaderParamsTuple, Subject<T>> _override_value_store = new Dictionary<LegSpreaderParamsTuple, Subject<T>>();
|
||||
|
||||
protected RowModelBase() { }
|
||||
|
||||
@@ -59,22 +59,22 @@ namespace mbts.spark {
|
||||
public virtual ICell create_cell(LegSpreaderParamsTuple leg_tup) => new Cell<T>(this, leg_tup);
|
||||
|
||||
public void set_enabled(LegSpreaderParamsTuple leg_tup, bool enabled) =>
|
||||
_enabled_store.GetOrAdd(leg_tup.SecurityKey, sec => new ReplaySubject<bool>(1)).OnNext(enabled);
|
||||
_enabled_store.GetOrAdd(leg_tup, sec => new ReplaySubject<bool>(1)).OnNext(enabled);
|
||||
|
||||
public void override_value(LegSpreaderParamsTuple leg_tup, T value) {
|
||||
if (_override_value_store.TryGetValue(leg_tup.SecurityKey, out Subject<T> subject))
|
||||
if (_override_value_store.TryGetValue(leg_tup, out Subject<T> subject))
|
||||
subject.OnNext(value);
|
||||
}
|
||||
|
||||
public IObservable<bool> observe_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_enabled_store.GetOrAdd(leg_tup.leg.Security.Id, sec => {
|
||||
_enabled_store.GetOrAdd(leg_tup, sec => {
|
||||
var rs = new ReplaySubject<bool>(1);
|
||||
rs.OnNext(_get_enabled(leg_tup));
|
||||
return rs;
|
||||
});
|
||||
|
||||
public IObservable<T> observe_override_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_override_value_store.GetOrAdd(leg_tup.leg.Security.Id, sec => new Subject<T>());
|
||||
_override_value_store.GetOrAdd(leg_tup, sec => new Subject<T>());
|
||||
|
||||
protected virtual bool _get_enabled(LegSpreaderParamsTuple leg_tup) => true;
|
||||
#region IRowModel (explicit)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using bts.api;
|
||||
using mbts.collections;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
@@ -18,8 +16,8 @@ namespace mbts.spark {
|
||||
ParameterSection[] section_path { get; }
|
||||
}
|
||||
|
||||
internal class RowVM : DataGridExRow<Security>, IParameterRowVM, IDisposable {
|
||||
readonly ObservableKeyedCollection<KeyValuePair<uint, (ICell cell, LegSpreaderParamsTuple leg)>, uint> _cells = new ObservableKeyedCollection<KeyValuePair<uint, (ICell cell, LegSpreaderParamsTuple leg)>, uint>(kv => kv.Key);
|
||||
internal class RowVM : DataGridExRow<LegSpreaderParamsTuple>, IParameterRowVM, IDisposable {
|
||||
readonly ObservableCollection<(LegSpreaderParamsTuple leg, ICell cell)> _cells = new ObservableCollection<(LegSpreaderParamsTuple leg, ICell cell)>();
|
||||
readonly Property<bool> _has_errors;
|
||||
IDisposable _dispose;
|
||||
|
||||
@@ -34,15 +32,16 @@ namespace mbts.spark {
|
||||
_has_errors = backing(nameof(has_errors), false);
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
ObservableAutoWrapper.connect(legs, _cells, lt => new KeyValuePair<uint, (ICell cell, LegSpreaderParamsTuple leg)>(lt.SecurityKey, (model.create_cell(lt), lt))),
|
||||
ObservableAutoWrapper.connect_item_hooks(_cells, kv => {
|
||||
kv.Value.cell.value_changed += raise_cell_value_changed;
|
||||
ObservableAutoWrapper.connect(legs, _cells, lt => (lt, model.create_cell(lt))),
|
||||
ObservableAutoWrapper.connect_item_hooks(_cells, tup => {
|
||||
var (leg, cell) = tup;
|
||||
cell.value_changed += raise_cell_value_changed;
|
||||
return new CompositeDisposable {
|
||||
Disposable.Create(() => kv.Value.cell.value_changed -= raise_cell_value_changed),
|
||||
kv.Value.cell as IDisposable ?? Disposable.empty
|
||||
Disposable.Create(() => cell.value_changed -= raise_cell_value_changed),
|
||||
cell as IDisposable ?? Disposable.empty
|
||||
};
|
||||
void raise_cell_value_changed() =>
|
||||
cell_value_changed?.Invoke(kv.Value.cell, kv.Value.leg);
|
||||
cell_value_changed?.Invoke(cell, leg);
|
||||
}, true));
|
||||
}
|
||||
|
||||
@@ -56,8 +55,13 @@ namespace mbts.spark {
|
||||
public bool read_only { get; }
|
||||
public bool has_errors => _has_errors.Value;
|
||||
|
||||
public override object get_cell(Security col_key) =>
|
||||
_cells.TryGetValue(col_key.Id, out KeyValuePair<uint, (ICell cell, LegSpreaderParamsTuple leg)> kv) ? kv.Value.cell : null;
|
||||
public override object get_cell(LegSpreaderParamsTuple leg_tup) =>
|
||||
// Note this is an O(n) op.
|
||||
// If need ever arises to improve perf here,
|
||||
// an id member could be added to the LegSpreaderParamsTuple,
|
||||
// which could serve as a sorting key to enable Log(n) lookups.
|
||||
leg_tup != null &&
|
||||
_cells.TryGetFirst(t => t.leg == leg_tup, out var match) ? match.cell : null;
|
||||
|
||||
public ParameterSection this[string section_no] =>
|
||||
int.TryParse(section_no, out int i) && i < section_path.Length ? section_path[i] : null;
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace mbts.spark {
|
||||
_close_new_leg_popup();
|
||||
}
|
||||
};
|
||||
remove_leg = new DelegateCommand<Security>(s => leg_tuples.removeFirst(l => l.leg.Security.Id == s.Id));
|
||||
remove_leg = new DelegateCommand<LegSpreaderParamsTuple>(l => leg_tuples.Remove(l));
|
||||
commit = new AsyncCommand(commit_async, () => leg_count >= 2);
|
||||
|
||||
if (override_auto_set_behavior) {
|
||||
@@ -112,6 +112,14 @@ namespace mbts.spark {
|
||||
commit.RaiseCanExecuteChanged();
|
||||
_validate_leg_count();
|
||||
};
|
||||
_resources.Add(ObservableAutoWrapper.connect_item_hooks(leg_tuples, l => {
|
||||
l.security_changed += on_sec_changed;
|
||||
return Disposable.Create(() => l.security_changed -= on_sec_changed);
|
||||
void on_sec_changed() {
|
||||
if (_set_name_automatically)
|
||||
_deduce_name();
|
||||
}
|
||||
}));
|
||||
|
||||
async Task commit_async() {
|
||||
RequestUpdate?.Invoke();
|
||||
@@ -130,12 +138,7 @@ namespace mbts.spark {
|
||||
if (valid) {
|
||||
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
|
||||
_synthetic.Symbol = _deduce_symbol(_synthetic);
|
||||
double tick_frac = _tick_factors.tick_unit / _tick_factors.tick_base;
|
||||
_synthetic.ContractSpec.LowTick = tick_frac;
|
||||
_synthetic.ContractSpec.HighTick = tick_frac;
|
||||
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
|
||||
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
|
||||
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
|
||||
_synthetic.ContractSpec = _tick_factors.to_spec();
|
||||
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
@@ -377,4 +380,4 @@ namespace mbts.spark {
|
||||
else clear_error(nameof(leg_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,13 +47,13 @@ namespace mbts.spark {
|
||||
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
|
||||
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
|
||||
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
|
||||
columns.Add(new HyperlinkElementColumn<Columns>(
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
Columns.n_working_orders,
|
||||
new Binding(nameof(Row.n_working_orders)),
|
||||
new Binding(nameof(Row.show_order_book)),
|
||||
n_working_orders_filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
id = col_id(Columns.n_working_orders),
|
||||
CommandBinding = new Binding(nameof(Row.show_order_book)),
|
||||
TextBinding = new Binding(nameof(Row.n_working_orders)),
|
||||
Header = DefaultColumnHeaders.N_Working_Orders,
|
||||
Width = DefaultColumnWidths.N_Working_Orders
|
||||
});
|
||||
|
||||
@@ -38,10 +38,10 @@ namespace mbts.spark {
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
int _total_row_count;
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
|
||||
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, settings, _get_observable(bus, model, settings), settings_manager) { }
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
public FillsVM(IBus bus, OrdersModel model, FillsSettings settings, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
/// Columns
|
||||
@@ -136,8 +136,6 @@ namespace mbts.spark {
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
if (_settings_manager != null)
|
||||
grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => _rows_manager.filter_changed += cb, cb => _rows_manager.filter_changed -= cb);
|
||||
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => _rows_manager.comparer_changed += cb, cb => _rows_manager.comparer_changed -= cb);
|
||||
TableUpdate<FillTuple, string> last_unfiltered_fills = TableUpdate<FillTuple, string>.mk_empty(FillTuple.key_fn);
|
||||
var fills_buffer = Observable.Create<TableUpdate<FillTuple, string>>(o => {
|
||||
// this is what .Cache() does, but is a little cheaper, since we know subscriptions and callbacks occur on the same thread (i.e., on this Dispatcher).
|
||||
@@ -145,29 +143,36 @@ namespace mbts.spark {
|
||||
_unfiltered_fills_changed += o.OnNext;
|
||||
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
|
||||
});
|
||||
if (settings.grid != null && _settings_manager != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = settings.grid;
|
||||
}
|
||||
_rows_manager.on_settings(settings.filters ?? new FillsFilterStates {
|
||||
fill_time = new TimeFilterTuple(
|
||||
TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
|
||||
},
|
||||
settings.sorting_criterion);
|
||||
// resources/subscriptions
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
|
||||
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
|
||||
RXCollection.connect(
|
||||
rows,
|
||||
fills_buffer.Filter(_rows_manager.observe_filter(), FillTuple.key_fn),
|
||||
_rows_manager.observe_comparer(),
|
||||
ft => new Row(ft, model),
|
||||
r => r.fill_tuple,
|
||||
Row.update,
|
||||
r => r.is_selected,
|
||||
(r, is_selected) => r.is_selected = is_selected),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
last_unfiltered_fills = t;
|
||||
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
|
||||
_unfiltered_fills_changed?.Invoke(t);
|
||||
}),
|
||||
state_obs.OnDispatcher().Subscribe(s => {
|
||||
if (s.grid != null && _settings_manager != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
var fs = s.filters ?? new FillsFilterStates {
|
||||
fill_time = new TimeFilterTuple(
|
||||
TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
|
||||
};
|
||||
_rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher().Subscribe(root_algo_type_observer),
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
@@ -214,13 +219,10 @@ namespace mbts.spark {
|
||||
((IKeyedColumn<Columns>)column).key;
|
||||
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
|
||||
var orders_hash = state.orders.is_null_or_empty() ? null : new HashSet<uint>(state.orders);
|
||||
var root_securities_hash = state.root_securities.is_null_or_empty() ? null : new HashSet<uint>(state.root_securities);
|
||||
return orders_hash == null && root_securities_hash == null ?
|
||||
model.on_fill_tuples_with_root :
|
||||
model.on_fill_tuples_with_root.Filter(f =>
|
||||
(orders_hash == null || orders_hash.Contains(f.fill.OrderKey)) &&
|
||||
(root_securities_hash == null || root_securities_hash.Contains(f.root_order_security?.security?.Id ?? 0)));
|
||||
if (!state.orders.is_null_or_empty()) {
|
||||
var orders_hash = new HashSet<uint>(state.orders);
|
||||
return model.on_fill_tuples_with_root.Filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
} else return model.on_fill_tuples_with_root;
|
||||
}
|
||||
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
|
||||
@@ -265,33 +267,40 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class Row : SecurityRow, IEquatable<Row>, IRowVM {
|
||||
public readonly FillTuple fill_tuple;
|
||||
public static readonly Func<Row, FillTuple, Row> update =
|
||||
(r, ft) => {
|
||||
using (_change_detector.detect_changes(r)) {
|
||||
r._badge_fill = null;
|
||||
r._root_algo_badge_color = null;
|
||||
r._order_metadata = null;
|
||||
r.fill_tuple = ft;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
static readonly IPropertyChangedDetector<Row> _change_detector =
|
||||
ObjectSnapshot.build_detector_except<Row>(nameof(fill_key), nameof(is_selected));
|
||||
|
||||
readonly OrdersModel _model;
|
||||
readonly FillImmutable _fill;
|
||||
readonly OrderRecordImmutable _order;
|
||||
AlgoData? _root_algo => fill_tuple.root_algo;
|
||||
AlgoDefinitionMetadata? _root_algo_meta => fill_tuple.root_algo_meta;
|
||||
Brush _badge_fill;
|
||||
Color? _root_algo_badge_color;
|
||||
OrderMetadata _order_metadata;
|
||||
bool _is_selected;
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) :
|
||||
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
|
||||
this.fill_tuple = fill_tuple;
|
||||
_fill = fill_tuple.fill;
|
||||
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
|
||||
_model = model;
|
||||
account = fill_tuple.account;
|
||||
}
|
||||
|
||||
public FillTuple fill_tuple { get; private set; }
|
||||
public DateTime fill_time => _fill.Timestamp.to_local_time();
|
||||
public TimeInForce time_in_force => _order.time_in_force();
|
||||
public OrderType order_type => _order.order_type();
|
||||
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
|
||||
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
|
||||
public string account { get; private set; }
|
||||
public string account => fill_tuple.account;
|
||||
public string user => _order.user();
|
||||
public Venue exchange => security.Exchange;
|
||||
public Side side => _fill.Side;
|
||||
@@ -301,6 +310,10 @@ namespace mbts.spark {
|
||||
public FillStatus fill_status => _fill.FillStatus;
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set => Set(ref _is_selected, value);
|
||||
}
|
||||
|
||||
// Root Algo
|
||||
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
|
||||
@@ -308,6 +321,11 @@ namespace mbts.spark {
|
||||
public Color root_algo_badge_color => _root_algo_badge_color ?? (_root_algo_badge_color = OrderUtils.badge_color(_root_algo_meta, root_algo_order_metadata)).Value;
|
||||
public string root_algo_display_name => fill_tuple.root_order_security?.display_name() ?? OrderType.None.display_name();
|
||||
|
||||
FillImmutable _fill => fill_tuple.fill;
|
||||
OrderRecordImmutable _order => fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
|
||||
AlgoData? _root_algo => fill_tuple.root_algo;
|
||||
AlgoDefinitionMetadata? _root_algo_meta => fill_tuple.root_algo_meta;
|
||||
|
||||
public bool Equals(Row other) => other != null && other._fill.Equals(_fill) && other._order.Equals(_order);
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||
@@ -316,4 +334,4 @@ namespace mbts.spark {
|
||||
void IRowVM.raise_invalidated() => invalidated?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:api="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:bts="clr-namespace:mbts.ui.controls"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
@@ -49,10 +48,15 @@
|
||||
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView Background="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
<c:DIYGrid.RowStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected}"/>
|
||||
</Style>
|
||||
</c:DIYGrid.RowStyle>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView Background="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
|
||||
@@ -31,5 +31,5 @@ namespace mbts.ui.spark {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row> { }
|
||||
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row, FillsView.Columns> { }
|
||||
}
|
||||
@@ -6,7 +6,6 @@ using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -26,7 +25,6 @@ namespace mbts.spark {
|
||||
FillsView _view;
|
||||
FillsSettings _state;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
|
||||
@@ -36,8 +34,6 @@ namespace mbts.spark {
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
|
||||
}
|
||||
|
||||
event Action<FillsSettings> _state_changed;
|
||||
|
||||
public override string window_type => window_type_name;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
@@ -54,8 +50,7 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
_commands = new List<SparkCommand> {
|
||||
@@ -77,15 +72,6 @@ namespace mbts.spark {
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
_subscriptions = Disposable.Create(() =>
|
||||
_state_changed?.Invoke(
|
||||
_state = new FillsSettings(_state.orders,
|
||||
_vm.get_sorting_criterion().ToList(),
|
||||
_vm.get_filters(),
|
||||
_vm.grid_settings_manager.create()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
@@ -93,4 +79,4 @@ namespace mbts.spark {
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,11 +72,5 @@ namespace bts.shell {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
settings.add_missing_columns(FillsSettings.DefaultGridSettings.columns, out new_settings);
|
||||
}
|
||||
|
||||
[Export(typeof(IFillsGridSettingsMigrationRule))]
|
||||
class EnsureSelectionHighlightTranslucency : IFillsGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
GridSettingsMigrationRules.SelectionHighlightIsTranslucent.apply(settings, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,6 @@ namespace bts.shell.fills {
|
||||
public static FillsSettings Default() =>
|
||||
new FillsSettings(null, null, new FillsFilterStates(), null);
|
||||
|
||||
public uint[] root_securities { get; set; }
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
|
||||
|
||||
@@ -284,6 +284,7 @@ namespace mbts.spark {
|
||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
|
||||
show_warn_on_cross = _default_show_warn_on_cross;
|
||||
enforce_max_clip = _default_enforce_max_clip;
|
||||
status_message_visible = true;
|
||||
}
|
||||
|
||||
public static LadderSettings Default {
|
||||
@@ -351,6 +352,7 @@ namespace mbts.spark {
|
||||
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
|
||||
max_clip = 0,
|
||||
price_display_format = PriceDisplayFormat.Default,
|
||||
status_message_visible = true,
|
||||
};
|
||||
rv.row_height = LadderRender.measure_row_height(rv);
|
||||
return rv;
|
||||
@@ -519,6 +521,9 @@ namespace mbts.spark {
|
||||
[ProtoMember(60)]
|
||||
public PriceDisplayFormat price_display_format { get; set; }
|
||||
|
||||
[ProtoMember(61), DefaultValue(true)]
|
||||
public bool status_message_visible { get; set; }
|
||||
|
||||
public void sanitize() =>
|
||||
sanitize(this);
|
||||
|
||||
@@ -623,7 +628,8 @@ namespace mbts.spark {
|
||||
a.mini_ob == b.mini_ob &&
|
||||
a.enforce_max_clip == b.enforce_max_clip &&
|
||||
a.max_clip == b.max_clip &&
|
||||
a.price_display_format == b.price_display_format;
|
||||
a.price_display_format == b.price_display_format &&
|
||||
a.status_message_visible == b.status_message_visible;
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
profile_equals(this, other) &&
|
||||
|
||||
@@ -79,7 +79,8 @@ namespace mbts.spark {
|
||||
_clear_visibility = new OrderEntryPanelItemVisibilityVM("Clear"),
|
||||
_cancel_buys_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Buys"),
|
||||
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
|
||||
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All");
|
||||
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All"),
|
||||
_status_message_visibility = new OrderEntryPanelItemVisibilityVM("Status Message");
|
||||
|
||||
public LadderSettingsVM(OrdersModel model, SecurityImmutable security) {
|
||||
_model = model;
|
||||
@@ -253,6 +254,7 @@ namespace mbts.spark {
|
||||
yield return _cancel_buys_visibility;
|
||||
yield return _cancel_sells_visibility;
|
||||
yield return _cancel_all_visibility;
|
||||
yield return _status_message_visibility;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +298,7 @@ namespace mbts.spark {
|
||||
yield return _cancel_buys_visibility;
|
||||
yield return _cancel_sells_visibility;
|
||||
yield return _cancel_all_visibility;
|
||||
yield return _status_message_visibility;
|
||||
// other
|
||||
yield return size_display;
|
||||
yield return favorite_algos_vm;
|
||||
@@ -361,6 +364,7 @@ namespace mbts.spark {
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
price_format = settings.net_change;
|
||||
price_display_format = settings.price_display_format;
|
||||
_status_message_visibility.show = settings.status_message_visible;
|
||||
// safety checks
|
||||
show_warn_on_cross = settings.show_warn_on_cross;
|
||||
enforce_max_clip = settings.enforce_max_clip;
|
||||
@@ -438,6 +442,7 @@ namespace mbts.spark {
|
||||
clone.zoom_slider_visible = _zoom_slider_visbility.show;
|
||||
clone.net_change = price_format;
|
||||
clone.price_display_format = price_display_format;
|
||||
clone.status_message_visible = _status_message_visibility.show;
|
||||
// safety checks
|
||||
clone.show_warn_on_cross = show_warn_on_cross;
|
||||
clone.enforce_max_clip = enforce_max_clip;
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace mbts.spark {
|
||||
readonly Property<bool> _auto_clear;
|
||||
readonly PropertyAutoWrapper<LadderSettings, bool> _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom;
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom, _show_status_message;
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
|
||||
double
|
||||
@@ -116,6 +116,7 @@ namespace mbts.spark {
|
||||
(_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible)).set,
|
||||
(_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible)).set,
|
||||
(_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible)).set,
|
||||
(_show_status_message = bool_prop(nameof(show_status_message), s => s.status_message_visible)).set,
|
||||
s => {
|
||||
if (
|
||||
Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) |
|
||||
@@ -341,6 +342,7 @@ namespace mbts.spark {
|
||||
public bool show_cancel_sells => _show_cancel_sells.view;
|
||||
public bool show_cancel_all => _show_cancel_all.view;
|
||||
public bool show_zoom => _show_zoom.view;
|
||||
public bool show_status_message => _show_status_message.view;
|
||||
// left panel scroll props
|
||||
public double left_panel_view_port_height {
|
||||
get => _left_panel_view_port_height;
|
||||
@@ -467,6 +469,10 @@ namespace mbts.spark {
|
||||
settings_invalidated?.Invoke();
|
||||
}
|
||||
|
||||
public void recenter_all_ladders() {
|
||||
_bus?.post(SparkTags.RequestRecenterLadder, null);
|
||||
}
|
||||
|
||||
public Task send_order_async((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm != null && args.button != MouseButton.Middle) {
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
|
||||
@@ -12,6 +12,7 @@ using mbts.ui.spark.algo;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -255,8 +256,9 @@ namespace mbts.spark {
|
||||
DependencyProperty.Register("Orders", typeof(ObservableCollection<LadderOrderVM>), typeof(LadderGridHost), new PropertyMetadata(null, orders_changed));
|
||||
|
||||
static void orders_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is LadderGridHost lgh)
|
||||
WPFHelper.manage_collection_change(e, lgh.orders_collection_changed);
|
||||
LadderGridHost lgh = d as LadderGridHost;
|
||||
WPFHelper.manage_collection_change(e, lgh.orders_collection_changed, src => lgh._ordershost.clear_orders(),
|
||||
src => lgh.orders_collection_changed(lgh, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)src)));
|
||||
}
|
||||
|
||||
void orders_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -488,8 +490,14 @@ namespace mbts.spark {
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
var column = _column_for_point(e.GetPosition(this));
|
||||
if (column.column_type == ColumnType.Price)
|
||||
_scroll_to_center();
|
||||
if (column.column_type == ColumnType.Price) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
_vm?.recenter_all_ladders();
|
||||
} else {
|
||||
_scroll_to_center();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Row Utility Methods
|
||||
|
||||
@@ -627,8 +627,8 @@
|
||||
message to have the topmost Z order
|
||||
-->
|
||||
<GridSplitter Grid.Column="0" Grid.RowSpan="2" Width="5" />
|
||||
<!-- Ladder Status message -->
|
||||
<Button x:Name="_status" Grid.ColumnSpan="4" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Command="{Binding status_click_command}">
|
||||
<!-- Ladder Status message -->
|
||||
<Button x:Name="_status" Grid.ColumnSpan="4" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Command="{Binding status_click_command}" Visibility="{Binding show_status_message, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<Button.Template>
|
||||
<ControlTemplate>
|
||||
<TextBlock Text="{Binding status_message}" Background="{Binding status_background}" Padding="5,0" />
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
DataContext = vm;
|
||||
}
|
||||
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public MiniOrderBookRowView() : base() {
|
||||
FocusManager.SetIsFocusScope(this, true);
|
||||
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
|
||||
|
||||
@@ -650,13 +650,12 @@ namespace mbts.spark {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
|
||||
: base(Columns.LastMessage, filter, DataGridFilterKnownTemplates.Regex) {
|
||||
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
Header = "Last Message";
|
||||
Width = DefaultColumnWidths.LastMessage;
|
||||
TextBinding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
ToolTipBinding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
|
||||
|
||||
@@ -245,7 +245,7 @@ namespace mbts.spark {
|
||||
.Subscribe(is_visible => {
|
||||
if (is_visible) {
|
||||
var init = _cached_fills_settings.to_startup_data();
|
||||
fills_vm.value = new FillsVM(bus, model, Observable.Return(init), model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
|
||||
fills_vm.value = new FillsVM(bus, model, init, model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), _fills_settings_manager);
|
||||
} else if (fills_vm.value != null) {
|
||||
_cached_fills_settings = _get_fills_settings(fills_vm.value);
|
||||
fills_vm.value = null;
|
||||
|
||||
Loaded 100 of 272 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user