Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d24062f03 | ||
|
|
44e0eae281 | ||
|
|
152ab05b4e | ||
|
|
4257a172c1 | ||
|
|
721a08c4b4 | ||
|
|
41a3535e84 | ||
|
|
626629bf06 | ||
|
|
487aaef02f | ||
|
|
f22fc03e56 | ||
|
|
5f45ddf210 | ||
|
|
d6a533ca5c | ||
|
|
20e58dc80b | ||
|
|
5773945791 | ||
|
|
721c889235 | ||
|
|
9957af655f | ||
|
|
eab92fb486 | ||
|
|
9f1cd47cfa | ||
|
|
90c3edbbe7 | ||
|
|
adee8b584c | ||
|
|
329ad323e0 | ||
|
|
c713b97441 | ||
|
|
eb79264b9b |
No files matched your search
+1
-1
@@ -34,4 +34,4 @@ trace_data/
|
|||||||
/doc/user/public/*
|
/doc/user/public/*
|
||||||
.ionide/*
|
.ionide/*
|
||||||
/.ignore
|
/.ignore
|
||||||
/build
|
CMakeDeps.cmake
|
||||||
Vendored
+3
-8
@@ -5,9 +5,7 @@
|
|||||||
"includePath": [
|
"includePath": [
|
||||||
"/usr/include",
|
"/usr/include",
|
||||||
"/usr/local/include",
|
"/usr/local/include",
|
||||||
"${workspaceRoot}",
|
"${workspaceRoot}"
|
||||||
"${workspaceFolder}/src",
|
|
||||||
"${workspaceRoot}/externals/linux/include"
|
|
||||||
],
|
],
|
||||||
"defines": [],
|
"defines": [],
|
||||||
"intelliSenseMode": "clang-x64",
|
"intelliSenseMode": "clang-x64",
|
||||||
@@ -15,16 +13,13 @@
|
|||||||
"path": [
|
"path": [
|
||||||
"/usr/include",
|
"/usr/include",
|
||||||
"/usr/local/include",
|
"/usr/local/include",
|
||||||
"${workspaceRoot}",
|
"${workspaceRoot}"
|
||||||
"${workspaceFolder}/src",
|
|
||||||
"${workspaceRoot}/externals/linux/include"
|
|
||||||
],
|
],
|
||||||
"limitSymbolsToIncludedHeaders": true,
|
"limitSymbolsToIncludedHeaders": true,
|
||||||
"databaseFilename": ""
|
"databaseFilename": ""
|
||||||
},
|
},
|
||||||
"cStandard": "c11",
|
"cStandard": "c11",
|
||||||
"cppStandard": "c++17",
|
"cppStandard": "c++17"
|
||||||
"compileCommands": "${workspaceFolder}/build/ninja_debug/compile_commands.json"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Windows",
|
"name": "Windows",
|
||||||
|
|||||||
Vendored
+5
-6
@@ -46,7 +46,7 @@
|
|||||||
"sourceFileMap": {
|
"sourceFileMap": {
|
||||||
"/home/bts/spark/": "${workspaceRoot}"
|
"/home/bts/spark/": "${workspaceRoot}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "(gdb) Launch",
|
"name": "(gdb) Launch",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
@@ -345,12 +345,11 @@
|
|||||||
"name": ".NET Core Remote Launch",
|
"name": ".NET Core Remote Launch",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "/usr/bin/dotnet",
|
"program": "sparkcmd",
|
||||||
"args": [
|
"args": [
|
||||||
"sparkcmd.dll",
|
|
||||||
"test",
|
"test",
|
||||||
"test_join_create_and_takeout_market"
|
|
||||||
],
|
],
|
||||||
|
"env": {"LD_LIBRARY_PATH" : "."},
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "/home/bts/spark/build/ninja_debug/server",
|
"cwd": "/home/bts/spark/build/ninja_debug/server",
|
||||||
"pipeTransport": {
|
"pipeTransport": {
|
||||||
@@ -361,11 +360,11 @@
|
|||||||
"debuggerPath": "/home/bts/vsdbg/vsdbg",
|
"debuggerPath": "/home/bts/vsdbg/vsdbg",
|
||||||
"pipeCwd": "${workspaceRoot}",
|
"pipeCwd": "${workspaceRoot}",
|
||||||
"quoteArgs": true
|
"quoteArgs": true
|
||||||
},
|
},
|
||||||
"sourceFileMap": {
|
"sourceFileMap": {
|
||||||
"/home/bts/spark/": "${workspaceRoot}"
|
"/home/bts/spark/": "${workspaceRoot}"
|
||||||
},
|
},
|
||||||
"justMyCode": false
|
"justMyCode": false
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Vendored
+10
-19
@@ -14,21 +14,24 @@
|
|||||||
"focus": false,
|
"focus": false,
|
||||||
"panel": "shared"
|
"panel": "shared"
|
||||||
},
|
},
|
||||||
"group": "build"
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "server_build",
|
"label": "server_build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": ".\\tools\\buildsparkd.cmd",
|
"command": ".\\tools\\buildsparkd.cmd",
|
||||||
"problemMatcher": {
|
"problemMatcher": {
|
||||||
"base": "$gcc",
|
"base": "$gcc",
|
||||||
"owner": "cpp",
|
"owner": "cpp",
|
||||||
"fileLocation": [
|
"fileLocation": [
|
||||||
"relative",
|
"relative",
|
||||||
"${workspaceFolder}"
|
"${workspaceFolder}"
|
||||||
],
|
],
|
||||||
"pattern": {
|
"pattern": {
|
||||||
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
|
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
|
||||||
"file": 8,
|
"file": 8,
|
||||||
"line": 9,
|
"line": 9,
|
||||||
"column": 10,
|
"column": 10,
|
||||||
@@ -46,18 +49,6 @@
|
|||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"label": "linux build (Debug)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "cd build/ninja_debug; time ninja",
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
},
|
|
||||||
"problemMatcher": [
|
|
||||||
"$gcc"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -48,6 +48,7 @@ else ()
|
|||||||
add_subdirectory(doc/user)
|
add_subdirectory(doc/user)
|
||||||
add_subdirectory(src/sdk)
|
add_subdirectory(src/sdk)
|
||||||
add_subdirectory(src/data_files)
|
add_subdirectory(src/data_files)
|
||||||
|
add_subdirectory(src/client)
|
||||||
|
|
||||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
|
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
|
||||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
|
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
|
||||||
@@ -59,7 +60,17 @@ else ()
|
|||||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||||
COMMENT "Creating server runtime layout"
|
COMMENT "Creating server runtime layout"
|
||||||
|
DEPENDS sparkd data_files_dir spark_client_dir
|
||||||
DEPENDS sparkd data_files_dir)
|
DEPENDS sparkd data_files_dir)
|
||||||
|
|
||||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||||
|
|
||||||
|
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||||
|
add_custom_command(TARGET server_layout POST_BUILD
|
||||||
|
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.0/linux-x64/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
|
||||||
|
else()
|
||||||
|
add_custom_command(TARGET server_layout POST_BUILD
|
||||||
|
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.0/linux-x64/publish/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
|
||||||
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title="Client API"
|
title="Client API"
|
||||||
redirect_to = "client/getting-started/overview/"
|
template="client.html"
|
||||||
sort_by="weight"
|
sort_by="weight"
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title="Getting Started"
|
title="Getting Started"
|
||||||
|
template="client.html"
|
||||||
sort_by="weight"
|
sort_by="weight"
|
||||||
weight = 1
|
weight = 1
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -1,25 +1,9 @@
|
|||||||
+++
|
+++
|
||||||
title = "Install Prerequisites"
|
title = "Install Prerequisites"
|
||||||
weight = 10
|
template="client.html"
|
||||||
|
weight = 1
|
||||||
+++
|
+++
|
||||||
|
|
||||||
# C#/.NET
|
## Coming Soon
|
||||||
|
|
||||||
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://www.nuget.org/packages/spark_sdk/) 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
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
+++
|
|
||||||
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.
|
|
||||||
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
+++
|
|
||||||
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
|
|
||||||
```
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
+++
|
|
||||||
title="Samples"
|
|
||||||
sort_by="weight"
|
|
||||||
weight = 30
|
|
||||||
+++
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
+++
|
|
||||||
title = "AdminService"
|
|
||||||
+++
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
+++
|
|
||||||
title = "OrderService"
|
|
||||||
+++
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
+++
|
|
||||||
title = "PositionService"
|
|
||||||
+++
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
+++
|
|
||||||
title = "SyntheticService"
|
|
||||||
+++
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
+++
|
|
||||||
title="Services"
|
|
||||||
#sort_by="weight"
|
|
||||||
weight = 10
|
|
||||||
+++
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
+++
|
|
||||||
title = "Market Data"
|
|
||||||
+++
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title="Algo API"
|
title="Algo API"
|
||||||
|
template="documentation.html"
|
||||||
redirect_to = "server/getting-started/overview/"
|
redirect_to = "server/getting-started/overview/"
|
||||||
sort_by="weight"
|
sort_by="weight"
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title="Getting Started"
|
title="Getting Started"
|
||||||
|
template="documentation.html"
|
||||||
sort_by="weight"
|
sort_by="weight"
|
||||||
weight = 1
|
weight = 1
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ The first step is to start the spark_sdk Docker container with the examples dire
|
|||||||
|
|
||||||
Windows Command Prompt:
|
Windows Command Prompt:
|
||||||
{% clipable() %}
|
{% clipable() %}
|
||||||
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
|
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
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
**Powershell** / Linux / Mac:
|
**Powershell** / Linux / Mac:
|
||||||
{% clipable() %}
|
{% clipable() %}
|
||||||
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
|
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
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
### Attach
|
### Attach
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ weight = 2
|
|||||||
|
|
||||||
## Request Access 🔑
|
## Request Access 🔑
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,5 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title="Reference"
|
title="Reference"
|
||||||
|
template="documentation.html"
|
||||||
sort_by="weight"
|
sort_by="weight"
|
||||||
weight = 2
|
weight = 2
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -6,9 +6,3 @@ weight = 40
|
|||||||
## I need to make a call that might take a few seconds. What is the best way to do this?
|
## 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.
|
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.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title="Samples"
|
title="Samples"
|
||||||
|
template="documentation.html"
|
||||||
sort_by="weight"
|
sort_by="weight"
|
||||||
weight = 3
|
weight = 3
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{% 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 %}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{% 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>
|
<!DOCTYPE html>
|
||||||
<html lang="en-us">
|
<html lang="en-gb">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/>
|
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/>
|
||||||
<link rel="icon" href="{{ get_url(path="favicon.ico") }}">
|
<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">
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||||
<script> dataLayer = [{ 'visitorType': 'geek' }]; </script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -37,13 +36,13 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="content content--reversed">
|
<div class="content {% block extra_content_class %}{% endblock extra_content_class %}">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<h1>Spark SDK</h1>
|
<h1>Spark SDK</h1>
|
||||||
<p class="hero__tagline">
|
<p class="hero__tagline">
|
||||||
Spark consists of a client UI
|
Spark consists of a client UI
|
||||||
(<a href="https://www.bluetradingsystems.com/bts-spark#ladder-algo-screen">Spark Client</a>),
|
(<a class="white" 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.
|
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.
|
Use the Spark SDK to extend Spark Server with your custom Algos, then use the Client SDK to launch and control them.
|
||||||
</p>
|
</p>
|
||||||
@@ -65,7 +64,7 @@
|
|||||||
<a href="{{ get_url(path="@/server/_index.md") }}" class="button">Get started</a>
|
<a href="{{ get_url(path="@/server/_index.md") }}" class="button">Get started</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="selling-point">
|
<div class="selling-point disabled">
|
||||||
<h2>
|
<h2>
|
||||||
Client SDK
|
Client SDK
|
||||||
<i class="fa fa-gamepad" aria-hidden="true"></i>
|
<i class="fa fa-gamepad" aria-hidden="true"></i>
|
||||||
@@ -73,9 +72,19 @@
|
|||||||
<p>
|
<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.
|
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>
|
</p>
|
||||||
<a href="{{ get_url(path="@/client/_index.md") }}" class="button">Get started</a>
|
<a href="" class="button" disabled>Get started</a>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,15 +94,6 @@
|
|||||||
© Blue Trading Systems
|
© Blue Trading Systems
|
||||||
</footer>
|
</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="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_index.en.js") }}"></script>
|
||||||
<script type="text/javascript" src="{{ get_url(path="search.js") }}"></script>
|
<script type="text/javascript" src="{{ get_url(path="search.js") }}"></script>
|
||||||
|
|||||||
@@ -1,39 +1,13 @@
|
|||||||
{% extends "index.html" %}
|
{% extends "documentation.html" %}
|
||||||
{# this has access to the "page" variable but documentation.html does not #}
|
|
||||||
|
|
||||||
{% block title %}{{ page.title }} | {{ super() }} {% endblock title %}
|
{% block title %}{{ page.title }} | {{ super() }} {% endblock title %}
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>{{page.title}}</h1>
|
||||||
|
{{page.content | safe}}
|
||||||
|
{% endblock doc_content %}
|
||||||
|
|
||||||
{% block next_link %}
|
{% block next_link %}
|
||||||
{% endblock next_link %}
|
<p>
|
||||||
|
|
||||||
{% 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 %}
|
{% if page.lighter %}
|
||||||
<a href="{{ page.lighter.permalink }}">
|
<a href="{{ page.lighter.permalink }}">
|
||||||
<i class="fa fa-chevron-left fa-fw" aria-hidden="true"></i>
|
<i class="fa fa-chevron-left fa-fw" aria-hidden="true"></i>
|
||||||
@@ -47,7 +21,5 @@
|
|||||||
<i class="fa fa-chevron-right fa-fw" aria-hidden="true"></i>
|
<i class="fa fa-chevron-right fa-fw" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
{% endblock next_link %}
|
||||||
</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 /home/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)
|
||||||
@@ -4,15 +4,6 @@ weight = 3
|
|||||||
sort_by = "weight"
|
sort_by = "weight"
|
||||||
insert_anchor_links = "right"
|
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
|
## 2.15.0
|
||||||
### User Visible Changes
|
### User Visible Changes
|
||||||
- Added ability to hide Ladder status messages via Ladder Settings -> Left Panel tab -> Show/Hide groupbox -> Status Message
|
- Added ability to hide Ladder status messages via Ladder Settings -> Left Panel tab -> Show/Hide groupbox -> Status Message
|
||||||
|
|||||||
+1
-1
Submodule docker/linux updated: 0df20ec20a...e106e6e704.
+1
-1
Submodule externals updated: c695e96940...af352fd677.
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(spark_client)
|
||||||
|
include(${CMAKE_SOURCE_DIR}/tools/safe_dep_glob.cmake)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE spark_client_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cs)
|
||||||
|
file(GLOB_RECURSE spark_client_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.csproj)
|
||||||
|
file(GLOB_RECURSE spark_ui_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.cs)
|
||||||
|
file(GLOB_RECURSE spark_ui_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.csproj)
|
||||||
|
list(REMOVE_ITEM spark_ui_sources ${spark_client_sources} )
|
||||||
|
list(REMOVE_ITEM spark_ui_csproj ${spark_client_csproj} )
|
||||||
|
|
||||||
|
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/../
|
||||||
|
COMMAND /bin/bash ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh
|
||||||
|
ARGS ${CMAKE_BUILD_TYPE} ${CMAKE_BINARY_DIR}
|
||||||
|
COMMENT "Building sparkcmd in netcore3"
|
||||||
|
DEPENDS libspark ${spark_client_sources} ${spark_client_csproj} ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh)
|
||||||
|
|
||||||
|
add_custom_target(spark_client_dir ALL DEPENDS ${CMAKE_BINARY_DIR}/../)
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<SparkRoot>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)\..\..))</SparkRoot>
|
<SparkRoot>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)\..\..))</SparkRoot>
|
||||||
<SourceRoot>$([System.IO.Path]::GetFullPath($(SparkRoot)\src))</SourceRoot>
|
<SourceRoot>$([System.IO.Path]::GetFullPath($(SparkRoot)\src))</SourceRoot>
|
||||||
<Externals>$(SparkRoot)\externals\windows</Externals>
|
<Externals>$(SparkRoot)\externals\windows</Externals>
|
||||||
|
<Externals Condition=" '$(OS)' != 'Windows_NT' ">$(SparkRoot)/externals/linux</Externals>
|
||||||
<DepotName>$([System.IO.Path]::GetFileName($([System.IO.Path]::GetFullPath($(SparkRoot)))))</DepotName>
|
<DepotName>$([System.IO.Path]::GetFileName($([System.IO.Path]::GetFullPath($(SparkRoot)))))</DepotName>
|
||||||
<ErrorReport>None</ErrorReport>
|
<ErrorReport>None</ErrorReport>
|
||||||
<Prefer32Bit>False</Prefer32Bit>
|
<Prefer32Bit>False</Prefer32Bit>
|
||||||
@@ -19,20 +20,25 @@
|
|||||||
<OutputPath>$(BaseOutputPath)\bin</OutputPath>
|
<OutputPath>$(BaseOutputPath)\bin</OutputPath>
|
||||||
<BaseIntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
<BaseIntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||||
<IntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</IntermediateOutputPath>
|
<IntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</IntermediateOutputPath>
|
||||||
|
<BaseIntermediateOutputPath Condition=" '$(OS)' != 'Windows_NT' ">$(BaseOutputPath)\linuxobj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||||
|
<IntermediateOutputPath Condition=" '$(OS)' != 'Windows_NT' ">$(BaseOutputPath)\linuxobj\$(MSBuildProjectName)</IntermediateOutputPath>
|
||||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
|
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvailableItemName Include="Proto2">
|
<AvailableItemName Include="Proto2">
|
||||||
<Targets>CompileProto3</Targets>
|
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto2</Targets>
|
||||||
|
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto2Linux</Targets>
|
||||||
</AvailableItemName>
|
</AvailableItemName>
|
||||||
<AvailableItemName Include="Proto3">
|
<AvailableItemName Include="Proto3">
|
||||||
<Targets>CompileProto3</Targets>
|
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto3</Targets>
|
||||||
|
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto3Linux</Targets>
|
||||||
</AvailableItemName>
|
</AvailableItemName>
|
||||||
<AvailableItemName Include="Proto3Excel">
|
<AvailableItemName Include="Proto3Excel">
|
||||||
<Targets>CompileProto3Excel</Targets>
|
<Targets Condition=" '$(OS)' == 'Windows_NT' ">CompileProto3Excel</Targets>
|
||||||
</AvailableItemName>
|
</AvailableItemName>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -43,6 +49,14 @@
|
|||||||
<protogen>$(Externals)\bin\protogen.exe</protogen>
|
<protogen>$(Externals)\bin\protogen.exe</protogen>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Label="ProtoBuf" Condition=" '$(OS)' != 'Windows_NT' ">
|
||||||
|
<ProtocPluginBase>$(SparkRoot)/src/lib/api/service_compiler</ProtocPluginBase>
|
||||||
|
<ProtoInclude>$(Externals)/include</ProtoInclude>
|
||||||
|
<protoc>$(Externals)/bin/protoc</protoc>
|
||||||
|
<protoc_rename>$(Externals)/bin/lowercase_files.sh</protoc_rename>
|
||||||
|
<protogen>$(Externals)/bin/protogen</protogen>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<Proto2>
|
<Proto2>
|
||||||
</Proto2>
|
</Proto2>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<Target Name="CompileProto2"
|
<Target Name="CompileProto2"
|
||||||
Condition="'@(Proto2)'!=''"
|
Condition="'@(Proto2)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||||
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
|
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
|
||||||
Outputs="$(GenDir)\proto2\%(Proto2.filename).cs"
|
Outputs="$(GenDir)\proto2\%(Proto2.filename).cs"
|
||||||
AfterTargets="PreBuildEvent">
|
AfterTargets="PreBuildEvent">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
passes wipes out the additional compile includes that we add.
|
passes wipes out the additional compile includes that we add.
|
||||||
-->
|
-->
|
||||||
<Target Name="CompileProto3" AfterTargets="GetAssemblyVersion"
|
<Target Name="CompileProto3" AfterTargets="GetAssemblyVersion"
|
||||||
Condition="'@(Proto3)'!=''"
|
Condition="'@(Proto3)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||||
Inputs="$(protoc);
|
Inputs="$(protoc);
|
||||||
%(Proto3.Identity);$(ProtocPluginBase)\plugin.py;
|
%(Proto3.Identity);$(ProtocPluginBase)\plugin.py;
|
||||||
$(ProtocPluginBase)\templates\c#\client.pb.cs;
|
$(ProtocPluginBase)\templates\c#\client.pb.cs;
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<Target Name="CompileProto3Excel" AfterTargets="GetAssemblyVersion"
|
<Target Name="CompileProto3Excel" AfterTargets="GetAssemblyVersion"
|
||||||
Condition="'@(Proto3Excel)'!=''"
|
Condition="'@(Proto3Excel)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||||
Inputs="$(protoc);
|
Inputs="$(protoc);
|
||||||
%(Proto3Excel.Identity);$(ProtocPluginBase)\plugin.py;
|
%(Proto3Excel.Identity);$(ProtocPluginBase)\plugin.py;
|
||||||
$(ProtocPluginBase)\templates\excel\excel.pb.cs;"
|
$(ProtocPluginBase)\templates\excel\excel.pb.cs;"
|
||||||
@@ -82,12 +82,74 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
|
||||||
|
<Target Name="CompileProto2Linux"
|
||||||
|
Condition="'@(Proto2)'!='' AND '$(OS)' != 'Windows_NT' "
|
||||||
|
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
|
||||||
|
Outputs="$(GenDir)/proto2/%(Proto2.filename).cs"
|
||||||
|
AfterTargets="PreBuildEvent">
|
||||||
|
<PropertyGroup>
|
||||||
|
<SourceRelativeFile>$([System.String]::Copy('%(proto2.fullpath)').Replace($(SourceRoot), ''))</SourceRelativeFile>
|
||||||
|
<SourceRelativeDir>$([System.IO.Path]::GetDirectoryName($(SourceRelativeFile)))</SourceRelativeDir>
|
||||||
|
<WorkingDirectory>$(GenDir)/proto2</WorkingDirectory>
|
||||||
|
</PropertyGroup>
|
||||||
|
<MakeDir Directories="$(WorkingDirectory)" />
|
||||||
|
<Copy SourceFiles="%(Proto2.FullPath)" DestinationFolder="$(WorkingDirectory)" SkipUnchangedFiles="True" UseHardLinksIfPossible="True" />
|
||||||
|
<Error Text="protogen.exe does not exist. If you forgot to clone with externals try: git submodule update --init --recursive" Condition="!Exists($(protogen))" />
|
||||||
|
<Message Importance="High" Text="Doing Proto2 %(proto2.filename)" />
|
||||||
|
<Exec Command="$(Protogen) %(Proto2.filename)%(proto2.extension) --csharp_out=. -I. -I$(SourceRoot) -I$(Externals)/include"
|
||||||
|
WorkingDirectory="$(WorkingDirectory)" />
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="$(WorkingDirectory)/%(Proto2.filename).cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="CompileProto3Linux" AfterTargets="GetAssemblyVersion"
|
||||||
|
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' "
|
||||||
|
Inputs="$(protoc);
|
||||||
|
%(Proto3.Identity);$(ProtocPluginBase)/plugin.py;
|
||||||
|
$(ProtocPluginBase)/templates/c#/client.pb.cs;
|
||||||
|
$(ProtocPluginBase)/templates/c#/server.pb.cs;
|
||||||
|
$(ProtocPluginBase)/templates/c#/immutable.pb.cs;"
|
||||||
|
Outputs="%(Proto3.OutCS);%(Proto3.OutCSServer);%(Proto3.OutCSClient);%(Proto3.OutCSImmutable)">
|
||||||
|
<MakeDir Directories="%(Proto3.OutBase)" />
|
||||||
|
<MakeDir Directories="$(IntermediateOutputPath)/gen" />
|
||||||
|
|
||||||
|
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
|
||||||
|
<Exec Command="
|
||||||
|
PYTHONPATH=$(Externals)/python $(protoc) \
|
||||||
|
%(Proto3.FullPath) \
|
||||||
|
--error_format=gcc \
|
||||||
|
--proto_path=$(protoinc):$(SourceRoot) \
|
||||||
|
--plugin='protoc-gen-service=$(ProtocPluginBase)/plugin.py' \
|
||||||
|
--service_out=lang=c#,strip_out_path=1:%(Proto3.OutBase) \
|
||||||
|
--csharp_out=%(CompileOptions)%(Proto3.OutBase)"
|
||||||
|
WorkingDirectory="%(relativedir)" />
|
||||||
|
|
||||||
|
<!-- <Exec Command="$(protoc_rename) %(Proto3.OutBase)" /> -->
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="%(Proto3.OutCS)" />
|
||||||
|
<Compile Include="%(Proto3.OutCSClient)" />
|
||||||
|
<Compile Include="%(Proto3.OutCSServer)" />
|
||||||
|
<Compile Include="%(Proto3.OutCSImmutable)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- <Target Name="RenameProto3Linux" AfterTargets="CompileProto3Linux"
|
||||||
|
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' ">
|
||||||
|
<Move SourceFiles="$(OutBase)\Apicore.cs" DestinationFiles="$(OutBase)\apicore.cs" />
|
||||||
|
<Move SourceFiles="$(OutBase)\Apioptions.cs" DestinationFiles="$(OutBase)\apioptions.cs" />
|
||||||
|
<Move SourceFiles="$(OutBase)\Co2.cs" DestinationFiles="$(OutBase)\co2.cs" />
|
||||||
|
</Target> -->
|
||||||
|
|
||||||
|
|
||||||
<Target Name="AfterClean" AfterTargets="Clean">
|
<Target Name="AfterClean" AfterTargets="Clean">
|
||||||
<Delete Files="%(Proto3.OutCS)" />
|
<Delete Files="%(Proto3.OutCS)" />
|
||||||
<Delete Files="%(Proto3.OutCSClient)" />
|
<Delete Files="%(Proto3.OutCSClient)" />
|
||||||
<Delete Files="%(Proto3.OutCSServer)" />
|
<Delete Files="%(Proto3.OutCSServer)" />
|
||||||
<Delete Files="%(Proto3.OutCSImmutable)" />
|
<Delete Files="%(Proto3.OutCSImmutable)" />
|
||||||
<Delete Files="$(GenDir)\proto2\*" />
|
<Delete Files="$(GenDir)/proto2/*" />
|
||||||
<Delete Files="%(Proto3Excel.OutCSExcel)" />
|
<Delete Files="%(Proto3Excel.OutCSExcel)" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#$1 = Debug / Release (CMAKE_BUILD_TYPE), $2 = CMAKE_BINARY_DIR
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
echo $PWD dotnet build -c $1 ./sparkcmd/
|
||||||
|
dotnet build -c $1 -o $2/../$1/bin/netcoreapp3.0/linux-x64 ./sparkcmd/
|
||||||
|
|
||||||
|
if [ "$1" == "Release" ]
|
||||||
|
then
|
||||||
|
dotnet publish -c $1 -o $2/../$1/bin/netcoreapp3.0/linux-x64 ./sparkcmd/
|
||||||
|
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.0/linux-x64/publish
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.0/linux-x64
|
||||||
@@ -3,13 +3,11 @@ using bts.api.core;
|
|||||||
using bts.api.orderman;
|
using bts.api.orderman;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using mbts;
|
using mbts;
|
||||||
using mbts.config;
|
|
||||||
using mbts.spark;
|
using mbts.spark;
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.spark;
|
using mbts.ui.spark;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@@ -21,26 +19,20 @@ namespace bts.shell {
|
|||||||
readonly IBus _bus;
|
readonly IBus _bus;
|
||||||
readonly OrdersModel _model;
|
readonly OrdersModel _model;
|
||||||
readonly ErrorReporter _error_reporter;
|
readonly ErrorReporter _error_reporter;
|
||||||
readonly bool _close_on_send;
|
|
||||||
IReadOnlyDictionary<uint, SecurityMD> _md_map;
|
IReadOnlyDictionary<uint, SecurityMD> _md_map;
|
||||||
TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> _orders;
|
TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> _orders;
|
||||||
IDisposable _dispose;
|
IDisposable _dispose;
|
||||||
|
|
||||||
[ImportingConstructor]
|
[ImportingConstructor]
|
||||||
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter)
|
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter) {
|
||||||
: this(bus, model, error_reporter, Configuration.get<bts.config.OrderTicketOptions>().close_on_send) { }
|
|
||||||
|
|
||||||
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter, bool close_on_send) {
|
|
||||||
_bus = bus;
|
_bus = bus;
|
||||||
_model = model;
|
_model = model;
|
||||||
_error_reporter = error_reporter;
|
_error_reporter = error_reporter;
|
||||||
_close_on_send = close_on_send;
|
|
||||||
_dispose = Disposable.Create(
|
_dispose = Disposable.Create(
|
||||||
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
|
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
|
||||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
|
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
|
||||||
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
|
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
|
||||||
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
|
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
|
||||||
bus.subscribe(SparkTags.ShowOrderTicketAutoLookupTag).Subscribe(_show_order_tickets),
|
|
||||||
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
|
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -62,28 +54,17 @@ namespace bts.shell {
|
|||||||
void _show_order_ticket(SecurityMD smd) =>
|
void _show_order_ticket(SecurityMD smd) =>
|
||||||
_show_order_window(new OrderTicket {
|
_show_order_window(new OrderTicket {
|
||||||
DataContext = new NewOrderTicketVM(_model, smd,
|
DataContext = new NewOrderTicketVM(_model, smd,
|
||||||
Observable.Return(LadderSettings.Default)) {
|
Observable.Return(LadderSettings.Default))
|
||||||
close_on_send = _close_on_send
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
|
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
|
||||||
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
|
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
|
||||||
_show_order_window(new OrderTicket {
|
_show_order_window(new OrderTicket {
|
||||||
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
|
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
|
||||||
Observable.Return(LadderSettings.Default.calculator_buttons)) {
|
Observable.Return(LadderSettings.Default.calculator_buttons))
|
||||||
close_on_send = _close_on_send
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _show_order_tickets(IImmutableSet<uint> securities) {
|
|
||||||
foreach (var id in securities.empty_if_null()) {
|
|
||||||
if (_md_map.TryGetValue(id, out var smd))
|
|
||||||
_show_order_ticket(smd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _show_modify_order(uint order_key) {
|
void _show_modify_order(uint order_key) {
|
||||||
if (_orders.state.TryGetValue(order_key, out var ori) && ori.sec.HasValue
|
if (_orders.state.TryGetValue(order_key, out var ori) && ori.sec.HasValue
|
||||||
&& (ori.order.HasDef && ori.order.Def.HasCustomDef && !(ori.order.Def.CustomDef.HasStop || ori.order.Def.CustomDef.HasStopLimit))) {
|
&& (ori.order.HasDef && ori.order.Def.HasCustomDef && !(ori.order.Def.CustomDef.HasStop || ori.order.Def.CustomDef.HasStopLimit))) {
|
||||||
@@ -97,4 +78,4 @@ namespace bts.shell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,6 @@ namespace mbts.ui {
|
|||||||
AttachAlgosKey = nameof(AttachAlgosKey),
|
AttachAlgosKey = nameof(AttachAlgosKey),
|
||||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||||
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
|
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
|
||||||
CopyBoxesGeometryKey = nameof(CopyBoxesGeometryKey),
|
|
||||||
DataEditGeometryKey = nameof(DataEditGeometryKey),
|
DataEditGeometryKey = nameof(DataEditGeometryKey),
|
||||||
DetachAlgosKey = nameof(DetachAlgosKey),
|
DetachAlgosKey = nameof(DetachAlgosKey),
|
||||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||||
@@ -46,7 +45,6 @@ namespace mbts.ui {
|
|||||||
KeyGeometryKey = nameof(KeyGeometryKey),
|
KeyGeometryKey = nameof(KeyGeometryKey),
|
||||||
LetterAKey = nameof(LetterAKey),
|
LetterAKey = nameof(LetterAKey),
|
||||||
LetterDKey = nameof(LetterDKey),
|
LetterDKey = nameof(LetterDKey),
|
||||||
MenuOrientationGeometryKey = nameof(MenuOrientationGeometryKey),
|
|
||||||
OrdersGeometryKey = nameof(OrdersGeometryKey),
|
OrdersGeometryKey = nameof(OrdersGeometryKey),
|
||||||
PauseGeometryKey = nameof(PauseGeometryKey),
|
PauseGeometryKey = nameof(PauseGeometryKey),
|
||||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||||
@@ -64,8 +62,6 @@ namespace mbts.ui {
|
|||||||
|
|
||||||
public static Geometry PlayGeometry => Geometry.Parse(get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
|
public static Geometry PlayGeometry => Geometry.Parse(get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
|
||||||
|
|
||||||
static readonly Pen _copy_boxes_pen = new Pen(Brushes.Black, HighDpiExtensions.RoundLayoutValue(5)).frozen();
|
|
||||||
|
|
||||||
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
|
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
|
||||||
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
|
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
|
||||||
|
|
||||||
@@ -160,7 +156,6 @@ namespace mbts.ui {
|
|||||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||||
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
|
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
|
||||||
public static ImageSource CopyBoxes => create_icon(CopyBoxesGeometryKey, null, _copy_boxes_pen);
|
|
||||||
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
|
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
|
||||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||||
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
|
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
|
||||||
@@ -184,7 +179,6 @@ namespace mbts.ui {
|
|||||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||||
public static ImageSource Key => create_icon(KeyGeometryKey);
|
public static ImageSource Key => create_icon(KeyGeometryKey);
|
||||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||||
public static ImageSource MenuOrientation => create_icon(MenuOrientationGeometryKey);
|
|
||||||
public static ImageSource Pause => create_icon(PauseGeometryKey);
|
public static ImageSource Pause => create_icon(PauseGeometryKey);
|
||||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||||
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
||||||
|
|||||||
@@ -46,18 +46,6 @@
|
|||||||
</sys:String>
|
</sys:String>
|
||||||
</x:Array>
|
</x:Array>
|
||||||
|
|
||||||
<x:Array x:Key="{x:Static mbts:Icons.CopyBoxesGeometryKey}" Type="{x:Type sys:String}">
|
|
||||||
<sys:String>
|
|
||||||
M-55,5 L-55,55 L-5,55
|
|
||||||
</sys:String>
|
|
||||||
<sys:String>
|
|
||||||
M-40,-10 L-40,40 L10,40 L10,-10 L-40,-10
|
|
||||||
</sys:String>
|
|
||||||
<sys:String>
|
|
||||||
M-10,10 L40,10 L40,-40 L-10,-40 L-10,10
|
|
||||||
</sys:String>
|
|
||||||
</x:Array>
|
|
||||||
|
|
||||||
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
|
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
|
||||||
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
|
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
|
||||||
</sys:String>
|
</sys:String>
|
||||||
@@ -292,10 +280,6 @@
|
|||||||
M22.399981,2.4000006C22.099982,2.4000006 21.899982,2.5 21.599982,2.6999998 21.199983,3.1000004 21.199983,3.8000002 21.599982,4.2000003L26.199979,8.9000001C26.599978,9.3000002 27.299977,9.3000002 27.699977,8.9000001 28.099977,8.5000002 28.099977,7.8000002 27.699977,7.4000006L23.099981,2.6999998C22.899981,2.5,22.699981,2.4000006,22.399981,2.4000006z M21.499982,0C22.299982,0,23.199981,0.30000019,23.79998,0.90000057L29.299976,6.4000001C30.599975,7.7000003,30.599975,9.7000003,29.299976,11L22.599981,17.7C21.299983,19,19.299984,19,17.999985,17.7L17.299985,17 12.699989,21.6 10.399991,20.900001 10.899991,23.400001 8.4999928,22.8 8.9999924,25.5 6.2999945,25.1 6.7999945,27.6 4.2999964,27 4.7999954,29.6 2.4999971,32 0,26.6 13.499989,13.1 16.999986,16.6 12.699989,12.3C11.399991,11,11.399991,9.0000002,12.699989,7.7000003L19.399984,1C19.899983,0.30000019,20.699983,0,21.499982,0z
|
M22.399981,2.4000006C22.099982,2.4000006 21.899982,2.5 21.599982,2.6999998 21.199983,3.1000004 21.199983,3.8000002 21.599982,4.2000003L26.199979,8.9000001C26.599978,9.3000002 27.299977,9.3000002 27.699977,8.9000001 28.099977,8.5000002 28.099977,7.8000002 27.699977,7.4000006L23.099981,2.6999998C22.899981,2.5,22.699981,2.4000006,22.399981,2.4000006z M21.499982,0C22.299982,0,23.199981,0.30000019,23.79998,0.90000057L29.299976,6.4000001C30.599975,7.7000003,30.599975,9.7000003,29.299976,11L22.599981,17.7C21.299983,19,19.299984,19,17.999985,17.7L17.299985,17 12.699989,21.6 10.399991,20.900001 10.899991,23.400001 8.4999928,22.8 8.9999924,25.5 6.2999945,25.1 6.7999945,27.6 4.2999964,27 4.7999954,29.6 2.4999971,32 0,26.6 13.499989,13.1 16.999986,16.6 12.699989,12.3C11.399991,11,11.399991,9.0000002,12.699989,7.7000003L19.399984,1C19.899983,0.30000019,20.699983,0,21.499982,0z
|
||||||
</sys:String>
|
</sys:String>
|
||||||
|
|
||||||
<sys:String x:Key="{x:Static mbts:Icons.MenuOrientationGeometryKey}">
|
|
||||||
M14.997001,19.999038L15.000001,23.499012C15.000001,24.327005,14.327008,25,13.500016,25L9.9970522,25 9.9970522,23.000015 13.000022,23.000015 12.997022,20.001038z M9.000001,19.000006L9.000001,30.000006 30.000001,30.000006 30.000001,19.000006z M2,2L2,23 7.000001,23 7.000001,18.500006C7.000001,17.673006,7.6730013,17.000006,8.500001,17.000006L13.000001,17.000006 13.000001,2z M1.5,0L13.500001,0C14.327001,0,15.000001,0.67300034,15.000001,1.5L15.000001,17.000006 26.956518,17.000006 23.162404,13.207182C22.772376,12.816143 22.772376,12.184205 23.162404,11.793225 23.357907,11.597736 23.613928,11.499992 23.869827,11.499992 24.125725,11.499992 24.381502,11.597736 24.576517,11.793225L25.889371,13.105454 25.866285,12.914201C25.554945,10.547287 24.699391,8.7684691 23.308905,7.6089654 21.974919,6.4954615 20.356436,6.1229606 19.066825,6.0297103L19.059648,6.0292783 20.545799,7.5160289C20.936786,7.9070184 20.936786,8.5390015 20.545799,8.929991 20.350807,9.1249857 20.094815,9.2229831 19.838825,9.2229831 19.582834,9.2229831 19.326843,9.1249857 19.131849,8.929991L15.185988,4.9830971 18.981854,1.1871986C19.371841,0.79620934 20.004819,0.79620934 20.395805,1.1871986 20.785791,1.5771885 20.785791,2.2101717 20.395805,2.601161L18.976535,4.020443 18.999199,4.0214248C20.641604,4.1119318 22.785724,4.567862 24.590892,6.0739603 26.493747,7.660778 27.606404,10.054969 27.91277,13.209869L27.913735,13.221022 29.341699,11.793225C29.536714,11.597736 29.792459,11.499992 30.048328,11.499992 30.304196,11.499992 30.560187,11.597736 30.75569,11.793225 31.146694,12.184205 31.146694,12.816143 30.75569,13.207182L26.962552,17.000006 30.500001,17.000006C31.327001,17.000006,32.000001,17.673006,32.000001,18.500006L32.000001,30.500006C32.000001,31.327005,31.327001,32.000006,30.500001,32.000006L8.500001,32.000006C7.6730013,32.000006,7.000001,31.327005,7.000001,30.500006L7.000001,25 1.5,25C0.67300034,25,0,24.327001,0,23.5L0,1.5C0,0.67300034,0.67300034,0,1.5,0z
|
|
||||||
</sys:String>
|
|
||||||
|
|
||||||
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
|
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
|
||||||
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
|
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
|
||||||
</sys:String>
|
</sys:String>
|
||||||
|
|||||||
@@ -48,5 +48,5 @@ namespace mbts.ui.spark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row> { }
|
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row, MarketViewView.Columns> { }
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,10 @@ using bts.shell;
|
|||||||
using bts.shell.fills;
|
using bts.shell.fills;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.utils;
|
using bts.utils;
|
||||||
using bts.view;
|
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.settings;
|
using mbts.ui.settings;
|
||||||
|
using mbts.ui.shell;
|
||||||
using mbts.ui.spark;
|
using mbts.ui.spark;
|
||||||
using mbts.ui.utility;
|
using mbts.ui.utility;
|
||||||
using mbts.utils;
|
using mbts.utils;
|
||||||
@@ -32,7 +32,6 @@ namespace mbts.spark {
|
|||||||
SecurityCriteria
|
SecurityCriteria
|
||||||
_selected_security_criteria_backing,
|
_selected_security_criteria_backing,
|
||||||
_sorted_selected_security_criteria_backing;
|
_sorted_selected_security_criteria_backing;
|
||||||
SparkMenuDisplayVM _menu_display_vm;
|
|
||||||
|
|
||||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> 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) {
|
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||||
@@ -49,9 +48,7 @@ namespace mbts.spark {
|
|||||||
public override UIElement Content => _view;
|
public override UIElement Content => _view;
|
||||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||||
public override MarketViewWindowFactory.StartupData SaveState =>
|
public override MarketViewWindowFactory.StartupData SaveState =>
|
||||||
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns)) {
|
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
|
||||||
menu_display = _menu_display_vm.safe_create()
|
|
||||||
};
|
|
||||||
|
|
||||||
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
|
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
|
||||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||||
@@ -104,8 +101,6 @@ namespace mbts.spark {
|
|||||||
public void thread_init() {
|
public void thread_init() {
|
||||||
_init_guard.check_access();
|
_init_guard.check_access();
|
||||||
Log.notnull(_state);
|
Log.notnull(_state);
|
||||||
_menu_display_vm = new SparkMenuDisplayVM();
|
|
||||||
_menu_display_vm.load_from(_state.menu_display);
|
|
||||||
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
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);
|
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
|
||||||
_view = new MarketViewView { DataContext = _vm };
|
_view = new MarketViewView { DataContext = _vm };
|
||||||
@@ -165,8 +160,7 @@ namespace mbts.spark {
|
|||||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||||
return dialog;
|
return dialog;
|
||||||
})),
|
}))
|
||||||
_menu_display_vm.spark_cmd
|
|
||||||
};
|
};
|
||||||
//
|
//
|
||||||
_vm.selection_changed += on_selection_changed;
|
_vm.selection_changed += on_selection_changed;
|
||||||
@@ -196,8 +190,5 @@ namespace mbts.spark {
|
|||||||
_vm.Dispose();
|
_vm.Dispose();
|
||||||
Commands.OfType<IDisposable>().DisposeAll();
|
Commands.OfType<IDisposable>().DisposeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
|
||||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using bts.api;
|
using bts.api;
|
||||||
using bts.proto;
|
using bts.proto;
|
||||||
using bts.settings;
|
using bts.settings;
|
||||||
using bts.view;
|
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.spark;
|
using mbts.spark;
|
||||||
using mbts.spark.filters;
|
using mbts.spark.filters;
|
||||||
@@ -54,8 +53,6 @@ namespace bts.shell {
|
|||||||
public readonly FilterStates filters;
|
public readonly FilterStates filters;
|
||||||
[ProtoMember(4)]
|
[ProtoMember(4)]
|
||||||
public GridSettings grid;
|
public GridSettings grid;
|
||||||
[ProtoMember(5)]
|
|
||||||
public SparkMenuDisplaySettings menu_display;
|
|
||||||
|
|
||||||
public StartupData() { }
|
public StartupData() { }
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
using mbts.spark.filters;
|
|
||||||
using mbts.ui;
|
|
||||||
using ProtoBuf;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace bts.shell.time_and_sales {
|
|
||||||
[ProtoContract]
|
|
||||||
class FilterStates {
|
|
||||||
[ProtoMember(1)]
|
|
||||||
public TimeFilterTuple time;
|
|
||||||
[ProtoMember(2)]
|
|
||||||
public string security;
|
|
||||||
[ProtoMember(3)]
|
|
||||||
public DoubleMinMaxState price;
|
|
||||||
[ProtoMember(4)]
|
|
||||||
public UIntMinMaxState size;
|
|
||||||
// [ProtoMember(5)]
|
|
||||||
// ProtoMember 5 cannot be used further
|
|
||||||
[ProtoMember(6)]
|
|
||||||
public string security_key;
|
|
||||||
[ProtoMember(7)]
|
|
||||||
public co2.AggressorSide[] aggressor_sides;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class TimeAndSalesFilterStatesExtensions {
|
|
||||||
public static Func<TimeAndSalesRowVM, bool> build_predicate(this FilterStates fs) {
|
|
||||||
var pb = new ColumnPredicateBuilder<TimeAndSalesRowVM>();
|
|
||||||
pb.add_regex(fs.security, r => r.security.ToString());
|
|
||||||
pb.add_time_filter(fs.time, r => r.time);
|
|
||||||
pb.add_price(fs.price, r => r.price, r => r.security);
|
|
||||||
pb.add_numeric(fs.size, r => r.size);
|
|
||||||
pb.add_discrete_values(fs.aggressor_sides, r => r.aggressor_side);
|
|
||||||
return pb.to_predicate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
using mbts.spark;
|
|
||||||
using mbts.ui;
|
|
||||||
using mbts.ui.settings;
|
|
||||||
using ProtoBuf;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using static mbts.ui.controls.DIYGrid;
|
|
||||||
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
|
|
||||||
|
|
||||||
namespace bts.shell.time_and_sales {
|
|
||||||
[ProtoContract]
|
|
||||||
class TimeAndSalesSettings {
|
|
||||||
[ProtoMember(1)]
|
|
||||||
public readonly SecurityCriteria security_criteria;
|
|
||||||
[ProtoMember(2)]
|
|
||||||
List<SortingCriteria> _sorting_criterion;
|
|
||||||
[ProtoMember(3)]
|
|
||||||
public FilterStates filters;
|
|
||||||
[ProtoMember(4)]
|
|
||||||
public GridSettings grid;
|
|
||||||
[ProtoMember(5)]
|
|
||||||
public SparkMenuDisplaySettings menu_display;
|
|
||||||
[ProtoMember(6)]
|
|
||||||
public bool use_defaults;
|
|
||||||
|
|
||||||
public TimeAndSalesSettings() { }
|
|
||||||
|
|
||||||
public TimeAndSalesSettings(SecurityCriteria security_criteria) :
|
|
||||||
this(security_criteria, new FilterStates(), null, null) { }
|
|
||||||
|
|
||||||
public TimeAndSalesSettings(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
|
|
||||||
this.security_criteria = security_criteria;
|
|
||||||
this.filters = filters;
|
|
||||||
this.grid = grid;
|
|
||||||
_sorting_criterion = sorting_criterion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
|
||||||
|
|
||||||
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
|
|
||||||
|
|
||||||
public static TimeAndSalesSettings Default(SecurityCriteria security_criteria) =>
|
|
||||||
new TimeAndSalesSettings(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
|
|
||||||
|
|
||||||
public TimeAndSalesSettings with(SecurityCriteria security_criteria) =>
|
|
||||||
new TimeAndSalesSettings(security_criteria, filters, grid);
|
|
||||||
|
|
||||||
public static class DefaultColumns {
|
|
||||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.col_id(), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
|
||||||
public static GridColumnSettings time => new GridColumnSettings(Columns.Time.col_id(), DefaultColumnHeaders.Time, width: DefaultColumnWidths.TimeOfDay);
|
|
||||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.col_id(), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
|
||||||
public static GridColumnSettings size => new GridColumnSettings(Columns.Size.col_id(), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
|
||||||
public static GridColumnSettings aggressor => new GridColumnSettings(Columns.Aggressor.col_id(), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
|
|
||||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.col_id(), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
|
||||||
|
|
||||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
|
||||||
get {
|
|
||||||
yield return new SortingCriteria(time.id, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<GridColumnSettings> all {
|
|
||||||
get {
|
|
||||||
yield return security;
|
|
||||||
yield return time;
|
|
||||||
yield return price;
|
|
||||||
yield return size;
|
|
||||||
yield return aggressor;
|
|
||||||
yield return security_key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,27 @@
|
|||||||
using bts.api;
|
using bts.api;
|
||||||
using bts.co2;
|
using bts.co2;
|
||||||
using mbts;
|
|
||||||
using mbts.co2;
|
using mbts.co2;
|
||||||
using mbts.spark;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
|
|
||||||
namespace bts.shell.time_and_sales {
|
namespace mbts.spark {
|
||||||
class TimeAndSalesModel : IDisposable {
|
class TimeAndSalesModel : IDisposable {
|
||||||
|
internal readonly SecurityCriteria security_criteria;
|
||||||
|
|
||||||
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
|
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
|
||||||
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
|
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
|
||||||
IDisposable _dispose;
|
IDisposable _dispose;
|
||||||
|
|
||||||
public TimeAndSalesModel(IBus bus, MDConsumer consumer, IObservable<SecurityCriteria> security_criteria) {
|
public TimeAndSalesModel(IBus bus, MDConsumer consumer, SecurityCriteria security_criteria) {
|
||||||
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
|
this.security_criteria = security_criteria;
|
||||||
|
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
|
||||||
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
|
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
|
||||||
var copy = curr_channels;
|
var copy = curr_channels;
|
||||||
return copy.Values.SelectMany(c => c.read_trades()).ToList();
|
return copy.Values.SelectMany(c => c.read_trades()).ToList();
|
||||||
}));
|
}));
|
||||||
_dispose = security_criteria.SelectManyInSeries(sc =>sc.subscribe_md_map(bus)).Subscribe(
|
_dispose = security_criteria.subscribe_md_map(bus).Subscribe(
|
||||||
md_map => {
|
md_map => {
|
||||||
var copy = new Dictionary<ChannelKey, ChannelConsumer>(curr_channels);
|
var copy = new Dictionary<ChannelKey, ChannelConsumer>(curr_channels);
|
||||||
copy.sync_with_demux(md_map, get_channel_key, get_channel_consumer, update_channel_consumer);
|
copy.sync_with_demux(md_map, get_channel_key, get_channel_consumer, update_channel_consumer);
|
||||||
@@ -94,4 +95,4 @@ namespace bts.shell.time_and_sales {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
using bts.api;
|
|
||||||
using bts.co2;
|
|
||||||
using bts.utils;
|
|
||||||
using bts.utils.csv;
|
|
||||||
using mbts;
|
|
||||||
using mbts.spark;
|
|
||||||
using mbts.ui.controls;
|
|
||||||
using mbts.ui.shell;
|
|
||||||
using mbts.utils;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace bts.shell.time_and_sales {
|
|
||||||
class TimeAndSalesRowVM : ViewModelBase, DIYGrid.IRowVM, ISecurityRow, IEquatable<TimeAndSalesRowVM> {
|
|
||||||
// key comparer guarentees deterministic sorting
|
|
||||||
// if the rows in the collection have unique row_keys.
|
|
||||||
public static readonly IComparer<TimeAndSalesRowVM> key_comparer =
|
|
||||||
new ComparerBuilder<TimeAndSalesRowVM>(new[] {
|
|
||||||
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
|
|
||||||
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
|
|
||||||
})
|
|
||||||
.to_comparer()
|
|
||||||
.to_safe();
|
|
||||||
|
|
||||||
readonly TradeMessageImmutable _trade;
|
|
||||||
|
|
||||||
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade) {
|
|
||||||
this.row_key = row_key;
|
|
||||||
this.security = security;
|
|
||||||
_trade = trade;
|
|
||||||
trade_key = trade.Key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action invalidated;
|
|
||||||
|
|
||||||
public int row_key { get; }
|
|
||||||
public uint trade_key { get; }
|
|
||||||
public uint security_key => security.Id;
|
|
||||||
public SecurityImmutable security { get; }
|
|
||||||
public DateTime time => ((ulong)_trade.Time).lldatetime().ToLocalTime();
|
|
||||||
public double price => _trade.price();
|
|
||||||
public uint size => _trade.Size;
|
|
||||||
public AggressorSide aggressor_side => _trade.AggressorSide;
|
|
||||||
|
|
||||||
public void raise_invalidated() =>
|
|
||||||
invalidated?.Invoke();
|
|
||||||
|
|
||||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
|
||||||
CsvHelper.create_ordering_maintainer(
|
|
||||||
typeof(TimeAndSalesRowVM),
|
|
||||||
new string[] {
|
|
||||||
nameof(security),
|
|
||||||
nameof(time),
|
|
||||||
nameof(price),
|
|
||||||
nameof(size),
|
|
||||||
nameof(aggressor_side),
|
|
||||||
nameof(security_key),
|
|
||||||
});
|
|
||||||
|
|
||||||
public bool Equals(TimeAndSalesRowVM other) =>
|
|
||||||
other != null &&
|
|
||||||
row_key == other.row_key;
|
|
||||||
|
|
||||||
public override bool Equals(object obj) =>
|
|
||||||
Equals(obj as TimeAndSalesRowVM);
|
|
||||||
|
|
||||||
public override int GetHashCode() =>
|
|
||||||
row_key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,363 +1,86 @@
|
|||||||
using bts.api;
|
using bts.api;
|
||||||
using bts.co2;
|
using bts.co2;
|
||||||
using bts.collections;
|
using bts.collections;
|
||||||
using bts.utils;
|
|
||||||
using mbts;
|
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.spark;
|
|
||||||
using mbts.spark.filters;
|
using mbts.spark.filters;
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.controls;
|
using mbts.ui.controls;
|
||||||
using mbts.ui.settings;
|
using mbts.ui.settings;
|
||||||
using mbts.ui.shell;
|
using mbts.ui.spark;
|
||||||
using mbts.ui.utility;
|
using mbts.ui.utility;
|
||||||
using mbts.utils;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Threading;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using static bts.shell.TimeAndSalesWindowFactory;
|
||||||
using static mbts.ui.controls.DIYGrid;
|
using static mbts.ui.controls.DIYGrid;
|
||||||
|
|
||||||
namespace bts.shell.time_and_sales {
|
namespace mbts.spark {
|
||||||
class TimeAndSalesVM : DisposableViewModelBase, IHandleGridSorting, IHandleRowSelection {
|
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
|
||||||
const int _max_row_count = 1000;
|
readonly IBus _bus;
|
||||||
static int _id_counter;
|
readonly TimeAndSalesModel _model;
|
||||||
static IReadOnlyList<Func<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>> _filter_getters =
|
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||||
ObjectSnapshot.property_delegates_of_type<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>();
|
|
||||||
|
|
||||||
readonly ColumnSortManager _sort_manager;
|
|
||||||
readonly RowFactory _row_factory = new RowFactory();
|
|
||||||
readonly LoggerContext _log;
|
|
||||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
|
||||||
readonly DeferrableActionWrapper
|
|
||||||
_refresh_filter,
|
|
||||||
_refresh_sort,
|
|
||||||
_refresh_selected_rows;
|
|
||||||
IComparer<TimeAndSalesRowVM> _comparer_backing;
|
|
||||||
Func<TimeAndSalesRowVM, bool> _filter_backing;
|
|
||||||
ImmutableHashSet<TimeAndSalesRowVM> _selected_rows = ImmutableHashSet<TimeAndSalesRowVM>.Empty;
|
|
||||||
ImmutableList<TimeAndSalesRowVM> _all_rows = ImmutableList<TimeAndSalesRowVM>.Empty;
|
|
||||||
ImmutableList<TradeInfo> _buffered_trades;
|
|
||||||
/// Although the sorting criteria can be obtained from the <see cref="_sort_manager"/> (<see cref="ColumnSortManager.SortingCriteria"/>),
|
|
||||||
/// a local copy is kept because the settings collector may call <see cref="create"/> from a thread
|
|
||||||
/// other than the dispatcher thread associated with this instance as a viewmodel,
|
|
||||||
/// and the <see cref="ColumnSortManager.SortingCriteria"/> property is not thread-safe.
|
|
||||||
/// Therefore, <see cref="create"/> returns this local copy, and thus avoids accessing the non-thread-safe property.
|
|
||||||
ImmutableList<SortingCriteria> _sorting_criteria = ImmutableList<SortingCriteria>.Empty;
|
|
||||||
TimeAndSalesRowVM _current_row;
|
|
||||||
bool _is_paused;
|
|
||||||
int
|
|
||||||
_load_settings_count,
|
|
||||||
_filter_change_count,
|
|
||||||
_sort_change_count;
|
|
||||||
|
|
||||||
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager) {
|
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
|
||||||
_sort_manager = new ColumnSortManager(columns.WrapSelector(c => c as ISortableDIYGridColumn, null), TimeAndSalesView.Columns.RowKey.ToString());
|
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
|
||||||
_comparer_backing = _sort_manager.BuildComparer(_get_column_comparer);
|
_bus = bus;
|
||||||
_log = new LoggerContext(Log.Ui, $"Time And Sales VM ({Interlocked.Increment(ref _id_counter)})", null);
|
_model = model;
|
||||||
_settings_manager = settings_manager;
|
_settings_manager = settings_manager;
|
||||||
// deferrable filter/sort refresh
|
show_ladder = DelegateCommandParamObserver.Create(current_row_observable, r => _show_ladder_async(r).ignore(), r => r != null);
|
||||||
_refresh_filter = new DeferrableActionWrapper(_refresh_filter_impl);
|
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||||
_refresh_sort = new DeferrableActionWrapper(_refresh_sort_impl);
|
if (startup_data.filters is FilterStates filters) {
|
||||||
_refresh_selected_rows = new DeferrableActionWrapper(() => selected_rows_changed?.Invoke(_selected_rows));
|
|
||||||
// grid settings
|
|
||||||
grid_settings_manager = new SimpleGridSettingsManager<TimeAndSalesRowVM>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
|
|
||||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
|
||||||
// Columns
|
|
||||||
add_column(
|
|
||||||
TimeAndSalesView.Columns.Security,
|
|
||||||
r => r.security.ToString(),
|
|
||||||
DefaultColumnHeaders.Security,
|
|
||||||
DefaultColumnWidths.Security,
|
|
||||||
security_filter,
|
|
||||||
DataGridFilterKnownTemplates.Regex);
|
|
||||||
add_column(
|
|
||||||
TimeAndSalesView.Columns.Time,
|
|
||||||
r => r.time.ToString("h:mm:ss tt"),
|
|
||||||
DefaultColumnHeaders.Time,
|
|
||||||
DefaultColumnWidths.Time,
|
|
||||||
time_filter,
|
|
||||||
DataGridFilterKnownTemplates.TimeFilter);
|
|
||||||
add_column(
|
|
||||||
TimeAndSalesView.Columns.Price,
|
|
||||||
r => r.security.spec_or_default().format(r.price),
|
|
||||||
DefaultColumnHeaders.Price,
|
|
||||||
DefaultColumnWidths.Price,
|
|
||||||
price_filter,
|
|
||||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
|
||||||
System.Windows.HorizontalAlignment.Right);
|
|
||||||
add_column(
|
|
||||||
TimeAndSalesView.Columns.Size,
|
|
||||||
r => r.size.ToString(),
|
|
||||||
DefaultColumnHeaders.Size,
|
|
||||||
DefaultColumnWidths.Size,
|
|
||||||
size_filter,
|
|
||||||
DataGridFilterKnownTemplates.UIntMinMax,
|
|
||||||
System.Windows.HorizontalAlignment.Right);
|
|
||||||
add_column(
|
|
||||||
TimeAndSalesView.Columns.Aggressor,
|
|
||||||
r => r.aggressor_side.ToString(),
|
|
||||||
DefaultColumnHeaders.Aggressor,
|
|
||||||
DefaultColumnWidths.Aggressor,
|
|
||||||
aggressor_filter,
|
|
||||||
DataGridFilterKnownTemplates.EnumValues);
|
|
||||||
add_column(
|
|
||||||
TimeAndSalesView.Columns.SecurityKey,
|
|
||||||
r => r.security_key.ToString(),
|
|
||||||
DefaultColumnHeaders.SecurityKey,
|
|
||||||
DefaultColumnWidths.SecurityKey,
|
|
||||||
security_key_filter,
|
|
||||||
DataGridFilterKnownTemplates.Regex);
|
|
||||||
|
|
||||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager);
|
|
||||||
// commands
|
|
||||||
show_ladder_cmd = new DelegateCommand(() => {
|
|
||||||
if (current_row != null)
|
|
||||||
request_show_ladder?.Invoke(current_row);
|
|
||||||
});
|
|
||||||
// init IDisposable resources
|
|
||||||
var resources = new List<IDisposable>();
|
|
||||||
resources.AddRange(_filters.Select(f => {
|
|
||||||
f.filter_changed += _refresh_filter.execute;
|
|
||||||
return Disposable.Create(() => f.filter_changed -= _refresh_filter.execute);
|
|
||||||
}));
|
|
||||||
_sort_manager.active_sorting_changed += _refresh_sort.execute;
|
|
||||||
resources.Add(Disposable.Create(() => _sort_manager.active_sorting_changed -= _refresh_sort.execute));
|
|
||||||
_dispose = Disposable.Create(resources.ToArray());
|
|
||||||
// local methods
|
|
||||||
void add_column(
|
|
||||||
TimeAndSalesView.Columns col_key,
|
|
||||||
Func<TimeAndSalesRowVM, string> get_cell_text,
|
|
||||||
string header,
|
|
||||||
double width,
|
|
||||||
IDataGridColumnFilter filter,
|
|
||||||
string filter_template,
|
|
||||||
System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
|
||||||
columns.Add(new RenderTextColumn<TimeAndSalesRowVM, TimeAndSalesView.Columns>(
|
|
||||||
col_key, get_cell_text, filter, filter_template, _get_aggressor_brush) {
|
|
||||||
id = col_key.col_id(), Header = header, Width = width, horizontal_alignment = h_alignment
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<ImmutableHashSet<TimeAndSalesRowVM>> selected_rows_changed;
|
|
||||||
public event Action<TimeAndSalesRowVM> current_row_changed, request_show_ladder;
|
|
||||||
|
|
||||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
|
||||||
public TimeAndSalesRowVM current_row {
|
|
||||||
get => _current_row;
|
|
||||||
set {
|
|
||||||
if (Set(ref _current_row, value))
|
|
||||||
current_row_changed?.Invoke(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public ObservableCollection<TimeAndSalesRowVM> rows { get; } = new ObservableCollection<TimeAndSalesRowVM>();
|
|
||||||
public ImmutableHashSet<TimeAndSalesRowVM> selected_rows => _selected_rows;
|
|
||||||
public int total_row_count => _all_rows.Count;
|
|
||||||
public SimpleGridSettingsManager<TimeAndSalesRowVM> grid_settings_manager { get; }
|
|
||||||
public bool is_paused {
|
|
||||||
get => _is_paused;
|
|
||||||
set {
|
|
||||||
if (_is_paused != value) {
|
|
||||||
_is_paused = value;
|
|
||||||
if (value) {
|
|
||||||
_buffered_trades = ImmutableList<TradeInfo>.Empty;
|
|
||||||
} else {
|
|
||||||
var resume = _buffered_trades;
|
|
||||||
_buffered_trades = null;
|
|
||||||
on_new_trades(resume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// filters
|
|
||||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<TimeAndSalesRowVM>(r => r.time);
|
|
||||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security.ToString(), nameof(TimeAndSalesRowVM.security));
|
|
||||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<TimeAndSalesRowVM>(r => r.price);
|
|
||||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, UIntMinMaxState> size_filter { get; } = new UIntMinMax<TimeAndSalesRowVM>(r => r.size, nameof(TimeAndSalesRowVM.size));
|
|
||||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<TimeAndSalesRowVM, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side);
|
|
||||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_key_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security_key.ToString());
|
|
||||||
// commands
|
|
||||||
public ICommand show_ladder_cmd { get; }
|
|
||||||
|
|
||||||
IComparer<TimeAndSalesRowVM> _comparer {
|
|
||||||
get => _comparer_backing;
|
|
||||||
set {
|
|
||||||
if (value == null) {
|
|
||||||
_log.warn("Comparer should not be set to null, will use the default comparer instead (based on trade time, then row key).");
|
|
||||||
_comparer_backing = new ComparerBuilder<TimeAndSalesRowVM>(new[] {
|
|
||||||
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
|
|
||||||
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
|
|
||||||
})
|
|
||||||
.to_comparer()
|
|
||||||
.to_safe();
|
|
||||||
} else {
|
|
||||||
_comparer_backing = value;
|
|
||||||
}
|
|
||||||
var sorted_rows = rows.ToArray();
|
|
||||||
var snapshot = _selected_rows;
|
|
||||||
Array.Sort(sorted_rows, _comparer_backing);
|
|
||||||
using (_refresh_selected_rows.defer_execution()) {
|
|
||||||
for (int i = 0; i < sorted_rows.Length; i++) {
|
|
||||||
if (rows[i].row_key != sorted_rows[i].row_key)
|
|
||||||
rows[i] = sorted_rows[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Func<TimeAndSalesRowVM, bool> _filter {
|
|
||||||
get => _filter_backing;
|
|
||||||
set {
|
|
||||||
var old_filter = _filter_backing;
|
|
||||||
var new_filter = value;
|
|
||||||
_filter_backing = new_filter;
|
|
||||||
if (new_filter != null) {
|
|
||||||
for (int i = 0; i < rows.Count; i++) {
|
|
||||||
if (!new_filter(rows[i]))
|
|
||||||
rows.RemoveAt(i);
|
|
||||||
}
|
|
||||||
rows.RemoveWhere(r => !new_filter(r));
|
|
||||||
if (old_filter != null) {
|
|
||||||
foreach (var r in _all_rows) {
|
|
||||||
if (!old_filter(r) && new_filter(r))
|
|
||||||
rows.InsertSorted(r, _comparer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (old_filter != null) {
|
|
||||||
foreach (var r in _all_rows) {
|
|
||||||
if (!old_filter(r))
|
|
||||||
rows.InsertSorted(r, _comparer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<IDataGridColumnFilter<TimeAndSalesRowVM>> _filters =>
|
|
||||||
_filter_getters.Select(fn => fn(this));
|
|
||||||
|
|
||||||
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting_criteria, GridSettings grid_settings) create() =>
|
|
||||||
(_get_filter_states(), _sorting_criteria, grid_settings_manager.create());
|
|
||||||
|
|
||||||
public void load(FilterStates filters, IEnumerable<SortingCriteria> sorting_criteria, GridSettings grid_settings) {
|
|
||||||
using (_refresh_filter.defer_execution())
|
|
||||||
using (_refresh_sort.defer_execution()) {
|
|
||||||
_log.info($"begin load settings ({++_load_settings_count})...");
|
|
||||||
var timer = new Stopwatch();
|
|
||||||
timer.Start();
|
|
||||||
grid_settings_manager.load_from(grid_settings);
|
|
||||||
_sort_manager.SortingCriteria = sorting_criteria;
|
|
||||||
// load filters
|
|
||||||
time_filter.load_state(filters.time);
|
time_filter.load_state(filters.time);
|
||||||
security_filter.load_state(filters.security);
|
security_filter.load_state(filters.security);
|
||||||
price_filter.load_state(filters.price);
|
price_filter.load_state(filters.price);
|
||||||
size_filter.load_state(filters.size);
|
size_filter.load_state(filters.size);
|
||||||
aggressor_filter.load_state(filters.aggressor_sides);
|
aggressor_filter.load_state(filters.aggressor_sides);
|
||||||
security_key_filter.load_state(filters.security_key);
|
security_key_filter.load_state(filters.security_key);
|
||||||
//
|
|
||||||
timer.Stop();
|
|
||||||
_log.info($"load settings ({_load_settings_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
|
|
||||||
}
|
}
|
||||||
|
// Columns
|
||||||
|
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, nameof(Row.security), security_filter, DataGridFilterKnownTemplates.Regex);
|
||||||
|
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, nameof(Row.time), time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||||
|
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
|
||||||
|
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
|
||||||
|
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||||
|
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||||
|
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||||
|
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
|
||||||
|
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||||
|
);
|
||||||
|
|
||||||
|
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
|
||||||
|
// if the collection is not populated, the column settings will not be applied
|
||||||
|
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
|
||||||
|
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||||
|
_resources.AddRange(
|
||||||
|
show_ladder,
|
||||||
|
DataGridFilterHelper.hook_filters(
|
||||||
|
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
|
||||||
|
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||||
|
);
|
||||||
|
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void on_new_trades(IReadOnlyList<TradeInfo> trades) {
|
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
|
||||||
/// The total row count is limited by <see cref="_max_row_count"/>;
|
public DelegateCommandParamObserver<Row> show_ladder { get; }
|
||||||
/// trades are kept in a circular buffer (FIFO).
|
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||||
/// If paused, just need to add trades to <see cref="_buffered_trades"/>.
|
|
||||||
if (is_paused) {
|
public GridSettings get_grid_save_state() {
|
||||||
if (_buffered_trades == null)
|
if (_settings_manager.instance_settings == null)
|
||||||
_buffered_trades = ImmutableList<TradeInfo>.Empty;
|
return null;
|
||||||
if (trades.Count >= _max_row_count) {
|
else
|
||||||
_buffered_trades = _buffered_trades
|
return grid_settings_manager.create();
|
||||||
.Clear()
|
|
||||||
.AddRange(ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count));
|
|
||||||
} else {
|
|
||||||
int overflow_count = _buffered_trades.Count + trades.Count - _max_row_count;
|
|
||||||
if (overflow_count > 0) {
|
|
||||||
var rows_to_remove = _buffered_trades.WrapSubRange(0, overflow_count);
|
|
||||||
_buffered_trades = _buffered_trades.RemoveRange(0, overflow_count);
|
|
||||||
}
|
|
||||||
_buffered_trades = _buffered_trades.AddRange(trades);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/// If not paused, update in the normal fasion:
|
|
||||||
/// add to <see cref="_all_rows"/> and <see cref="rows"/>
|
|
||||||
if (trades.Count >= _max_row_count) {
|
|
||||||
trades = ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count);
|
|
||||||
_all_rows = _all_rows.Clear();
|
|
||||||
rows.Clear();
|
|
||||||
} else {
|
|
||||||
int overflow_count = _all_rows.Count + trades.Count - _max_row_count;
|
|
||||||
if (overflow_count > 0) {
|
|
||||||
var rows_to_remove = _all_rows.WrapSubRange(0, overflow_count);
|
|
||||||
_all_rows = _all_rows.RemoveRange(0, overflow_count);
|
|
||||||
rows.RemoveSorted(rows_to_remove, _comparer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var all_rows_bd = _all_rows.ToBuilder();
|
|
||||||
foreach (var t in trades) {
|
|
||||||
var r = _row_factory.create(t.security, t.trade);
|
|
||||||
all_rows_bd.Add(r);
|
|
||||||
if (_filter_backing == null || _filter_backing(r))
|
|
||||||
rows.InsertSorted(r, _comparer);
|
|
||||||
}
|
|
||||||
_all_rows = all_rows_bd.ToImmutable();
|
|
||||||
RaisePropertyChanged(nameof(total_row_count));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
|
public StartupData create() =>
|
||||||
try {
|
new StartupData(_model.security_criteria, _get_filter_states(), get_grid_save_state(), rows_view.get_sorting_criteria(columns));
|
||||||
if (e.Column is ISortableDIYGridColumn sc) {
|
|
||||||
_log.info($"handling sort event for column {sc.id}.");
|
|
||||||
_sort_manager.OnColumnClick(sc, KeyboardExtensions.IsShiftDown());
|
|
||||||
var sorted_rows = rows.ToArray();
|
|
||||||
var snapshot = _selected_rows;
|
|
||||||
_refresh_sort_impl();
|
|
||||||
} else {
|
|
||||||
_log.warn($"ignoring sort event because column '{e.Column?.id}' does not implement {nameof(ISortableDIYGridColumn)}.");
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
_log.error($"error handling sort event: {ex}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void row_selection_changed(SelectionChangedEventArgs e) {
|
private Brush _get_aggressor_brush(Row r) {
|
||||||
var bd = _selected_rows.ToBuilder();
|
|
||||||
if (e.AddedItems != null)
|
|
||||||
bd.AddRange(e.AddedItems.OfType<TimeAndSalesRowVM>());
|
|
||||||
if (e.RemovedItems != null)
|
|
||||||
bd.removeRange(e.RemovedItems.OfType<TimeAndSalesRowVM>());
|
|
||||||
_selected_rows = bd.ToImmutable();
|
|
||||||
_refresh_selected_rows.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
static IComparer<TimeAndSalesRowVM> _get_column_comparer(string column_name) =>
|
|
||||||
_get_column_comparer(EnumHelper.parse<TimeAndSalesView.Columns>(column_name));
|
|
||||||
|
|
||||||
static IComparer<TimeAndSalesRowVM> _get_column_comparer(TimeAndSalesView.Columns column) {
|
|
||||||
switch (column) {
|
|
||||||
case TimeAndSalesView.Columns.Aggressor: return wrap(r => r.aggressor_side);
|
|
||||||
case TimeAndSalesView.Columns.Time: return wrap(r => r.time);
|
|
||||||
case TimeAndSalesView.Columns.Security: return wrap(r => r.security);
|
|
||||||
case TimeAndSalesView.Columns.Price: return wrap(r => r.price);
|
|
||||||
case TimeAndSalesView.Columns.Size: return wrap(r => r.size);
|
|
||||||
case TimeAndSalesView.Columns.SecurityKey: return wrap(r => r.security_key);
|
|
||||||
case TimeAndSalesView.Columns.RowKey: return wrap(r => r.row_key);
|
|
||||||
default: throw ExceptionBuilder.Argument.unexpected_case(column, nameof(column));
|
|
||||||
}
|
|
||||||
IComparer<TimeAndSalesRowVM> wrap<T>(Func<TimeAndSalesRowVM, T> selector) =>
|
|
||||||
Comparer<T>.Default.wrap(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
Brush _get_aggressor_brush(TimeAndSalesRowVM r) {
|
|
||||||
BTSColor c;
|
BTSColor c;
|
||||||
Brush b;
|
Brush b;
|
||||||
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
|
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
|
||||||
@@ -367,6 +90,14 @@ namespace bts.shell.time_and_sales {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Filters
|
||||||
|
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<Row>(r => r.time, nameof(Row.time));
|
||||||
|
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||||
|
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
|
||||||
|
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> size_filter { get; } = new UIntMinMax<Row>(r => r.size, nameof(Row.size));
|
||||||
|
public IDataGridColumnPersistentFilter<Row, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<Row, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side, nameof(Row.aggressor_side));
|
||||||
|
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||||
|
|
||||||
FilterStates _get_filter_states() =>
|
FilterStates _get_filter_states() =>
|
||||||
new FilterStates {
|
new FilterStates {
|
||||||
time = time_filter.get_state(),
|
time = time_filter.get_state(),
|
||||||
@@ -376,35 +107,73 @@ namespace bts.shell.time_and_sales {
|
|||||||
security_key = security_key_filter.get_state(),
|
security_key = security_key_filter.get_state(),
|
||||||
aggressor_sides = aggressor_filter.get_state()
|
aggressor_sides = aggressor_filter.get_state()
|
||||||
};
|
};
|
||||||
|
#endregion
|
||||||
|
|
||||||
void _refresh_sort_impl() {
|
protected override IEnumerable<SortingCriteria> get_default_sorting() {
|
||||||
_log.info($"begin change sort ({++_sort_change_count})...");
|
yield return new SortingCriteria(nameof(Row.time), false);
|
||||||
var timer = new Stopwatch();
|
|
||||||
timer.Start();
|
|
||||||
_comparer = _sort_manager.BuildComparer(_get_column_comparer, TimeAndSalesRowVM.key_comparer);
|
|
||||||
_sorting_criteria = ImmutableList.CreateRange(_sort_manager.SortingCriteria);
|
|
||||||
timer.Stop();
|
|
||||||
_log.info($"sort change ({_sort_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _refresh_filter_impl() {
|
protected override Row row_factory(TradeInfo row_model) =>
|
||||||
_log.info($"begin change filter ({++_filter_change_count})...");
|
new Row(this, row_model.security, row_model.trade);
|
||||||
var timer = new Stopwatch();
|
|
||||||
timer.Start();
|
|
||||||
_filter = _get_filter_states().build_predicate();
|
async Task _show_ladder_async(Row row) {
|
||||||
timer.Stop();
|
var smd_lookup = await SparkUtils.get_security_md_async(_bus, row.security.Id);
|
||||||
_log.info($"filter change ({_filter_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
|
if (smd_lookup.success) _bus.post(SparkTags.ShowLadderTag, smd_lookup.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RowFactory {
|
public class Row : SecurityRow, DIYGrid.IRowVM {
|
||||||
// the row key must be unique to each row in the collection
|
readonly uint _trade_key;
|
||||||
int _row_id_counter;
|
|
||||||
|
|
||||||
public TimeAndSalesRowVM create(TradeInfo trade_info) =>
|
readonly string
|
||||||
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade);
|
_security_text,
|
||||||
|
_time_text,
|
||||||
|
_price_text,
|
||||||
|
_size_text,
|
||||||
|
_aggressor_text,
|
||||||
|
_trade_key_text,
|
||||||
|
_security_key_text;
|
||||||
|
|
||||||
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade) =>
|
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
|
||||||
new TimeAndSalesRowVM(_row_id_counter++, security, trade);
|
_trade_key = trade.Key;
|
||||||
|
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
|
||||||
|
price = trade.price();
|
||||||
|
size = trade.Size;
|
||||||
|
aggressor_side = trade.AggressorSide;
|
||||||
|
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
|
||||||
|
_security_text = security.ToString();
|
||||||
|
_time_text = time.ToString("M/d h:mm:ss tt");
|
||||||
|
_price_text = security.ContractSpec.format(price);
|
||||||
|
_size_text = size.ToString();
|
||||||
|
_aggressor_text = aggressor_side.ToString();
|
||||||
|
_trade_key_text = _trade_key.ToString();
|
||||||
|
_security_key_text = security_key.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action invalidated;
|
||||||
|
|
||||||
|
public uint trade_key => _trade_key;
|
||||||
|
public DateTime time { get; }
|
||||||
|
public double price { get; }
|
||||||
|
public uint size { get; }
|
||||||
|
public AggressorSide aggressor_side { get; }
|
||||||
|
public ICommand show_ladder { get; }
|
||||||
|
|
||||||
|
public string this[TimeAndSalesView.Columns col] {
|
||||||
|
get {
|
||||||
|
switch (col) {
|
||||||
|
case TimeAndSalesView.Columns.Security: return _security_text;
|
||||||
|
case TimeAndSalesView.Columns.Time: return _time_text;
|
||||||
|
case TimeAndSalesView.Columns.Price: return _price_text;
|
||||||
|
case TimeAndSalesView.Columns.Size: return _size_text;
|
||||||
|
case TimeAndSalesView.Columns.Aggressor: return _aggressor_text;
|
||||||
|
case TimeAndSalesView.Columns.SecurityKey: return _security_key_text;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void raise_invalidated() => invalidated?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="bts.shell.time_and_sales.TimeAndSalesView"
|
x:Class="mbts.ui.spark.TimeAndSalesView"
|
||||||
x:ClassModifier="internal"
|
x:ClassModifier="internal"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||||
xmlns:tas="clr-namespace:bts.shell.time_and_sales"
|
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||||
FocusManager.IsFocusScope="True"
|
FocusManager.IsFocusScope="True"
|
||||||
Background="{StaticResource CommonBackground}"
|
Background="{StaticResource CommonBackground}"
|
||||||
>
|
>
|
||||||
@@ -26,14 +26,13 @@
|
|||||||
x:Name="_grid"
|
x:Name="_grid"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Columns="{Binding columns}"
|
Columns="{Binding columns}"
|
||||||
Rows="{Binding rows}"
|
Rows="{Binding rows_view}"
|
||||||
CurrentRow="{Binding current_row}"
|
CurrentRow="{Binding current_row}"
|
||||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||||
>
|
>
|
||||||
<c:DIYGrid.InputBindings>
|
<c:DIYGrid.InputBindings>
|
||||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding show_ladder_cmd}"/>
|
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||||
<KeyBinding Key="Return" Command="{Binding show_ladder_cmd}"/>
|
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||||
<KeyBinding Key="A" Modifiers="Ctrl" Command="{Binding select_all_cmd}"/>
|
|
||||||
</c:DIYGrid.InputBindings>
|
</c:DIYGrid.InputBindings>
|
||||||
<c:DIYGrid.CommandBindings>
|
<c:DIYGrid.CommandBindings>
|
||||||
<CommandBinding
|
<CommandBinding
|
||||||
@@ -44,18 +43,13 @@
|
|||||||
</c:DIYGrid.CommandBindings>
|
</c:DIYGrid.CommandBindings>
|
||||||
<c:DIYGrid.RowTemplate>
|
<c:DIYGrid.RowTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<tas:TimeAndSalesRowView/>
|
<usp:TimeAndSalesRowView/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</c:DIYGrid.RowTemplate>
|
</c:DIYGrid.RowTemplate>
|
||||||
</c:DIYGrid>
|
</c:DIYGrid>
|
||||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||||
<StatusBarItem HorizontalAlignment="Right">
|
<StatusBarItem HorizontalAlignment="Right">
|
||||||
<TextBlock>
|
<TextBlock Text=" "/>
|
||||||
<Run Text="Trades:"/>
|
|
||||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
|
||||||
<Run Text="of"/>
|
|
||||||
<Run Text="{Binding total_row_count, Mode=OneWay}" />
|
|
||||||
</TextBlock>
|
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
</StatusBar>
|
</StatusBar>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
using mbts.ui.controls;
|
using mbts.spark;
|
||||||
|
using mbts.ui.controls;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
|
||||||
namespace bts.shell.time_and_sales {
|
namespace mbts.ui.spark {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for TimeAndSalesView.xaml
|
/// Interaction logic for TimeAndSalesView.xaml
|
||||||
/// </summary>
|
/// </summary>
|
||||||
partial class TimeAndSalesView : UserControl {
|
partial class TimeAndSalesView : UserControl {
|
||||||
public enum Columns { Time, Security, Price, Size, SecurityKey, Aggressor, RowKey }
|
public enum Columns { Time, Security, Price, Size, OrderType, SecurityKey, Aggressor }
|
||||||
|
|
||||||
public TimeAndSalesView() =>
|
public TimeAndSalesView() =>
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesRowVM> { }
|
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row, TimeAndSalesView.Columns> { }
|
||||||
|
}
|
||||||
static class TimeAndSalesViewColumnsExtensions {
|
|
||||||
public static string col_id(this TimeAndSalesView.Columns column) =>
|
|
||||||
column.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,218 +1,125 @@
|
|||||||
using bts.api;
|
using bts;
|
||||||
|
using bts.api;
|
||||||
|
using bts.shell;
|
||||||
using bts.shell.fills;
|
using bts.shell.fills;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.view;
|
|
||||||
using bts.utils.csv;
|
|
||||||
using mbts;
|
|
||||||
using mbts.co2;
|
using mbts.co2;
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.spark;
|
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.settings;
|
using mbts.ui.settings;
|
||||||
|
using mbts.ui.shell;
|
||||||
using mbts.ui.spark;
|
using mbts.ui.spark;
|
||||||
using mbts.ui.utility;
|
using mbts.ui.utility;
|
||||||
using mbts.utils;
|
using mbts.utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using static bts.shell.TimeAndSalesWindowFactory;
|
||||||
|
|
||||||
namespace bts.shell.time_and_sales {
|
namespace mbts.spark {
|
||||||
class TimeAndSalesWindow : SparkWindow<TimeAndSalesSettings>, ISharedWindow {
|
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||||
readonly MDConsumer _consumer;
|
readonly MDConsumer _consumer;
|
||||||
readonly TimeAndSalesWindowFactory _factory;
|
readonly TimeAndSalesWindowFactory _factory;
|
||||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||||
readonly bool _can_pause_updates;
|
SingleValuePump<TimeAndSalesVM> _vm;
|
||||||
TimeAndSalesVM _vm;
|
|
||||||
TimeAndSalesModel _model;
|
TimeAndSalesModel _model;
|
||||||
SecurityCriteria _security_criteria_backing;
|
StartupData _state;
|
||||||
TimeAndSalesSettings _state;
|
|
||||||
TimeAndSalesView _view;
|
TimeAndSalesView _view;
|
||||||
SparkCommand[] _commands;
|
SparkCommand[] _commands;
|
||||||
SparkMenuDisplayVM _menu_display_vm;
|
|
||||||
IDisposable _subscriptions;
|
IDisposable _subscriptions;
|
||||||
|
|
||||||
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, TimeAndSalesSettings state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager, bool can_pause_updates)
|
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager)
|
||||||
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
|
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
|
||||||
Log.assert(state != null);
|
Log.assert(state != null);
|
||||||
_consumer = consumer;
|
_consumer = consumer;
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
_state = state ?? TimeAndSalesSettings.Default(new SecurityCriteria());
|
_state = state;
|
||||||
if (_state.security_criteria == null)
|
|
||||||
_state = _state.with(new SecurityCriteria());
|
|
||||||
_security_criteria_backing = _state.security_criteria;
|
|
||||||
_settings_manager = settings_manager;
|
_settings_manager = settings_manager;
|
||||||
_can_pause_updates = can_pause_updates;
|
|
||||||
thread_id = mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().time_and_sales ? _window_type : null;
|
|
||||||
Icon.Freeze();
|
Icon.Freeze();
|
||||||
}
|
}
|
||||||
|
|
||||||
event Action<SecurityCriteria> _security_criteria_changed;
|
|
||||||
|
|
||||||
public override string window_type => _window_type;
|
public override string window_type => _window_type;
|
||||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||||
public override UIElement Content => _view;
|
public override UIElement Content => _view;
|
||||||
public override double MinWidth => TimeAndSalesSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
public override double MinWidth => StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||||
public string thread_id { get; }
|
public string thread_id { get; }
|
||||||
public override TimeAndSalesSettings SaveState {
|
public override StartupData SaveState => _state = _vm.value.create();
|
||||||
get {
|
|
||||||
_ensure_state(true);
|
|
||||||
return _state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SecurityCriteria _security_criteria {
|
|
||||||
get => _security_criteria_backing;
|
|
||||||
set {
|
|
||||||
_security_criteria_backing = value;
|
|
||||||
_state = null;
|
|
||||||
_security_criteria_changed?.Invoke(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose() =>
|
public override void Dispose() =>
|
||||||
_dispose_resources();
|
_dispose_resources();
|
||||||
|
|
||||||
public void thread_init() {
|
public void thread_init() {
|
||||||
_dispose_resources();
|
_dispose_resources();
|
||||||
_ensure_state();
|
_vm = new SingleValuePump<TimeAndSalesVM>();
|
||||||
if (!_state.use_defaults)
|
var connectable_builder = new ConnectableSeriesBuilder<TimeAndSalesVM>(_vm.observable);
|
||||||
_settings_manager.instance_settings = _state.grid;
|
|
||||||
_menu_display_vm = new SparkMenuDisplayVM();
|
|
||||||
_menu_display_vm.load_from(_state.menu_display);
|
|
||||||
_vm = new TimeAndSalesVM(_settings_manager);
|
|
||||||
var selected_rows_src = Observable.Create<ImmutableHashSet<TimeAndSalesRowVM>>(o => {
|
|
||||||
_vm.selected_rows_changed += o.OnNext;
|
|
||||||
return Disposable.Create(() => _vm.selected_rows_changed -= o.OnNext);
|
|
||||||
});
|
|
||||||
var current_row_src = Observable.Create<TimeAndSalesRowVM>(o => {
|
|
||||||
if (_vm.current_row != null)
|
|
||||||
o.OnNext(_vm.current_row);
|
|
||||||
_vm.current_row_changed += o.OnNext;
|
|
||||||
return Disposable.Create(() => _vm.current_row_changed -= o.OnNext);
|
|
||||||
});
|
|
||||||
var current_sec_key_src = current_row_src.Select(r => {
|
|
||||||
var result = ImmutableHashSet<uint>.Empty;
|
|
||||||
if (r != null)
|
|
||||||
result = result.Add(r.security_key);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||||
var security_criteria_changed = new Action<SecurityCriteria>(sc => { });
|
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||||
SparkCommand toggle_is_paused_cmd = null;
|
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||||
toggle_is_paused_cmd = new SparkCommandCommand(get_is_paused_cmd_title(), Icons.Pause, new DelegateCommand(() => {
|
|
||||||
_vm.is_paused = !_vm.is_paused;
|
|
||||||
toggle_is_paused_cmd.title = get_is_paused_cmd_title();
|
|
||||||
})) {
|
|
||||||
show_in_toolbar = _can_pause_updates,
|
|
||||||
show_in_context_menu = _can_pause_updates
|
|
||||||
};
|
|
||||||
_commands = new SparkCommand[] {
|
_commands = new SparkCommand[] {
|
||||||
// csv export
|
SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
|
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||||
// ladder
|
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
|
||||||
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
|
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||||
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
|
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||||
// ticket
|
|
||||||
SparkCommands.ShowOrderTicketAutoLookup(SparkCommands.KeyGestures.ShowOrderTicket)
|
|
||||||
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
|
|
||||||
// fills
|
|
||||||
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills)
|
|
||||||
.complete(selected_rows_src, always_true, get_fills_startup_data),
|
|
||||||
// mview
|
|
||||||
SparkCommands.ShowMarketView
|
|
||||||
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria),
|
|
||||||
// pause
|
|
||||||
toggle_is_paused_cmd,
|
|
||||||
// change securities
|
|
||||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _security_criteria)),
|
|
||||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
// 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(() => {
|
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||||
_settings_manager,
|
_settings_manager,
|
||||||
s => s.time_and_sales_grid,
|
s => s.time_and_sales_grid,
|
||||||
UserSettings.FileExtensions.GRID,
|
UserSettings.FileExtensions.GRID,
|
||||||
s => _vm.grid_settings_manager.create_vm(
|
s => _vm.value.grid_settings_manager.create_vm(
|
||||||
// we need to get the most up to date GridSettings when launching b/c users may
|
// 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.
|
// have changed some settings on the grid directly, like column widths.
|
||||||
s.with(_vm.columns.Select(c => c.create()))
|
s.with(_vm.value.columns.Select(c => c.create()))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||||
return dialog;
|
return dialog;
|
||||||
})),
|
}))
|
||||||
_menu_display_vm.spark_cmd
|
|
||||||
};
|
};
|
||||||
_vm.load(_state.filters, _state.sorting_criterion, _state.grid);
|
_subscriptions = Disposable.Create(
|
||||||
_vm.request_show_ladder += show_ladder;
|
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(update_criteria),
|
||||||
var security_criteria_src = _bus.subscribe(update_securities_cmd.tag);
|
connectable_builder.connect_all());
|
||||||
var subs = new List<IDisposable> {
|
_initialize();
|
||||||
security_criteria_src.Subscribe(sc => _security_criteria = sc),
|
|
||||||
Disposable.Create(() => _vm.request_show_ladder -= show_ladder)
|
|
||||||
};
|
|
||||||
_model = new TimeAndSalesModel(_bus, _consumer, Observable.Create<SecurityCriteria>(o => {
|
|
||||||
o.OnNext(_security_criteria);
|
|
||||||
return security_criteria_src.Subscribe(o);
|
|
||||||
}));
|
|
||||||
subs.Add(_model.trades.OnDispatcher().Subscribe(_vm.on_new_trades));
|
|
||||||
_subscriptions = Disposable.Create(subs.ToArray());
|
|
||||||
if (_view == null)
|
|
||||||
_view = new TimeAndSalesView();
|
|
||||||
_view.DataContext = _vm;
|
|
||||||
// local methods
|
|
||||||
bool always_true<T>(T _) => true;
|
bool always_true<T>(T _) => true;
|
||||||
|
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
|
||||||
FillsSettings get_fills_startup_data(ImmutableHashSet<TimeAndSalesRowVM> rows) =>
|
|
||||||
new FillsSettings(new FillsFilterStates {
|
new FillsSettings(new FillsFilterStates {
|
||||||
security = rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria
|
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
|
||||||
});
|
});
|
||||||
|
void update_criteria(SecurityCriteria c) {
|
||||||
string get_is_paused_cmd_title() =>
|
_state = _vm.value.create().with(c);
|
||||||
$"{(_vm.is_paused ? "Resume" : "Pause")} Updates";
|
_initialize();
|
||||||
|
|
||||||
void show_ladder(TimeAndSalesRowVM row) {
|
|
||||||
if (row != null)
|
|
||||||
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet.Create(new[] { row.security_key }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
|
||||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
|
||||||
|
|
||||||
void _dispose_resources() {
|
void _dispose_resources() {
|
||||||
Disposable.dispose(ref _subscriptions);
|
Disposable.dispose(ref _subscriptions);
|
||||||
|
Disposable.dispose(ref _vm);
|
||||||
|
Disposable.dispose(ref _model);
|
||||||
if (_commands != null) {
|
if (_commands != null) {
|
||||||
_commands.OfType<IDisposable>().DisposeAll();
|
_commands.OfType<IDisposable>().DisposeAll();
|
||||||
_commands = null;
|
_commands = null;
|
||||||
}
|
}
|
||||||
Disposable.dispose(ref _model);
|
|
||||||
Disposable.dispose(ref _vm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _ensure_state(bool force_recreate = false) {
|
void _initialize() {
|
||||||
if (_state == null || force_recreate) {
|
Disposable.dispose(ref _model);
|
||||||
if (_vm == null) {
|
_model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
|
||||||
_state = TimeAndSalesSettings.Default(_security_criteria);
|
// the previous _vm.value is automatically disposed when _vm.value is set - no need to dispose it here
|
||||||
} else {
|
_vm.value = new TimeAndSalesVM(_bus, _settings_manager, _model, _state);
|
||||||
var (filters, sorting, grid) = _vm.create();
|
if (_view == null)
|
||||||
_state = new TimeAndSalesSettings(_security_criteria, filters, grid, sorting.ToList()) {
|
_view = new TimeAndSalesView();
|
||||||
use_defaults = _settings_manager.instance_settings == null
|
_view.DataContext = _vm.value;
|
||||||
};
|
|
||||||
}
|
|
||||||
// should be safe to assume _menu_display_vm and _vm will either both be null, or both be non null,
|
|
||||||
// but redundant null checks don't hurt, and just may prevent a crash.
|
|
||||||
if (_menu_display_vm != null)
|
|
||||||
_state.menu_display = _menu_display_vm.safe_create();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,33 @@
|
|||||||
using bts.api;
|
using bts.api;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.shell.time_and_sales;
|
|
||||||
using mbts;
|
using mbts;
|
||||||
using mbts.co2;
|
using mbts.co2;
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.spark;
|
using mbts.spark;
|
||||||
|
using mbts.spark.filters;
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.settings;
|
using mbts.ui.settings;
|
||||||
|
using ProtoBuf;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
|
using static mbts.ui.controls.DIYGrid;
|
||||||
using mbts.config;
|
using static mbts.ui.spark.TimeAndSalesView;
|
||||||
|
|
||||||
namespace bts.shell {
|
namespace bts.shell {
|
||||||
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesSettings> {
|
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesWindowFactory.StartupData> {
|
||||||
readonly IBus _bus;
|
readonly IBus _bus;
|
||||||
readonly MDConsumer _consumer;
|
readonly MDConsumer _consumer;
|
||||||
readonly UserSettingsHub _settings_hub;
|
readonly UserSettingsHub _settings_hub;
|
||||||
readonly TimeAndSalesConfig _time_and_sales_conf;
|
|
||||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||||
|
|
||||||
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
|
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
|
||||||
_bus = bus;
|
_bus = bus;
|
||||||
_consumer = consumer;
|
_consumer = consumer;
|
||||||
_settings_hub = settings_hub;
|
_settings_hub = settings_hub;
|
||||||
_time_and_sales_conf = new TimeAndSalesConfig(Configuration.get<bts.config.TimeAndSalesOptions>());
|
|
||||||
_dispose = Disposable.Create(
|
_dispose = Disposable.Create(
|
||||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||||
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
|
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
|
||||||
@@ -35,21 +36,91 @@ namespace bts.shell {
|
|||||||
|
|
||||||
public static string col_id(Columns col) => col.ToString();
|
public static string col_id(Columns col) => col.ToString();
|
||||||
|
|
||||||
public override Task<IShellWindow> load(TimeAndSalesSettings state) =>
|
public override Task<IShellWindow> load(StartupData state) =>
|
||||||
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
|
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
|
||||||
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g)),
|
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g))));
|
||||||
_time_and_sales_conf.can_pause_updates));
|
|
||||||
|
|
||||||
public async void show(SecurityCriteria criteria) {
|
public async void show(SecurityCriteria criteria) {
|
||||||
var w = await load(new TimeAndSalesSettings(criteria));
|
var w = await load(new StartupData(criteria));
|
||||||
_bus.post(SparkTags.ShowWindow, w);
|
_bus.post(SparkTags.ShowWindow, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly struct TimeAndSalesConfig {
|
[ProtoContract]
|
||||||
public readonly bool can_pause_updates;
|
public class StartupData {
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public readonly SecurityCriteria security_criteria;
|
||||||
|
[ProtoMember(2)]
|
||||||
|
List<SortingCriteria> _sorting_criterion;
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public FilterStates filters;
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public GridSettings grid;
|
||||||
|
|
||||||
public TimeAndSalesConfig(bts.config.TimeAndSalesOptions options) =>
|
public StartupData() { }
|
||||||
can_pause_updates = options.can_pause_updates;
|
|
||||||
|
public StartupData(SecurityCriteria security_criteria) :
|
||||||
|
this(security_criteria, new FilterStates(), null, null) { }
|
||||||
|
|
||||||
|
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
|
||||||
|
this.security_criteria = security_criteria;
|
||||||
|
this.filters = filters;
|
||||||
|
this.grid = grid;
|
||||||
|
_sorting_criterion = sorting_criterion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||||
|
|
||||||
|
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
|
||||||
|
|
||||||
|
public static StartupData Default(SecurityCriteria security_criteria) =>
|
||||||
|
new StartupData(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
|
||||||
|
|
||||||
|
public StartupData with(SecurityCriteria security_criteria) =>
|
||||||
|
new StartupData(security_criteria, filters, grid);
|
||||||
|
|
||||||
|
public static class DefaultColumns {
|
||||||
|
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||||
|
public static GridColumnSettings time => new GridColumnSettings(col_id(Columns.Time), DefaultColumnHeaders.Time, width: DefaultColumnWidths.Time);
|
||||||
|
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||||
|
public static GridColumnSettings size => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||||
|
public static GridColumnSettings aggressor => new GridColumnSettings(col_id(Columns.Aggressor), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
|
||||||
|
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible:false, width: DefaultColumnWidths.SecurityKey);
|
||||||
|
|
||||||
|
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||||
|
get {
|
||||||
|
yield return new SortingCriteria(time.id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<GridColumnSettings> all {
|
||||||
|
get {
|
||||||
|
yield return security;
|
||||||
|
yield return time;
|
||||||
|
yield return price;
|
||||||
|
yield return size;
|
||||||
|
yield return aggressor;
|
||||||
|
yield return security_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class FilterStates {
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public TimeFilterTuple time;
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public string security;
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public DoubleMinMaxState price;
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public UIntMinMaxState size;
|
||||||
|
// [ProtoMember(5)]
|
||||||
|
// ProtoMember 5 cannot be used further
|
||||||
|
[ProtoMember(6)]
|
||||||
|
public string security_key;
|
||||||
|
[ProtoMember(7)]
|
||||||
|
public co2.AggressorSide[] aggressor_sides;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,5 +20,5 @@ namespace mbts.ui.spark {
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row> { }
|
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ using bts.api;
|
|||||||
using bts.shell;
|
using bts.shell;
|
||||||
using bts.shell.spreader;
|
using bts.shell.spreader;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.view;
|
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.settings;
|
using mbts.ui.settings;
|
||||||
@@ -28,7 +27,6 @@ namespace mbts.spark {
|
|||||||
StartupData _state;
|
StartupData _state;
|
||||||
OrdersModel _orders_model;
|
OrdersModel _orders_model;
|
||||||
SyntheticManagementView _view;
|
SyntheticManagementView _view;
|
||||||
SparkMenuDisplayVM _menu_display_vm;
|
|
||||||
IDisposable _subscriptions;
|
IDisposable _subscriptions;
|
||||||
IReadOnlyList<SparkCommand> _commands;
|
IReadOnlyList<SparkCommand> _commands;
|
||||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||||
@@ -45,7 +43,7 @@ namespace mbts.spark {
|
|||||||
public override string window_type => _window_type;
|
public override string window_type => _window_type;
|
||||||
public override UIElement Content => _view;
|
public override UIElement Content => _view;
|
||||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||||
public override StartupData SaveState => _state = _vm.value.create().with(menu_display: _menu_display_vm.safe_create());
|
public override StartupData SaveState => _state = _vm.value.create();
|
||||||
public override double MinWidth =>
|
public override double MinWidth =>
|
||||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||||
@@ -55,8 +53,6 @@ namespace mbts.spark {
|
|||||||
|
|
||||||
public void thread_init() {
|
public void thread_init() {
|
||||||
Log.notnull(_state);
|
Log.notnull(_state);
|
||||||
_menu_display_vm = new SparkMenuDisplayVM();
|
|
||||||
_menu_display_vm.load_from(_state.menu_display);
|
|
||||||
|
|
||||||
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
|
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
|
||||||
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
|
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
|
||||||
@@ -101,8 +97,7 @@ namespace mbts.spark {
|
|||||||
new SparkCommandCommand(
|
new SparkCommandCommand(
|
||||||
"Import Synthetics...", Icons.Import, FileDialogCommand.open_file(import, extensions: SyntheticSecurityParser.file_extension)) {
|
"Import Synthetics...", Icons.Import, FileDialogCommand.open_file(import, extensions: SyntheticSecurityParser.file_extension)) {
|
||||||
show_in_toolbar = false
|
show_in_toolbar = false
|
||||||
},
|
}
|
||||||
_menu_display_vm.spark_cmd
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_subscriptions = Disposable.Create(
|
_subscriptions = Disposable.Create(
|
||||||
@@ -134,9 +129,6 @@ namespace mbts.spark {
|
|||||||
Commands.OfType<IDisposable>().DisposeAll();
|
Commands.OfType<IDisposable>().DisposeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
|
||||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
|
||||||
|
|
||||||
struct RowModificationParams {
|
struct RowModificationParams {
|
||||||
public readonly SecurityImmutable security;
|
public readonly SecurityImmutable security;
|
||||||
public readonly bool has_working_orders;
|
public readonly bool has_working_orders;
|
||||||
|
|||||||
@@ -48,28 +48,14 @@ namespace bts.shell {
|
|||||||
public readonly FilterStates filters;
|
public readonly FilterStates filters;
|
||||||
[ProtoMember(3)]
|
[ProtoMember(3)]
|
||||||
public readonly GridSettings grid;
|
public readonly GridSettings grid;
|
||||||
[ProtoMember(4)]
|
|
||||||
public readonly SparkMenuDisplaySettings menu_display;
|
|
||||||
|
|
||||||
public StartupData() : this(new List<SortingCriteria>(), null, null) { }
|
public StartupData() : this(new List<SortingCriteria>(), null, null) { }
|
||||||
|
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
|
||||||
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default)
|
|
||||||
: this(sorting_criteria, filters, grid, SparkMenuDisplaySettings.Default) { }
|
|
||||||
|
|
||||||
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid, SparkMenuDisplaySettings menu_display) {
|
|
||||||
this.sorting_criteria = sorting_criteria;
|
this.sorting_criteria = sorting_criteria;
|
||||||
this.filters = filters;
|
this.filters = filters;
|
||||||
this.grid = grid;
|
this.grid = grid;
|
||||||
this.menu_display = menu_display;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StartupData with(List<SortingCriteria> sorting_criteria = null, FilterStates filters = null, GridSettings grid = null, SparkMenuDisplaySettings? menu_display = null) =>
|
|
||||||
new StartupData(
|
|
||||||
sorting_criteria ?? this.sorting_criteria,
|
|
||||||
filters ?? this.filters,
|
|
||||||
grid ?? this.grid,
|
|
||||||
menu_display ?? this.menu_display);
|
|
||||||
|
|
||||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||||
|
|
||||||
static class DefaultColumns {
|
static class DefaultColumns {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
<sys:Double x:Key="VerticalPush2">8</sys:Double>
|
<sys:Double x:Key="VerticalPush2">8</sys:Double>
|
||||||
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
||||||
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
||||||
<Thickness x:Key="Margin.LP1_RP1" Left="{StaticResource HorizontalPush1}" Right="{StaticResource HorizontalPush1}"/>
|
|
||||||
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
|
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
|
||||||
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
||||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||||
@@ -131,6 +130,22 @@
|
|||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<VisualStateManager.VisualStateGroups>
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.EditPane}}">
|
||||||
|
<VisualState Name="{x:Static u:VisualStates.Active}">
|
||||||
|
<Storyboard>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState Name="{x:Static u:VisualStates.Inactive}">
|
||||||
|
<Storyboard>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
</VisualStateManager.VisualStateGroups>
|
</VisualStateManager.VisualStateGroups>
|
||||||
<AdornerDecorator>
|
<AdornerDecorator>
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -138,19 +153,13 @@
|
|||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<ListBox
|
<ListBox
|
||||||
x:Name="_tree_view"
|
x:Name="_tree_view"
|
||||||
Grid.RowSpan="2"
|
|
||||||
ItemsSource="{Binding definitions_view}"
|
ItemsSource="{Binding definitions_view}"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
MinWidth="180"
|
MinWidth="180"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderThickness="0,0,1,0"
|
|
||||||
SelectedItem="{Binding selected_definition}"
|
SelectedItem="{Binding selected_definition}"
|
||||||
>
|
>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
@@ -164,176 +173,275 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
<Grid x:Name="edit_pane" Grid.Column="1">
|
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
|
||||||
<u:StateManager.VisualState>
|
<Grid x:Name="edit_pane">
|
||||||
<Binding Path="selected_definition">
|
<Grid.RowDefinitions>
|
||||||
<Binding.Converter>
|
<RowDefinition Height="Auto"/>
|
||||||
<cv:ValueMap Default="{x:Static u:VisualStates.Active}">
|
<RowDefinition Height="Auto"/>
|
||||||
<cv:KeyValue Key="{x:Null}" Value="{x:Static u:VisualStates.Inactive}"/>
|
<RowDefinition Height="*"/>
|
||||||
</cv:ValueMap>
|
<RowDefinition Height="Auto"/>
|
||||||
</Binding.Converter>
|
</Grid.RowDefinitions>
|
||||||
</Binding>
|
<Grid u:StateManager.VisualStateCollection="{Binding content_visual_states}" Margin="{StaticResource Margin.TP1}" >
|
||||||
</u:StateManager.VisualState>
|
<VisualStateManager.VisualStateGroups>
|
||||||
<VisualStateManager.VisualStateGroups>
|
<!--Name-->
|
||||||
<VisualStateGroup>
|
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||||
<VisualState Name="{x:Static u:VisualStates.Active}"/>
|
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}"/>
|
||||||
<VisualState Name="{x:Static u:VisualStates.Inactive}">
|
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||||
<Storyboard>
|
<Storyboard>
|
||||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||||
</BooleanAnimationUsingKeyFrames>
|
</BooleanAnimationUsingKeyFrames>
|
||||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
</Storyboard>
|
||||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
</VisualState>
|
||||||
</BooleanAnimationUsingKeyFrames>
|
<VisualState Name="{x:Static CannotRenameDefinition, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
<Storyboard>
|
||||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
|
||||||
</BooleanAnimationUsingKeyFrames>
|
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definitions."/>
|
||||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="algo_editor_scroll_viewer" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
</ObjectAnimationUsingKeyFrames>
|
||||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||||
</BooleanAnimationUsingKeyFrames>
|
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(FrameworkElement.ToolTip).(UIElement.Visibility)">
|
</BooleanAnimationUsingKeyFrames>
|
||||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
</Storyboard>
|
||||||
</ObjectAnimationUsingKeyFrames>
|
</VisualState>
|
||||||
</Storyboard>
|
<VisualState Name="{x:Static CannotRenameDefinitionWhileTemplatesPresent, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||||
</VisualState>
|
<Storyboard>
|
||||||
</VisualStateGroup>
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
|
||||||
</VisualStateManager.VisualStateGroups>
|
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definition while templates present."/>
|
||||||
<Grid.ToolTip>
|
</ObjectAnimationUsingKeyFrames>
|
||||||
<ToolTip Visibility="Collapsed">
|
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||||
<StackPanel Orientation="Vertical">
|
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||||
<TextBlock FontStyle="Italic">Select an existing algo to edit from the list to the left.</TextBlock>
|
</BooleanAnimationUsingKeyFrames>
|
||||||
<StackPanel Orientation="Horizontal">
|
</Storyboard>
|
||||||
<TextBlock FontStyle="Italic">Or click the</TextBlock>
|
</VisualState>
|
||||||
<Image Source="{x:Static u:Icons.AddBoxes}" Height="20" Margin="2,0"/>
|
<VisualState Name="{x:Static CannotRenameTemplate, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||||
<TextBlock FontStyle="Italic"> button to create a new one.</TextBlock>
|
<Storyboard>
|
||||||
</StackPanel>
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
|
||||||
</StackPanel>
|
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename templates."/>
|
||||||
</ToolTip>
|
</ObjectAnimationUsingKeyFrames>
|
||||||
</Grid.ToolTip>
|
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||||
<Grid.RowDefinitions>
|
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||||
<RowDefinition Height="27"/>
|
</BooleanAnimationUsingKeyFrames>
|
||||||
<RowDefinition Height="*"/>
|
</Storyboard>
|
||||||
</Grid.RowDefinitions>
|
</VisualState>
|
||||||
<Grid.ColumnDefinitions>
|
</VisualStateGroup>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<!--Abbreviation-->
|
||||||
<ColumnDefinition Width="*"/>
|
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
|
||||||
<ColumnDefinition Width="Auto"/>
|
<VisualState Name="{x:Static Populated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
|
||||||
</Grid.ColumnDefinitions>
|
<Storyboard>
|
||||||
<Rectangle Grid.ColumnSpan="3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Fill="{x:Static u:BTSColors.dialog_skirt_brush}" UseLayoutRounding="True"/>
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
|
||||||
<Button
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||||
x:Name="custom_badge_color_picker_button"
|
</ObjectAnimationUsingKeyFrames>
|
||||||
Background="{Binding selected_definition.badge_fill}"
|
</Storyboard>
|
||||||
Margin="{StaticResource Margin.LP1}"
|
</VisualState>
|
||||||
ToolTip="Change the icon color used for this algo."
|
<VisualState Name="{x:Static Unpopulated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
|
||||||
IsEnabled="True"
|
<Storyboard>
|
||||||
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
|
||||||
/>
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||||
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
|
</ObjectAnimationUsingKeyFrames>
|
||||||
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
|
||||||
<Grid>
|
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
|
||||||
<Grid.RowDefinitions>
|
</ColorAnimationUsingKeyFrames>
|
||||||
<RowDefinition Height="Auto"/>
|
</Storyboard>
|
||||||
<RowDefinition Height="*"/>
|
</VisualState>
|
||||||
</Grid.RowDefinitions>
|
<VisualState Name="{x:Static NotSupportedForTemplates, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
|
||||||
<CheckBox
|
<Storyboard>
|
||||||
x:Name="default_badge_color_checkbox"
|
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(ToolTipService.ShowOnDisabled)">
|
||||||
VerticalAlignment="Center"
|
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
|
||||||
VerticalContentAlignment="Center"
|
</BooleanAnimationUsingKeyFrames>
|
||||||
Margin="6,4,0,4"
|
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||||
IsChecked="{Binding selected_definition.custom_badge_color.enabled, Converter={StaticResource NotConverter}}"
|
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||||
>
|
</BooleanAnimationUsingKeyFrames>
|
||||||
<CheckBox.ToolTip>
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
|
||||||
<TextBlock>
|
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
|
||||||
<Run>Use the default icon color for this algo.</Run>
|
</ColorAnimationUsingKeyFrames>
|
||||||
<LineBreak/>
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_not_supported_for_templates_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
|
||||||
<Run FontStyle="Italic">Hint: the default color is that of the algo-root.</Run>
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
<!--Badge Selector-->
|
||||||
|
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
|
||||||
|
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="default_badge" Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||||
|
<DiscreteDoubleKeyFrame KeyTime="0" Value="0.5"/>
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
|
||||||
|
<Storyboard>
|
||||||
|
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||||
|
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||||
|
</BooleanAnimationUsingKeyFrames>
|
||||||
|
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="default_badge_radio_button" Storyboard.TargetProperty="(ToggleButton.IsChecked)">
|
||||||
|
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
|
||||||
|
</BooleanAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateManager.VisualStateGroups>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
|
||||||
|
<ColumnDefinition Width="3*"/>
|
||||||
|
<ColumnDefinition Width="2*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock VerticalAlignment="Center">Name:</TextBlock>
|
||||||
|
<TextBox
|
||||||
|
x:Name="name_text_box"
|
||||||
|
Grid.Column="1"
|
||||||
|
ToolTipService.ShowOnDisabled="True"
|
||||||
|
Margin="{StaticResource Margin.LP1}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||||
|
/>
|
||||||
|
<Grid x:Name="abbreviation_root" Grid.Column="2">
|
||||||
|
<Grid.ToolTip>
|
||||||
|
<Grid>
|
||||||
|
<TextBlock x:Name="abbreviation_active_tooltip" Visibility="Collapsed">
|
||||||
|
<TextBlock.Inlines>
|
||||||
|
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
|
||||||
|
</TextBlock.Inlines>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</CheckBox.ToolTip>
|
<TextBlock x:Name="abbreviation_not_supported_for_templates_tooltip" Visibility="Collapsed">
|
||||||
<Grid x:Name="default_badge_content_root">
|
Not supported for templates.
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</Grid.ToolTip>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
|
||||||
|
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock>Icon:</TextBlock>
|
||||||
|
<RadioButton
|
||||||
|
x:Name="default_badge_radio_button"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Margin="{StaticResource Margin.LP1}"
|
||||||
|
GroupName="Badge"
|
||||||
|
>
|
||||||
|
<RadioButton.ToolTip>
|
||||||
|
<ToolTip DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock>Use the default icon color for this algo.</TextBlock>
|
||||||
|
<StackPanel TextElement.FontStyle="Italic" Orientation="Horizontal">
|
||||||
|
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
|
||||||
|
<usp:OrderTypeItem
|
||||||
|
Margin="2,0"
|
||||||
|
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||||
|
DisplayName="{Binding selected_definition.algo_root.display_name}"
|
||||||
|
/>
|
||||||
|
<TextBlock>).</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</ToolTip>
|
||||||
|
</RadioButton.ToolTip>
|
||||||
|
<Grid x:Name="default_badge_content_root">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock>Default</TextBlock>
|
||||||
|
<Rectangle
|
||||||
|
x:Name="default_badge"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="{StaticResource Margin.LP1}"
|
||||||
|
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||||
|
Style="{StaticResource RectangleBadgeStyle}"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</RadioButton>
|
||||||
|
<RadioButton
|
||||||
|
Grid.Column="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Margin="{StaticResource Margin.LP2}"
|
||||||
|
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
|
||||||
|
GroupName="Badge"
|
||||||
|
>
|
||||||
|
<RadioButton.ToolTip>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock>Use Default Icon</TextBlock>
|
<TextBlock Grid.ColumnSpan="3">Specify a custom icon color for this algo.</TextBlock>
|
||||||
|
<TextBlock Grid.Row="1" FontStyle="Italic">Hint: Click</TextBlock>
|
||||||
<Rectangle
|
<Rectangle
|
||||||
x:Name="default_badge"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="{StaticResource Margin.LP1}"
|
Margin="3,0"
|
||||||
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||||
Style="{StaticResource RectangleBadgeStyle}"
|
Style="{StaticResource RectangleBadgeStyle}"
|
||||||
/>
|
/>
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
|
||||||
</Grid>
|
</Grid>
|
||||||
</CheckBox>
|
</RadioButton.ToolTip>
|
||||||
<cp:ColorPickerControl
|
<Grid>
|
||||||
Grid.Row="1"
|
<Grid.ColumnDefinitions>
|
||||||
ToolTipService.ShowOnDisabled="True"
|
<ColumnDefinition Width="Auto"/>
|
||||||
Margin="{StaticResource Margin.TP1}"
|
<ColumnDefinition Width="*"/>
|
||||||
Background="Transparent"
|
</Grid.ColumnDefinitions>
|
||||||
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
<TextBlock>Custom</TextBlock>
|
||||||
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
<Button
|
||||||
PreviewMouseDown="_on_badge_color_picker_activated"
|
x:Name="custom_badge_color_picker_button"
|
||||||
PreviewGotKeyboardFocus="_on_badge_color_picker_activated"
|
Grid.Column="1"
|
||||||
/>
|
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||||
</Grid>
|
Margin="{StaticResource Margin.LP1}"
|
||||||
</Border>
|
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
|
||||||
</Popup>
|
/>
|
||||||
<TextBox
|
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
|
||||||
x:Name="name_text_box"
|
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
|
||||||
Grid.Column="1"
|
<cp:ColorPickerControl
|
||||||
u:TextBoxBehavior.TextPrompt="Name"
|
Background="Transparent"
|
||||||
Margin="{StaticResource Margin.LP1}"
|
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||||
HorizontalAlignment="Stretch"
|
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||||
VerticalAlignment="Center"
|
/>
|
||||||
MinWidth="50"
|
</Border>
|
||||||
IsEnabled="True"
|
</Popup>
|
||||||
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
</Grid>
|
||||||
ToolTip="This algo's full name."
|
</RadioButton>
|
||||||
/>
|
</Grid>
|
||||||
<TextBox
|
|
||||||
x:Name="abbreviation_text_box"
|
|
||||||
Grid.Column="2"
|
|
||||||
u:TextBoxBehavior.TextPrompt="Abbrev."
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="{StaticResource Margin.LP1_RP1}"
|
|
||||||
MinWidth="50"
|
|
||||||
IsEnabled="True"
|
|
||||||
Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
|
||||||
>
|
|
||||||
<TextBox.ToolTip>
|
|
||||||
<TextBlock>
|
|
||||||
<TextBlock.Inlines>
|
|
||||||
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
|
|
||||||
<LineBreak/>
|
|
||||||
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
|
|
||||||
</TextBlock.Inlines>
|
|
||||||
</TextBlock>
|
|
||||||
</TextBox.ToolTip>
|
|
||||||
</TextBox>
|
|
||||||
<ScrollViewer x:Name="algo_editor_scroll_viewer" Grid.Row="1" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto">
|
|
||||||
<usp:AlgoEditorView
|
<usp:AlgoEditorView
|
||||||
|
Grid.Row="2"
|
||||||
DataContext="{Binding selected_definition}"
|
DataContext="{Binding selected_definition}"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||||
|
Margin="0,6,0,0"
|
||||||
Padding="12,8"
|
Padding="12,8"
|
||||||
/>
|
/>
|
||||||
</ScrollViewer>
|
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
|
||||||
</Grid>
|
<StackPanel.Margin>
|
||||||
<c:DialogSkirt
|
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
|
||||||
Grid.Row="2"
|
</StackPanel.Margin>
|
||||||
Grid.Column="1"
|
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||||
BorderThickness="0"
|
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
|
||||||
>
|
|
||||||
<c:DialogSkirt.RightItems>
|
|
||||||
<x:Array Type="{x:Type Button}">
|
|
||||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
|
||||||
Save Algo
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding revert_cmd_tooltip}">
|
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||||
Revert Algo
|
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
|
||||||
</Button>
|
</Button>
|
||||||
</x:Array>
|
</StackPanel>
|
||||||
</c:DialogSkirt.RightItems>
|
</Grid>
|
||||||
</c:DialogSkirt>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</AdornerDecorator>
|
</AdornerDecorator>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -19,17 +19,6 @@ namespace mbts.ui.spark {
|
|||||||
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
|
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _on_badge_color_picker_activated(object sender, MouseButtonEventArgs e) =>
|
|
||||||
_on_badge_color_picker_activated();
|
|
||||||
|
|
||||||
void _on_badge_color_picker_activated(object sender, KeyboardFocusChangedEventArgs e) =>
|
|
||||||
_on_badge_color_picker_activated();
|
|
||||||
|
|
||||||
void _on_badge_color_picker_activated() {
|
|
||||||
if (DataContext is AlgoStudioVM vm && vm.selected_definition != null)
|
|
||||||
vm.selected_definition.custom_badge_color.enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class VisualStateNames {
|
public static class VisualStateNames {
|
||||||
public static class EditPane {
|
public static class EditPane {
|
||||||
public const string GroupName = nameof(EditPane);
|
public const string GroupName = nameof(EditPane);
|
||||||
@@ -55,7 +44,8 @@ namespace mbts.ui.spark {
|
|||||||
|
|
||||||
public static readonly string
|
public static readonly string
|
||||||
Populated = $"{GroupName}{nameof(Populated)}",
|
Populated = $"{GroupName}{nameof(Populated)}",
|
||||||
Unpopulated = $"{GroupName}{nameof(Unpopulated)}";
|
Unpopulated = $"{GroupName}{nameof(Unpopulated)}",
|
||||||
|
NotSupportedForTemplates = $"{GroupName}{nameof(NotSupportedForTemplates)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BadgeSelector {
|
public static class BadgeSelector {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||||
|
|
||||||
@@ -69,12 +68,23 @@ namespace mbts.spark {
|
|||||||
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
||||||
// add definition
|
// add definition
|
||||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
||||||
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.CopyBoxes, _vm.clone_selected_definition_cmd));
|
// clone definition
|
||||||
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
|
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
|
||||||
show_in_toolbar = _ui_configuration.can_save_specific
|
_commands.Add(clone_selected_definition_spark_cmd);
|
||||||
});
|
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
|
||||||
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
|
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
|
||||||
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
|
// save
|
||||||
|
if (_ui_configuration.can_save_specific) {
|
||||||
|
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
|
||||||
|
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||||
|
if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||||
|
_vm.save_definition(d).ignore();
|
||||||
|
}, content_is_dirty_src));
|
||||||
|
}
|
||||||
|
// save all
|
||||||
|
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
|
||||||
|
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
|
||||||
|
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
|
||||||
// undo/redo
|
// undo/redo
|
||||||
if (_ui_configuration.can_undo_redo) {
|
if (_ui_configuration.can_undo_redo) {
|
||||||
// undo
|
// undo
|
||||||
@@ -107,11 +117,30 @@ namespace mbts.spark {
|
|||||||
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
||||||
}
|
}
|
||||||
// remove
|
// remove
|
||||||
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
|
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
|
||||||
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
|
if (value is AlgoDefinitionVM d) {
|
||||||
vm => vm != null,
|
string prompt; MessageBoxImage icon;
|
||||||
out var remove_cmd_observer)));
|
if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
|
prompt = $"Are you sure you want to delete '{d.name}?'";
|
||||||
|
icon = MessageBoxImage.Question;
|
||||||
|
} else {
|
||||||
|
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
|
||||||
|
prompt = null;
|
||||||
|
icon = default;
|
||||||
|
}
|
||||||
|
if (prompt != null) {
|
||||||
|
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
|
||||||
|
switch (resp) {
|
||||||
|
case BTSMessageBoxButtons.Yes: break;
|
||||||
|
case BTSMessageBoxButtons.No:
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_vm.remove_definition(d).ignore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_commands.Add(remove_spark_cmd);
|
||||||
|
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _dispose_core() {
|
void _dispose_core() {
|
||||||
@@ -120,4 +149,4 @@ namespace mbts.spark {
|
|||||||
Disposable.dispose(ref _vm);
|
Disposable.dispose(ref _vm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+23
-10
@@ -6,6 +6,7 @@ using mbts.utils;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace mbts.spark {
|
namespace mbts.spark {
|
||||||
@@ -15,6 +16,7 @@ namespace mbts.spark {
|
|||||||
readonly AdminModel _admin_model;
|
readonly AdminModel _admin_model;
|
||||||
readonly ErrorReporter _error_reporter;
|
readonly ErrorReporter _error_reporter;
|
||||||
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
|
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
|
||||||
|
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
|
||||||
IDisposable _dispose;
|
IDisposable _dispose;
|
||||||
|
|
||||||
[ImportingConstructor]
|
[ImportingConstructor]
|
||||||
@@ -32,13 +34,17 @@ namespace mbts.spark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||||
|
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates => Observable.Empty<TableUpdate<AlgoTemplateData, AlgoKey>>();
|
||||||
public IAlgoDefinitionMetadataService metadata_service { get; }
|
public IAlgoDefinitionMetadataService metadata_service { get; }
|
||||||
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
||||||
|
|
||||||
|
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
|
||||||
|
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
|
||||||
|
|
||||||
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
||||||
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
||||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
|
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
|
||||||
r => r.Success, r => r.ErrorMessage, "saving definition");
|
r => r.Success, r => r.ErrorMessage, "saving template");
|
||||||
if (success)
|
if (success)
|
||||||
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
||||||
return null;
|
return null;
|
||||||
@@ -47,22 +53,23 @@ namespace mbts.spark {
|
|||||||
public async Task<bool> remove_definition(AlgoKey definition_key) {
|
public async Task<bool> remove_definition(AlgoKey definition_key) {
|
||||||
var req = new AlgoDefinitionRemoveRequest();
|
var req = new AlgoDefinitionRemoveRequest();
|
||||||
_set_key(ref req, definition_key);
|
_set_key(ref req, definition_key);
|
||||||
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(
|
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(() => _service.remove_algo_definition_async(req), r => r.Success, r => r.ErrorMessage, "remove template");
|
||||||
() => _service.remove_algo_definition_async(req),
|
|
||||||
r => r.Success,
|
|
||||||
r => r.ErrorMessage,
|
|
||||||
"remove definition");
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) => Task.FromResult((AlgoTemplateData?)null);
|
||||||
|
|
||||||
|
public Task<bool> remove_template(AlgoKey key) =>
|
||||||
|
Task.FromResult(false);
|
||||||
|
|
||||||
public void Dispose() =>
|
public void Dispose() =>
|
||||||
Disposable.dispose(ref _dispose);
|
Disposable.dispose(ref _dispose);
|
||||||
|
|
||||||
static AlgoDefinition _api_definition(AlgoData definition_data) =>
|
static AlgoDefinition _template(AlgoData definition_data) =>
|
||||||
new AlgoDefinition { Name = definition_data.name, Definition = definition_data.value.CloneToMutable() };
|
new AlgoDefinition { Name = definition_data.name, Definition = definition_data.value.CloneToMutable() };
|
||||||
|
|
||||||
static AlgoDefinitionImmutable _api_definitioni(AlgoData definition_data) =>
|
static AlgoDefinitionImmutable _templatei(AlgoData definition_data) =>
|
||||||
new AlgoDefinitionImmutable(_api_definition(definition_data));
|
new AlgoDefinitionImmutable(_template(definition_data));
|
||||||
|
|
||||||
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
|
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
|
||||||
AlgoData.from_record(recordi);
|
AlgoData.from_record(recordi);
|
||||||
@@ -92,5 +99,11 @@ namespace mbts.spark {
|
|||||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _report_error(string present_progressive_desc, string msg) =>
|
||||||
|
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
|
||||||
|
|
||||||
|
void _report_no_user_id(string desc) =>
|
||||||
|
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,13 @@ using System.Threading.Tasks;
|
|||||||
namespace mbts.spark {
|
namespace mbts.spark {
|
||||||
interface IAlgoStudioModel {
|
interface IAlgoStudioModel {
|
||||||
IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||||
|
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
|
||||||
|
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key);
|
||||||
IAlgoDefinitionMetadataService metadata_service { get; }
|
IAlgoDefinitionMetadataService metadata_service { get; }
|
||||||
Task<AlgoData?> save_definition(AlgoData definition_data);
|
Task<AlgoData?> save_definition(AlgoData definition_data);
|
||||||
|
Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data);
|
||||||
Task<bool> remove_definition(AlgoKey definition_key);
|
Task<bool> remove_definition(AlgoKey definition_key);
|
||||||
|
Task<bool> remove_template(AlgoKey template_key);
|
||||||
bts.config.AlgoStudioOptions ui_configuration { get; }
|
bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,13 +68,7 @@ namespace mbts.spark {
|
|||||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||||
key = def_data.key;
|
key = def_data.key;
|
||||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
||||||
// badge color picker palettes
|
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
|
||||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
|
|
||||||
var root_algo_color_item = new NamedColorVM(new NamedColor { name = "(Root Algo Type)" }, false);
|
|
||||||
var algo_type_color_palette = new ColorPaletteVM(ColorPalette.factory_order_colors_palette(model.factory));
|
|
||||||
algo_type_color_palette.named_colors.Insert(0, root_algo_color_item);
|
|
||||||
custom_badge_color_palettes.Insert(0, algo_type_color_palette);
|
|
||||||
//
|
|
||||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||||
var conf = _model.algo_model.ui_configuration;
|
var conf = _model.algo_model.ui_configuration;
|
||||||
// init backing props
|
// init backing props
|
||||||
@@ -97,7 +91,6 @@ namespace mbts.spark {
|
|||||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||||
_can_redo.Changed += on_can_redo_changed;
|
_can_redo.Changed += on_can_redo_changed;
|
||||||
_can_undo.Changed += on_can_undo_changed;
|
_can_undo.Changed += on_can_undo_changed;
|
||||||
custom_badge_color.enabled_changed += on_use_custom_badge_color_changed;
|
|
||||||
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
|
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
|
||||||
// load initial state
|
// load initial state
|
||||||
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
||||||
@@ -110,13 +103,16 @@ namespace mbts.spark {
|
|||||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||||
_algo_root.observe_current(true).SubscribeEach(vm => {
|
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||||
if (vm != null) {
|
vm == null ? null :
|
||||||
root_algo_color_item.assign_color(vm.meta.Color.argb_color());
|
Disposable.Create(
|
||||||
return vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute());
|
vm.subscribe_is_dirty().Subscribe(v => {
|
||||||
}
|
if (!v) {
|
||||||
return null;
|
_version.increment_version();
|
||||||
}),
|
_on_committed_changed();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||||
Disposable.Create(() => {
|
Disposable.Create(() => {
|
||||||
_can_redo.Changed -= on_can_redo_changed;
|
_can_redo.Changed -= on_can_redo_changed;
|
||||||
@@ -157,10 +153,6 @@ namespace mbts.spark {
|
|||||||
// local methods
|
// local methods
|
||||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||||
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
|
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
|
||||||
void on_use_custom_badge_color_changed() {
|
|
||||||
if (!custom_badge_color.enabled && algo_root != null)
|
|
||||||
custom_badge_color.value = algo_root.meta.Color.argb_color();
|
|
||||||
}
|
|
||||||
void on_local_changed() {
|
void on_local_changed() {
|
||||||
_version.increment_version();
|
_version.increment_version();
|
||||||
if (algo_root != null)
|
if (algo_root != null)
|
||||||
@@ -198,7 +190,6 @@ namespace mbts.spark {
|
|||||||
public AlgoDisplayGroup group => key.display_group();
|
public AlgoDisplayGroup group => key.display_group();
|
||||||
public bool can_redo => _can_redo.Value;
|
public bool can_redo => _can_redo.Value;
|
||||||
public bool can_undo => _can_undo.Value;
|
public bool can_undo => _can_undo.Value;
|
||||||
//public bool use_custom_badge_color
|
|
||||||
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
|
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
|
||||||
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
|
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
|
||||||
public Brush badge_fill => _badge_fill;
|
public Brush badge_fill => _badge_fill;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using System.Linq;
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||||
|
|
||||||
@@ -81,19 +82,18 @@ namespace mbts.spark {
|
|||||||
_persisted_meta = meta;
|
_persisted_meta = meta;
|
||||||
definitions_view.Refresh();
|
definitions_view.Refresh();
|
||||||
}),
|
}),
|
||||||
|
ObservableAutoWrapper.connect(subscribe_definition().Select(def => def?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||||
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
|
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) {
|
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
|
||||||
val => {
|
_save_cmd_tooltip.refresh();
|
||||||
_save_cmd_tooltip.refresh();
|
_revert_cmd_tooltip.refresh();
|
||||||
_revert_cmd_tooltip.refresh();
|
} }),
|
||||||
}
|
|
||||||
}),
|
|
||||||
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
|
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
|
||||||
// local methods
|
// local methods
|
||||||
string get_revert_tooltip() {
|
string get_revert_tooltip() {
|
||||||
var def = selected_definition;
|
var def = selected_definition;
|
||||||
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
|
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
|
||||||
(def.is_dirty ? $"Revert changes to '{def.name}'" : "No changes to revert") :
|
(def.is_dirty ? $"Revert changes to '{def.name}' templates" : "No changes to revert") :
|
||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
async Task save_async(IAlgoStudioContentVM content) {
|
async Task save_async(IAlgoStudioContentVM content) {
|
||||||
@@ -105,7 +105,7 @@ namespace mbts.spark {
|
|||||||
if (def != null) {
|
if (def != null) {
|
||||||
switch (def.key.mode) {
|
switch (def.key.mode) {
|
||||||
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
||||||
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}'" : "No changes to save";
|
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}' templates" : "No changes to save";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using bts.collections;
|
|||||||
using bts.shell.fills;
|
using bts.shell.fills;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.utils;
|
using bts.utils;
|
||||||
using bts.utils.csv;
|
|
||||||
using mbts.collections;
|
using mbts.collections;
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.spark.filters;
|
using mbts.spark.filters;
|
||||||
@@ -331,27 +330,6 @@ namespace mbts.spark {
|
|||||||
public override bool Equals(object obj) => Equals(obj as Row);
|
public override bool Equals(object obj) => Equals(obj as Row);
|
||||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||||
|
|
||||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
|
||||||
CsvHelper.create_ordering_maintainer(typeof(Row),
|
|
||||||
new string[] {
|
|
||||||
nameof(security),
|
|
||||||
nameof(order_type),
|
|
||||||
nameof(root_algo_display_name),
|
|
||||||
nameof(side),
|
|
||||||
nameof(fill_time),
|
|
||||||
nameof(user),
|
|
||||||
nameof(account),
|
|
||||||
nameof(exchange),
|
|
||||||
nameof(time_in_force),
|
|
||||||
nameof(size),
|
|
||||||
nameof(price),
|
|
||||||
nameof(fill_kind),
|
|
||||||
nameof(fill_status),
|
|
||||||
nameof(fill_key),
|
|
||||||
nameof(order_key),
|
|
||||||
nameof(security_key)
|
|
||||||
});
|
|
||||||
|
|
||||||
public event Action invalidated;
|
public event Action invalidated;
|
||||||
void IRowVM.raise_invalidated() => invalidated?.Invoke();
|
void IRowVM.raise_invalidated() => invalidated?.Invoke();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ namespace mbts.ui.spark {
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row> { }
|
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row, FillsView.Columns> { }
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,6 @@ using bts.api;
|
|||||||
using bts.shell;
|
using bts.shell;
|
||||||
using bts.shell.fills;
|
using bts.shell.fills;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.view;
|
|
||||||
using bts.utils.csv;
|
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.settings;
|
using mbts.ui.settings;
|
||||||
@@ -26,7 +24,6 @@ namespace mbts.spark {
|
|||||||
FillsVM _vm;
|
FillsVM _vm;
|
||||||
FillsView _view;
|
FillsView _view;
|
||||||
FillsSettings _state;
|
FillsSettings _state;
|
||||||
SparkMenuDisplayVM _menu_display_vm;
|
|
||||||
IReadOnlyList<SparkCommand> _commands;
|
IReadOnlyList<SparkCommand> _commands;
|
||||||
|
|
||||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
|
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
|
||||||
@@ -41,10 +38,7 @@ namespace mbts.spark {
|
|||||||
public override UIElement Content => _view;
|
public override UIElement Content => _view;
|
||||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||||
|
|
||||||
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state()) {
|
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
|
||||||
menu_display = _menu_display_vm.safe_create()
|
|
||||||
};
|
|
||||||
|
|
||||||
public override double MinWidth =>
|
public override double MinWidth =>
|
||||||
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||||
@@ -56,13 +50,10 @@ namespace mbts.spark {
|
|||||||
public void thread_init() {
|
public void thread_init() {
|
||||||
_init_guard.check_access();
|
_init_guard.check_access();
|
||||||
Log.notnull(_state);
|
Log.notnull(_state);
|
||||||
_menu_display_vm = new SparkMenuDisplayVM();
|
|
||||||
_menu_display_vm.load_from(_state.menu_display);
|
|
||||||
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
|
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
|
||||||
_view = new FillsView { DataContext = _vm };
|
_view = new FillsView { DataContext = _vm };
|
||||||
|
|
||||||
_commands = new List<SparkCommand> {
|
_commands = new List<SparkCommand> {
|
||||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
|
|
||||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
// 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(() => {
|
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||||
@@ -79,8 +70,7 @@ namespace mbts.spark {
|
|||||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||||
return dialog;
|
return dialog;
|
||||||
})),
|
}))
|
||||||
_menu_display_vm.spark_cmd
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,8 +78,5 @@ namespace mbts.spark {
|
|||||||
Disposable.dispose(ref _vm);
|
Disposable.dispose(ref _vm);
|
||||||
Commands.OfType<IDisposable>().DisposeAll();
|
Commands.OfType<IDisposable>().DisposeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
|
||||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,8 +21,6 @@ namespace bts.shell.fills {
|
|||||||
public FillsFilterStates filters;
|
public FillsFilterStates filters;
|
||||||
[ProtoMember(4)]
|
[ProtoMember(4)]
|
||||||
public GridSettings grid;
|
public GridSettings grid;
|
||||||
[ProtoMember(5)]
|
|
||||||
public SparkMenuDisplaySettings menu_display;
|
|
||||||
|
|
||||||
public FillsSettings() { }
|
public FillsSettings() { }
|
||||||
public FillsSettings(uint[] orders) : this(orders, null, null) { }
|
public FillsSettings(uint[] orders) : this(orders, null, null) { }
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
using bts.api.orderman;
|
using bts.api.orderman;
|
||||||
using bts.collections;
|
|
||||||
using bts.persist.algo;
|
|
||||||
using bts.proto;
|
|
||||||
using bts.utils;
|
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.settings;
|
using bts.utils;
|
||||||
using mbts.ui.spark;
|
|
||||||
using ProtoBuf;
|
using ProtoBuf;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using bts.proto;
|
||||||
|
using mbts.ui.spark;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using mbts.ui.settings;
|
||||||
|
|
||||||
namespace mbts.spark {
|
namespace mbts.spark {
|
||||||
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
||||||
@@ -185,97 +182,33 @@ namespace mbts.spark {
|
|||||||
_type_hash;
|
_type_hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
|
||||||
enum LadderFavoriteAlgoFlags {
|
|
||||||
Sticky = 1,
|
|
||||||
AutoRevert = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This struct contains properties for a single algo favorite item.
|
|
||||||
/// The various <see cref="AlgoPersistedKey"/> arrays in <see cref="LadderFavoriteAlgosSettings"/>,
|
|
||||||
/// can be used to create a collection of <see cref="LadderFavoriteAlgoItem"/>s.
|
|
||||||
/// In fact, the method <see cref="LadderFavoriteAlgosSettings.to_algo_items"/> does just that.
|
|
||||||
/// </summary>
|
|
||||||
readonly struct LadderFavoriteAlgoItem : IEquatable<LadderFavoriteAlgoItem> {
|
|
||||||
public readonly AlgoKey key;
|
|
||||||
public readonly int sort_index;
|
|
||||||
public readonly LadderFavoriteAlgoFlags flags;
|
|
||||||
|
|
||||||
public LadderFavoriteAlgoItem(AlgoKey key, int sort_index, LadderFavoriteAlgoFlags flags) {
|
|
||||||
this.key = key;
|
|
||||||
this.sort_index = sort_index;
|
|
||||||
this.flags = flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(LadderFavoriteAlgoItem other) =>
|
|
||||||
key == other.key &&
|
|
||||||
flags == other.flags;
|
|
||||||
|
|
||||||
public override bool Equals(object obj) =>
|
|
||||||
obj is LadderFavoriteAlgoItem other && Equals(other);
|
|
||||||
|
|
||||||
public override int GetHashCode() =>
|
|
||||||
key.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
[ProtoContract]
|
[ProtoContract]
|
||||||
struct LadderFavoriteAlgosSettings : IEquatable<LadderFavoriteAlgosSettings> {
|
struct LadderFavoriteAlgosSettings : IEquatable<LadderFavoriteAlgosSettings> {
|
||||||
[ProtoMember(1)]
|
[ProtoMember(1)]
|
||||||
readonly AlgoPersistedKey[] _favorite_algos;
|
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
|
||||||
[ProtoMember(2)]
|
[ProtoMember(2)]
|
||||||
public readonly int column_count;
|
public readonly int column_count;
|
||||||
[ProtoMember(3)]
|
[ProtoMember(3)]
|
||||||
readonly AlgoPersistedKey[] _stickied_algos;
|
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
|
||||||
[ProtoMember(4)]
|
|
||||||
readonly AlgoPersistedKey[] _auto_revert_algos;
|
|
||||||
|
|
||||||
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos, AlgoKey[] auto_revert_algos)
|
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos)
|
||||||
: this(ArrayHelper.safe_convert(favorite_algos, k => new AlgoPersistedKey(k.persisted)),
|
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)),
|
||||||
ArrayHelper.safe_convert(stickied_algos, k => new AlgoPersistedKey(k.persisted)),
|
column_count,
|
||||||
ArrayHelper.safe_convert(auto_revert_algos, k => new AlgoPersistedKey(k.persisted)),
|
ArrayHelper.safe_convert(stickied_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted))) { }
|
||||||
column_count) { }
|
|
||||||
|
|
||||||
LadderFavoriteAlgosSettings(AlgoPersistedKey[] favorite_algos, AlgoPersistedKey[] stickied_algos, AlgoPersistedKey[] auto_revert_algos, int column_count) {
|
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
|
||||||
_favorite_algos = favorite_algos;
|
_favorite_algos = favorite_algos;
|
||||||
_stickied_algos = stickied_algos;
|
|
||||||
_auto_revert_algos = auto_revert_algos;
|
|
||||||
this.column_count = column_count;
|
this.column_count = column_count;
|
||||||
|
_stickied_algos = stickied_algos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<AlgoKey> favorite_algos => _favorite_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
|
public IReadOnlyList<AlgoKey> favorite_algos => ArrayHelper.safe_convert(_favorite_algos, k => k.to_algo_key());
|
||||||
public IReadOnlyList<AlgoKey> stickied_algos => _stickied_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
|
public IReadOnlyList<AlgoKey> stickied_algos => ArrayHelper.safe_convert(_stickied_algos, k => k.to_algo_key());
|
||||||
public IReadOnlyList<AlgoKey> auto_revert_algos => _auto_revert_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A more sane representation of the data contained in this proto,
|
|
||||||
/// so callers don't need to join the various key lists together to determine which algos are sticky, auto revert, etc.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An immutable dictionary of favorite algo data.</returns>
|
|
||||||
public ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> build_map() {
|
|
||||||
var map = ImmutableDictionary.CreateBuilder<AlgoKey, LadderFavoriteAlgoItem>();
|
|
||||||
if (_favorite_algos != null) {
|
|
||||||
var stickied_algo_set = new HashSet<AlgoKey>(stickied_algos);
|
|
||||||
var auto_revert_algo_set = new HashSet<AlgoKey>(auto_revert_algos);
|
|
||||||
for (int i = 0; i < _favorite_algos.Length; i++) {
|
|
||||||
var pk = _favorite_algos[i];
|
|
||||||
var k = pk.to_algo_key();
|
|
||||||
var flags = (LadderFavoriteAlgoFlags)0;
|
|
||||||
if (stickied_algo_set.Contains(k))
|
|
||||||
flags |= LadderFavoriteAlgoFlags.Sticky;
|
|
||||||
if (auto_revert_algo_set.Contains(k))
|
|
||||||
flags |= LadderFavoriteAlgoFlags.AutoRevert;
|
|
||||||
map[k] = new LadderFavoriteAlgoItem(k, i, flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map.ToImmutable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(LadderFavoriteAlgosSettings other) =>
|
public bool Equals(LadderFavoriteAlgosSettings other) =>
|
||||||
column_count == other.column_count &&
|
column_count == other.column_count &&
|
||||||
favorite_algos.SequenceEqual(other.favorite_algos) &&
|
favorite_algos.SequenceEqual(other.favorite_algos) &&
|
||||||
stickied_algos.SequenceEqual(other.stickied_algos) &&
|
stickied_algos.SequenceEqual(other.stickied_algos);
|
||||||
auto_revert_algos.SequenceEqual(other.auto_revert_algos);
|
|
||||||
|
|
||||||
public override bool Equals(object obj) =>
|
public override bool Equals(object obj) =>
|
||||||
EqualityComparer.equals_obj(this, obj);
|
EqualityComparer.equals_obj(this, obj);
|
||||||
@@ -284,8 +217,7 @@ namespace mbts.spark {
|
|||||||
EqualityComparer.aggregate_hash(
|
EqualityComparer.aggregate_hash(
|
||||||
column_count,
|
column_count,
|
||||||
EqualityComparer.aggregate_hash_limited(favorite_algos),
|
EqualityComparer.aggregate_hash_limited(favorite_algos),
|
||||||
EqualityComparer.aggregate_hash_limited(stickied_algos),
|
EqualityComparer.aggregate_hash_limited(stickied_algos));
|
||||||
EqualityComparer.aggregate_hash_limited(auto_revert_algos));
|
|
||||||
|
|
||||||
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
|
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
|
||||||
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
|
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
|
||||||
@@ -349,7 +281,7 @@ namespace mbts.spark {
|
|||||||
order_entry_panel_initial_width = _default_order_entry_panel_width;
|
order_entry_panel_initial_width = _default_order_entry_panel_width;
|
||||||
zoom_factor = _default_zoom_factor;
|
zoom_factor = _default_zoom_factor;
|
||||||
use_defaults = true;
|
use_defaults = true;
|
||||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null, null);
|
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
|
||||||
show_warn_on_cross = _default_show_warn_on_cross;
|
show_warn_on_cross = _default_show_warn_on_cross;
|
||||||
enforce_max_clip = _default_enforce_max_clip;
|
enforce_max_clip = _default_enforce_max_clip;
|
||||||
status_message_visible = true;
|
status_message_visible = true;
|
||||||
|
|||||||
@@ -70,11 +70,7 @@ namespace bts.shell {
|
|||||||
if (version < _version_where_stickied_algos_introduced) {
|
if (version < _version_where_stickied_algos_introduced) {
|
||||||
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
|
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
|
||||||
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
|
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
|
||||||
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(
|
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(favorite_algos_array, state.settings.favorite_algos.column_count, favorite_algos_array);
|
||||||
favorite_algos_array,
|
|
||||||
state.settings.favorite_algos.column_count,
|
|
||||||
favorite_algos_array,
|
|
||||||
state.settings.favorite_algos.auto_revert_algos.ToArray());
|
|
||||||
}
|
}
|
||||||
return load(state);
|
return load(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,15 +268,14 @@ namespace mbts.spark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// on Stop/Limit order updates
|
|
||||||
// When not triggered, allow for modification to StopPrice
|
|
||||||
// else treat modification as normal (Price)
|
|
||||||
public async void try_set_price(OrderRecordImmutable order, double new_price) {
|
public async void try_set_price(OrderRecordImmutable order, double new_price) {
|
||||||
|
// don't change stop or stop_limits => server crash
|
||||||
|
if (order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.StopLimit) return;
|
||||||
if (order.working_quantity() <= 0) return;
|
if (order.working_quantity() <= 0) return;
|
||||||
_phantom_price = new_price;
|
_phantom_price = new_price;
|
||||||
RaisePropertyChanged(nameof(price));
|
RaisePropertyChanged(nameof(price));
|
||||||
++_n_active_price_change_ops;
|
++_n_active_price_change_ops;
|
||||||
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity, null)) {
|
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity)) {
|
||||||
// wait 3 secs, then give up if price change still hasn't taken effect
|
// wait 3 secs, then give up if price change still hasn't taken effect
|
||||||
await Task.Delay(3000);
|
await Task.Delay(3000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using mbts.ui.utility;
|
|||||||
using mbts.utils;
|
using mbts.utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -747,17 +746,15 @@ namespace mbts.spark {
|
|||||||
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
|
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
|
||||||
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
|
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
|
||||||
int _column_count = 2;
|
int _column_count = 2;
|
||||||
ImmutableHashSet<AlgoPersistedKey>
|
HashSet<AlgoPersistedKey> _sticky_algo_keys;
|
||||||
_sticky_algo_keys,
|
IDisposable _sticky_algos_hook;
|
||||||
_auto_revert_algo_keys;
|
|
||||||
IDisposable _algo_data_hook;
|
|
||||||
|
|
||||||
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
|
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
|
||||||
_algo_definitions = algo_definitions;
|
_algo_definitions = algo_definitions;
|
||||||
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
|
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
|
||||||
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
|
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
|
||||||
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
|
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
|
||||||
_dispose = Disposable.Create(() => Disposable.dispose(ref _algo_data_hook));
|
_dispose = Disposable.Create(() => Disposable.dispose(ref _sticky_algos_hook));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<FavoriteAlgoItem> items { get; }
|
public ObservableCollection<FavoriteAlgoItem> items { get; }
|
||||||
@@ -774,47 +771,32 @@ namespace mbts.spark {
|
|||||||
fai.selector.try_select(k);
|
fai.selector.try_select(k);
|
||||||
return fai;
|
return fai;
|
||||||
}));
|
}));
|
||||||
_algo_data_hook?.Dispose();
|
_sticky_algo_keys = new HashSet<AlgoPersistedKey>(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
|
||||||
_sticky_algo_keys = ImmutableHashSet.CreateRange(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
|
Disposable.replace(ref _sticky_algos_hook,
|
||||||
_auto_revert_algo_keys = ImmutableHashSet.CreateRange(value.auto_revert_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
|
ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
|
||||||
var sticky_algos = _sticky_algo_keys;
|
x.selector.subscribe_value().Subscribe(algo => {
|
||||||
var auto_revert_algos = _auto_revert_algo_keys;
|
if (algo != null &&
|
||||||
_algo_data_hook = ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
|
algo.key.is_persisted &&
|
||||||
x.selector.subscribe_value().Subscribe(algo => {
|
(x.is_sticky = _sticky_algo_keys.Remove(new AlgoPersistedKey(algo.key.persisted))) &&
|
||||||
if (algo != null && algo.key.is_persisted) {
|
_sticky_algo_keys.Count == 0)
|
||||||
var pk = new AlgoPersistedKey(algo.key.persisted);
|
//
|
||||||
// sticky
|
Disposable.dispose(ref _sticky_algos_hook);
|
||||||
x.is_sticky = _sticky_algo_keys.Contains(pk);
|
})));
|
||||||
sticky_algos = sticky_algos.Remove(pk);
|
|
||||||
// auto revert
|
|
||||||
x.auto_revert = _auto_revert_algo_keys.Contains(pk);
|
|
||||||
auto_revert_algos = auto_revert_algos.Remove(pk);
|
|
||||||
if (sticky_algos.Count == 0 && auto_revert_algos.Count == 0)
|
|
||||||
Disposable.dispose(ref _algo_data_hook);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LadderFavoriteAlgosSettings create() {
|
public LadderFavoriteAlgosSettings create() =>
|
||||||
return new LadderFavoriteAlgosSettings(_raw_items
|
new LadderFavoriteAlgosSettings(_raw_items
|
||||||
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
||||||
.Where(k => k.HasValue && k.Value.is_persisted)
|
.Where(k => k.HasValue && k.Value.is_persisted)
|
||||||
.Select(k => k.Value)
|
.Select(k => k.Value)
|
||||||
.ToArray(),
|
.ToArray(),
|
||||||
column_count,
|
column_count,
|
||||||
// sticky
|
|
||||||
build_filtered_algo_array(fai => fai.is_sticky),
|
|
||||||
// auto revert
|
|
||||||
build_filtered_algo_array(fai => fai.auto_revert));
|
|
||||||
|
|
||||||
AlgoKey[] build_filtered_algo_array(Func<FavoriteAlgoItem, bool> filter) =>
|
|
||||||
_raw_items
|
_raw_items
|
||||||
.Where(filter)
|
.Where(fai => fai.is_sticky)
|
||||||
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
||||||
.Where(k => k.HasValue && k.Value.is_persisted)
|
.Where(k => k.HasValue && k.Value.is_persisted)
|
||||||
.Select(k => k.Value)
|
.Select(k => k.Value)
|
||||||
.ToArray();
|
.ToArray());
|
||||||
}
|
|
||||||
|
|
||||||
FavoriteAlgoItem _item_factory() =>
|
FavoriteAlgoItem _item_factory() =>
|
||||||
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
|
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
|
||||||
@@ -827,9 +809,7 @@ namespace mbts.spark {
|
|||||||
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
|
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
|
||||||
|
|
||||||
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
|
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
|
||||||
bool
|
bool _is_sticky;
|
||||||
_auto_revert,
|
|
||||||
_is_sticky;
|
|
||||||
|
|
||||||
protected FavoriteAlgoItem() { }
|
protected FavoriteAlgoItem() { }
|
||||||
|
|
||||||
@@ -840,7 +820,6 @@ namespace mbts.spark {
|
|||||||
|
|
||||||
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
|
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
|
||||||
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
|
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
|
||||||
public bool auto_revert { get => _auto_revert; set => Set(ref _auto_revert, value); }
|
|
||||||
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
|
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
|
||||||
|
|
||||||
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
|
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using mbts.ui.shell;
|
|||||||
using mbts.utils;
|
using mbts.utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -35,6 +34,7 @@ namespace mbts.spark {
|
|||||||
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
_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_status_message;
|
_show_cancel_sells, _show_cancel_all, _show_zoom, _show_status_message;
|
||||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||||
|
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
|
||||||
double
|
double
|
||||||
_position,
|
_position,
|
||||||
_day_position,
|
_day_position,
|
||||||
@@ -49,7 +49,6 @@ namespace mbts.spark {
|
|||||||
IEqualityComparer<double> _equality;
|
IEqualityComparer<double> _equality;
|
||||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||||
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
|
|
||||||
|
|
||||||
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
||||||
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||||
@@ -169,9 +168,17 @@ namespace mbts.spark {
|
|||||||
|
|
||||||
foreach (var os in _cache.Values) {
|
foreach (var os in _cache.Values) {
|
||||||
var wo = os.order;
|
var wo = os.order;
|
||||||
var slot_price_target = wo.ui_price();
|
var slot_price_target = wo.price();
|
||||||
var order_type = wo.order_type();
|
var order_type = wo.order_type();
|
||||||
var filled = wo.filled();
|
var filled = wo.filled();
|
||||||
|
if (order_type == CustomOrderDefinition.CustomOneofCase.Stop) {
|
||||||
|
// if the stop order has been submitted but hasn't been triggered
|
||||||
|
// we draw the badge at the stop/trigger price, else the price price
|
||||||
|
if (filled == 0) { slot_price_target = wo.stop_price(); }
|
||||||
|
} else if (order_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
|
||||||
|
// consistent behavior as Stop order above.
|
||||||
|
if (filled == 0) { slot_price_target = wo.stop_limit_price(); }
|
||||||
|
}
|
||||||
|
|
||||||
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
|
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
|
||||||
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
|
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
|
||||||
@@ -232,8 +239,8 @@ namespace mbts.spark {
|
|||||||
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
|
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
|
||||||
return Disposable.Create(
|
return Disposable.Create(
|
||||||
settings.OnDispatcher().Subscribe(s => {
|
settings.OnDispatcher().Subscribe(s => {
|
||||||
_favorite_algo_map = s.favorite_algos.build_map();
|
_sticky_algos.SetItems(s.favorite_algos.stickied_algos);
|
||||||
order_properties_vm.on_favorite_algos(_favorite_algo_map, s.favorite_algos.column_count);
|
order_properties_vm.on_favorite_algos(s.favorite_algos);
|
||||||
}),
|
}),
|
||||||
order_properties_vm,
|
order_properties_vm,
|
||||||
position_src.OnDispatcher().Subscribe(pos => {
|
position_src.OnDispatcher().Subscribe(pos => {
|
||||||
@@ -402,9 +409,9 @@ namespace mbts.spark {
|
|||||||
|
|
||||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||||
var order = op.order;
|
var order = op.order;
|
||||||
double p = order.ui_price();
|
double p = order.price();
|
||||||
foreach (var o in lineage(order.ParentKey)) {
|
foreach (var o in lineage(order.ParentKey)) {
|
||||||
if (_equality.Equals(p, o.ui_price()))
|
if (_equality.Equals(p, o.price()))
|
||||||
order = o;
|
order = o;
|
||||||
}
|
}
|
||||||
return order;
|
return order;
|
||||||
@@ -503,16 +510,10 @@ namespace mbts.spark {
|
|||||||
if (auto_clear)
|
if (auto_clear)
|
||||||
size_calculator.value = 0;
|
size_calculator.value = 0;
|
||||||
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
|
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
|
||||||
if (fav_algo != null) {
|
if (fav_algo != null && !_sticky_algos.Contains(fav_algo.key) && !order_properties_vm.try_pop_selected_algo_definition()) {
|
||||||
if (_favorite_algo_map.TryGetValue(fav_algo.key, out var d)) {
|
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
|
||||||
if (!d.flags.HasFlag(LadderFavoriteAlgoFlags.Sticky) && !order_properties_vm.try_pop_selected_algo_definition()) {
|
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
|
||||||
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
|
order_properties_vm.selected_algo_definition = r;
|
||||||
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
|
|
||||||
order_properties_vm.selected_algo_definition = r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (d.flags.HasFlag(LadderFavoriteAlgoFlags.AutoRevert))
|
|
||||||
fav_algo.algo_root.revert_all_params();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ using System.Windows.Media.Animation;
|
|||||||
|
|
||||||
namespace mbts.spark {
|
namespace mbts.spark {
|
||||||
partial class LadderGridHost : Control {
|
partial class LadderGridHost : Control {
|
||||||
const int _min_row_count = 501, _max_row_count = 2001;
|
const int _min_row_count = 501, _max_row_count = 2001, _column_min_width = 8;
|
||||||
|
|
||||||
int _row_count = _min_row_count;
|
int _row_count = _min_row_count;
|
||||||
|
|
||||||
readonly VisualHost _visualHost = new VisualHost();
|
readonly VisualHost _visualHost = new VisualHost();
|
||||||
@@ -109,8 +108,6 @@ namespace mbts.spark {
|
|||||||
|
|
||||||
public event Action visible_columns_changed;
|
public event Action visible_columns_changed;
|
||||||
|
|
||||||
public static double ColumnMinWidth => 6;
|
|
||||||
|
|
||||||
public IEnumerable<ColumnType> visible_column_types =>
|
public IEnumerable<ColumnType> visible_column_types =>
|
||||||
columns.Where(c => c.settings.show)
|
columns.Where(c => c.settings.show)
|
||||||
.Select(c => c.column_type);
|
.Select(c => c.column_type);
|
||||||
@@ -575,7 +572,7 @@ namespace mbts.spark {
|
|||||||
case ColumnType.Price: new_col_width = (int)price_column_width; break;
|
case ColumnType.Price: new_col_width = (int)price_column_width; break;
|
||||||
case ColumnType.Ask: new_col_width = (int)ask_column_width; break;
|
case ColumnType.Ask: new_col_width = (int)ask_column_width; break;
|
||||||
case ColumnType.Volume: new_col_width = (int)VolumeColumnWidth; break;
|
case ColumnType.Volume: new_col_width = (int)VolumeColumnWidth; break;
|
||||||
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, ColumnMinWidth); break;
|
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, _column_min_width); break;
|
||||||
}
|
}
|
||||||
int true_adj = new_col_width - (int)column.settings.width;
|
int true_adj = new_col_width - (int)column.settings.width;
|
||||||
column.settings.width = (uint)new_col_width;
|
column.settings.width = (uint)new_col_width;
|
||||||
|
|||||||
@@ -828,7 +828,6 @@
|
|||||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
|
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
|
||||||
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
|
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
|
||||||
@@ -838,12 +837,7 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ComboBox.ItemTemplate>
|
</ComboBox.ItemTemplate>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<CheckBox Grid.Column="2" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}" ToolTip="Stay Enabled" Margin="4,0">
|
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
|
||||||
Stay Enabled
|
|
||||||
</CheckBox>
|
|
||||||
<CheckBox Grid.Column="3" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding auto_revert}" ToolTip="Auto Revert" Margin="0,0,4,0">
|
|
||||||
Auto Revert
|
|
||||||
</CheckBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</u:KeyedTemplateSelector.Default>
|
</u:KeyedTemplateSelector.Default>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<Setter Property="BorderThickness" Value="0,0,1,0"/>
|
<Setter Property="BorderThickness" Value="0,0,1,0"/>
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
<Setter Property="Height" Value="23" />
|
<Setter Property="Height" Value="23" />
|
||||||
<Setter Property="MinWidth" Value="{x:Static spark:LadderGridHost.ColumnMinWidth}"/>
|
<Setter Property="MinWidth" Value="30" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
|
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
|||||||
DataContext = vm;
|
DataContext = vm;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||||
public MiniOrderBookRowView() : base() {
|
public MiniOrderBookRowView() : base() {
|
||||||
FocusManager.SetIsFocusScope(this, true);
|
FocusManager.SetIsFocusScope(this, true);
|
||||||
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
|
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
|
||||||
|
|||||||
@@ -650,13 +650,12 @@ namespace mbts.spark {
|
|||||||
ListSortDirection? _cached_sort_direction;
|
ListSortDirection? _cached_sort_direction;
|
||||||
|
|
||||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
|
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();
|
id = key.ToString();
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
Header = "Last Message";
|
Header = "Last Message";
|
||||||
Width = DefaultColumnWidths.LastMessage;
|
Width = DefaultColumnWidths.LastMessage;
|
||||||
TextBinding = new Binding(nameof(OrderBookRowVM.last_msg));
|
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||||
ToolTipBinding = new Binding(nameof(OrderBookRowVM.last_msg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
|
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using bts.api.orderman;
|
|||||||
using bts.collections;
|
using bts.collections;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.utils;
|
using bts.utils;
|
||||||
using bts.utils.csv;
|
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.controls;
|
using mbts.ui.controls;
|
||||||
using System;
|
using System;
|
||||||
@@ -12,7 +11,6 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||||
@@ -62,35 +60,6 @@ namespace mbts.spark {
|
|||||||
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
|
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
|
||||||
CsvHelper.create_ordering_maintainer(
|
|
||||||
typeof(OrderBookRowVM),
|
|
||||||
new string[] {
|
|
||||||
nameof(security_text),
|
|
||||||
nameof(symbol),
|
|
||||||
nameof(time_created),
|
|
||||||
nameof(last_change),
|
|
||||||
nameof(user),
|
|
||||||
nameof(account),
|
|
||||||
nameof(exchange),
|
|
||||||
nameof(time_in_force),
|
|
||||||
nameof(order_type),
|
|
||||||
nameof(root_algo_display_name),
|
|
||||||
nameof(order_sub_type),
|
|
||||||
nameof(side),
|
|
||||||
nameof(quantity),
|
|
||||||
nameof(filled_quantity),
|
|
||||||
nameof(price),
|
|
||||||
nameof(avg_price),
|
|
||||||
nameof(last_price),
|
|
||||||
nameof(piq),
|
|
||||||
nameof(status),
|
|
||||||
// last message (could contains commas, so skipping)
|
|
||||||
nameof(security_key),
|
|
||||||
nameof(order_key),
|
|
||||||
nameof(parent_order_key),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
public OrderSecurityRootPair order_tuple { get; private set; }
|
public OrderSecurityRootPair order_tuple { get; private set; }
|
||||||
public OrderRecordImmutable order => order_tuple.order.order;
|
public OrderRecordImmutable order => order_tuple.order.order;
|
||||||
@@ -119,7 +88,7 @@ namespace mbts.spark {
|
|||||||
public DateTime last_change => order.last_change();
|
public DateTime last_change => order.last_change();
|
||||||
public string last_change_text => _fmt_date(last_change);
|
public string last_change_text => _fmt_date(last_change);
|
||||||
public OrderStatus status => order.status();
|
public OrderStatus status => order.status();
|
||||||
public double price => order.ui_price();
|
public double price => order.price();
|
||||||
public double last_price => order.last_price();
|
public double last_price => order.last_price();
|
||||||
public double avg_price => order.avg_price();
|
public double avg_price => order.avg_price();
|
||||||
public string price_text => _fmt_price(price);
|
public string price_text => _fmt_price(price);
|
||||||
@@ -300,14 +269,13 @@ namespace mbts.spark {
|
|||||||
if ((Columns.OrderType & dirty) != 0)
|
if ((Columns.OrderType & dirty) != 0)
|
||||||
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
|
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
|
||||||
// quantity
|
// quantity
|
||||||
if ((Columns.Size & dirty) != 0) {
|
if ((Columns.Size & dirty) != 0)
|
||||||
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
|
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
|
||||||
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
|
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
|
||||||
// via the Mini OB update column mechanism (vs the EditableQty column)
|
// via the Mini OB update column mechanism (vs the EditableQty column)
|
||||||
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
|
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
|
||||||
// row will no longer match the filter
|
// row will no longer match the filter
|
||||||
prospective_quantity = quantity;
|
prospective_quantity = quantity;
|
||||||
}
|
|
||||||
// price
|
// price
|
||||||
if ((Columns.Price & dirty) != 0) {
|
if ((Columns.Price & dirty) != 0) {
|
||||||
RaisePropertyChanged(nameof(price));
|
RaisePropertyChanged(nameof(price));
|
||||||
@@ -446,7 +414,7 @@ namespace mbts.spark {
|
|||||||
dirty |= Columns.LastPrice;
|
dirty |= Columns.LastPrice;
|
||||||
if (oldval.order.order.avg_price() != newval.order.order.avg_price())
|
if (oldval.order.order.avg_price() != newval.order.order.avg_price())
|
||||||
dirty |= Columns.AvgFilled;
|
dirty |= Columns.AvgFilled;
|
||||||
if (oldval.order.order.ui_price() != newval.order.order.ui_price())
|
if (oldval.order.order.price() != newval.order.order.price())
|
||||||
dirty |= Columns.Price;
|
dirty |= Columns.Price;
|
||||||
if (oldval.order.order.quantity() != newval.order.order.quantity())
|
if (oldval.order.order.quantity() != newval.order.order.quantity())
|
||||||
dirty |= Columns.Size;
|
dirty |= Columns.Size;
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ using bts.shell;
|
|||||||
using bts.shell.fills;
|
using bts.shell.fills;
|
||||||
using bts.shell.orderbook;
|
using bts.shell.orderbook;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.utils.csv;
|
|
||||||
using bts.view;
|
using bts.view;
|
||||||
using mbts.persist;
|
using mbts.persist;
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.extensions;
|
using mbts.ui.extensions;
|
||||||
|
using mbts.ui.shell;
|
||||||
using mbts.ui.spark;
|
using mbts.ui.spark;
|
||||||
using mbts.ui.utility;
|
using mbts.ui.utility;
|
||||||
using mbts.utils;
|
using mbts.utils;
|
||||||
@@ -31,7 +31,6 @@ namespace mbts.spark {
|
|||||||
OrderBookVM _vm;
|
OrderBookVM _vm;
|
||||||
OrderBookView _view;
|
OrderBookView _view;
|
||||||
OrderBookWindowFactory _factory;
|
OrderBookWindowFactory _factory;
|
||||||
SparkMenuDisplayVM _menu_display_vm;
|
|
||||||
SparkCommand[] _commands;
|
SparkCommand[] _commands;
|
||||||
|
|
||||||
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
|
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
|
||||||
@@ -45,14 +44,7 @@ namespace mbts.spark {
|
|||||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
|
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override OrderBookStartupData SaveState {
|
public override OrderBookStartupData SaveState => _state = _vm?.create();
|
||||||
get {
|
|
||||||
_state = _vm?.create();
|
|
||||||
if (_state != null)
|
|
||||||
_state.menu_display = _menu_display_vm.safe_create();
|
|
||||||
return _state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string window_type => _window_type;
|
public override string window_type => _window_type;
|
||||||
public override UIElement Content => _view;
|
public override UIElement Content => _view;
|
||||||
@@ -71,9 +63,6 @@ namespace mbts.spark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void thread_init() {
|
public void thread_init() {
|
||||||
_menu_display_vm = new SparkMenuDisplayVM();
|
|
||||||
if (_state != null)
|
|
||||||
_menu_display_vm.load_from(_state.menu_display);
|
|
||||||
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
|
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
|
||||||
_vm.load_from(_state);
|
_vm.load_from(_state);
|
||||||
_view = new OrderBookView(_vm);
|
_view = new OrderBookView(_vm);
|
||||||
@@ -195,7 +184,6 @@ namespace mbts.spark {
|
|||||||
show_grid_settings_command.group = CommandGroups.Auxiliary;
|
show_grid_settings_command.group = CommandGroups.Auxiliary;
|
||||||
|
|
||||||
_commands = new SparkCommand[] {
|
_commands = new SparkCommand[] {
|
||||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
|
|
||||||
toggle_show_right_panel_command,
|
toggle_show_right_panel_command,
|
||||||
attach_selected_orders_command,
|
attach_selected_orders_command,
|
||||||
detach_selected_orders_command,
|
detach_selected_orders_command,
|
||||||
@@ -209,8 +197,7 @@ namespace mbts.spark {
|
|||||||
show_grid_settings_command,
|
show_grid_settings_command,
|
||||||
// hidden cmds (i.e., keyboard shortcuts at window level)
|
// hidden cmds (i.e., keyboard shortcuts at window level)
|
||||||
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
|
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
|
||||||
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
|
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders))
|
||||||
_menu_display_vm.spark_cmd
|
|
||||||
};
|
};
|
||||||
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
|
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
|
||||||
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
|
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
|
||||||
@@ -238,13 +225,11 @@ namespace mbts.spark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentViewCreated(SparkWindowView parent_view) {
|
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||||
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
|
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
|
||||||
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
|
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
|
||||||
_vm.is_attaching_orders = false;
|
_vm.is_attaching_orders = false;
|
||||||
}));
|
}));
|
||||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class CommandGroups {
|
static class CommandGroups {
|
||||||
public const string
|
public const string
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ namespace bts.shell.orderbook {
|
|||||||
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
||||||
// numeric
|
// numeric
|
||||||
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
||||||
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
|
pb.add_price(fs.price, o => o.order.order.price(), o => o.order.security);
|
||||||
pb.add_numeric(fs.qty_filled, o => o.order.order.filled());
|
pb.add_numeric(fs.qty_filled, o => o.order.order.filled());
|
||||||
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
||||||
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
||||||
|
|||||||
@@ -65,9 +65,6 @@ namespace bts.shell.orderbook {
|
|||||||
// use defaults
|
// use defaults
|
||||||
[ProtoMember(25)]
|
[ProtoMember(25)]
|
||||||
public bool use_defaults;
|
public bool use_defaults;
|
||||||
// menu display
|
|
||||||
[ProtoMember(27)]
|
|
||||||
public SparkMenuDisplaySettings menu_display { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace mbts.spark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||||
public OrderBookRowView() : base() { }
|
public OrderBookRowView() : base() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,388 +1,375 @@
|
|||||||
using bts.api;
|
using bts.api;
|
||||||
using bts.collections;
|
using bts.collections;
|
||||||
using bts.settings;
|
using bts.settings;
|
||||||
using bts.shell.pnl;
|
using bts.shell.pnl;
|
||||||
using bts.spark;
|
using bts.spark;
|
||||||
using bts.utils;
|
using bts.utils;
|
||||||
using bts.view;
|
using mbts;
|
||||||
using mbts;
|
using mbts.config;
|
||||||
using mbts.config;
|
using mbts.persist;
|
||||||
using mbts.persist;
|
using mbts.spark;
|
||||||
using mbts.spark;
|
using mbts.ui;
|
||||||
using mbts.ui;
|
using mbts.ui.controls;
|
||||||
using mbts.ui.controls;
|
using mbts.ui.settings;
|
||||||
using mbts.ui.settings;
|
using mbts.ui.spark;
|
||||||
using mbts.ui.spark;
|
using mbts.ui.utility;
|
||||||
using mbts.ui.utility;
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Immutable;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Specialized;
|
||||||
using System.Collections.Specialized;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel.Composition;
|
||||||
using System.ComponentModel.Composition;
|
using System.Linq;
|
||||||
using System.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
using System.Windows;
|
||||||
using System.Windows;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media;
|
|
||||||
|
namespace bts.shell {
|
||||||
namespace bts.shell {
|
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
|
||||||
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
|
const string _window_type = "PNL", _window_title = "PNL";
|
||||||
const string _window_type = "PNL", _window_title = "PNL";
|
readonly IBus _bus;
|
||||||
readonly IBus _bus;
|
readonly OrdersModel _orders_model;
|
||||||
readonly OrdersModel _orders_model;
|
readonly UserSettingsHub _settings_hub;
|
||||||
readonly UserSettingsHub _settings_hub;
|
readonly PNLConfig _pnl_conf;
|
||||||
readonly PNLConfig _pnl_conf;
|
readonly IShellWindowThreadIDProvider _thread_id_provider;
|
||||||
readonly IShellWindowThreadIDProvider _thread_id_provider;
|
|
||||||
|
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
|
||||||
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
|
_bus = bus;
|
||||||
_bus = bus;
|
_orders_model = orders_model;
|
||||||
_orders_model = orders_model;
|
_settings_hub = settings_hub;
|
||||||
_settings_hub = settings_hub;
|
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
|
||||||
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
|
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
|
||||||
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
|
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
|
||||||
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
|
}
|
||||||
}
|
|
||||||
|
public override Task<IShellWindow> load(PNLSaveState state) =>
|
||||||
public override Task<IShellWindow> load(PNLSaveState state) =>
|
Task.FromResult(_create(state.settings));
|
||||||
Task.FromResult(_create(state.settings));
|
|
||||||
|
void _show(PNLSettings settings) =>
|
||||||
void _show(PNLSettings settings) =>
|
_bus.post(SparkTags.ShowWindow, _create(settings));
|
||||||
_bus.post(SparkTags.ShowWindow, _create(settings));
|
|
||||||
|
IShellWindow _create(PNLSettings settings) {
|
||||||
IShellWindow _create(PNLSettings settings) {
|
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
|
||||||
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
|
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
|
||||||
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
|
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
|
||||||
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
|
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
|
||||||
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
|
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
|
||||||
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
|
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
|
||||||
Action<SparkWindowView> on_parent_view_created_impl = null;
|
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
|
||||||
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
|
var content = new PNLView {
|
||||||
var init_settings = settings ?? PNLSettings.Default;
|
DataContext = vm
|
||||||
var menu_display_vm = new SparkMenuDisplayVM();
|
};
|
||||||
menu_display_vm.load_from(init_settings.menu_display);
|
vm.sorting_changed += sh => {
|
||||||
on_parent_view_created_impl = menu_display_vm.safe_bind_to_view;
|
try {
|
||||||
var vm = new PNLVM(_bus, _orders_model, init_settings);
|
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
|
||||||
var content = new PNLView {
|
} catch { }
|
||||||
DataContext = vm
|
};
|
||||||
};
|
vm.SubscribeDisposed(
|
||||||
vm.sorting_changed += sh => {
|
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
|
||||||
try {
|
.OnDispatcher()
|
||||||
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
|
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
|
||||||
} catch { }
|
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||||
};
|
|
||||||
vm.SubscribeDisposed(
|
Func<PNLSaveState> get_save_state = () => {
|
||||||
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
|
var cols = cached_columns.ToArray(c => c.create());
|
||||||
.OnDispatcher()
|
var filters = vm.get_filters_states();
|
||||||
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
|
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
|
||||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
var calc_type = vm.calculation_type;
|
||||||
|
var state = settings_manager.instance_settings;
|
||||||
Func<PNLSaveState> get_save_state = () => {
|
if (state == null) {
|
||||||
var cols = cached_columns.ToArray(c => c.create());
|
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
|
||||||
var filters = vm.get_filters_states();
|
var defaults = settings_manager.default_settings;
|
||||||
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
|
if (calc_type == defaults.calculation_type &&
|
||||||
var calc_type = vm.calculation_type;
|
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
|
||||||
var state = settings_manager.instance_settings;
|
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
||||||
if (state == null) {
|
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
|
||||||
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
|
return new PNLSaveState { use_defaults = true, settings = null };
|
||||||
var defaults = settings_manager.default_settings;
|
}
|
||||||
if (calc_type == defaults.calculation_type &&
|
state = defaults;
|
||||||
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
|
}
|
||||||
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
|
||||||
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
|
state = state.with(
|
||||||
return new PNLSaveState { use_defaults = true, settings = null };
|
sorting_criterion,
|
||||||
}
|
vm.anchors,
|
||||||
state = defaults;
|
filters,
|
||||||
}
|
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
|
||||||
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
|
calc_type,
|
||||||
state = state.with(
|
vm.header_row_display);
|
||||||
sorting_criterion,
|
return new PNLSaveState { use_defaults = false, settings = state };
|
||||||
vm.anchors,
|
};
|
||||||
filters,
|
IEnumerable<SparkCommand> commands = new[] {
|
||||||
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
|
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
|
||||||
calc_type,
|
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
|
||||||
vm.header_row_display,
|
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
|
||||||
menu_display: menu_display_vm.create());
|
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||||
return new PNLSaveState { use_defaults = false, settings = state };
|
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||||
};
|
},
|
||||||
IEnumerable<SparkCommand> commands = new[] {
|
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
|
||||||
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
|
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||||
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
|
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||||
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
|
},
|
||||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
settings_manager.instance_settings = get_save_state().settings;
|
||||||
},
|
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
|
||||||
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
|
settings_manager,
|
||||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
us => us.pnl,
|
||||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
UserSettings.FileExtensions.PNL,
|
||||||
},
|
s => new PNLSettingsVM(s));
|
||||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||||
settings_manager.instance_settings = get_save_state().settings;
|
dialog_vm.RequestClose += on_request_close;
|
||||||
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
|
return dialog;
|
||||||
settings_manager,
|
void on_request_close(object sender, EventArgs e) =>
|
||||||
us => us.pnl,
|
WindowUtilities.force_close(dialog);
|
||||||
UserSettings.FileExtensions.PNL,
|
}))
|
||||||
s => new PNLSettingsVM(s));
|
};
|
||||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
IDisposable dispose = Disposable.Create(
|
||||||
dialog_vm.RequestClose += on_request_close;
|
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||||
return dialog;
|
vm.load_from(s);
|
||||||
void on_request_close(object sender, EventArgs e) =>
|
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
|
||||||
WindowUtilities.force_close(dialog);
|
var fallback_family = new FontFamily(s.grid.font_family);
|
||||||
})),
|
// group
|
||||||
menu_display_vm.spark_cmd
|
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
|
||||||
};
|
var group_appearance = s.group_cell_appearance.to_appearance();
|
||||||
IDisposable dispose = Disposable.Create(
|
// header
|
||||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
|
||||||
vm.load_from(s);
|
var header_appearance = s.header_cell_appearance.to_appearance();
|
||||||
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
|
//
|
||||||
var fallback_family = new FontFamily(s.grid.font_family);
|
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
|
||||||
// group
|
if (col_setting_map.TryGetValue(c.id, out var cs)) {
|
||||||
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
|
c.load_from(cs);
|
||||||
var group_appearance = s.group_cell_appearance.to_appearance();
|
}
|
||||||
// header
|
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
|
||||||
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
|
c.load_group_params(
|
||||||
var header_appearance = s.header_cell_appearance.to_appearance();
|
group_appearance.font_size,
|
||||||
//
|
gcs.font_weight_override ?? group_appearance.font_weight,
|
||||||
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
|
gcs.font_color_override ?? group_appearance.foreground,
|
||||||
if (col_setting_map.TryGetValue(c.id, out var cs)) {
|
group_appearance.font_family);
|
||||||
c.load_from(cs);
|
}
|
||||||
}
|
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
|
||||||
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
|
c.load_header_params(
|
||||||
c.load_group_params(
|
header_appearance.font_size,
|
||||||
group_appearance.font_size,
|
hcs.font_weight_override ?? header_appearance.font_weight,
|
||||||
gcs.font_weight_override ?? group_appearance.font_weight,
|
hcs.font_color_override ?? header_appearance.foreground,
|
||||||
gcs.font_color_override ?? group_appearance.foreground,
|
header_appearance.font_family);
|
||||||
group_appearance.font_family);
|
}
|
||||||
}
|
}
|
||||||
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
|
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
|
||||||
c.load_header_params(
|
throw new InvalidOperationException();
|
||||||
header_appearance.font_size,
|
}, c => c.id.ToLower(), cs => cs.id.ToLower());
|
||||||
hcs.font_weight_override ?? header_appearance.font_weight,
|
}),
|
||||||
hcs.font_color_override ?? header_appearance.foreground,
|
content._grid.Columns.Subscribe((s, e) => {
|
||||||
header_appearance.font_family);
|
switch (e.Action) {
|
||||||
}
|
case NotifyCollectionChangedAction.Add: add_new(); break;
|
||||||
}
|
case NotifyCollectionChangedAction.Remove: remove_old(); break;
|
||||||
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
|
case NotifyCollectionChangedAction.Replace:
|
||||||
throw new InvalidOperationException();
|
case NotifyCollectionChangedAction.Move:
|
||||||
}, c => c.id.ToLower(), cs => cs.id.ToLower());
|
remove_old();
|
||||||
}),
|
add_new();
|
||||||
content._grid.Columns.Subscribe((s, e) => {
|
break;
|
||||||
switch (e.Action) {
|
case NotifyCollectionChangedAction.Reset:
|
||||||
case NotifyCollectionChangedAction.Add: add_new(); break;
|
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||||
case NotifyCollectionChangedAction.Remove: remove_old(); break;
|
break;
|
||||||
case NotifyCollectionChangedAction.Replace:
|
}
|
||||||
case NotifyCollectionChangedAction.Move:
|
void add_new() =>
|
||||||
remove_old();
|
cached_columns = e.NewStartingIndex >= 0 ?
|
||||||
add_new();
|
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
|
||||||
break;
|
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
|
||||||
case NotifyCollectionChangedAction.Reset:
|
|
||||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
void remove_old() =>
|
||||||
break;
|
cached_columns = e.OldStartingIndex >= 0 ?
|
||||||
}
|
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
|
||||||
void add_new() =>
|
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
|
||||||
cached_columns = e.NewStartingIndex >= 0 ?
|
}),
|
||||||
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
|
Disposable.wrap_collection(commands.OfType<IDisposable>()),
|
||||||
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
|
vm);
|
||||||
|
return (content, commands, get_save_state, dispose);
|
||||||
void remove_old() =>
|
},
|
||||||
cached_columns = e.OldStartingIndex >= 0 ?
|
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
|
||||||
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
|
min_height_offset: 60);
|
||||||
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
|
}
|
||||||
}),
|
|
||||||
Disposable.wrap_collection(commands.OfType<IDisposable>()),
|
readonly struct PNLConfig {
|
||||||
menu_display_vm,
|
public readonly bool
|
||||||
vm);
|
enable_animations,
|
||||||
return (content, commands, get_save_state, dispose);
|
show_pause_resume;
|
||||||
},
|
|
||||||
// the on_parent_view_created_impl delegate is initialized in the main create delegate,
|
public PNLConfig(bts.config.PNLOptions options) {
|
||||||
// so it can't be passed directly as an argument because it would just be null,
|
enable_animations = options.enable_animations;
|
||||||
// hence the proxy/wrapper delegate below.
|
show_pause_resume = options.show_pause_resume;
|
||||||
on_parent_view_created: pview => on_parent_view_created_impl?.Invoke(pview),
|
}
|
||||||
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
|
}
|
||||||
min_height_offset: 60);
|
|
||||||
}
|
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||||
|
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||||
readonly struct PNLConfig {
|
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||||
public readonly bool
|
if (settings != null && (
|
||||||
enable_animations,
|
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
|
||||||
show_pause_resume;
|
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
|
||||||
|
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
|
||||||
public PNLConfig(bts.config.PNLOptions options) {
|
//
|
||||||
enable_animations = options.enable_animations;
|
new_settings = settings;
|
||||||
show_pause_resume = options.show_pause_resume;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
new_settings = null;
|
||||||
|
return false;
|
||||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
}
|
||||||
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
|
||||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
|
||||||
if (settings != null && (
|
if (_apply(actual_triggers, default_triggers, out var result)) {
|
||||||
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
|
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
||||||
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
|
return true;
|
||||||
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
|
}
|
||||||
//
|
return false;
|
||||||
new_settings = settings;
|
}
|
||||||
return true;
|
|
||||||
}
|
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||||
new_settings = null;
|
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
|
||||||
return false;
|
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
|
||||||
}
|
var snapshot = actual_trigger_map;
|
||||||
|
foreach (var kv in snapshot) {
|
||||||
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
|
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
|
||||||
if (_apply(actual_triggers, default_triggers, out var result)) {
|
// assume 1 animation per column
|
||||||
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
|
||||||
return true;
|
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
|
||||||
}
|
actual_col_anims.Difference(default_col_anims,
|
||||||
return false;
|
out var exclusive_actual,
|
||||||
}
|
out var exclusive_defaults,
|
||||||
|
out var diff);
|
||||||
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
var new_col_anims = exclusive_defaults.ToList();
|
||||||
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
|
if (new_col_anims.Count > 0) {
|
||||||
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
|
// add the original col animations to the new ones (combine them)
|
||||||
var snapshot = actual_trigger_map;
|
new_col_anims.AddRange(actual_col_anims);
|
||||||
foreach (var kv in snapshot) {
|
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
|
||||||
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
|
}
|
||||||
// assume 1 animation per column
|
}
|
||||||
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
|
}
|
||||||
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
|
foreach (var kv in default_trigger_map) {
|
||||||
actual_col_anims.Difference(default_col_anims,
|
if (!actual_trigger_map.ContainsKey(kv.Key))
|
||||||
out var exclusive_actual,
|
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
|
||||||
out var exclusive_defaults,
|
}
|
||||||
out var diff);
|
if (actual_trigger_map != snapshot) {
|
||||||
var new_col_anims = exclusive_defaults.ToList();
|
result = actual_trigger_map.Values;
|
||||||
if (new_col_anims.Count > 0) {
|
return true;
|
||||||
// add the original col animations to the new ones (combine them)
|
}
|
||||||
new_col_anims.AddRange(actual_col_anims);
|
result = null;
|
||||||
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
foreach (var kv in default_trigger_map) {
|
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||||
if (!actual_trigger_map.ContainsKey(kv.Key))
|
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||||
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
|
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||||
}
|
if (settings != null) {
|
||||||
if (actual_trigger_map != snapshot) {
|
var defaults = PNLSettings.Default;
|
||||||
result = actual_trigger_map.Values;
|
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||||
return true;
|
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||||
}
|
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||||
result = null;
|
new_settings = settings;
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
new_settings = null;
|
||||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
return false;
|
||||||
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
|
}
|
||||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
|
||||||
if (settings != null) {
|
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||||
var defaults = PNLSettings.Default;
|
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
|
||||||
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
settings = set_columns(settings, new_cols);
|
||||||
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
return true;
|
||||||
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
}
|
||||||
new_settings = settings;
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
new_settings = null;
|
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||||
return false;
|
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||||
}
|
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||||
|
if (settings != null) {
|
||||||
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||||
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
|
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||||
settings = set_columns(settings, new_cols);
|
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||||
return true;
|
new_settings = settings;
|
||||||
}
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
new_settings = null;
|
||||||
|
return false;
|
||||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
}
|
||||||
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
|
|
||||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||||
if (settings != null) {
|
if (_apply(columns, out var new_cols)) {
|
||||||
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
settings = set_columns(settings, new_cols);
|
||||||
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
return true;
|
||||||
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
}
|
||||||
new_settings = settings;
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
}
|
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
|
||||||
new_settings = null;
|
var col_list = columns.ToList();
|
||||||
return false;
|
int orig_count = col_list.Count;
|
||||||
}
|
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
|
||||||
|
if (col_list.Count < orig_count) {
|
||||||
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
result = col_list;
|
||||||
if (_apply(columns, out var new_cols)) {
|
return true;
|
||||||
settings = set_columns(settings, new_cols);
|
}
|
||||||
return true;
|
result = null;
|
||||||
}
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
|
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||||
var col_list = columns.ToList();
|
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||||
int orig_count = col_list.Count;
|
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||||
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
|
if (settings != null) {
|
||||||
if (col_list.Count < orig_count) {
|
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
|
||||||
result = col_list;
|
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
|
||||||
return true;
|
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
|
||||||
}
|
new_settings = settings;
|
||||||
result = null;
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
new_settings = null;
|
||||||
|
return false;
|
||||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
}
|
||||||
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
|
||||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
|
||||||
if (settings != null) {
|
if (_apply(triggers, out var new_triggers)) {
|
||||||
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
|
settings = set_triggers(settings, new_triggers);
|
||||||
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
|
return true;
|
||||||
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
|
}
|
||||||
new_settings = settings;
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
}
|
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||||
new_settings = null;
|
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
|
||||||
return false;
|
var snapshot = trigger_map;
|
||||||
}
|
foreach (var t in trigger_map.Values) {
|
||||||
|
var anims = t.animations.ToList();
|
||||||
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
|
int orig_count = anims.Count;
|
||||||
if (_apply(triggers, out var new_triggers)) {
|
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
|
||||||
settings = set_triggers(settings, new_triggers);
|
if (anims.Count < orig_count)
|
||||||
return true;
|
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
|
||||||
}
|
}
|
||||||
return false;
|
if (trigger_map != snapshot) {
|
||||||
}
|
result = trigger_map.Values;
|
||||||
|
return true;
|
||||||
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
}
|
||||||
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
|
result = null;
|
||||||
var snapshot = trigger_map;
|
return false;
|
||||||
foreach (var t in trigger_map.Values) {
|
}
|
||||||
var anims = t.animations.ToList();
|
}
|
||||||
int orig_count = anims.Count;
|
|
||||||
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
|
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||||
if (anims.Count < orig_count)
|
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
|
||||||
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
|
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||||
}
|
if (settings != null && !settings.grid.enable_background_color) {
|
||||||
if (trigger_map != snapshot) {
|
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
|
||||||
result = trigger_map.Values;
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
new_settings = null;
|
||||||
result = null;
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
|
||||||
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
|
|
||||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
|
||||||
if (settings != null && !settings.grid.enable_background_color) {
|
|
||||||
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
new_settings = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -224,8 +224,6 @@ namespace bts.shell.pnl {
|
|||||||
}
|
}
|
||||||
[ProtoMember(6)]
|
[ProtoMember(6)]
|
||||||
public PNLHeaderRowDisplay header_row_display { get; set; }
|
public PNLHeaderRowDisplay header_row_display { get; set; }
|
||||||
[ProtoMember(15)]
|
|
||||||
public SparkMenuDisplaySettings menu_display { get; set; }
|
|
||||||
|
|
||||||
public IReadOnlyList<Columns> anchors => _anchors ?? (_anchors = Array.Empty<Columns>());
|
public IReadOnlyList<Columns> anchors => _anchors ?? (_anchors = Array.Empty<Columns>());
|
||||||
public IReadOnlyList<PNLAnimationTriggerData> animations => _animations ?? (_animations = default_animations.ToArray());
|
public IReadOnlyList<PNLAnimationTriggerData> animations => _animations ?? (_animations = default_animations.ToArray());
|
||||||
@@ -277,8 +275,7 @@ namespace bts.shell.pnl {
|
|||||||
IEnumerable<PNLAnimationTriggerData> animations = null,
|
IEnumerable<PNLAnimationTriggerData> animations = null,
|
||||||
IEnumerable<PNLAnimationTriggerData> group_header_animations = null,
|
IEnumerable<PNLAnimationTriggerData> group_header_animations = null,
|
||||||
IEnumerable<PNLAnimationTriggerData> header_row_animations = null,
|
IEnumerable<PNLAnimationTriggerData> header_row_animations = null,
|
||||||
IEnumerable<GridColumnSettings> header_columns = null,
|
IEnumerable<GridColumnSettings> header_columns = null) =>
|
||||||
SparkMenuDisplaySettings? menu_display = null) =>
|
|
||||||
new PNLSettings {
|
new PNLSettings {
|
||||||
calculation_type = calculation_type ?? this.calculation_type,
|
calculation_type = calculation_type ?? this.calculation_type,
|
||||||
header_row_display = header_row_display ?? this.header_row_display,
|
header_row_display = header_row_display ?? this.header_row_display,
|
||||||
@@ -298,8 +295,7 @@ namespace bts.shell.pnl {
|
|||||||
_group_header_animations = group_header_animations?.ToArray() ?? _group_header_animations,
|
_group_header_animations = group_header_animations?.ToArray() ?? _group_header_animations,
|
||||||
_header_row_animations = header_row_animations?.ToArray() ?? _header_row_animations,
|
_header_row_animations = header_row_animations?.ToArray() ?? _header_row_animations,
|
||||||
// grid
|
// grid
|
||||||
grid = grid ?? this.grid,
|
grid = grid ?? this.grid
|
||||||
menu_display = menu_display ?? this.menu_display
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool Equals(PNLSettings other) =>
|
public bool Equals(PNLSettings other) =>
|
||||||
@@ -490,4 +486,4 @@ namespace bts.shell.pnl {
|
|||||||
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> group_triggers(this PNLSettings settings) => settings.group_header_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
|
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> group_triggers(this PNLSettings settings) => settings.group_header_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
|
||||||
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> header_triggers(this PNLSettings settings) => settings.header_row_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
|
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> header_triggers(this PNLSettings settings) => settings.header_row_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,10 +29,6 @@
|
|||||||
<Color x:Key="GroupRowForegroundColor">#FF000000</Color>
|
<Color x:Key="GroupRowForegroundColor">#FF000000</Color>
|
||||||
<!--Converters-->
|
<!--Converters-->
|
||||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
|
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
|
||||||
<cv:ValueConverterPipe x:Key="GroupRowHeightConverter">
|
|
||||||
<cv:RangeBlockConverter Minimum="{x:Static pnl:PNLView.MinGroupRowHeight}"/>
|
|
||||||
<cv:GridLengthConverter UnitType="Pixel"/>
|
|
||||||
</cv:ValueConverterPipe>
|
|
||||||
<cv:MultiValueConverterPipe x:Key="GroupRowBackgroundConverter">
|
<cv:MultiValueConverterPipe x:Key="GroupRowBackgroundConverter">
|
||||||
<cv:PartialConverter/>
|
<cv:PartialConverter/>
|
||||||
<cv:ColorToBrushConverter/>
|
<cv:ColorToBrushConverter/>
|
||||||
@@ -262,7 +258,7 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="TextCellBase" TargetType="c:TextCell">
|
<Style x:Key="RowTextCell" TargetType="c:TextCell">
|
||||||
<Setter Property="Background">
|
<Setter Property="Background">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<SolidColorBrush Color="{Binding background}"/>
|
<SolidColorBrush Color="{Binding background}"/>
|
||||||
@@ -273,14 +269,10 @@
|
|||||||
<SolidColorBrush Color="{Binding foreground}"/>
|
<SolidColorBrush Color="{Binding foreground}"/>
|
||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
</Setter>
|
</Setter>
|
||||||
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
|
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
<Setter Property="Text" Value="{Binding text}"/>
|
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
|
||||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style x:Key="RowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
|
|
||||||
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
||||||
|
<Setter Property="Text" Value="{Binding text}"/>
|
||||||
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<x:Array Type="{x:Type c:CachedColumnParam}">
|
<x:Array Type="{x:Type c:CachedColumnParam}">
|
||||||
@@ -292,7 +284,20 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
|
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding background}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Foreground">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding foreground}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
|
||||||
|
<Setter Property="Text" Value="{Binding text}"/>
|
||||||
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<x:Array Type="{x:Type c:CachedColumnParam}">
|
<x:Array Type="{x:Type c:CachedColumnParam}">
|
||||||
@@ -304,8 +309,21 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
|
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding background}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Foreground">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding foreground}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
<Setter Property="Margin" Value="0,0,0,1"/>
|
<Setter Property="Margin" Value="0,0,0,1"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
|
||||||
|
<Setter Property="Text" Value="{Binding text}"/>
|
||||||
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<x:Array Type="{x:Type c:CachedColumnParam}">
|
<x:Array Type="{x:Type c:CachedColumnParam}">
|
||||||
@@ -317,22 +335,7 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="HyperlinkCellBase" TargetType="c:HyperlinkCell">
|
<Style x:Key="HyperlinkCell" TargetType="c:HyperlinkCell">
|
||||||
<Setter Property="Background">
|
|
||||||
<Setter.Value>
|
|
||||||
<SolidColorBrush Color="{Binding background}"/>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
<Setter Property="BorderBrush">
|
|
||||||
<Setter.Value>
|
|
||||||
<SolidColorBrush Color="{Binding border}"/>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
<Setter Property="Foreground">
|
|
||||||
<Setter.Value>
|
|
||||||
<SolidColorBrush Color="{Binding foreground}"/>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||||
@@ -346,23 +349,41 @@
|
|||||||
</MultiBinding>
|
</MultiBinding>
|
||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
</Setter>
|
</Setter>
|
||||||
<Setter Property="Cursor">
|
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<Binding Path="IsMouseOverClickArea" RelativeSource="{RelativeSource Self}">
|
<x:Array Type="{x:Type c:CachedColumnParam}">
|
||||||
<Binding.Converter>
|
<c:CachedColumnParam SourcePropertyName="font_family" TargetProperty="c:HyperlinkCell.FontFamily"/>
|
||||||
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Cursors.Arrow}" ValueForTrue="{x:Static Cursors.Hand}"/>
|
<c:CachedColumnParam SourcePropertyName="font_size" TargetProperty="c:HyperlinkCell.FontSize"/>
|
||||||
</Binding.Converter>
|
<c:CachedColumnParam SourcePropertyName="font_brush" TargetProperty="c:HyperlinkCell.Foreground"/>
|
||||||
</Binding>
|
<c:CachedColumnParam SourcePropertyName="horizontal_alignment" TargetProperty="c:HyperlinkCell.HorizontalContentAlignment"/>
|
||||||
|
</x:Array>
|
||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
</Setter>
|
</Setter>
|
||||||
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
|
|
||||||
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
|
||||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
|
||||||
<Setter Property="Text" Value="{Binding text}"/>
|
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
|
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding background}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Foreground">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding foreground}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="BorderBrush">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding border}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
<Setter Property="Padding" Value="{x:Static c:DIYGrid.AnimatableCellPadding}"/>
|
<Setter Property="Padding" Value="{x:Static c:DIYGrid.AnimatableCellPadding}"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
|
||||||
|
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
||||||
|
<Setter Property="Text" Value="{Binding text}"/>
|
||||||
|
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
|
||||||
|
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
|
||||||
<Setter Property="CommandParameter">
|
<Setter Property="CommandParameter">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
|
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
|
||||||
@@ -382,9 +403,24 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
|
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding background}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Foreground">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding foreground}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
<Setter Property="Margin" Value="0,0,0,1"/>
|
<Setter Property="Margin" Value="0,0,0,1"/>
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
|
||||||
|
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
||||||
|
<Setter Property="Text" Value="{Binding text}"/>
|
||||||
|
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
|
||||||
|
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
|
||||||
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<x:Array Type="{x:Type c:CachedColumnParam}">
|
<x:Array Type="{x:Type c:CachedColumnParam}">
|
||||||
@@ -418,7 +454,23 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
|
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding background}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Foreground">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding foreground}"/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
|
||||||
|
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
||||||
|
<Setter Property="Text" Value="{Binding text}"/>
|
||||||
|
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
|
||||||
|
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
|
||||||
<Setter Property="CommandParameter">
|
<Setter Property="CommandParameter">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
|
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
|
||||||
@@ -454,7 +506,7 @@
|
|||||||
<Grid
|
<Grid
|
||||||
c:DIYGrid.SyncWithColumnGeometrySource="{Binding ElementName=_grid}"
|
c:DIYGrid.SyncWithColumnGeometrySource="{Binding ElementName=_grid}"
|
||||||
c:DIYGrid.SyncWithColumnGeometry="True"
|
c:DIYGrid.SyncWithColumnGeometry="True"
|
||||||
Height="{Binding RowHeight, ElementName=_grid, Converter={x:Static pnl:PNLView.HeaderRowHeightConverter}}"
|
Height="{Binding RowHeight, ElementName=_grid}"
|
||||||
>
|
>
|
||||||
<Grid.Visibility>
|
<Grid.Visibility>
|
||||||
<Binding Path="header_row_display">
|
<Binding Path="header_row_display">
|
||||||
@@ -557,6 +609,7 @@
|
|||||||
c:DIYGrid.CachedColumnParamsContainer="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"
|
c:DIYGrid.CachedColumnParamsContainer="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"
|
||||||
VirtualizingPanel.ScrollUnit="Pixel"
|
VirtualizingPanel.ScrollUnit="Pixel"
|
||||||
ScrollViewer.CanContentScroll="True"
|
ScrollViewer.CanContentScroll="True"
|
||||||
|
RowHeight="24"
|
||||||
Rows="{Binding rows_view}"
|
Rows="{Binding rows_view}"
|
||||||
SelectionHighlight="{Binding selection_highlight}"
|
SelectionHighlight="{Binding selection_highlight}"
|
||||||
SupportGroupItems="True"
|
SupportGroupItems="True"
|
||||||
@@ -1000,7 +1053,7 @@
|
|||||||
<!--Row Template-->
|
<!--Row Template-->
|
||||||
<c:DIYGrid.RowTemplate>
|
<c:DIYGrid.RowTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}" Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}">
|
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}">
|
||||||
<Grid.Background>
|
<Grid.Background>
|
||||||
<SolidColorBrush Color="{Binding DataContext.grid_background_color, RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
|
<SolidColorBrush Color="{Binding DataContext.grid_background_color, RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
|
||||||
</Grid.Background>
|
</Grid.Background>
|
||||||
@@ -1094,7 +1147,7 @@
|
|||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
</VisualStateManager.VisualStateGroups>
|
</VisualStateManager.VisualStateGroups>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GroupRowHeightConverter}}"/>
|
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GridLengthPixelConverter}}"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Gray" StrokeThickness="1"/>
|
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Gray" StrokeThickness="1"/>
|
||||||
@@ -1164,4 +1217,4 @@
|
|||||||
<Rectangle Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="Gray"/>
|
<Rectangle Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="Gray"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</AdornerDecorator>
|
</AdornerDecorator>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -6,7 +6,6 @@ using mbts.ui;
|
|||||||
using mbts.ui.controls;
|
using mbts.ui.controls;
|
||||||
using mbts.ui.controls.adorners;
|
using mbts.ui.controls.adorners;
|
||||||
using mbts.ui.converters;
|
using mbts.ui.converters;
|
||||||
using mbts.ui.dpi;
|
|
||||||
using mbts.ui.extensions;
|
using mbts.ui.extensions;
|
||||||
using mbts.ui.spark;
|
using mbts.ui.spark;
|
||||||
using mbts.ui.utility;
|
using mbts.ui.utility;
|
||||||
@@ -32,10 +31,6 @@ namespace bts.shell.pnl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
partial class PNLView : UserControl {
|
partial class PNLView : UserControl {
|
||||||
public static readonly double
|
|
||||||
MinGroupRowHeight = 24.0.RoundLayoutValue(),
|
|
||||||
MinHeaderRowHeight = 24.0.RoundLayoutValue();
|
|
||||||
|
|
||||||
public enum Columns {
|
public enum Columns {
|
||||||
Account = 1,
|
Account = 1,
|
||||||
// Security
|
// Security
|
||||||
@@ -90,7 +85,6 @@ namespace bts.shell.pnl {
|
|||||||
public static readonly IValueConverter
|
public static readonly IValueConverter
|
||||||
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
|
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
|
||||||
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
|
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
|
||||||
HeaderRowHeightConverter = new RangeBlockConverter<double> { Minimum = MinHeaderRowHeight },
|
|
||||||
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
|
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
|
||||||
|
|
||||||
public static readonly IMultiValueConverter
|
public static readonly IMultiValueConverter
|
||||||
@@ -169,22 +163,17 @@ namespace bts.shell.pnl {
|
|||||||
void _grid_loaded(object sender, RoutedEventArgs e) {
|
void _grid_loaded(object sender, RoutedEventArgs e) {
|
||||||
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
|
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
|
||||||
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
|
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
|
||||||
rp.SetBinding(MarginProperty,
|
rp.SetBinding(MarginProperty, new Binding(nameof(PNLVM.header_row_display)) {
|
||||||
BindingHelper.create_multi(
|
Converter = ValueConverter.create<PNLHeaderRowDisplay, Thickness>(hrd => {
|
||||||
ValueConverter.create<PNLHeaderRowDisplay, double, Thickness>((hrd, row_height) => {
|
double top = 0;
|
||||||
double top = 0;
|
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
|
||||||
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
|
top += 24;
|
||||||
top += row_height;
|
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
|
||||||
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
|
top += 24;
|
||||||
top += row_height;
|
return new Thickness(0, top, 0, 0);
|
||||||
return new Thickness(0, top, 0, 0);
|
}),
|
||||||
}),
|
Source = DataContext
|
||||||
new Binding(nameof(PNLVM.header_row_display)),
|
});
|
||||||
new Binding {
|
|
||||||
Path = new PropertyPath(DIYGrid.RowHeightProperty),
|
|
||||||
Converter = HeaderRowHeightConverter,
|
|
||||||
Source = _grid
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ namespace mbts.spark {
|
|||||||
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
|
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
|
||||||
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
|
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
|
||||||
model.send_order(security, properties_vm.create(side), price, size_calculator.value.Value).ignore();
|
model.send_order(security, properties_vm.create(side), price, size_calculator.value.Value).ignore();
|
||||||
if (close_on_send)
|
RequestClose?.Invoke(this, new EventArgs());
|
||||||
RequestClose?.Invoke(this, new EventArgs());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
set_price_command = new DelegateCommand<double>(set_price);
|
set_price_command = new DelegateCommand<double>(set_price);
|
||||||
@@ -85,7 +84,6 @@ namespace mbts.spark {
|
|||||||
public CalculatorLogic size_calculator { get; }
|
public CalculatorLogic size_calculator { get; }
|
||||||
public OrderPropertiesEditorVM properties_vm { get; }
|
public OrderPropertiesEditorVM properties_vm { get; }
|
||||||
public BBOVM bbo { get; }
|
public BBOVM bbo { get; }
|
||||||
public bool close_on_send { get; set; }
|
|
||||||
public ICommand send_order { get; }
|
public ICommand send_order { get; }
|
||||||
public ICommand set_price_command { get; }
|
public ICommand set_price_command { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,113 +1,90 @@
|
|||||||
using bts;
|
using bts;
|
||||||
using bts.api;
|
using bts.api;
|
||||||
using bts.api.orderman;
|
using bts.api.orderman;
|
||||||
using bts.utils;
|
using bts.utils;
|
||||||
using mbts.spark.utilities;
|
using mbts.spark.utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Linq;
|
||||||
using System.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Linq;
|
|
||||||
|
namespace mbts.spark {
|
||||||
namespace mbts.spark {
|
/// <summary>
|
||||||
/// <summary>
|
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
||||||
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
/// </summary>
|
||||||
/// </summary>
|
class OrderParamsAggregator {
|
||||||
class OrderParamsAggregator {
|
readonly TimeSpan _throttle;
|
||||||
readonly TimeSpan _throttle;
|
readonly CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> _algo_def_mon =
|
||||||
readonly CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> _algo_def_mon =
|
new CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable>(
|
||||||
new CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable>(
|
ask => ask,
|
||||||
ask => ask,
|
OrderDataHelper.EqualsHelpers.custom_order_defi_structure_comparer,
|
||||||
OrderDataHelper.EqualsHelpers.custom_order_defi_structure_comparer,
|
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
|
||||||
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
|
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||||
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgo> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgo>();
|
||||||
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgo> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgo>();
|
readonly DynamicOrderHelper _dyn_helper;
|
||||||
readonly DynamicOrderHelper _dyn_helper;
|
readonly Func<uint, OrderSecurityAlgo?> _lookup_order;
|
||||||
readonly Func<uint, OrderSecurityAlgo?> _lookup_order;
|
|
||||||
|
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle, Func<uint, OrderSecurityAlgo?> lookup_order) {
|
||||||
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle, Func<uint, OrderSecurityAlgo?> lookup_order) {
|
_dyn_helper = dyn_helper;
|
||||||
_dyn_helper = dyn_helper;
|
_throttle = throttle;
|
||||||
_throttle = throttle;
|
_lookup_order = lookup_order;
|
||||||
_lookup_order = lookup_order;
|
_group_mon.add_member_mon(_algo_def_mon, _build_algo_tree);
|
||||||
_group_mon.add_member_mon(_algo_def_mon, _build_algo_tree);
|
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
|
||||||
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
|
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
|
||||||
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
|
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
|
||||||
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
|
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
|
||||||
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
|
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
|
||||||
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
|
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
|
||||||
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
|
}
|
||||||
}
|
|
||||||
|
public SecurityImmutable? security => _security_mon.common_value;
|
||||||
public SecurityImmutable? security => _security_mon.common_value;
|
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
|
||||||
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
|
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
|
||||||
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
|
public uint? size => _size_mon.common_value_nullable();
|
||||||
public uint? size => _size_mon.common_value_nullable();
|
public AccountTuple? account => _account_mon.common_value_nullable();
|
||||||
public AccountTuple? account => _account_mon.common_value_nullable();
|
public bool all_active => _all_active_mon.common_value;
|
||||||
public bool all_active => _all_active_mon.common_value;
|
|
||||||
|
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals, Enumerable.Empty<CustomOrderDefinitionImmutable>());
|
||||||
/// <summary>
|
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
|
||||||
/// Notifies observers when the set of <see cref="CustomOrderDefinitionImmutable"/>s changes.
|
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
|
||||||
/// The algos in the set are the distinct group of algos for the selected <see cref="OrderSecurityAlgo"/>s.
|
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
||||||
/// </summary>
|
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
|
||||||
// Note: this might be better optimized by adding a 'begin_reset' method to CommonValueMonitor that records the current state,
|
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
|
||||||
// and accumulates subsequent changes into a batch change. If the 'begin_reset' method returned an IDisposable,
|
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
|
||||||
// the batch change object could be examined when Dispose is called,
|
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
|
||||||
// to avoid firing the common_values_changed event if the accumulated changes cancel each other out.
|
|
||||||
|
public IDisposable defer_updates() =>
|
||||||
// However, such a change would be non-trivial, so this method will suffice for now.
|
_group_mon.defer_updates();
|
||||||
// It should perform fine in practice, even with a large number of selected orders,
|
|
||||||
// because the input is throttled.
|
public void Add(OrderSecurityAlgo order_tuple) =>
|
||||||
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() =>
|
_group_mon.add(order_tuple);
|
||||||
Observable.Create<ImmutableHashSet<CustomOrderDefinitionImmutable>>(o => {
|
|
||||||
var last_algos = ImmutableHashSet.Create(OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
|
public void Remove(OrderSecurityAlgo order_tuple) =>
|
||||||
return _algo_def_mon
|
_group_mon.remove(order_tuple);
|
||||||
.SubscribeThrottled(_throttle, (ask, vals) => vals, Array.Empty<CustomOrderDefinitionImmutable>())
|
|
||||||
.Subscribe(algos => {
|
public void Clear() =>
|
||||||
// only notify the observer if the algo set has actually changed.
|
_group_mon.clear();
|
||||||
if (algos.Count != last_algos.Count || !algos.All(last_algos.Contains))
|
|
||||||
o.OnNext(last_algos = ImmutableHashSet.CreateRange(OrderDataHelper.EqualsHelpers.custom_order_defi_comparer, algos));
|
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgo osam) =>
|
||||||
});
|
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||||
});
|
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||||
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
|
osam.security : null;
|
||||||
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
|
|
||||||
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
CustomOrderDefinitionImmutable _build_algo_tree(OrderSecurityAlgo root) =>
|
||||||
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
|
root.order.Def.AttachTargetOrderKey == 0 ?
|
||||||
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
|
root.order.Def.CustomDef :
|
||||||
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
|
new CustomOrderDefinitionImmutable(_add_attached_targets(root.order.Def.CustomDef.CloneToMutable(), root.order.Key, root.order.Def.AttachTargetOrderKey));
|
||||||
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
|
|
||||||
|
CustomOrderDefinition _add_attached_targets(CustomOrderDefinition mroot, uint root_id, uint attached_target) {
|
||||||
public IDisposable defer_updates() =>
|
var lin = _lineage(root_id, attached_target);
|
||||||
_group_mon.defer_updates();
|
if (lin != null && lin.Count > 0) {
|
||||||
|
var mparent = mroot;
|
||||||
public void Add(OrderSecurityAlgo order_tuple) =>
|
var pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
|
||||||
_group_mon.add(order_tuple);
|
|
||||||
|
|
||||||
public void Remove(OrderSecurityAlgo order_tuple) =>
|
|
||||||
_group_mon.remove(order_tuple);
|
|
||||||
|
|
||||||
public void Clear() =>
|
|
||||||
_group_mon.clear();
|
|
||||||
|
|
||||||
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgo osam) =>
|
|
||||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
|
||||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
|
||||||
osam.security : null;
|
|
||||||
|
|
||||||
CustomOrderDefinitionImmutable _build_algo_tree(OrderSecurityAlgo root) =>
|
|
||||||
root.order.Def.AttachTargetOrderKey == 0 ?
|
|
||||||
root.order.Def.CustomDef :
|
|
||||||
new CustomOrderDefinitionImmutable(_add_attached_targets(root.order.Def.CustomDef.CloneToMutable(), root.order.Key, root.order.Def.AttachTargetOrderKey));
|
|
||||||
|
|
||||||
CustomOrderDefinition _add_attached_targets(CustomOrderDefinition mroot, uint root_id, uint attached_target) {
|
|
||||||
var lin = _lineage(root_id, attached_target);
|
|
||||||
if (lin != null && lin.Count > 0) {
|
|
||||||
var mparent = mroot;
|
|
||||||
var pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
|
|
||||||
if (pcad.HasValue) {
|
if (pcad.HasValue) {
|
||||||
do {
|
do {
|
||||||
var mchild = lin.Pop().order.Def.CustomDef.CloneToMutable();
|
var mchild = lin.Pop().order.Def.CustomDef.CloneToMutable();
|
||||||
@@ -115,44 +92,44 @@ namespace mbts.spark {
|
|||||||
mparent = mchild;
|
mparent = mchild;
|
||||||
pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
|
pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
|
||||||
} while (lin.Count > 0 && pcad.HasValue);
|
} while (lin.Count > 0 && pcad.HasValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mroot;
|
return mroot;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stack<OrderSecurityAlgo> _lineage(uint root, uint target) {
|
Stack<OrderSecurityAlgo> _lineage(uint root, uint target) {
|
||||||
// check args/preconditions
|
// check args/preconditions
|
||||||
if (root == 0) throw new ArgumentException("Cannot be zero.", nameof(root));
|
if (root == 0) throw new ArgumentException("Cannot be zero.", nameof(root));
|
||||||
if (target == 0) throw new ArgumentException("Cannot be zero.", nameof(target));
|
if (target == 0) throw new ArgumentException("Cannot be zero.", nameof(target));
|
||||||
if (target == root) return null;
|
if (target == root) return null;
|
||||||
var inner = _lookup_order(target);
|
var inner = _lookup_order(target);
|
||||||
if (!inner.HasValue)
|
if (!inner.HasValue)
|
||||||
return null;
|
return null;
|
||||||
// if 'target' has an attach target iteself (nested), push the nested lineage onto the stack first,
|
// if 'target' has an attach target iteself (nested), push the nested lineage onto the stack first,
|
||||||
// then push the original target onto the stack, and try to hop up each algo-generation
|
// then push the original target onto the stack, and try to hop up each algo-generation
|
||||||
// via parent-key lookups until we reach the root.
|
// via parent-key lookups until we reach the root.
|
||||||
// **If the lineage is disjointed, and we never reach the root, return null**
|
// **If the lineage is disjointed, and we never reach the root, return null**
|
||||||
var ival = inner.Value;
|
var ival = inner.Value;
|
||||||
var lin_stack = ival.order.attached_target_order_key() != 0 ?
|
var lin_stack = ival.order.attached_target_order_key() != 0 ?
|
||||||
_lineage(ival.order.Key, ival.order.attached_target_order_key()) ?? new Stack<OrderSecurityAlgo>() :
|
_lineage(ival.order.Key, ival.order.attached_target_order_key()) ?? new Stack<OrderSecurityAlgo>() :
|
||||||
new Stack<OrderSecurityAlgo>();
|
new Stack<OrderSecurityAlgo>();
|
||||||
lin_stack.Push(ival);
|
lin_stack.Push(ival);
|
||||||
target = ival.order.ParentKey;
|
target = ival.order.ParentKey;
|
||||||
while (target != root) {
|
while (target != root) {
|
||||||
if (target == 0) {
|
if (target == 0) {
|
||||||
// **disjointed lineage**
|
// **disjointed lineage**
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
inner = _lookup_order(target);
|
inner = _lookup_order(target);
|
||||||
if (!inner.HasValue) {
|
if (!inner.HasValue) {
|
||||||
// **disjointed lineage**
|
// **disjointed lineage**
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ival = inner.Value;
|
ival = inner.Value;
|
||||||
lin_stack.Push(ival);
|
lin_stack.Push(ival);
|
||||||
target = ival.order.ParentKey;
|
target = ival.order.ParentKey;
|
||||||
}
|
}
|
||||||
return lin_stack;
|
return lin_stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using bts.api;
|
|||||||
using bts.api.core;
|
using bts.api.core;
|
||||||
using bts.api.orderman;
|
using bts.api.orderman;
|
||||||
using bts.collections;
|
using bts.collections;
|
||||||
|
using bts.spark;
|
||||||
using bts.utils;
|
using bts.utils;
|
||||||
using mbts.collections;
|
using mbts.collections;
|
||||||
using mbts.spark.utilities;
|
using mbts.spark.utilities;
|
||||||
@@ -13,7 +14,6 @@ using mbts.ui.utility;
|
|||||||
using mbts.utils;
|
using mbts.utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -62,7 +62,7 @@ namespace mbts.spark {
|
|||||||
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
|
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
|
||||||
readonly AlgoDefinitionVM the_anchor_algo_def_vm;
|
readonly AlgoDefinitionVM the_anchor_algo_def_vm;
|
||||||
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
|
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
|
||||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
|
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
|
||||||
IDisposable _algo_param_rows_hook;
|
IDisposable _algo_param_rows_hook;
|
||||||
int _favorite_algos_column_count;
|
int _favorite_algos_column_count;
|
||||||
bool _open_algo_popup_on_select, _use_algo_param_grid;
|
bool _open_algo_popup_on_select, _use_algo_param_grid;
|
||||||
@@ -239,15 +239,12 @@ namespace mbts.spark {
|
|||||||
|
|
||||||
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
|
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
|
||||||
Disposable.dispose(ref _algo_param_rows_hook);
|
Disposable.dispose(ref _algo_param_rows_hook);
|
||||||
if (old_value != null) {
|
if (old_value != null)
|
||||||
// Taha feels that we should always revert to a Limit order either when the user explicity unchecks a sticky button
|
// Taha feels that we should always revert to a Limit order either when the user explicity unchecks a sticky button
|
||||||
// or when a non-sticky button is pressed. Rather than completely remove the stack at this time (b/c it may be come
|
// or when a non-sticky button is pressed. Rather than completely remove the stack at this time (b/c it may be come
|
||||||
// back in the future), let's just always push a specfic def_vm.key onto it [Limit] ;)
|
// back in the future), let's just always push a specfic def_vm.key onto it [Limit] ;)
|
||||||
//_selected_algo_stack.Push(old_value.key);
|
//_selected_algo_stack.Push(old_value.key);
|
||||||
_selected_algo_stack.Push(the_anchor_algo_def_vm.key);
|
_selected_algo_stack.Push(the_anchor_algo_def_vm.key);
|
||||||
if (_favorite_algo_map.TryGetValue(old_value.key, out var tup) && tup.flags.HasFlag(LadderFavoriteAlgoFlags.AutoRevert))
|
|
||||||
old_value.algo_root.revert_all_params();
|
|
||||||
}
|
|
||||||
if (new_value != null) {
|
if (new_value != null) {
|
||||||
if (use_algo_param_grid)
|
if (use_algo_param_grid)
|
||||||
_algo_param_rows_hook = _connect_algo_param_rows(new_value, _algo_param_rows);
|
_algo_param_rows_hook = _connect_algo_param_rows(new_value, _algo_param_rows);
|
||||||
@@ -343,9 +340,6 @@ namespace mbts.spark {
|
|||||||
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
|
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
|
||||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||||
|
|
||||||
public IObservable<AlgoDefinitionVM> observe_algo_definition_vm(bool start_immediately = false) =>
|
|
||||||
_selected_algo_definition.observe_selected_item(start_immediately);
|
|
||||||
|
|
||||||
public OrderDefinition create(Side side) {
|
public OrderDefinition create(Side side) {
|
||||||
if (HasErrors)
|
if (HasErrors)
|
||||||
throw new InvalidOperationException("Cannot create order when errors are present.");
|
throw new InvalidOperationException("Cannot create order when errors are present.");
|
||||||
@@ -371,12 +365,12 @@ namespace mbts.spark {
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void on_favorite_algos(ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> favorite_algo_map, int favorite_algos_column_count) {
|
public void on_favorite_algos(LadderFavoriteAlgosSettings value) {
|
||||||
_favorite_algo_map = favorite_algo_map;
|
_favorite_algos_index = value.favorite_algos.Select((v, i) => (v, i)).ToDictionaryCrushed(t => t.v, t => t.i);
|
||||||
foreach (var d in algo_definitions) {
|
foreach (var d in algo_definitions) {
|
||||||
d.favorite_index = _get_favorite_algo_index(d.key);
|
d.favorite_index = _get_favorite_algo_index(d.key);
|
||||||
}
|
}
|
||||||
this.favorite_algos_column_count = favorite_algos_column_count;
|
favorite_algos_column_count = value.column_count;
|
||||||
RaisePropertyChanged(nameof(selected_favorite_algo_definition));
|
RaisePropertyChanged(nameof(selected_favorite_algo_definition));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +383,7 @@ namespace mbts.spark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int? _get_favorite_algo_index(AlgoKey key) =>
|
int? _get_favorite_algo_index(AlgoKey key) =>
|
||||||
key.mode == AlgoKey.Mode.Persisted && _favorite_algo_map.TryGetValue(key, out var tup) ? tup.sort_index : (int?)null;
|
key.mode == AlgoKey.Mode.Persisted && _favorite_algos_index.TryGetValue(key, out int i) ? i : (int?)null;
|
||||||
|
|
||||||
void _on_template_metadata(IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> meta) {
|
void _on_template_metadata(IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> meta) {
|
||||||
_algo_metadata = meta;
|
_algo_metadata = meta;
|
||||||
|
|||||||
@@ -129,13 +129,6 @@ namespace mbts.spark {
|
|||||||
public static IEnumerable<IAlgoParamVM> enumerate_all_params(this IAlgoVM vm) =>
|
public static IEnumerable<IAlgoParamVM> enumerate_all_params(this IAlgoVM vm) =>
|
||||||
vm == null ? Enumerable.Empty<IAlgoParamVM>() : vm.algo_params.Concat(vm.child_algo_selectors.SelectMany(s => enumerate_all_params(s.selected_algo)));
|
vm == null ? Enumerable.Empty<IAlgoParamVM>() : vm.algo_params.Concat(vm.child_algo_selectors.SelectMany(s => enumerate_all_params(s.selected_algo)));
|
||||||
|
|
||||||
public static void revert_all_params(this IAlgoVM vm) {
|
|
||||||
foreach (var p in vm.enumerate_all_params()) {
|
|
||||||
if (p.is_dirty)
|
|
||||||
p.revert();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IObservable<bool> subscribe_is_dirty(this IAlgoParamVM param_vm) =>
|
public static IObservable<bool> subscribe_is_dirty(this IAlgoParamVM param_vm) =>
|
||||||
Observable.Create<bool>(o => {
|
Observable.Create<bool>(o => {
|
||||||
o.OnNext(param_vm.is_dirty);
|
o.OnNext(param_vm.is_dirty);
|
||||||
|
|||||||
@@ -35,24 +35,11 @@ namespace mbts.spark {
|
|||||||
public static double price(this OrderRecordImmutable order) =>
|
public static double price(this OrderRecordImmutable order) =>
|
||||||
order.HasState ? order.State.Price : double.NaN;
|
order.HasState ? order.State.Price : double.NaN;
|
||||||
|
|
||||||
public static double ui_price(this OrderRecordImmutable order) {
|
|
||||||
if (order.HasState) {
|
|
||||||
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop && !order.stop_or_stop_limit_has_been_triggered()) {
|
|
||||||
return order.stop_price();
|
|
||||||
}
|
|
||||||
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit && !order.stop_or_stop_limit_has_been_triggered()) {
|
|
||||||
return order.stop_limit_price();
|
|
||||||
}
|
|
||||||
return order.State.Price;
|
|
||||||
}
|
|
||||||
return double.NaN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double stop_price(this OrderRecordImmutable order) =>
|
public static double stop_price(this OrderRecordImmutable order) =>
|
||||||
order.HasState && order.State.HasStop && order.State.Stop.HasNativeState ? order.State.Stop.NativeState.StopPx : double.NaN;
|
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasStop ? order.Def.CustomDef.Stop.StopPrice : double.NaN;
|
||||||
|
|
||||||
public static double stop_limit_price(this OrderRecordImmutable order) =>
|
public static double stop_limit_price(this OrderRecordImmutable order) =>
|
||||||
order.HasState && order.State.HasStopLimit && order.State.StopLimit.HasNativeState ? order.State.StopLimit.NativeState.StopPx : double.NaN;
|
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasStopLimit ? order.Def.CustomDef.StopLimit.StopPrice : double.NaN;
|
||||||
|
|
||||||
public static double avg_price(this OrderRecordImmutable order) =>
|
public static double avg_price(this OrderRecordImmutable order) =>
|
||||||
order.HasState ? order.State.AvgPrice : double.NaN;
|
order.HasState ? order.State.AvgPrice : double.NaN;
|
||||||
@@ -261,20 +248,6 @@ namespace mbts.spark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool stop_or_stop_limit_has_been_triggered(this OrderRecordImmutable order) {
|
|
||||||
if (order.HasState && (order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit)) {
|
|
||||||
var native_state = order.State.native_order_state();
|
|
||||||
if (native_state.HasValue) {
|
|
||||||
if (order.State.HasStop || order.State.HasStopLimit) {
|
|
||||||
return native_state.Value.NativeType == NativeOrderType.Limit;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string type_id(this CustomOrderDefinition.CustomOneofCase order_type) =>
|
public static string type_id(this CustomOrderDefinition.CustomOneofCase order_type) =>
|
||||||
order_type.ToString().ToUpper();
|
order_type.ToString().ToUpper();
|
||||||
|
|
||||||
|
|||||||
@@ -154,9 +154,7 @@ namespace mbts.spark {
|
|||||||
.GroupTable(OrderSecurityHelper.key_fn, tup => tup.order.Def.SecurityKey)
|
.GroupTable(OrderSecurityHelper.key_fn, tup => tup.order.Def.SecurityKey)
|
||||||
.Cache();
|
.Cache();
|
||||||
// working account orders
|
// working account orders
|
||||||
var working_account_order_groups = working_orders
|
var working_account_order_groups = working_orders.GroupTable(t => t.order.Key, t => new PNLKey(t.order.account_id(), t.order.security_key()));
|
||||||
.Filter(o => o.order.is_exchange_order())
|
|
||||||
.GroupTable(t => t.order.Key, t => new PNLKey(t.order.account_id(), t.order.security_key()));
|
|
||||||
// security account pnl
|
// security account pnl
|
||||||
security_account_pnl = on_pnl
|
security_account_pnl = on_pnl
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -351,21 +349,6 @@ namespace mbts.spark {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CustomOrderDefinitionImmutable? _create_custom_def_for_stop_and_stop_limit(OrderRecordImmutable order, ref double stop_price) {
|
|
||||||
CustomOrderDefinition modified_custom_def = null;
|
|
||||||
var ord_type = order.order_type();
|
|
||||||
if (ord_type == CustomOrderDefinition.CustomOneofCase.Stop) {
|
|
||||||
modified_custom_def = order.Def.CustomDef.CloneToMutable();
|
|
||||||
modified_custom_def.Stop.StopPrice = stop_price;
|
|
||||||
} else if (ord_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
|
|
||||||
modified_custom_def = order.Def.CustomDef.CloneToMutable();
|
|
||||||
double temp_price = order.price();
|
|
||||||
modified_custom_def.StopLimit.StopPrice = stop_price;
|
|
||||||
stop_price = temp_price;
|
|
||||||
}
|
|
||||||
return modified_custom_def;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> update_order(OrderRecordImmutable order, double? new_price = null, uint? new_size = null, CustomOrderDefinitionImmutable? new_algo = null) {
|
public async Task<bool> update_order(OrderRecordImmutable order, double? new_price = null, uint? new_size = null, CustomOrderDefinitionImmutable? new_algo = null) {
|
||||||
if (order.working_quantity() <= 0) {
|
if (order.working_quantity() <= 0) {
|
||||||
// order already filled to completion
|
// order already filled to completion
|
||||||
@@ -373,16 +356,10 @@ namespace mbts.spark {
|
|||||||
}
|
}
|
||||||
var size = new_size ?? order.quantity();
|
var size = new_size ?? order.quantity();
|
||||||
var price = new_price ?? order.price();
|
var price = new_price ?? order.price();
|
||||||
if (!(order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit)
|
if (size == order.quantity() && price == order.price() && !new_algo.HasValue) {
|
||||||
&& size == order.quantity() && price == order.price() && !new_algo.HasValue) {
|
|
||||||
// no parameters would be changed by this update
|
// no parameters would be changed by this update
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit) &&
|
|
||||||
!order.stop_or_stop_limit_has_been_triggered() && !new_algo.HasValue) {
|
|
||||||
// note the ref price parameter, if this is a stop limit then we swap the fields
|
|
||||||
new_algo = _create_custom_def_for_stop_and_stop_limit(order, ref price);
|
|
||||||
}
|
|
||||||
_activity.update_order(order, price, size, new_algo.HasValue);
|
_activity.update_order(order, price, size, new_algo.HasValue);
|
||||||
var result = await _service.update_order_async(new Update() {
|
var result = await _service.update_order_async(new Update() {
|
||||||
OrderKey = order.Key,
|
OrderKey = order.Key,
|
||||||
|
|||||||
@@ -19,8 +19,7 @@
|
|||||||
<LangVersion>7.2</LangVersion>
|
<LangVersion>7.2</LangVersion>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<WarningsAsErrors />
|
<WarningsAsErrors />
|
||||||
<Version>$(major).$(minor).$(patch)</Version>
|
<Version>1.0.0</Version>
|
||||||
<Version Condition="'$(version)'=='..'">1.0.0</Version>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../spark_api/spark_api.csproj" />
|
<ProjectReference Include="../spark_api/spark_api.csproj" />
|
||||||
|
|||||||
@@ -525,11 +525,8 @@
|
|||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<control:SparkMenuItem
|
<control:SparkMenuItem
|
||||||
Command="{Binding command}"
|
Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
|
||||||
Style="{StaticResource SparkMenuButton}"
|
|
||||||
Icon="{Binding icon}"
|
|
||||||
Collapsed="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type control:SparkMenu}}, Path=ShowText, Converter={StaticResource NotConverter}}"
|
Collapsed="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type control:SparkMenu}}, Path=ShowText, Converter={StaticResource NotConverter}}"
|
||||||
ContentFlowDirection="{Binding Orientation, RelativeSource={RelativeSource AncestorType=control:SparkMenu}, Converter={x:Static control:SparkMenu.ItemContentFlowDirectionConverter}}"
|
|
||||||
>
|
>
|
||||||
<control:SparkMenuItem.Content>
|
<control:SparkMenuItem.Content>
|
||||||
<MultiBinding Converter="{x:Static ut:CommandHelper.DescriptionConverter}">
|
<MultiBinding Converter="{x:Static ut:CommandHelper.DescriptionConverter}">
|
||||||
@@ -549,11 +546,7 @@
|
|||||||
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
|
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"
|
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"
|
||||||
>
|
>
|
||||||
<StackPanel
|
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
IsItemsHost="True"
|
|
||||||
Orientation="{Binding Orientation, RelativeSource={RelativeSource TemplatedParent}}"
|
|
||||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
|
||||||
/>
|
|
||||||
</Border>
|
</Border>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
@@ -566,4 +559,4 @@
|
|||||||
<Setter Property="Command" Value="{Binding command}"/>
|
<Setter Property="Command" Value="{Binding command}"/>
|
||||||
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
|
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
|
||||||
</Style>
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
@@ -1,15 +1,10 @@
|
|||||||
using bts.api;
|
using bts.api;
|
||||||
using mbts;
|
using mbts;
|
||||||
using mbts.ui;
|
using mbts.ui;
|
||||||
using mbts.ui.utility;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using static mbts.spark.SparkTags;
|
using static mbts.spark.SparkTags;
|
||||||
@@ -65,7 +60,6 @@ namespace bts.spark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public ObservableCollection<SparkCommand> child_items { get; } = new ObservableCollection<SparkCommand>();
|
|
||||||
|
|
||||||
public abstract void execute(IBus bus);
|
public abstract void execute(IBus bus);
|
||||||
public virtual bool canExecute() => true;
|
public virtual bool canExecute() => true;
|
||||||
@@ -334,8 +328,6 @@ namespace bts.spark {
|
|||||||
public class SparkCommandVM : mbts.ui.shell.ViewModelBase, ICommand {
|
public class SparkCommandVM : mbts.ui.shell.ViewModelBase, ICommand {
|
||||||
readonly SparkCommand _cmd;
|
readonly SparkCommand _cmd;
|
||||||
readonly IBus _bus;
|
readonly IBus _bus;
|
||||||
readonly ISelectable _selectable_cmd;
|
|
||||||
object _first_child_group;
|
|
||||||
|
|
||||||
public SparkCommandVM(SparkCommand cmd, IBus bus) {
|
public SparkCommandVM(SparkCommand cmd, IBus bus) {
|
||||||
_cmd = cmd;
|
_cmd = cmd;
|
||||||
@@ -344,20 +336,6 @@ namespace bts.spark {
|
|||||||
cmd.group_changed += () => RaisePropertyChanged(nameof(group));
|
cmd.group_changed += () => RaisePropertyChanged(nameof(group));
|
||||||
cmd.show_in_toolbar_changed += () => RaisePropertyChanged(nameof(show_in_toolbar));
|
cmd.show_in_toolbar_changed += () => RaisePropertyChanged(nameof(show_in_toolbar));
|
||||||
cmd.show_in_context_menu_changed += () => RaisePropertyChanged(nameof(show_in_context_menu));
|
cmd.show_in_context_menu_changed += () => RaisePropertyChanged(nameof(show_in_context_menu));
|
||||||
if (cmd.child_items != null) {
|
|
||||||
ObservableAutoWrapper.connect(cmd.child_items, child_items, item => new SparkCommandVM(item, bus));
|
|
||||||
child_items_view = new ListCollectionView(child_items);
|
|
||||||
child_items_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(group)));
|
|
||||||
if (child_items_view.Groups is INotifyCollectionChanged child_groups_ncc) {
|
|
||||||
child_groups_ncc.Subscribe((s, e) => refresh_first_child_group());
|
|
||||||
refresh_first_child_group();
|
|
||||||
void refresh_first_child_group() =>
|
|
||||||
first_child_group = (child_items_view.Groups.FirstOrDefault() as CollectionViewGroup)?.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_selectable_cmd = cmd as ISelectable;
|
|
||||||
if (_selectable_cmd != null)
|
|
||||||
_selectable_cmd.is_selected_changed += _ => RaisePropertyChanged(nameof(is_selected));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string title => _cmd.title;
|
public string title => _cmd.title;
|
||||||
@@ -366,22 +344,7 @@ namespace bts.spark {
|
|||||||
public ImageSource icon => _cmd.icon;
|
public ImageSource icon => _cmd.icon;
|
||||||
public bool show_in_toolbar => _cmd.show_in_toolbar;
|
public bool show_in_toolbar => _cmd.show_in_toolbar;
|
||||||
public bool show_in_context_menu => _cmd.show_in_context_menu;
|
public bool show_in_context_menu => _cmd.show_in_context_menu;
|
||||||
public bool is_selectable => _selectable_cmd != null;
|
|
||||||
public bool is_selected {
|
|
||||||
get => _selectable_cmd?.is_selected ?? false;
|
|
||||||
set {
|
|
||||||
if (_selectable_cmd != null)
|
|
||||||
_selectable_cmd.is_selected = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public ICommand command => this;
|
public ICommand command => this;
|
||||||
public ObservableCollection<SparkCommandVM> child_items { get; } = new ObservableCollection<SparkCommandVM>();
|
|
||||||
public ListCollectionView child_items_view { get; }
|
|
||||||
public object first_child_group {
|
|
||||||
get => _first_child_group;
|
|
||||||
set => Set(ref _first_child_group, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
event EventHandler ICommand.CanExecuteChanged { add { _cmd.CanExecuteChanged += value; } remove { _cmd.CanExecuteChanged -= value; } }
|
event EventHandler ICommand.CanExecuteChanged { add { _cmd.CanExecuteChanged += value; } remove { _cmd.CanExecuteChanged -= value; } }
|
||||||
|
|
||||||
bool ICommand.CanExecute(object parameter) => _cmd.canExecute();
|
bool ICommand.CanExecute(object parameter) => _cmd.canExecute();
|
||||||
|
|||||||
@@ -57,9 +57,6 @@ namespace bts.spark {
|
|||||||
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowPickOne, ShowLadder());
|
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowPickOne, ShowLadder());
|
||||||
|
|
||||||
// Order Ticket
|
// Order Ticket
|
||||||
public static PartialCommand<IImmutableSet<uint>> ShowOrderTicketAutoLookup(KeyGesture key_gesture = null) =>
|
|
||||||
SparkCommand.create("Order Ticket", Icons.AddOrders, SparkTags.ShowOrderTicketAutoLookupTag, key_gesture);
|
|
||||||
|
|
||||||
public static PartialCommand<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicket(KeyGesture key_gesture = null) =>
|
public static PartialCommand<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicket(KeyGesture key_gesture = null) =>
|
||||||
SparkCommand.create("Order Ticket", Icons.AddOrders, SparkTags.ShowOrderTicketTag, key_gesture);
|
SparkCommand.create("Order Ticket", Icons.AddOrders, SparkTags.ShowOrderTicketTag, key_gesture);
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ namespace mbts.spark {
|
|||||||
public static ITag<(uint key, double price, uint size)> UpdateOrder { get; } = new SparkTag<(uint, double, uint)>();
|
public static ITag<(uint key, double price, uint size)> UpdateOrder { get; } = new SparkTag<(uint, double, uint)>();
|
||||||
|
|
||||||
public static ITag<uint> ShowModifyOrderTag { get; } = new SparkTag<uint>(nameof(ShowModifyOrderTag));
|
public static ITag<uint> ShowModifyOrderTag { get; } = new SparkTag<uint>(nameof(ShowModifyOrderTag));
|
||||||
public static ITag<IImmutableSet<uint>> ShowOrderTicketAutoLookupTag { get; } = new SparkTag<IImmutableSet<uint>>(nameof(ShowOrderTicketAutoLookupTag));
|
|
||||||
public static ITag<SecurityMD> ShowOrderTicketFromSecurityTag = new SparkTag<SecurityMD>(nameof(ShowOrderTicketFromSecurityTag));
|
public static ITag<SecurityMD> ShowOrderTicketFromSecurityTag = new SparkTag<SecurityMD>(nameof(ShowOrderTicketFromSecurityTag));
|
||||||
public static ITag<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicketTag = new SparkTag<(OrderDefinitionImmutable def, double price, uint size)>(nameof(ShowOrderTicketTag));
|
public static ITag<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicketTag = new SparkTag<(OrderDefinitionImmutable def, double price, uint size)>(nameof(ShowOrderTicketTag));
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ namespace mbts.ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class DelegateCommand : DelegateCommand<object> {
|
public class DelegateCommand : DelegateCommand<object> {
|
||||||
public static DelegateCommand empty => new DelegateCommand(() => { });
|
|
||||||
|
|
||||||
public DelegateCommand(Action execute) : this(_ => execute()) { }
|
public DelegateCommand(Action execute) : this(_ => execute()) { }
|
||||||
public DelegateCommand(Action execute, Func<bool> can_execute) : this(_ => execute(), _ => can_execute()) { }
|
public DelegateCommand(Action execute, Func<bool> can_execute) : this(_ => execute(), _ => can_execute()) { }
|
||||||
public DelegateCommand(Action<object> execute) : base(execute) { }
|
public DelegateCommand(Action<object> execute) : base(execute) { }
|
||||||
|
|||||||
@@ -65,10 +65,8 @@ namespace mbts.ui {
|
|||||||
var cmd = new AsyncCommand(() => execute(param), () => can_execute, allow_concurrent_executions);
|
var cmd = new AsyncCommand(() => execute(param), () => can_execute, allow_concurrent_executions);
|
||||||
observer = new AnonymousObserver<T>(p => {
|
observer = new AnonymousObserver<T>(p => {
|
||||||
param = p;
|
param = p;
|
||||||
if (can_execute != get_can_execute(param)) {
|
if (can_execute != get_can_execute(param))
|
||||||
can_execute = !can_execute;
|
|
||||||
cmd.RaiseCanExecuteChanged();
|
cmd.RaiseCanExecuteChanged();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace bts.utils.diagnostics {
|
|
||||||
class OpTimer : IDisposable {
|
|
||||||
readonly Stopwatch _stopwatch = new Stopwatch();
|
|
||||||
|
|
||||||
public TimeSpan elapsed => _stopwatch.Elapsed;
|
|
||||||
|
|
||||||
public static OpTimer Hot() {
|
|
||||||
var ot = new OpTimer();
|
|
||||||
ot.Start();
|
|
||||||
return ot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start() =>
|
|
||||||
_stopwatch.Start();
|
|
||||||
|
|
||||||
public void Dispose() =>
|
|
||||||
_stopwatch.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using bts.api.core;
|
|
||||||
using bts.api.orderman;
|
|
||||||
using mbts;
|
|
||||||
using mbts.spark;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
|
|
||||||
namespace bts.utils.diagnostics {
|
|
||||||
static class TestDataFactory {
|
|
||||||
#if DEBUG
|
|
||||||
public static IObservable<TableUpdate<OrderSecurity, uint>> OrdersWithRandomPIQ(IObservable<TableUpdate<OrderSecurity, uint>> orders, TimeSpan throttle) =>
|
|
||||||
Observable.Create<TableUpdate<OrderSecurity, uint>>(o => {
|
|
||||||
TableUpdate<OrderSecurity, uint> last_orders = null;
|
|
||||||
var output = TableUpdate<OrderSecurity, uint>.mk_empty(p => p.order.Key);
|
|
||||||
var piq_offsets = Enumerable.Range(1, 10).Select(i => (uint)i).ToArray();
|
|
||||||
var rand = new Random();
|
|
||||||
return Disposable.Create(
|
|
||||||
orders.Subscribe(new_orders => {
|
|
||||||
last_orders = new_orders;
|
|
||||||
notify();
|
|
||||||
}),
|
|
||||||
Observable.Interval(throttle).Subscribe(_ => notify()));
|
|
||||||
void notify() {
|
|
||||||
var bd = last_orders.to_builder();
|
|
||||||
foreach (var os in last_orders.state.Values) {
|
|
||||||
if (os.order.is_active()) {
|
|
||||||
var rpiq = os.piq ?? 0;
|
|
||||||
rpiq += piq_offsets[rand.Next(piq_offsets.Length)];
|
|
||||||
bd.add_or_update(os.order.Key, new OrderSecurity(os.security, os.order, rpiq, os.algo, os.meta));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
last_orders = bd.to_table();
|
|
||||||
if (last_orders.has_changes())
|
|
||||||
o.OnNext(last_orders);
|
|
||||||
|
|
||||||
}
|
|
||||||
}).Cache();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loaded 100 of 260 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user