Compare commits
215
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e998edc20f | ||
|
|
1c1be2b334 | ||
|
|
5d89257643 | ||
|
|
897e134714 | ||
|
|
e6cb97028b | ||
|
|
6c0dc80d43 | ||
|
|
ce3a4562d9 | ||
|
|
217f0cd4cc | ||
|
|
1e649ccce6 | ||
|
|
cbc69ec5fd | ||
|
|
0d1eedc34a | ||
|
|
4e85bf4520 | ||
|
|
5d0023a80b | ||
|
|
e300de2425 | ||
|
|
569877752e | ||
|
|
4846cf7693 | ||
|
|
16a46955f4 | ||
|
|
13a6d68b60 | ||
|
|
1affd992de | ||
|
|
d951258891 | ||
|
|
f5dfb0264b | ||
|
|
3b8f5c5093 | ||
|
|
253d213818 | ||
|
|
f89cb3d61e | ||
|
|
eb6573399b | ||
|
|
8ba8ce76ce | ||
|
|
e65b8e9d4d | ||
|
|
2fc379fbef | ||
|
|
fb96a75a8e | ||
|
|
240d15af47 | ||
|
|
2530e43797 | ||
|
|
b05d85d6c3 | ||
|
|
52f2e77310 | ||
|
|
bd00c6de3e | ||
|
|
87aea4812f | ||
|
|
21ae3b14c8 | ||
|
|
708ae185f1 | ||
|
|
5c6dbd7bd1 | ||
|
|
f19a280b7f | ||
|
|
47cf9e2fe6 | ||
|
|
df75518389 | ||
|
|
90c371c72f | ||
|
|
9609f7e6cf | ||
|
|
30c83b7c72 | ||
|
|
382d8b468c | ||
|
|
cbe6d07a86 | ||
|
|
6d67700ee6 | ||
|
|
e9cf1db184 | ||
|
|
e4c98d947e | ||
|
|
ab9de97d6e | ||
|
|
4e425a3380 | ||
|
|
ac67c84ce7 | ||
|
|
79120ea7a5 | ||
|
|
a85a7f624a | ||
|
|
19e4da9936 | ||
|
|
1494c37cc7 | ||
|
|
af1fbaf3ed | ||
|
|
82a93e39f8 | ||
|
|
36684f8ee5 | ||
|
|
c8cd2ae1ad | ||
|
|
93afb5b602 | ||
|
|
429b0bb8f8 | ||
|
|
80ce5447e5 | ||
|
|
609b0dceb1 | ||
|
|
ea47042816 | ||
|
|
4b23e17534 | ||
|
|
f69301611a | ||
|
|
c04ac5cd48 | ||
|
|
aa39349fc4 | ||
|
|
d1c915fef3 | ||
|
|
69bdf07423 | ||
|
|
4115a6c790 | ||
|
|
8f8aae57de | ||
|
|
362f75c8b2 | ||
|
|
39e855e691 | ||
|
|
73303ccd33 | ||
|
|
6826494898 | ||
|
|
69fcfbd6ce | ||
|
|
c1d62a2ff6 | ||
|
|
1fa419eae2 | ||
|
|
e3a3eb6d85 | ||
|
|
18a32f9a05 | ||
|
|
b7c127b2d8 | ||
|
|
f83f94fb07 | ||
|
|
23f76a1c80 | ||
|
|
cc0f6ea448 | ||
|
|
cbb6936553 | ||
|
|
633433cab9 | ||
|
|
3ab7ed0b71 | ||
|
|
fc1adf8c28 | ||
|
|
79cf313b5b | ||
|
|
00834a668c | ||
|
|
32c9c23d7e | ||
|
|
5ac8d2ada6 | ||
|
|
54eb6ceaa9 | ||
|
|
540db91311 | ||
|
|
1ee2193c88 | ||
|
|
84877af947 | ||
|
|
388cf92a70 | ||
|
|
840441a749 | ||
|
|
c39a510d09 | ||
|
|
299d46aa2c | ||
|
|
345f6ed9fe | ||
|
|
31aa9d9bf6 | ||
|
|
0c063a3876 | ||
|
|
af253198d2 | ||
|
|
cc381d605c | ||
|
|
c2f954a79d | ||
|
|
e79a73147a | ||
|
|
6a921ae900 | ||
|
|
0f13d27efe | ||
|
|
d52347cd71 | ||
|
|
0ab628fb6c | ||
|
|
1a8a492ef7 | ||
|
|
74066c0ffe | ||
|
|
0269d5fa86 | ||
|
|
84a5a11a9c | ||
|
|
a01e6df7fa | ||
|
|
d4caccdbfb | ||
|
|
4ede8a62ea | ||
|
|
ba11248ac9 | ||
|
|
22cde8aa0d | ||
|
|
3dba49e899 | ||
|
|
23d7a9e1ad | ||
|
|
7bd9d3f4ad | ||
|
|
7740f64bfd | ||
|
|
7ccbb69f5c | ||
|
|
b4ba527c12 | ||
|
|
a62a41a0aa | ||
|
|
904023c5fa | ||
|
|
a9fc3d43a0 | ||
|
|
4febc07aa0 | ||
|
|
198e76f722 | ||
|
|
836301e75f | ||
|
|
ad37a652c5 | ||
|
|
5bed54c953 | ||
|
|
5675c4e117 | ||
|
|
16f1e568a5 | ||
|
|
e375274cbe | ||
|
|
cf3ef9323a | ||
|
|
7e2e2bf1b0 | ||
|
|
781c9dcc86 | ||
|
|
a8a922160d | ||
|
|
367bd0e3b9 | ||
|
|
4215d96ba9 | ||
|
|
50d18eb10d | ||
|
|
b65fedc781 | ||
|
|
88c8663d29 | ||
|
|
8c7dd2460d | ||
|
|
f20ee21ce7 | ||
|
|
a61e21bd87 | ||
|
|
39014696f3 | ||
|
|
af278d2d87 | ||
|
|
636e170f44 | ||
|
|
3d4fe8f832 | ||
|
|
94285918bf | ||
|
|
2ba7f949cc | ||
|
|
fc82ccf221 | ||
|
|
e71b1518f0 | ||
|
|
7738da7d16 | ||
|
|
c91eedcca0 | ||
|
|
4f5f4c61f3 | ||
|
|
2f31fb6284 | ||
|
|
d18caa7ba1 | ||
|
|
ac88586c8f | ||
|
|
968dfbac2a | ||
|
|
0501418da6 | ||
|
|
4efd853c96 | ||
|
|
a384eb9f73 | ||
|
|
4747fe5535 | ||
|
|
94eb1e37cb | ||
|
|
fd1ae60e49 | ||
|
|
62d06180ea | ||
|
|
dba3b9371c | ||
|
|
aadfe2a677 | ||
|
|
c0c34efc47 | ||
|
|
b66fb02174 | ||
|
|
3cf026ff47 | ||
|
|
8619084fa7 | ||
|
|
f6df644ab1 | ||
|
|
1183dee418 | ||
|
|
ffaba90c96 | ||
|
|
9eb760b3c7 | ||
|
|
70fcb30c67 | ||
|
|
553ab7b858 | ||
|
|
8dcf41e9cf | ||
|
|
a0bf0e93f4 | ||
|
|
5529e80e17 | ||
|
|
5df7cec55a | ||
|
|
8f705d42b3 | ||
|
|
fc2f85b842 | ||
|
|
ffe5eee584 | ||
|
|
b7897fc7ac | ||
|
|
b4d18a62c9 | ||
|
|
c6b450f45c | ||
|
|
6a46049610 | ||
|
|
21712711f7 | ||
|
|
9f1bb177b8 | ||
|
|
dcd2cc22e1 | ||
|
|
af91ee1512 | ||
|
|
b4d63aa3cc | ||
|
|
d0e32911ad | ||
|
|
75fa582774 | ||
|
|
69155162ca | ||
|
|
2c865c123a | ||
|
|
0ec955cc90 | ||
|
|
0c4e22537d | ||
|
|
601eb4ece1 | ||
|
|
b84a638199 | ||
|
|
34350fd469 | ||
|
|
a469da5cc6 | ||
|
|
8be8a67f18 | ||
|
|
abc2e55610 | ||
|
|
cfd15d3371 | ||
|
|
44369bae43 |
No files matched your search
Vendored
+4
-10
@@ -75,7 +75,6 @@
|
||||
"name": "SparkVM Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": ["run"],
|
||||
"stopAtEntry": false,
|
||||
@@ -97,12 +96,7 @@
|
||||
"description": "Skip STL",
|
||||
"text": "skip -gfi /usr/include/c++/7/bits/*.h",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "LD LIBRARY",
|
||||
"text": "set env LD_LIBRARY_PATH=.",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
@@ -214,10 +208,10 @@
|
||||
"name": "MD Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": ["test", "[mbo]"],
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": ["test", "[md]"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
|
||||
@@ -55,8 +55,6 @@ else ()
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${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 ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/order_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMENT "Creating server runtime layout"
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
+++
|
||||
title = "BTS Basic Algos"
|
||||
weight = 10
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
|
||||
|
||||
## Lean
|
||||
|
||||
The goal of the lean order is to watch the opposite-inside market size and cross when that size goes below the threshold (this also implies a cross if that size goes to zero and the market trades away from you) If the market trades toward you, you can opt into several different behaviors
|
||||
|
||||
##### Parameters
|
||||
* Cross Thresh - size thresh in absolute or ratio terms
|
||||
* Max Chase Ticks - max levels to pay up, in ticks
|
||||
|
||||
|
||||
## Join
|
||||
The goal of the join order is to watch the opposite-inside market and join when the market flips (that is, when the best offer becomes the best bid) and has at least threshold size.
|
||||
|
||||
##### Parameters
|
||||
* Join Thresh - size thresh in absolute or ratio terms
|
||||
|
||||
|
||||
## Top
|
||||
|
||||
The goal of the top order is to not trade by yourself, but the intent at the time of order entry is to get filled.If the order is on the market (say current bid price) AND the quantity on that bid price is below top thresh then cancel the order else remain working.
|
||||
|
||||
##### Parameters
|
||||
* Top Thresh - size thresh in absolute or ratio terms
|
||||
|
||||
|
||||
## Cover
|
||||
A Cover order’s purpose is to fire a hedge order (of any type) in response to fills from it’s child order on the opposite side of the market N ticks away
|
||||
|
||||
##### Parameters
|
||||
* Profit Ticks - ticks above or below fill price to place profit / cover order
|
||||
|
||||
|
||||
## OCO
|
||||
An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill.
|
||||
|
||||
Composing this order with Cover simulates scalping functionality
|
||||
|
||||
##### Parameters
|
||||
* Stop Ticks - ticks below or above main order to place stop / other order
|
||||
@@ -5,7 +5,6 @@ sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
|
||||
{{ youtube(id="RZU9Wj0cETo") }}
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/XEZpuPAgMC0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
@@ -406,8 +406,6 @@ insert_anchor_links = "right"
|
||||
|
||||
## Algo Studio
|
||||
|
||||
{{ youtube(id="DHIa5tnifKc") }}
|
||||
|
||||
- The BTS Algo Studio was built for users to experience algo composability
|
||||
never seen before in a professional futures trading system.
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title = "Client"
|
||||
weight = 1
|
||||
sort_by = "weight"
|
||||
redirect_to = "bts_spark_sdk/Client/overview/"
|
||||
+++
|
||||
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title = "Server"
|
||||
weight = 2
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title = "BTS Spark SDK"
|
||||
weight = 2
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
@@ -4,43 +4,6 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 1.6.0
|
||||
### User Visible Changes
|
||||
#### New Features
|
||||
- You can now view your order's position in queue in the ladder
|
||||
#### Bug Fixes
|
||||
- Fix a bug in the order popup in the ladder which could lead to a crash.
|
||||
|
||||
## 1.5.9
|
||||
### User Visible Changes
|
||||
- Fix for stunnel connections on Windows 7
|
||||
|
||||
## 1.5.8
|
||||
### User Visible Changes
|
||||
- Fixed a bug where it was possible to crash the UI by entering an order through the order ticket if Enter was used to dismiss the dialog.
|
||||
- Fixed a spreader reporting bug which could cause the server to crash if an algo was used to control certain types of synthetics
|
||||
- Improved user message when the UI fails to login
|
||||
- Integrated stunnel into UI to make VPN setup unnecessary in unsecured environments.
|
||||
## 1.5.7
|
||||
### User Visible Changes
|
||||
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/index.md#algo-studio) and [Spreader](./bts_spark/Spreader/index.md)
|
||||
- Fixed a bug that occasionally would cause windows to be position outside the visible screen on startup
|
||||
- Fixed a bug in the order book form that would occasionally cause the Recreate Order option to be unavailable
|
||||
- Added a confirmation step to Cancel All, Cancel Buys and Cancel Sells commands
|
||||
- Fixed Aggressor coloring and ladder high/low coloring in the SIM execution gateway
|
||||
- Added initial support for ICE market data
|
||||
- In an effort to streamline the PnL report we've made the following changes:
|
||||
- The PnL report will by default filter for Futures contracts only
|
||||
- If you wish to see Spread contracts again you may change Security Type column filter
|
||||
- The PnL report will now only show rows in which there is a position (net and/or day)
|
||||
- The Exchange, Start PnL, Total Net Pos, and Total Net PnL columns are now hidden by default
|
||||
- These columns can be restored by toggling their visibility within the PnL's Settings form
|
||||
- A new Traded Today column has been added to the PnL
|
||||
- Filtering on this column will allow a user to restrict the report to only show today's position
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
- Added logging to help troubleshoot shell exit hang
|
||||
|
||||
## 1.5.6
|
||||
### User Visible Changes
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
|
||||
@@ -54,8 +54,3 @@ img {
|
||||
background: #ccc;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:560px;
|
||||
height:315px;
|
||||
}
|
||||
+1
-1
Submodule externals updated: 67a79263ba...d0e8645b5e.
@@ -1,6 +1,3 @@
|
||||
1.5.7
|
||||
- Fixed Aggressor coloring and ladder high/low coloring in SIM
|
||||
|
||||
1.4.2
|
||||
- Be sure the system has a bts user before deployment. bts is the superuser, he has all permissions and can see all market data
|
||||
- After deployment re-permission all existing users for trading, riskadmin, and/or md admin permissions as appropriate
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
@@ -68,77 +66,18 @@ namespace mbts.ui.shell
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
Log.Gen.info("Application.OnExit");
|
||||
if (_runtime != null) _runtime.Dispose();
|
||||
utils.BTSNativeMethods.finit_spark_lib();
|
||||
base.OnExit(e);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
void start_stunnel(string path, uint job_error)
|
||||
{
|
||||
string config_error;
|
||||
if (!mbts.config.Configuration.try_initialize(out config_error))
|
||||
{
|
||||
_stunnel_process = Process.Start(path + "bin\\stunnel.exe");
|
||||
if (_stunnel_process != null)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (mbts.config.Configuration.try_initialize(out config_error))
|
||||
break;
|
||||
System.Threading.Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
else
|
||||
stunnel_job_error = "Stunnel Job Error code: " + job_error +
|
||||
" could not start stunnel " + (config_error ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
void kill_stunnel()
|
||||
{
|
||||
try
|
||||
{
|
||||
Process[] ps = Process.GetProcessesByName("stunnel");
|
||||
ps.ForEach(p =>
|
||||
{
|
||||
try { p.Kill(); }
|
||||
catch {}
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
Process _stunnel_process;
|
||||
static internal string stunnel_job_error { get; private set;}
|
||||
void init_stunnel(string[] argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
|
||||
if (File.Exists(path + "config\\stunnel.conf"))
|
||||
{
|
||||
uint job_error = BTSUINativeMethods.setup_process_job_object();
|
||||
//if we can't put this process and stunnel into a job, try to stop stunnel then restart
|
||||
if(job_error != 0)
|
||||
kill_stunnel();
|
||||
start_stunnel(path, job_error);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
stunnel_job_error = "Error starting stunnel: " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
public new int Run() {
|
||||
var argv = Environment.GetCommandLineArgs();
|
||||
try {
|
||||
if (argv.Contains("-multiple") || _mutex == null || _mutex.WaitOne(TimeSpan.Zero, true))
|
||||
{
|
||||
if(_mutex == null || _mutex.WaitOne(TimeSpan.Zero, true))
|
||||
init_stunnel(argv);
|
||||
try {
|
||||
if (argv.Contains("-multiple") ||
|
||||
_mutex == null ||
|
||||
_mutex.WaitOne(TimeSpan.Zero, true)) {
|
||||
return base.Run();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -6,7 +6,6 @@ using bts.api.persistence;
|
||||
using bts.api.position;
|
||||
using bts.api.synthetic;
|
||||
using bts.co2;
|
||||
using bts.md;
|
||||
using bts.metrics;
|
||||
using bts.support;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -51,9 +50,6 @@ namespace Spark {
|
||||
AlgoServiceWrapper _algo_service_wrapper;
|
||||
[Export]
|
||||
public AlgoServiceWrapper algo_service_wrapper => _algo_service_wrapper ?? (_algo_service_wrapper = new AlgoServiceWrapper(context));
|
||||
|
||||
[Export]
|
||||
public MBOService mbo_service => context.locate<MBOService>();
|
||||
}
|
||||
|
||||
class AlgoServiceWrapper {
|
||||
|
||||
@@ -22,7 +22,9 @@ namespace mbts.ui {
|
||||
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
|
||||
AddFileGeometryKey = nameof(AddFileGeometryKey),
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
|
||||
DocumentGeometryKey = nameof(DocumentGeometryKey),
|
||||
@@ -39,6 +41,7 @@ namespace mbts.ui {
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
PencilGeometryKey = nameof(PencilGeometryKey),
|
||||
PlayGeometryKey = nameof(PlayGeometryKey),
|
||||
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
|
||||
StarGeometryKey = nameof(StarGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
TrashGeometryKey = nameof(TrashGeometryKey),
|
||||
@@ -115,6 +118,7 @@ namespace mbts.ui {
|
||||
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
|
||||
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
|
||||
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
|
||||
public static ImageSource AttachAlgos => create_icon(AttachAlgosGeometryKey);
|
||||
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
|
||||
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
|
||||
|
||||
@@ -139,6 +143,7 @@ namespace mbts.ui {
|
||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon(DetachAlgosGeometryKey);
|
||||
public static ImageSource Disk => create_icon(DiskGeometryKey);
|
||||
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
|
||||
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
|
||||
@@ -158,6 +163,7 @@ namespace mbts.ui {
|
||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
||||
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
|
||||
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
|
||||
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
|
||||
public static ImageSource Star => create_icon(StarGeometryKey);
|
||||
|
||||
@@ -40,6 +40,69 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/freepik
|
||||
Ttile: Unlink free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<x:Array x:Key="{x:Static mbts:Icons.AttachAlgosGeometryKey}" Type="sys:String">
|
||||
<sys:String>
|
||||
M51.173,3.801c-5.068-5.068-13.315-5.066-18.384,0l-9.192,9.192c-0.781,0.781-0.781,2.047,0,2.828
|
||||
c0.781,0.781,2.047,0.781,2.828,0l9.192-9.192c1.691-1.69,3.951-2.622,6.363-2.622c2.413,0,4.673,0.932,6.364,2.623
|
||||
s2.623,3.951,2.623,6.364c0,2.412-0.932,4.672-2.623,6.363L36.325,31.379c-3.51,3.508-9.219,3.508-12.729,0
|
||||
c-0.781-0.781-2.047-0.781-2.828,0s-0.781,2.048,0,2.828c2.534,2.534,5.863,3.801,9.192,3.801s6.658-1.267,9.192-3.801
|
||||
l12.021-12.021c2.447-2.446,3.795-5.711,3.795-9.192C54.968,9.512,53.62,6.248,51.173,3.801z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M27.132,40.57l-7.778,7.778c-1.691,1.691-3.951,2.623-6.364,2.623c-2.412,0-4.673-0.932-6.364-2.623
|
||||
c-3.509-3.509-3.509-9.219,0-12.728L17.94,24.306c1.691-1.69,3.951-2.622,6.364-2.622c2.412,0,4.672,0.932,6.363,2.622
|
||||
c0.781,0.781,2.047,0.781,2.828,0s0.781-2.047,0-2.828c-5.067-5.067-13.314-5.068-18.384,0L3.797,32.793
|
||||
c-2.446,2.446-3.794,5.711-3.794,9.192c0,3.48,1.348,6.745,3.795,9.191c2.446,2.447,5.711,3.795,9.191,3.795
|
||||
c3.481,0,6.746-1.348,9.192-3.795l7.778-7.778c0.781-0.781,0.781-2.047,0-2.828S27.913,39.789,27.132,40.57z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/freepik
|
||||
Ttile: Disconnected chains free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<x:Array x:Key="{x:Static mbts:Icons.DetachAlgosGeometryKey}" Type="sys:String">
|
||||
<sys:String>
|
||||
M462.557,77.627c-14.323-25.556-41.488-41.423-70.889-41.423c-13.835,0-27.54,3.585-39.602,10.364
|
||||
l-152.557,85.635l17.55,31.279l152.565-85.635c6.747-3.78,14.371-5.779,22.069-5.779c16.428,0,31.596,8.852,39.594,23.11
|
||||
c5.909,10.535,7.356,22.752,4.072,34.408c-3.268,11.656-10.876,21.346-21.411,27.255l-138.291,77.604
|
||||
c-2.634-16.851-10.494-32.392-22.793-44.699c-21.687-21.663-56.063-29.328-84.935-18.964l-12.485,4.495l12.128,33.734
|
||||
l12.534-4.471c16.127-5.869,35.319-1.496,47.406,10.567c6.974,6.982,11.388,15.826,12.786,25.41
|
||||
c-0.585-0.138-1.171-0.285-1.731-0.447c-11.681-3.284-21.354-10.9-27.271-21.476l-6.519-11.518l-31.303,17.59l6.56,11.518
|
||||
c10.6,18.915,27.93,32.571,48.836,38.44c0.723,0.187,1.455,0.39,2.187,0.561c-1.105,1.398-2.284,2.731-3.536,3.983
|
||||
l-114.32,114.328c-8.551,8.543-19.931,13.241-32.035,13.241c-12.112,0-23.492-4.698-32.027-13.241
|
||||
c-8.535-8.543-13.233-19.907-13.233-32.027c0-12.112,4.698-23.483,13.233-32.018l99.291-99.282l-25.369-25.369l-99.282,99.282
|
||||
C8.446,319.427,0,339.806,0,361.468c0,21.679,8.446,42.049,23.776,57.396c15.339,15.339,35.709,23.768,57.388,23.768
|
||||
s42.049-8.429,57.388-23.768l114.345-114.32c8.372-8.405,14.81-18.566,18.842-29.71c6.625-1.764,12.924-4.3,18.777-7.6
|
||||
l140.99-79.115c18.875-10.608,32.514-27.946,38.375-48.828C475.749,118.4,473.148,96.493,462.557,77.627z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M173.171,141.769c4.503-0.821,7.511-5.145,6.69-9.657l-17.103-94.942
|
||||
c-0.821-4.519-5.162-7.527-9.665-6.722c-4.511,0.821-7.527,5.145-6.706,9.673l17.111,94.925
|
||||
C164.311,139.575,168.644,142.574,173.171,141.769z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M43.561,191.776l94.934-17.119c4.519-0.821,7.527-5.145,6.706-9.657
|
||||
c-0.821-4.528-5.145-7.543-9.649-6.722L40.61,175.405c-4.503,0.797-7.519,5.145-6.698,9.657
|
||||
C34.717,189.582,39.058,192.589,43.561,191.776z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/chanut
|
||||
Ttile: Cancel free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<sys:String x:Key="{x:Static mbts:Icons.ProhibitedGeometryKey}">
|
||||
M32.759,0C14.696,0,0,14.695,0,32.759s14.695,32.759,32.759,32.759s32.759-14.695,32.759-32.759S50.822,0,32.759,0z
|
||||
M6,32.759C6,18.004,18.004,6,32.759,6c6.648,0,12.734,2.443,17.419,6.472L12.472,50.178C8.443,45.493,6,39.407,6,32.759z
|
||||
M32.759,59.518c-5.948,0-11.447-1.953-15.895-5.248l37.405-37.405c3.295,4.448,5.248,9.947,5.248,15.895
|
||||
C59.518,47.514,47.514,59.518,32.759,59.518z
|
||||
</sys:String>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/smashicons
|
||||
Ttile: File free icon
|
||||
|
||||
@@ -137,12 +137,6 @@ namespace mbts.spark {
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
public string market_description {get;}
|
||||
public string cleared_alias { get; }
|
||||
public string product_description { get; }
|
||||
public string hub_alias { get; }
|
||||
public string strip_name{ get; }
|
||||
public string mic_code { get; }
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
|
||||
|
||||
@@ -57,13 +57,6 @@ namespace mbts.spark {
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.md.security.HasIce ? r.md.security.Ice.MarketDescription : string.Empty, nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.md.security.HasIce ? r.md.security.Ice.ClearedAlias : string.Empty, nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.md.security.HasIce ? r.md.security.Ice.ProductName : string.Empty, nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.md.security.HasIce ? r.md.security.Ice.HubAlias : string.Empty, nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.md.security.HasIce ? r.md.security.Ice.StripName : string.Empty, nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.md.security.HasIce ? r.md.security.Ice.MicCode : string.Empty, nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
@@ -201,14 +194,6 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
@@ -231,13 +216,7 @@ namespace mbts.spark {
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
market_description = market_description_filter.get_state(),
|
||||
cleared_alias = cleared_alias_filter.get_state(),
|
||||
product_description = product_description_filter.get_state(),
|
||||
hub_alias = hub_alias_filter.get_state(),
|
||||
strip_name = strip_name_filter.get_state(),
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -8,9 +8,7 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for MarketSheetsView.xaml
|
||||
/// </summary>
|
||||
partial class MarketViewView : UserControl {
|
||||
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey,
|
||||
MarketDescription, ClearedAlias, ProductDescription, HubAlias, StripName, MicCode
|
||||
}
|
||||
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey }
|
||||
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Windows;
|
||||
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
|
||||
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -32,6 +32,7 @@ namespace mbts.spark {
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
|
||||
}
|
||||
|
||||
event Action<MarketViewWindowFactory.StartupData> _state_changed;
|
||||
@@ -47,7 +48,7 @@ namespace mbts.spark {
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
|
||||
@@ -97,12 +97,6 @@ namespace bts.shell {
|
||||
public static GridColumnSettings session_low => new GridColumnSettings(col_id(Columns.SessionLow), DefaultColumnHeaders.SessionLow, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings session_high => new GridColumnSettings(col_id(Columns.SessionHigh), DefaultColumnHeaders.SessionHigh, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings market_description => new GridColumnSettings(col_id(Columns.MarketDescription), DefaultColumnHeaders.MarketDescription, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings cleared_alias => new GridColumnSettings(col_id(Columns.ClearedAlias), DefaultColumnHeaders.ClearedAlias, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings product_description => new GridColumnSettings(col_id(Columns.ProductDescription), DefaultColumnHeaders.ProductDescription, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings hub_alias => new GridColumnSettings(col_id(Columns.HubAlias), DefaultColumnHeaders.HubAlias, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings strip_name => new GridColumnSettings(col_id(Columns.StripName), DefaultColumnHeaders.StripName, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings mic_code => new GridColumnSettings(col_id(Columns.MicCode), DefaultColumnHeaders.MicCode, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
|
||||
|
||||
|
||||
@@ -134,12 +128,6 @@ namespace bts.shell {
|
||||
yield return session_low;
|
||||
yield return session_high;
|
||||
yield return security_key;
|
||||
yield return market_description;
|
||||
yield return cleared_alias;
|
||||
yield return product_description;
|
||||
yield return hub_alias;
|
||||
yield return strip_name;
|
||||
yield return mic_code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,18 +177,6 @@ namespace bts.shell {
|
||||
public UIntMinMaxState ask_direct_size;
|
||||
[ProtoMember(20)]
|
||||
public UIntMinMaxState ask_implied_size;
|
||||
[ProtoMember(21)]
|
||||
public string market_description;
|
||||
[ProtoMember(22)]
|
||||
public string cleared_alias;
|
||||
[ProtoMember(23)]
|
||||
public string product_description;
|
||||
[ProtoMember(24)]
|
||||
public string hub_alias;
|
||||
[ProtoMember(25)]
|
||||
public string strip_name;
|
||||
[ProtoMember(26)]
|
||||
public string mic_code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||
readonly MDConsumer _consumer;
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
@@ -48,7 +48,7 @@ namespace mbts.spark {
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id { get; }
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
|
||||
public override void Dispose() =>
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Windows;
|
||||
using static bts.shell.SyntheticManagementWindowFactory;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "SyntheticManagement";
|
||||
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
|
||||
StartupData _state;
|
||||
@@ -33,8 +33,20 @@ namespace mbts.spark {
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().synthetic_management ? "SyntheticManagement" : null;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
Log.notnull(_state);
|
||||
|
||||
@@ -101,16 +113,6 @@ namespace mbts.spark {
|
||||
else _view.DataContext = _vm.value;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public override void Dispose() {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
@@ -559,71 +557,14 @@
|
||||
</Grid>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
<GroupBox
|
||||
x:Name="content_host"
|
||||
<usp:AlgoEditorView
|
||||
Grid.Row="2"
|
||||
DataContext="{Binding content_source}"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding content_source.algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
>
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="BorderBrush" Value="Transparent">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="usp:AlgoStudioView.IsMouseOverNodeContent" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GroupBox.Style>
|
||||
<GroupBox.Header>
|
||||
<ComboBox
|
||||
Grid.Column="3"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
SelectedItem="{Binding selected_definition.algo_root}"
|
||||
ItemsSource="{Binding selected_definition.algos_view}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Width="140"
|
||||
>
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding content_source}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding selected_definition.algo_root}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
>
|
||||
<ContentControl.Resources>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</GroupBox>
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="3" Margin="{StaticResource Margin.TP2}" Background="Transparent">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:ArrayExtension Type="{x:Type Button}">
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
@@ -18,26 +16,11 @@ namespace mbts.ui.spark {
|
||||
public AlgoStudioView() {
|
||||
InitializeComponent();
|
||||
DataContextChanged += (s, e) => set_controller();
|
||||
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
|
||||
content_host.MouseEnter += content_host_MouseEnter;
|
||||
content_host.MouseLeave += content_host_MouseLeave;
|
||||
set_controller();
|
||||
// local methods
|
||||
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
|
||||
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
|
||||
}
|
||||
|
||||
#region IsMouseOverNodeContent
|
||||
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
|
||||
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoStudioView), new PropertyMetadata(false));
|
||||
|
||||
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
|
||||
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
|
||||
#endregion
|
||||
|
||||
public new AlgoStudioVM DataContext {
|
||||
get => base.DataContext as AlgoStudioVM;
|
||||
set => base.DataContext = value;
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Windows;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class AlgoStudioWindow : SparkWindow<StartupData>, IShellWindow {
|
||||
internal class AlgoStudioWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
public const string
|
||||
NAME = "Algo Studio",
|
||||
TemplatesEnabledKey = nameof(TemplatesEnabledKey),
|
||||
@@ -37,7 +37,7 @@ namespace mbts.spark {
|
||||
: base(NAME, bus, factory, SparkCommands.ShowAlgoStudio.icon) {
|
||||
_model = model;
|
||||
_state = state;
|
||||
thread_init();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().algo_studio ? "AlgoStudio" : null;
|
||||
}
|
||||
|
||||
public override StartupData SaveState => _state = _vm.create();
|
||||
@@ -45,12 +45,12 @@ namespace mbts.spark {
|
||||
public override string window_type => NAME;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public string thread_id => NAME;
|
||||
public override double MinWidth =>
|
||||
SPARK_MENU_COLLAPSED_WIDTH +
|
||||
LAUNCHER_BUTTON_BORDER_WIDTH +
|
||||
DEFAULT_CONTENT_WIDTH;
|
||||
public override double MinHeight => DEFAULT_CONTENT_HEIGHT;
|
||||
public string thread_id { get; }
|
||||
|
||||
bts.config.AlgoStudioOptions _ui_configuration => _model.algo_model.ui_configuration;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using ProtoBuf;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -10,12 +11,6 @@ namespace mbts.spark {
|
||||
public readonly string name;
|
||||
public readonly AlgoStructureKey structure;
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinitionImmutable definition)
|
||||
: this(definition.CustomCase.type_id(), definition) { }
|
||||
|
||||
public AlgoNameStructureKey(string name, CustomOrderDefinitionImmutable definition)
|
||||
: this(name, new AlgoStructureKey(definition)) { }
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
|
||||
|
||||
@@ -31,31 +26,22 @@ namespace mbts.spark {
|
||||
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
|
||||
[ProtoMember(1)]
|
||||
readonly int[] _structure;
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(new[] { (int)base_type }) { }
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition) {
|
||||
int[] s = new[] { (int)definition.CustomCase };
|
||||
_build_structure(definition, ref s, 1);
|
||||
_structure = s;
|
||||
}
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
|
||||
|
||||
public AlgoStructureKey(int[] structure) =>
|
||||
_structure = structure;
|
||||
|
||||
public CustomOrderDefinitionImmutable build_def() {
|
||||
int pos = 0;
|
||||
var mdef = new CustomOrderDefinition();
|
||||
_build_def(_structure, ref pos, ref mdef);
|
||||
return new CustomOrderDefinitionImmutable(mdef);
|
||||
}
|
||||
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
|
||||
|
||||
public bool Equals(AlgoStructureKey other) =>
|
||||
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
|
||||
_structure.Length == other._structure.Length &&
|
||||
_structure.SequenceEqual(other._structure);
|
||||
|
||||
@@ -67,35 +53,105 @@ namespace mbts.spark {
|
||||
|
||||
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinitionImmutable definition, ref int[] structure, int pos) =>
|
||||
_build_structure(definition.CloneToMutable(), ref structure, pos);
|
||||
static class AlgoStructureSerializer {
|
||||
const int
|
||||
_dynamic_mask = 1 << 31,
|
||||
_child_mask_offset = 30;
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
int del_mask = 1 << (31 - di);
|
||||
structure[pos++] = (int)child_algo.CustomCase | del_mask;
|
||||
_build_structure(child_algo, ref structure, pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ArrayHelper.trim(ref structure, pos);
|
||||
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
to_array(algo.CloneToMutable(), dynamic_type_index);
|
||||
|
||||
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
int[] s = new[] { (int)algo.CustomCase };
|
||||
_build_structure(algo, ref s, dynamic_type_index);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
|
||||
for (int i = pos; i < structure.Length; i++) {
|
||||
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
|
||||
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
|
||||
|
||||
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
|
||||
if (structure == null || structure.Count == 0)
|
||||
return new CustomOrderDefinition();
|
||||
int pos = 1;
|
||||
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
|
||||
_build_def(structure, ref pos, ref mdef, dynamic_helper);
|
||||
return mdef;
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (structure == null || structure.Length == 0)
|
||||
structure = new int[1];
|
||||
structure[0] = (int)algo.CustomCase;
|
||||
int p = 1;
|
||||
_build_structure(algo, ref structure, ref p, dynamic_type_index);
|
||||
ArrayHelper.trim(ref structure, p);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
|
||||
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
|
||||
} else {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
int mask = 1 << (31 - di);
|
||||
if ((structure[i] & mask) == mask) {
|
||||
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
|
||||
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
|
||||
pos++;
|
||||
_build_def(structure, ref pos, ref child_algo);
|
||||
cad[di].assign_child(algo, child_algo);
|
||||
children = cad.Select((c, i) => (c.get_child(algo), i));
|
||||
children = children.Where(c => c.child_algo != null);
|
||||
}
|
||||
foreach ((var child, int field_pos) in children) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
|
||||
_build_structure(child, ref structure, ref pos, dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mask for a particular child-algo field of a particular algo definition.
|
||||
/// </summary>
|
||||
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
|
||||
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
|
||||
|
||||
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
|
||||
var local_algo = algo;
|
||||
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
var dyn_def = algo.Dynamic;
|
||||
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
|
||||
dynamic_helper.add_dynamic_params(ref dyn_def);
|
||||
children = dyn_def.OrderParams
|
||||
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
|
||||
} else {
|
||||
children = OrderDataHelper.get_child_algo_data(algo)
|
||||
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
|
||||
}
|
||||
using (var children_en = children.GetEnumerator()) {
|
||||
while (pos < structure.Count && children_en.MoveNext()) {
|
||||
(var assign_child, int field_pos) = children_en.Current;
|
||||
int mask = _child_algo_mask(field_pos);
|
||||
if ((structure[pos] & mask) == mask) {
|
||||
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
|
||||
var child_algo = OrderDataHelper.create_algo(type);
|
||||
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
|
||||
assign_child(child_algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ namespace mbts.spark {
|
||||
readonly DeferrableActionWrapper _refresh_on_local_changes;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
|
||||
readonly Property<AlgoFormats> _selected_format;
|
||||
AlgoEditingPermissions _editing_permissions;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
int? _favorite_index;
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
@@ -54,29 +55,43 @@ namespace mbts.spark {
|
||||
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
|
||||
bool _connected;
|
||||
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format);
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
if (connect)
|
||||
rv.connect();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format);
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format = AlgoFormats.Exchange) {
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
// init core data
|
||||
_model = model;
|
||||
_permissions_mask.mask = init_permissions;
|
||||
_permissions_mask.raw = init_permissions;
|
||||
var resources = new CompositeDisposable();
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_values)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
else if (model.algo_model.templates_enabled()) {
|
||||
resources.Add(_templates.Subscribe((s, e) => {
|
||||
if (_templates.Count == 0) {
|
||||
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
} else {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
}
|
||||
}));
|
||||
}
|
||||
key = def_data.key;
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
||||
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
|
||||
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
|
||||
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
|
||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
|
||||
_editing_permissions = _get_permissions();
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
var conf = _model.algo_model.ui_configuration;
|
||||
var resources = new CompositeDisposable();
|
||||
// init backing props
|
||||
_name = backing(nameof(name), def_data.name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
@@ -87,7 +102,7 @@ namespace mbts.spark {
|
||||
_is_selected = backing(nameof(is_selected), true);
|
||||
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
|
||||
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data, _editing_permissions)));
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
|
||||
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
|
||||
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
@@ -113,7 +128,6 @@ namespace mbts.spark {
|
||||
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.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
templates.ObserveSelf().Select(_ => _get_permissions()).DistinctUntilChanged().OnDispatcher().Subscribe(on_editing_permissions),
|
||||
_algo_root.observe_current(true).SubscribeHookLatest(vm => {
|
||||
if (vm == null)
|
||||
return Disposable.empty;
|
||||
@@ -152,16 +166,22 @@ namespace mbts.spark {
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
resources.Add(_selected_format.observe_current(true).SubscribeHookLatest(f => {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(f);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(f));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}));
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeHookLatest(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
_dispose = resources;
|
||||
on_editing_permissions(_get_permissions());
|
||||
await algo_root.load_from_async(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
@@ -169,11 +189,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
// local methods
|
||||
void on_editing_permissions(AlgoEditingPermissions eps) {
|
||||
_editing_permissions = eps;
|
||||
foreach (var vm in algo_roots)
|
||||
vm.set_permissions(eps);
|
||||
}
|
||||
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_local_changed() {
|
||||
@@ -230,6 +245,9 @@ namespace mbts.spark {
|
||||
// visual states
|
||||
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
|
||||
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
|
||||
// editing permissions
|
||||
public AlgoEditingPermissions editing_permissions_mask { get => _permissions_mask.mask; set => _permissions_mask.mask = value; }
|
||||
public AlgoEditingPermissions editing_permissions => _permissions_mask.result;
|
||||
|
||||
#region IAlgoSelector
|
||||
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed { add => algo_root_changed += value; remove => algo_root_changed -= value; }
|
||||
@@ -248,7 +266,7 @@ namespace mbts.spark {
|
||||
|
||||
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
|
||||
var orig_algo = algo_root?.id;
|
||||
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, _editing_permissions, true));
|
||||
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
|
||||
if (!try_select(orig_algo) && !try_select(default_algo))
|
||||
algo_root = algo_roots.FirstOrDefault();
|
||||
//
|
||||
@@ -298,6 +316,19 @@ namespace mbts.spark {
|
||||
|
||||
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
|
||||
|
||||
public void load_def(CustomOrderDefinitionImmutable def) {
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_from(def);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
vm.load_from(def);
|
||||
algo_root = vm;
|
||||
} else {
|
||||
algo_roots.Add(vm = _mk_algo_root(def));
|
||||
algo_root = vm;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(AlgoData data) {
|
||||
_assert_can_load(data.key);
|
||||
if (algo_root != null)
|
||||
@@ -309,7 +340,7 @@ namespace mbts.spark {
|
||||
|
||||
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
|
||||
public AlgoDefinitionVM to_persisted(AlgoData def) =>
|
||||
new AlgoDefinitionVM(_model, def);
|
||||
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
|
||||
|
||||
public AlgoData create_algo_def() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
@@ -322,16 +353,6 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
public override string ToString() => algo_root == null ? $"!:{name}" : $"{algo_root.order_type}:{name}";
|
||||
|
||||
AlgoEditingPermissions _get_permissions() {
|
||||
var permissions = AlgoEditingPermissions.None;
|
||||
if (_model.algo_model.ui_configuration.can_modify_definition_values) {
|
||||
permissions |= AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues;
|
||||
}
|
||||
if (_model.algo_model.ui_configuration.can_modify_definition_structure || (_model.algo_model.templates_enabled() && _templates.Count == 0))
|
||||
permissions |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
return permissions;
|
||||
}
|
||||
|
||||
void _assert_can_load(AlgoKey data_key) {
|
||||
if (key.mode != data_key.mode)
|
||||
throw new InvalidOperationException($"{typeof(AlgoDefinitionVM)} '{nameof(key)}.{nameof(AlgoKey.mode)}' must be {data_key.mode} (actually {key.mode}), to load specified data.");
|
||||
@@ -362,7 +383,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), editing_permissions: _editing_permissions, true);
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
|
||||
|
||||
void _on_metadata(AlgoDefinitionMetadata metadata) {
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.AlgoEditorView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
||||
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
||||
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
|
||||
<!--Converters-->
|
||||
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
|
||||
<!--Default Styles-->
|
||||
<Style x:Key="ContentHostGroupBoxStyle" TargetType="GroupBox">
|
||||
<Setter Property="Padding" Value="8"/>
|
||||
</Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="u:TextBoxBehavior.EnterKeyBehavior" Value="CommitAndRelease"/>
|
||||
<Setter Property="u:TextBoxBehavior.ReleaseOnEscape" Value="True"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
|
||||
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<GroupBox
|
||||
x:Name="content_host"
|
||||
Grid.Row="2"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
>
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="BorderBrush" Value="Transparent">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="usp:AlgoEditorView.IsMouseOverNodeContent" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GroupBox.Style>
|
||||
<GroupBox.Header>
|
||||
<ComboBox
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
Width="140"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
ItemsSource="{Binding algos_view}"
|
||||
SelectedItem="{Binding algo_root}"
|
||||
>
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.IsEnabled>
|
||||
<Binding Path="editing_permissions">
|
||||
<Binding.Converter>
|
||||
<cv:EnumHasFlagsConverter>
|
||||
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</ComboBox.IsEnabled>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding algo_root}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<ContentControl.Resources>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</GroupBox>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using mbts.ui.extensions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoEditorView : UserControl {
|
||||
public AlgoEditorView() {
|
||||
InitializeComponent();
|
||||
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
|
||||
content_host.MouseEnter += content_host_MouseEnter;
|
||||
content_host.MouseLeave += content_host_MouseLeave;
|
||||
// local methods
|
||||
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
|
||||
}
|
||||
|
||||
#region IsMouseOverNodeContent
|
||||
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
|
||||
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoEditorView), new PropertyMetadata(false));
|
||||
|
||||
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
|
||||
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,13 @@ using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
@@ -42,7 +45,7 @@ namespace mbts.spark {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
/// Columns
|
||||
@@ -98,16 +101,17 @@ namespace mbts.spark {
|
||||
// grid settings
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
if (_settings_manager != null)
|
||||
grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
//
|
||||
_dispose = Disposable.Create(
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
_rows.update(t);
|
||||
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
|
||||
}),
|
||||
state_obs.OnDispatcher().Subscribe(s => {
|
||||
if (s.grid != null) {
|
||||
if (s.grid != null && _settings_manager != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
@@ -120,8 +124,8 @@ namespace mbts.spark {
|
||||
};
|
||||
rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
fills_source.Connect()
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
|
||||
);
|
||||
// local methods
|
||||
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
|
||||
@@ -153,12 +157,12 @@ namespace mbts.spark {
|
||||
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
|
||||
_sort_manager.create();
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
|
||||
static Columns _col_key(ColumnBase column) =>
|
||||
((IKeyedColumn<Columns>)column).key;
|
||||
|
||||
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
|
||||
public FilterStates get_filters() =>
|
||||
_filters_manager.create();
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// This class provides a stable DataContext for a fills view, with an internal <see cref="FillsVM"/> that may be disposed and recreated as needed.
|
||||
/// </summary>
|
||||
class FillsVMContainer : IHandleGridSorting, IDisposable, IWillLetYouKnowWhenIDispose {
|
||||
bool _disposed;
|
||||
FillsVM _value;
|
||||
|
||||
public event Action<bool> OnDisposed;
|
||||
|
||||
public FillsVM value {
|
||||
get => _value;
|
||||
set {
|
||||
if (value != _value) {
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(FillsVMContainer));
|
||||
Disposable.replace(ref _value, value);
|
||||
if (_value != null) {
|
||||
rows.add_collection(_value.rows);
|
||||
columns.add_collection(_value.columns);
|
||||
} else {
|
||||
rows.clear_collections();
|
||||
columns.clear_collections();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableAggregateCollection<FillsVM.Row> rows { get; } = new ObservableAggregateCollection<FillsVM.Row>();
|
||||
public ObservableAggregateCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableAggregateCollection<DIYGrid.ColumnBase>();
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(FillsVMContainer));
|
||||
value = null;
|
||||
OnDisposed?.Invoke(true);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
|
||||
if (!_disposed)
|
||||
_value?.on_sorting(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ using StartupData = bts.shell.FillsWindowFactory.StartupData;
|
||||
namespace mbts.spark {
|
||||
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
public const string window_type_name = "Fills";
|
||||
const string _thread_id = "Fills";
|
||||
readonly AllowOnce _init_guard = new AllowOnce("Fills");
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -33,6 +32,7 @@ namespace mbts.spark {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
|
||||
}
|
||||
|
||||
event Action<FillsWindowFactory.StartupData> _state_changed;
|
||||
@@ -48,7 +48,7 @@ namespace mbts.spark {
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace bts.shell {
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_md_auxiliary_model = md_auxiliary_model;
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled);
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public MiniOrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,13 @@ using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
|
||||
string sort_member_path { get; }
|
||||
ListSortDirection? SortDirection { get; set; }
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
@@ -78,7 +77,6 @@ namespace mbts.spark {
|
||||
Add(symbol_column = new SimpleTextColumn<string[]>(
|
||||
Columns.Symbol,
|
||||
DefaultColumnHeaders.Symbol,
|
||||
nameof(OrderBookRowVM.symbol),
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
@@ -92,7 +90,6 @@ namespace mbts.spark {
|
||||
Add(time_created_column = new SimpleTextColumn<TimeFilterTuple>(
|
||||
Columns.Time,
|
||||
"Time",
|
||||
nameof(OrderBookRowVM.time_created),
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -104,7 +101,6 @@ namespace mbts.spark {
|
||||
Add(last_change_column = new SimpleTextColumn<TimeFilterTuple>(
|
||||
Columns.LastChange,
|
||||
"Last Change",
|
||||
nameof(OrderBookRowVM.last_change),
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -116,7 +112,6 @@ namespace mbts.spark {
|
||||
Add(user_column = new SimpleTextColumn<string[]>(
|
||||
Columns.User,
|
||||
DefaultColumnHeaders.User,
|
||||
nameof(OrderBookRowVM.user),
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
@@ -129,7 +124,6 @@ namespace mbts.spark {
|
||||
Add(account_column = new SimpleTextColumn<string[]>(
|
||||
Columns.Account,
|
||||
DefaultColumnHeaders.Account,
|
||||
nameof(OrderBookRowVM.account),
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
|
||||
@@ -144,7 +138,6 @@ namespace mbts.spark {
|
||||
Add(exchange_column = new SimpleTextColumn<Venue[]>(
|
||||
Columns.Exchange,
|
||||
DefaultColumnHeaders.Exchange,
|
||||
nameof(OrderBookRowVM.exchange),
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
|
||||
@@ -157,7 +150,6 @@ namespace mbts.spark {
|
||||
Add(time_in_force_column = new SimpleTextColumn<TimeInForce[]>(
|
||||
Columns.TimeInForce,
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
nameof(OrderBookRowVM.time_in_force),
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
|
||||
@@ -176,14 +168,13 @@ namespace mbts.spark {
|
||||
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
|
||||
Columns.SubType,
|
||||
"Sub Type",
|
||||
nameof(OrderBookRowVM.order_sub_type),
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
ort => ort.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
SubTypeFilterTemplateKey,
|
||||
OrderBookGridView.SubTypeFilterTemplateKey,
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
@@ -205,7 +196,6 @@ namespace mbts.spark {
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
nameof(OrderBookRowVM.price),
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -221,7 +211,6 @@ namespace mbts.spark {
|
||||
Add(avg_price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.AvgFilled,
|
||||
"Avg Price",
|
||||
nameof(OrderBookRowVM.avg_price),
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -233,7 +222,6 @@ namespace mbts.spark {
|
||||
Add(last_price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.LastPrice,
|
||||
"Last Filled",
|
||||
nameof(OrderBookRowVM.last_price),
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -245,7 +233,6 @@ namespace mbts.spark {
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
nameof(OrderBookRowVM.status),
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
|
||||
@@ -263,7 +250,6 @@ namespace mbts.spark {
|
||||
Add(security_key_column = new SimpleTextColumn<string>(
|
||||
Columns.SecurityKey,
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
nameof(OrderBookRowVM.security_key),
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -275,7 +261,6 @@ namespace mbts.spark {
|
||||
Add(order_key_column = new SimpleTextColumn<string>(
|
||||
Columns.OrderKey,
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
nameof(OrderBookRowVM.order_key),
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -287,7 +272,6 @@ namespace mbts.spark {
|
||||
Add(parent_key_column = new SimpleTextColumn<string>(
|
||||
Columns.ParentKey,
|
||||
"Parent Key",
|
||||
nameof(OrderBookRowVM.parent_order_key),
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -295,6 +279,17 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.parent_order_key)));
|
||||
// attached target order key
|
||||
Add(attached_target_order_key_column = new SimpleTextColumn<string>(
|
||||
Columns.AttachedTargetOrderKey,
|
||||
DefaultColumnHeaders.AttachedTargetOrderKey,
|
||||
r => r.order.attached_target_order_key().ToString(),
|
||||
DefaultColumnWidths.AttachedTargetOrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
ort => ort.order.attached_target_order_key().ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order.attached_target_order_key())));
|
||||
}
|
||||
|
||||
public event Action filter_changed;
|
||||
@@ -323,10 +318,13 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<string> security_key_column { get; }
|
||||
public SimpleTextColumn<string> order_key_column { get; }
|
||||
public SimpleTextColumn<string> parent_key_column { get; }
|
||||
public SimpleTextColumn<string> attached_target_order_key_column { get; }
|
||||
|
||||
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() => (
|
||||
new FilterStates {
|
||||
order_key_regex = order_key_column.filter.get_state(),
|
||||
parent_key_regex = parent_key_column.filter.get_state(),
|
||||
attached_target_order_key_regex = attached_target_order_key_column.filter.get_state(),
|
||||
time_created = time_created_column.filter.get_state(),
|
||||
users = user_column.filter.get_state(),
|
||||
accounts = account_column.filter.get_state(),
|
||||
@@ -364,6 +362,8 @@ namespace mbts.spark {
|
||||
time_in_force_column.filter.load_state(fs.tifs);
|
||||
exchange_column.filter.load_state(fs.exchanges);
|
||||
order_key_column.filter.load_state(fs.order_key_regex);
|
||||
parent_key_column.filter.load_state(fs.parent_key_regex);
|
||||
attached_target_order_key_column.filter.load_state(fs.attached_target_order_key_regex);
|
||||
avg_price_column.filter.load_state(fs.avg_price);
|
||||
last_price_column.filter.load_state(fs.last_price);
|
||||
status_column.filter.load_state(fs.statuses);
|
||||
@@ -480,13 +480,12 @@ namespace mbts.spark {
|
||||
protected readonly Func<OrderBookRowVM, string> _get_text;
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, string sort_prop, double width,
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
_get_text = get_text;
|
||||
sort_member_path = sort_prop;
|
||||
this.filter = filter;
|
||||
_filter_template = filter_template;
|
||||
_cached_sort_direction = SortDirection;
|
||||
@@ -515,9 +514,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SimpleTextColumn(Columns key, string header, string sort_prop, Func<OrderBookRowVM, string> get_text, double width,
|
||||
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
: base(key, header, get_text, sort_prop, width, filter, filter_template, comparer) { }
|
||||
: base(key, header, get_text, width, filter, filter_template, comparer) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
|
||||
@@ -527,7 +526,7 @@ namespace mbts.spark {
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
|
||||
@@ -536,9 +535,7 @@ namespace mbts.spark {
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), nameof(OrderBookRowVM.quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) {
|
||||
sort_member_path = nameof(OrderBookRowVM.quantity);
|
||||
}
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
|
||||
@@ -547,9 +544,7 @@ namespace mbts.spark {
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) {
|
||||
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
|
||||
}
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
|
||||
@@ -562,7 +557,6 @@ namespace mbts.spark {
|
||||
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
sort_member_path = nameof(OrderBookRowVM.order_type);
|
||||
font_weight_binding = RenderUtils.get_font_weight_binding();
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
@@ -587,8 +581,7 @@ namespace mbts.spark {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
Header = "Last Message";
|
||||
Width = DIYGrid.DefaultColumnWidths.LastMessage;
|
||||
sort_member_path = nameof(OrderBookRowVM.last_msg);
|
||||
Width = DefaultColumnWidths.LastMessage;
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
@@ -616,7 +609,6 @@ namespace mbts.spark {
|
||||
this.filter = filter;
|
||||
Header = DefaultColumnHeaders.Security;
|
||||
Width = DefaultColumnWidths.Security;
|
||||
sort_member_path = nameof(OrderBookRowVM.security);
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
@@ -634,7 +626,7 @@ namespace mbts.spark {
|
||||
toggle_button.SetBinding(ToggleButton.IsCheckedProperty, new Binding(nameof(OrderBookRowVM.is_expanded)));
|
||||
toggle_button.SetBinding(ToggleButton.MarginProperty, new Binding(nameof(OrderBookRowVM.depth_level)) { Converter = _margin_converter });
|
||||
toggle_button.SetBinding(ToggleButton.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_parent)) { Converter = _expander_visiblilty_converter });
|
||||
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
|
||||
toggle_button.SetResourceReference(ToggleButton.StyleProperty, OrderBookGridView.ExpandCollapseToggleStyleKey);
|
||||
//
|
||||
var security_view = new TextBlock { Foreground = font_brush };
|
||||
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
@@ -651,6 +643,7 @@ namespace mbts.spark {
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.Children.Add(toggle_button);
|
||||
grid.Children.Add(security_view);
|
||||
|
||||
grid.VerticalAlignment = VerticalAlignment.Center;
|
||||
//
|
||||
var border = new Border { Child = grid };
|
||||
|
||||
@@ -6,16 +6,14 @@ using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM {
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -46,25 +44,13 @@ namespace mbts.spark {
|
||||
modify_order_price?.TryRaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
//// This takes care of the case where the OB has all rows visible
|
||||
//// and an order is cancelled outside of the OB (say via the ladder).
|
||||
//// Since the OB window Commands only revaluate their CanExecuteChanged
|
||||
//// when the OrderBookVM.selected_order_changes, this emulates the selected
|
||||
//// order changing since the current_row setter invokes the selected_order_changed
|
||||
//// handler
|
||||
public IObservable<OrderSecurityAlgoMeta> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurityAlgoMeta>(
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => invalidated -= cb);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
this.invalidated -= OrderBookRowVM_invalidated;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
|
||||
|
||||
// Core
|
||||
public OrderSecurityAlgoMeta order_tuple { get; private set; }
|
||||
@@ -142,8 +128,8 @@ namespace mbts.spark {
|
||||
public ICommand toggle_command => _toggle_expanded_cmd ?? (
|
||||
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
|
||||
|
||||
ICommand DIYGrid.IActionableRowVM.default_command => _show_order_modify_cmd ??
|
||||
(_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
public ICommand show_order_modify_cmd =>
|
||||
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
|
||||
public ICommand cancel_command => _cancel_cmd ??
|
||||
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working));
|
||||
@@ -185,10 +171,10 @@ namespace mbts.spark {
|
||||
public void update(OrderSecurityAlgoMeta order_tuple) {
|
||||
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
bool had_security = _security.HasValue;
|
||||
var old_meta = this.order_tuple.meta;
|
||||
var old_order_tuple = this.order_tuple;
|
||||
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
|
||||
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
|
||||
OrderRecordImmutable old_order = order;
|
||||
OrderRecordImmutable old_order = old_order_tuple.order;
|
||||
this.order_tuple = order_tuple;
|
||||
// most cells are "render-cells," which don't use databindings - the only way to update such cells is to raise the invalidated event
|
||||
bool invalidate = !(
|
||||
@@ -215,10 +201,12 @@ namespace mbts.spark {
|
||||
// need to raise invalidated to refresh the price cells (the security.ContractSpec determines how they are formatted - Ticks!)
|
||||
invalidate = true;
|
||||
}
|
||||
if (old_meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
_badge_fill = null;
|
||||
RaisePropertyChanged(nameof(badge_fill));
|
||||
}
|
||||
if (old_order_tuple.display_name() != order_tuple.display_name())
|
||||
RaisePropertyChanged(nameof(display_name));
|
||||
if (old_order.status() != order.status()) {
|
||||
invalidate = true;
|
||||
RaisePropertyChanged(nameof(status));
|
||||
@@ -228,6 +216,7 @@ namespace mbts.spark {
|
||||
}
|
||||
if (could_modify_execute.HasValue && could_modify_execute.Value != _show_order_modify_cmd.CanExecute(null))
|
||||
_show_order_modify_cmd.RaiseCanExecuteChanged();
|
||||
updated?.Invoke((old_order_tuple, order_tuple));
|
||||
if (invalidate)
|
||||
raise_invalidated();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
@@ -11,68 +14,149 @@ using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using _GridSettingsManager =
|
||||
mbts.ui.GridSettingsManager<
|
||||
mbts.spark.OrderBookRowVM,
|
||||
bts.api.orderman.OrderSecurity,
|
||||
bts.shell.INotificationServiceDataCache,
|
||||
mbts.OrderRecordTuplePredicate,
|
||||
bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookVM : ViewModelBase, IDisposable, IHandleRowSelection, IHandleGridSorting, IWillLetYouKnowWhenIDispose {
|
||||
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting {
|
||||
const double
|
||||
_min_visible_right_panel_width = 200,
|
||||
_min_visible_fills_slave_grid_height = 100;
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
readonly Property<bool> _fills_slave_grid_is_visible;
|
||||
readonly OrderBookRowCollection _row_collection;
|
||||
readonly ObservableCollection<OrderBookRowVM> _selected_rows = new ObservableCollection<OrderBookRowVM>();
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly List<IDisposable> _resources;
|
||||
readonly SelectedAlgosMonitor _selected_algo_mon;
|
||||
double
|
||||
_raw_right_panel_width = OrderBookStartupData.Default.right_panel_width,
|
||||
_raw_fills_slave_grid_height = OrderBookStartupData.Default.fills_slave_grid_height;
|
||||
OrderBookStartupData _state;
|
||||
bool _is_row_collection_populated;
|
||||
bool
|
||||
_algo_editor_is_visible = OrderBookStartupData.Default.algo_editor_is_visible,
|
||||
_is_attaching_algos,
|
||||
_is_row_collection_populated;
|
||||
|
||||
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool rows_are_initially_expanded = false) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_algo_mon = new SelectedAlgosMonitor(model.dynamic_helper);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, new AlgoData(new CustomOrderDefinitionImmutable(new CustomOrderDefinition { Limit = new LimitOrderDefinition() })), editing_permissions: AlgoEditingPermissions.AllowEditValues));
|
||||
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
|
||||
order_mod_vm.algo_vm.algo_root = null;
|
||||
attach_vm = new OrderParamsVM(this);
|
||||
_fills_slave_grid_is_visible = backing(nameof(fills_slave_grid_is_visible), false);
|
||||
_settings_manager = settings_manager;
|
||||
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(x => (x.added.Values, x.removed.Values)));
|
||||
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
|
||||
columns = new ObservableAppendCollection<DIYGrid.ColumnBase>(_columns);
|
||||
_row_collection = new OrderBookRowCollection(ort => new OrderBookRowVM(this, ort, rows_are_initially_expanded), _columns.get_sorting_data().comparer) { filter = _columns.get_filter() };
|
||||
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
|
||||
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
|
||||
_current_row = backing<OrderBookRowVM>(nameof(current_row));
|
||||
_columns.filter_changed += on_filter_changed;
|
||||
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
|
||||
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
|
||||
var modify_selected_algos_async_cmd = new AsyncCommand(_modify_selected_algos_async, () => _selected_algo_mon.all_active);
|
||||
modify_selected_algos_command = modify_selected_algos_async_cmd;
|
||||
var send_attached_orders_async_cmd = new AsyncCommand(send_attached_orders_async, () => _selected_algo_mon.all_active);
|
||||
send_attached_orders_cmd = send_attached_orders_async_cmd;
|
||||
cancel_attach_orders_cmd = new DelegateCommand(() => is_attaching_algos = false);
|
||||
toggle_algo_editor_is_visible_cmd = new DelegateCommand(() => algo_editor_is_visible = !algo_editor_is_visible);
|
||||
toggle_fills_slave_grid_is_visible_cmd = new DelegateCommand(() => fills_slave_grid_is_visible = !fills_slave_grid_is_visible);
|
||||
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, rows, core_columns);
|
||||
|
||||
_resources = new List<IDisposable> {
|
||||
IDisposable attach_vm_hooks = null;
|
||||
begin_attaching += on_begin_attaching;
|
||||
end_attaching += on_end_attaching;
|
||||
concat_disposable(
|
||||
connect_common_selected_params(order_mod_vm),
|
||||
_selected_algo_mon.subscribe_all_active().Subscribe(on_all_selected_orders_active_changed),
|
||||
model.on_order_tuples.OnDispatcher().Subscribe(_row_collection.update),
|
||||
grid_settings_manager,
|
||||
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
|
||||
order_mod_vm.algo_vm.connect_data(_selected_algo_mon.subscribe_common_spreader_sec()),
|
||||
order_mod_vm.algo_vm,
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(_selected_algo_mon.on_security_md_map),
|
||||
attach_vm,
|
||||
_fills_slave_grid_is_visible.observe_current(true)
|
||||
.Subscribe(is_visible => fills_vm.value = is_visible ?
|
||||
new FillsVM(
|
||||
bus, model, Observable.Return(FillsWindowFactory.StartupData.Default()),
|
||||
model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), null
|
||||
) :
|
||||
null),
|
||||
Disposable.Create(() => {
|
||||
_columns.filter_changed -= on_filter_changed;
|
||||
_row_collection.CollectionChanged -= _row_collection_CollectionChanged;
|
||||
begin_attaching -= on_begin_attaching;
|
||||
end_attaching -= on_end_attaching;
|
||||
Disposable.dispose(ref attach_vm_hooks);
|
||||
Disposable.dispose(_row_collection);
|
||||
}),
|
||||
_columns,
|
||||
_current_row.SubscribeHookLatest(
|
||||
r => r.newval?.subscribe_data().Subscribe(
|
||||
data => selected_order_changed?.Invoke(data.order)
|
||||
)
|
||||
)
|
||||
};
|
||||
_columns);
|
||||
if (settings_manager != null) {
|
||||
_resources.Add(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
|
||||
concat_disposable(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
|
||||
}
|
||||
IObservable<Func<FillTuple, bool>> mk_fills_slave_predicate_src() =>
|
||||
selected_rows.ObserveSelf(true)
|
||||
.BufferLatest(TimeSpan.FromSeconds(0.2))
|
||||
.Select(_ => mk_fills_slave_predicate());
|
||||
Func<FillTuple, bool> mk_fills_slave_predicate() {
|
||||
var keys = new HashSet<uint>(selected_rows.SelectMany(r => new[] { r }.Concat(_row_collection.get_descendent_rows(r))).Select(r => r.order_key));
|
||||
return ft => keys.Contains(ft.fill.OrderKey);
|
||||
}
|
||||
void on_filter_changed() => _row_collection.filter = _columns.get_filter();
|
||||
|
||||
void on_all_selected_orders_active_changed(bool newval) {
|
||||
order_mod_vm.algo_vm.editing_permissions_mask = newval ? AlgoEditingPermissions.AllowEditValues : AlgoEditingPermissions.None;
|
||||
modify_selected_algos_async_cmd.RaiseCanExecuteChanged();
|
||||
send_attached_orders_async_cmd.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
async Task send_attached_orders_async() {
|
||||
is_attaching_algos = false;
|
||||
try {
|
||||
double price = attach_vm.price;
|
||||
uint size = attach_vm.size_calculator.value ?? 0;
|
||||
var algo = attach_vm.algo_vm.algo_root.created_assigned();
|
||||
var order_tups = selected_rows.Select(r => r.order_tuple);
|
||||
await Task.WhenAll(order_tups.Select(t => _model.send_order(t.order.Def.with(attach_target_order_key: t.order.Key, custom_def: algo), price, size)));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Error sending attached orders: {ex}.");
|
||||
}
|
||||
}
|
||||
|
||||
void on_begin_attaching() => Disposable.replace(ref attach_vm_hooks, connect_common_selected_params(attach_vm));
|
||||
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
|
||||
|
||||
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
|
||||
Disposable.Create(
|
||||
_selected_algo_mon.subscribe_common_algo().Subscribe(newval => {
|
||||
if (newval.HasValue) {
|
||||
order_mod_vm.algo_vm.load_def(newval.Value);
|
||||
} else {
|
||||
order_mod_vm.algo_vm.algo_root = null;
|
||||
}
|
||||
}),
|
||||
_selected_algo_mon.subscribe_common_sec_md().Subscribe(newval => vm.security_md = newval),
|
||||
_selected_algo_mon.subscribe_price().Subscribe(newval => vm.price = newval),
|
||||
_selected_algo_mon.subscribe_size().Subscribe(newval => vm.size_calculator.value = newval));
|
||||
}
|
||||
|
||||
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -80,7 +164,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public event Action<OrderRecordImmutable?> selected_order_changed;
|
||||
public event Action<bool> OnDisposed;
|
||||
public event Action begin_attaching, end_attaching;
|
||||
|
||||
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
|
||||
public ObservableCollection<DIYGrid.ColumnBase> core_columns => _columns;
|
||||
@@ -99,11 +183,56 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
public OrderRecordImmutable? current_order => current_row?.order;
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => _selected_rows.Select(r => r.order);
|
||||
public AlgoDefinitionVM algo_vm => order_mod_vm.algo_vm;
|
||||
public OrderParamsVM order_mod_vm { get; }
|
||||
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
|
||||
public bool fills_slave_grid_is_visible { get => _fills_slave_grid_is_visible.Value; set => _fills_slave_grid_is_visible.set(value); }
|
||||
public OrderParamsVM attach_vm { get; }
|
||||
public ObservableCollection<OrderBookRowVM> selected_rows { get; } = new ObservableCollection<OrderBookRowVM>();
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => selected_rows.Select(r => r.order);
|
||||
public _GridSettingsManager grid_settings_manager { get; }
|
||||
public bool is_attaching_algos {
|
||||
get => _is_attaching_algos;
|
||||
set {
|
||||
if (Set(ref _is_attaching_algos, value))
|
||||
(value ? begin_attaching : end_attaching)?.Invoke();
|
||||
}
|
||||
}
|
||||
public ICommand collapse_all_rows_command { get; }
|
||||
public ICommand expand_all_rows_command { get; }
|
||||
|
||||
public ICommand modify_selected_algos_command { get; }
|
||||
public ICommand send_attached_orders_cmd { get; }
|
||||
public ICommand cancel_attach_orders_cmd { get; }
|
||||
public ICommand toggle_algo_editor_is_visible_cmd { get; }
|
||||
public ICommand toggle_fills_slave_grid_is_visible_cmd { get; }
|
||||
public bool algo_editor_is_visible {
|
||||
get => _algo_editor_is_visible;
|
||||
set {
|
||||
if (Set(ref _algo_editor_is_visible, value))
|
||||
RaisePropertyChanged(nameof(right_panel_width));
|
||||
}
|
||||
}
|
||||
public double raw_right_panel_width => _raw_right_panel_width;
|
||||
public double right_panel_width {
|
||||
get => algo_editor_is_visible ? Math.Max(raw_right_panel_width, _min_visible_right_panel_width) : 0;
|
||||
set {
|
||||
if (_raw_right_panel_width != value) {
|
||||
_raw_right_panel_width = value;
|
||||
if (algo_editor_is_visible)
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public double fills_slave_grid_height {
|
||||
get => algo_editor_is_visible ? Math.Max(_raw_fills_slave_grid_height, _min_visible_fills_slave_grid_height) : 0;
|
||||
set {
|
||||
if (_raw_fills_slave_grid_height != value) {
|
||||
_raw_fills_slave_grid_height = value;
|
||||
if (algo_editor_is_visible)
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool is_row_collection_populated {
|
||||
get => _is_row_collection_populated;
|
||||
set => Set(ref _is_row_collection_populated, value);
|
||||
@@ -114,16 +243,18 @@ namespace mbts.spark {
|
||||
if (e.RemovedItems != null) {
|
||||
foreach (var row_vm in e.RemovedItems.Cast<OrderBookRowVM>()) {
|
||||
row_vm.is_selected = false;
|
||||
_selected_algo_mon.on_row_unselected(row_vm);
|
||||
}
|
||||
}
|
||||
if (e.AddedItems != null) {
|
||||
foreach (var row_vm in e.AddedItems.Cast<OrderBookRowVM>()) {
|
||||
row_vm.is_selected = true;
|
||||
_selected_algo_mon.on_row_selected(row_vm);
|
||||
}
|
||||
}
|
||||
RowSelectionHelper.apply_changes(_selected_rows, e);
|
||||
RowSelectionHelper.apply_changes(selected_rows, e);
|
||||
selected_order_changed?.Invoke(current_order);
|
||||
} catch (Exception ex) {
|
||||
} catch(Exception ex) {
|
||||
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
|
||||
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
|
||||
}
|
||||
@@ -132,12 +263,16 @@ namespace mbts.spark {
|
||||
public OrderBookStartupData create() {
|
||||
var (filters, sorting) = _columns.create();
|
||||
var gs = grid_settings_manager.create();
|
||||
return new OrderBookStartupData(filters, gs.general.with(sorting_criterion: sorting), gs.predicate_animations, use_defaults: _settings_manager.instance_settings == null);
|
||||
return new OrderBookStartupData(filters, gs.general.with(sorting_criterion: sorting), _algo_editor_is_visible, raw_right_panel_width, _raw_fills_slave_grid_height, fills_slave_grid_is_visible, gs.predicate_animations, use_defaults: _settings_manager.instance_settings == null);
|
||||
}
|
||||
|
||||
public void load_from(OrderBookStartupData state) {
|
||||
if (state == null) return;
|
||||
_state = state;
|
||||
algo_editor_is_visible = state.algo_editor_is_visible;
|
||||
right_panel_width = state.right_panel_width;
|
||||
fills_slave_grid_height = state.fills_slave_grid_height;
|
||||
fills_slave_grid_is_visible = state.fills_slave_grid_is_visible;
|
||||
var gs = state.grid_settings_wrapper();
|
||||
if (gs != null) {
|
||||
grid_settings_manager.load_from(gs);
|
||||
@@ -182,6 +317,13 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void begin_attach_algos() => is_attaching_algos = true;
|
||||
|
||||
public void detach_selected_algos() =>
|
||||
_detach_orders(selected_rows.Where(r => r.order.can_detach()).ToList()).ignore();
|
||||
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
|
||||
|
||||
internal void show_order_modify(OrderBookRowVM row) =>
|
||||
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
|
||||
|
||||
@@ -217,11 +359,40 @@ namespace mbts.spark {
|
||||
dr.is_visible = false;
|
||||
}
|
||||
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
|
||||
async Task _attach_orders(IReadOnlyList<OrderBookRowVM> rows, OrderBookRowVM target) {
|
||||
Log.Ui.info($"{nameof(OrderBookVM)} begin attaching orders ({rows.Count} total)...");
|
||||
try {
|
||||
await Task.WhenAll(rows.Select(r => _model.cancel_order(r.order_key)));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Error canceling orders, in advance of attachment: {ex}.");
|
||||
}
|
||||
Log.Ui.info($"{nameof(OrderBookVM)} succesfully canceled orders ({rows.Count} total), in advance of attachment. Begin sending attached orders ({rows.Count} total)...");
|
||||
try {
|
||||
await Task.WhenAll(rows.Select(r => _model.send_order(r.order.Def.with(attach_target_order_key: target.order_key), r.order.State.Price, (uint)r.order.working_quantity())));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Error sending attached orders: {ex}.");
|
||||
}
|
||||
Log.Ui.info($"{nameof(OrderBookVM)} succesfully sent attached orders ({rows.Count} total).");
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(_resources);
|
||||
OnDisposed?.Invoke(true);
|
||||
async Task _detach_orders(IReadOnlyList<OrderBookRowVM> orders) {
|
||||
Log.Ui.info($"{nameof(OrderBookVM)} begin detaching orders ({orders.Count} total)...");
|
||||
try {
|
||||
await Task.WhenAll(orders.Select(r => _model.detach_order(r.order_key)));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"{nameof(OrderBookVM)} error detaching orders: {ex}.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task _modify_selected_algos_async() {
|
||||
var malgo = order_mod_vm.algo_vm.algo_root?.created_assigned();
|
||||
var algo = malgo == null ? (CustomOrderDefinitionImmutable?)null : new CustomOrderDefinitionImmutable(malgo);
|
||||
double? price = DoubleHelper.nan_nullable(order_mod_vm.price);
|
||||
uint? size = order_mod_vm.size_calculator.value;
|
||||
foreach (var r in selected_rows) {
|
||||
var ralgo = algo.HasValue && !OrderDataHelper.EqualsHelpers.equals(r.order.Def.CustomDef, algo.Value) ? algo : null;
|
||||
await _model.update_order(r.order, new_price: price, new_size: size, new_algo: ralgo);
|
||||
}
|
||||
}
|
||||
|
||||
static _GridSettingsManager _create_grid_settings_manager(
|
||||
@@ -236,25 +407,21 @@ namespace mbts.spark {
|
||||
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
}
|
||||
return new _GridSettingsManager(
|
||||
owner_title, bus, model, rows, columns,
|
||||
() => new OrderRecordTuplePredicate(),
|
||||
owner_title, bus, notification_service, model, rows, columns,
|
||||
() => {
|
||||
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
var pvm = PredicateBuilderVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
|
||||
pvm.selected_predicate_type = pvm.predicate_types.FirstOrDefault();
|
||||
return pvm;
|
||||
},
|
||||
col_id => {
|
||||
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
var pvm = PredicateBuilderVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
|
||||
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
|
||||
return pvm;
|
||||
},
|
||||
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))),
|
||||
p => p.create_predicate(notification_service.data_cache),
|
||||
p => p.create_notification_predicate(notification_service.data_cache),
|
||||
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
|
||||
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
|
||||
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_builder, a.cell_animations, a.row_animations, a.sound_notifications))),
|
||||
(notification_service == null) ? null : notification_service.data_cache,
|
||||
col_settings_flags);
|
||||
}
|
||||
|
||||
@@ -280,8 +447,225 @@ namespace mbts.spark {
|
||||
case Columns.SecurityKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.SecurityKey;
|
||||
case Columns.OrderKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.OrderKey;
|
||||
case Columns.ParentKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.ParentKey;
|
||||
case Columns.AttachedTargetOrderKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.AttachedTargetOrderKey;
|
||||
default: return default;
|
||||
}
|
||||
}
|
||||
|
||||
static IObservable<ISet<uint>> _subscribe_selected_order_keys(ObservableCollection<OrderBookRowVM> selected_rows) =>
|
||||
Observable.Create<ISet<uint>>(o => {
|
||||
bool waiting_for_mouse_up = false;
|
||||
var sub_container = new DisposableContainer();
|
||||
var builder = ImmutableHashSet<uint>.Empty.ToBuilder();
|
||||
foreach (var row in selected_rows)
|
||||
builder.Add(row.order_key);
|
||||
var selected_keys = builder.ToImmutable();
|
||||
o.OnNext(selected_keys);
|
||||
return Disposable.Create(selected_rows.Subscribe(on_selection_changed), sub_container);
|
||||
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
if (waiting_for_mouse_up)
|
||||
return;
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (OrderBookRowVM row in e.NewItems)
|
||||
selected_keys = selected_keys.Add(row.order_key);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (OrderBookRowVM row in e.OldItems)
|
||||
selected_keys = selected_keys.Remove(row.order_key);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
selected_keys = ImmutableHashSet<uint>.Empty;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
waiting_for_mouse_up = true;
|
||||
wait_for_mouse_up();
|
||||
async void wait_for_mouse_up() {
|
||||
await Wait.until_true(() => Mouse.LeftButton == MouseButtonState.Released, TimeSpan.FromSeconds(60), 333);
|
||||
builder = ImmutableHashSet<uint>.Empty.ToBuilder();
|
||||
foreach (var row in selected_rows)
|
||||
builder.Add(row.order_key);
|
||||
selected_keys = builder.ToImmutable();
|
||||
o.OnNext(selected_keys);
|
||||
waiting_for_mouse_up = false;
|
||||
}
|
||||
} else {
|
||||
o.OnNext(selected_keys);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public class OrderParamsVM : DisposableViewModelBase {
|
||||
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
|
||||
readonly OrderBookVM _owner;
|
||||
SecurityMD? _security_md;
|
||||
double _price = 0;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner)
|
||||
: this(owner,
|
||||
AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
new AlgoData(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg)),
|
||||
editing_permissions: AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues)) { }
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
_owner = owner;
|
||||
size_calculator = new CalculatorLogic(null, Observable.Return(LadderSettings.Default.calculator_buttons));
|
||||
this.algo_vm = algo_vm;
|
||||
_accounts = backing(
|
||||
nameof(selected_account),
|
||||
_model.factory.subscribe_account_tuples(),
|
||||
_model.admin_model.on_account_tuples.Where(ts => ts.Count > 0).Select(ts => new InlineUserProperty<AccountTuple>(ts[0])).Take(1),
|
||||
a => new AccountTuple?(a),
|
||||
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
|
||||
EqualityComparer<AccountTuple?>.Default,
|
||||
new[] { new PropertyValidation<AccountTuple?>(a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account")) }.ToReadOnlyList());
|
||||
set_price_command = new DelegateCommand<double>(p => price = p);
|
||||
bbo = new BBOVM { security_md = security_md };
|
||||
_dispose = Disposable.Create( bbo, algo_vm);
|
||||
}
|
||||
|
||||
public ReadOnlyObservableCollection<AccountTuple> accounts => _accounts.items;
|
||||
public AccountTuple? selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
|
||||
public SecurityImmutable? security => _owner._selected_algo_mon.common_sec;
|
||||
public SecurityMD? security_md {
|
||||
get => _security_md;
|
||||
set {
|
||||
if (Set(ref _security_md, value)) {
|
||||
RaisePropertyChanged(nameof(security), nameof(spec));
|
||||
bbo.security_md = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public ContractSpecificationImmutable? spec => security?.ContractSpec;
|
||||
public double price { get => _price; set => Set(ref _price, value); }
|
||||
public CalculatorLogic size_calculator { get; }
|
||||
public AlgoDefinitionVM algo_vm { get; }
|
||||
public BBOVM bbo { get; }
|
||||
public ICommand send_order { get; }
|
||||
public ICommand set_price_command { get; }
|
||||
|
||||
OrdersModel _model => _owner._model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
||||
/// </summary>
|
||||
class SelectedAlgosMonitor {
|
||||
static readonly TimeSpan _throttle = TimeSpan.FromSeconds(0.1);
|
||||
readonly CommonValueMonitor<AlgoSecurityKey> _algo_mon = new CommonValueMonitor<AlgoSecurityKey>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_sec_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _sec_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta>();
|
||||
readonly DynamicOrderHelper _dyn_helper;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public SelectedAlgosMonitor(DynamicOrderHelper dyn_helper) {
|
||||
_dyn_helper = dyn_helper;
|
||||
_group_mon.add_member_mon(_algo_mon, _algo_key);
|
||||
_group_mon.add_member_mon(_spreader_sec_mon, _get_spreader_sec, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_sec_mon, osam => osam.security, s => s.HasValue);
|
||||
_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(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
|
||||
_group_mon.add_member_mon(_active_mon, osam => osam.order.is_active());
|
||||
}
|
||||
|
||||
event Action _security_md_map_changed;
|
||||
|
||||
public CustomOrderDefinitionImmutable? common_algo => _algo_mon.common_value_nullable()?.algo;
|
||||
public SecurityImmutable? common_sec => _sec_mon.common_value;
|
||||
public SecurityMD? common_sec_md => common_sec is SecurityImmutable sec && _security_md_map.TryGetValue(sec.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
|
||||
public SecurityImmutable? common_spreader_sec => _spreader_sec_mon.common_value;
|
||||
public double common_price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
|
||||
public uint? common_size => _size_mon.common_value_nullable();
|
||||
public AccountTuple? common_account => _account_mon.common_value_nullable();
|
||||
public bool all_active => _active_mon.common_value;
|
||||
|
||||
public IObservable<CustomOrderDefinitionImmutable?> subscribe_common_algo() => _algo_mon.SubscribeThrottledNullable(_throttle).Select(ask => ask.HasValue ? ask.Value.algo: (CustomOrderDefinitionImmutable?)null);
|
||||
public IObservable<SecurityImmutable?> subscribe_common_spreader_sec() => _spreader_sec_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityImmutable?> subscribe_common_sec() => _sec_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityMD?> subscribe_common_sec_md() =>
|
||||
Observable.Create<SecurityMD?>(o => {
|
||||
SecurityMD? last_value = common_sec_md;
|
||||
o.OnNext(last_value);
|
||||
_sec_mon.common_value_changed += on_common_sec_changed;
|
||||
_security_md_map_changed += refresh;
|
||||
return Disposable.Create(() => {
|
||||
_sec_mon.common_value_changed -= on_common_sec_changed;
|
||||
_security_md_map_changed -= refresh;
|
||||
});
|
||||
void on_common_sec_changed(SecurityImmutable? _) => refresh();
|
||||
void refresh() {
|
||||
var curr = common_sec_md;
|
||||
if (curr != last_value)
|
||||
o.OnNext(last_value = curr);
|
||||
}
|
||||
});
|
||||
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<bool> subscribe_all_active() => _active_mon.SubscribeThrottled(_throttle);
|
||||
|
||||
public void on_row_selected(OrderBookRowVM row) {
|
||||
_group_mon.add(row.order_tuple);
|
||||
row.updated += _on_row_updated;
|
||||
}
|
||||
|
||||
public void on_row_unselected(OrderBookRowVM row) {
|
||||
_group_mon.remove(row.order_tuple);
|
||||
row.updated -= _on_row_updated;
|
||||
}
|
||||
|
||||
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
|
||||
_security_md_map = security_md_map;
|
||||
_security_md_map_changed?.Invoke();
|
||||
}
|
||||
|
||||
void _on_row_updated((OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) ch) {
|
||||
_group_mon.remove(ch.oldval);
|
||||
_group_mon.add(ch.newval);
|
||||
}
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgoMeta osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurityAlgoMeta osam) =>
|
||||
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
|
||||
|
||||
SecurityMD? _get_sec_md(OrderSecurityAlgoMeta osam) =>
|
||||
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
|
||||
|
||||
struct AlgoSecurityKey {
|
||||
public readonly CustomOrderDefinitionImmutable algo;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoStructureKey key;
|
||||
|
||||
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
|
||||
this.algo = algo;
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoSecurityKey other) =>
|
||||
security == other.security &&
|
||||
key == other.key;
|
||||
|
||||
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
<UserControl x:Class="mbts.spark.OrderBookView" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:uc="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600" d:DesignWidth="1080"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookView.SubTypeFilterTemplateKey}">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
|
||||
>
|
||||
<c:ListBoxEx.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:ListBoxEx.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="{x:Static sp:OrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={uc:Nested uc:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookView.RowTemplateKey}">
|
||||
<sp:OrderBookRowView/>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Rows="{Binding rows}"
|
||||
RowStyle="{StaticResource {x:Static sp:OrderBookView.RowStyleKey}}"
|
||||
RowTemplate="{StaticResource {x:Static sp:OrderBookView.RowTemplateKey}}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
|
||||
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="RenderTransform" TargetName="ExpandPath">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToolTip}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="Content" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</c:DIYGrid.Resources>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.CommandBindings>
|
||||
<CommandBinding
|
||||
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
|
||||
Executed="{x:Static c:DIYGrid.DefaultRowActionExecuted}"
|
||||
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Orders:"/>
|
||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -2,27 +2,31 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
//using IOrderBookWSM = mbts.persist.IWindowSettingsManager<mbts.spark.GridSettingsWithNotifications<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate>>;
|
||||
using OrderBookGridSettings = bts.shell.OrderBookWindowFactory.OrderBookGridSettings;
|
||||
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class OrderBookWindow : SparkWindow<OrderBookWindowFactory.OrderBookStartupData>, ISharedWindow {
|
||||
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "OrderBook", _default_title = "Order Book";
|
||||
const string _window_type = "OrderBook", _default_title = "Order Book";
|
||||
readonly INotificationService _notification_service;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
IDisposable _attachment_exit_sub;
|
||||
OrderBookWindowFactory.OrderBookStartupData _state;
|
||||
OrdersModel _model;
|
||||
OrderBookVM _vm;
|
||||
@@ -30,17 +34,6 @@ namespace mbts.spark {
|
||||
OrderBookWindowFactory _factory;
|
||||
SparkCommand[] _commands;
|
||||
|
||||
SparkCommand _show_fills_command
|
||||
, _modify_order_command
|
||||
, _cancel_buy_orders_command
|
||||
, _cancel_sell_orders_command
|
||||
, _cancel_all_orders_command
|
||||
, _cancel_selected_orders_command
|
||||
, _recreate_selected_orders_command
|
||||
, _toggle_collapse_all_command
|
||||
, _toggle_expand_all_command
|
||||
, _show_grid_settings_command;
|
||||
|
||||
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
|
||||
OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
|
||||
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
|
||||
@@ -49,6 +42,7 @@ namespace mbts.spark {
|
||||
_notification_service = notification_service;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
|
||||
}
|
||||
|
||||
public override OrderBookWindowFactory.OrderBookStartupData SaveState => _state = _vm?.create();
|
||||
@@ -61,10 +55,11 @@ namespace mbts.spark {
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
public string thread_id => ORDERBOOK_THREAD_ID;
|
||||
public string thread_id { get; }
|
||||
|
||||
public override void Dispose() {
|
||||
_commands.OfType<IDisposable>().DisposeAll();
|
||||
Disposable.dispose(ref _attachment_exit_sub);
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
|
||||
@@ -73,78 +68,62 @@ namespace mbts.spark {
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
|
||||
_show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
var show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
_modify_order_command = SparkCommands.ShowModifyOrder.complete(
|
||||
() => _vm.current_order.Value.Key,
|
||||
can_execute: () => {
|
||||
if (_vm.current_order.HasValue) {
|
||||
return _vm.current_order.Value.State.Status == bts.api.orderman.OrderStatus.Working;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
var toggle_right_panel_is_visible_command = new SparkCommandCommand(
|
||||
get_toggle_algo_editor_cmd_title(),
|
||||
Icons.ModifyOrder,
|
||||
_vm.toggle_algo_editor_is_visible_cmd,
|
||||
new KeyGesture(Key.M, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
_cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
|
||||
var cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
|
||||
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Buy).Select(o => o.order.Key)),
|
||||
wbids => wbids.Any(),
|
||||
() => {
|
||||
var resp = BTSMessageBox.Show(
|
||||
"Are you sure you want to cancel all BUY orders?",
|
||||
"Cancel Buy Orders",
|
||||
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
|
||||
MessageBoxImage.Question);
|
||||
return resp == BTSMessageBoxButtons.Yes;
|
||||
}
|
||||
wbids => wbids.Any()
|
||||
);
|
||||
|
||||
_cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
|
||||
var cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
|
||||
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Sell).Select(o => o.order.Key)),
|
||||
wasks => wasks.Any(),
|
||||
() => {
|
||||
var resp = BTSMessageBox.Show(
|
||||
"Are you sure you want to cancel all SELL orders?",
|
||||
"Cancel Sell Orders",
|
||||
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
|
||||
MessageBoxImage.Question);
|
||||
return resp == BTSMessageBoxButtons.Yes;
|
||||
}
|
||||
wasks => wasks.Any()
|
||||
);
|
||||
|
||||
_cancel_all_orders_command = SparkCommands.CancelOrders.complete(
|
||||
var cancel_all_orders_command = SparkCommands.CancelOrders.complete(
|
||||
_model.working_orders.Select(os => os.Select(o => o.Key)),
|
||||
worders => worders.Any(),
|
||||
() => {
|
||||
var resp = BTSMessageBox.Show(
|
||||
"Are you sure you want to cancel ALL orders?",
|
||||
"Cancel All Orders",
|
||||
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
|
||||
MessageBoxImage.Question);
|
||||
return resp == BTSMessageBoxButtons.Yes;
|
||||
}
|
||||
worders => worders.Any()
|
||||
);
|
||||
|
||||
_cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
|
||||
var cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
|
||||
() => _vm.selected_orders.Where(x => x.status() == OrderStatus.Working).Select(x => x.Key),
|
||||
can_execute: () => _vm.selected_orders.Any(x => x.status() == OrderStatus.Working)
|
||||
);
|
||||
|
||||
_recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
|
||||
var recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
|
||||
() =>
|
||||
_vm.selected_orders.Where(x => _passes_recreate_search_criteria(x))
|
||||
_vm.selected_orders.Where(x => passes_recreate_search_criteria(x))
|
||||
.Select(x => (x.Def, x.State.Price, x.State.Quantity - x.State.FilledQuantity)),
|
||||
can_execute: () => _vm.selected_orders.Any(x => _passes_recreate_search_criteria(x))
|
||||
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
|
||||
);
|
||||
|
||||
_toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
|
||||
var attach_selected_orders_command = new SparkCommandCommand(
|
||||
"Attach To Selected Orders",
|
||||
Icons.AttachAlgos,
|
||||
new DelegateCommand(_vm.begin_attach_algos, () => _vm.selected_rows.Count > 0),
|
||||
new KeyGesture(Key.A, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
var detach_selected_orders_command = new SparkCommandCommand(
|
||||
"Detach Selected Orders",
|
||||
Icons.DetachAlgos,
|
||||
new DelegateCommand(_vm.detach_selected_algos, () => _vm.selected_rows.Count > 0),
|
||||
new KeyGesture(Key.D, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
var toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
|
||||
Icons.RightArrow,
|
||||
_vm.collapse_all_rows_command);
|
||||
|
||||
_toggle_expand_all_command = new SparkCommandCommand("Expand All",
|
||||
var toggle_expand_all_command = new SparkCommandCommand("Expand All",
|
||||
Icons.DownArrow,
|
||||
_vm.expand_all_rows_command);
|
||||
|
||||
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.order_book_grid,
|
||||
@@ -169,31 +148,44 @@ namespace mbts.spark {
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
_commands = new SparkCommand[] {
|
||||
_show_fills_command,
|
||||
_modify_order_command,
|
||||
_cancel_selected_orders_command,
|
||||
_cancel_buy_orders_command,
|
||||
_cancel_sell_orders_command,
|
||||
_cancel_all_orders_command,
|
||||
_recreate_selected_orders_command,
|
||||
_toggle_collapse_all_command,
|
||||
_toggle_expand_all_command,
|
||||
_show_grid_settings_command
|
||||
_commands = new SparkCommand[] {
|
||||
show_fills_command,
|
||||
toggle_right_panel_is_visible_command,
|
||||
cancel_selected_orders_command,
|
||||
cancel_buy_orders_command,
|
||||
cancel_sell_orders_command,
|
||||
cancel_all_orders_command,
|
||||
recreate_selected_orders_command,
|
||||
attach_selected_orders_command,
|
||||
detach_selected_orders_command,
|
||||
toggle_collapse_all_command,
|
||||
toggle_expand_all_command,
|
||||
show_grid_settings_command
|
||||
};
|
||||
_vm.selected_order_changed += _vm_selected_order_changed;
|
||||
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.algo_editor_is_visible))
|
||||
.Subscribe(_ => toggle_right_panel_is_visible_command.title = get_toggle_algo_editor_cmd_title());
|
||||
_vm.selected_order_changed += vm_selected_order_changed;
|
||||
// local methods
|
||||
string get_toggle_algo_editor_cmd_title() =>
|
||||
$"{(_vm.algo_editor_is_visible ? "Hide" : "Show")} Order Params";
|
||||
|
||||
bool passes_recreate_search_criteria(OrderRecordImmutable record) =>
|
||||
(record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error) &&
|
||||
!record.has_parent() &&
|
||||
record.State.Quantity > record.State.FilledQuantity;
|
||||
|
||||
void vm_selected_order_changed(OrderRecordImmutable? e) {
|
||||
cancel_selected_orders_command.raiseCanExecuteChanged();
|
||||
recreate_selected_orders_command.raiseCanExecuteChanged();
|
||||
attach_selected_orders_command.raiseCanExecuteChanged();
|
||||
detach_selected_orders_command.raiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _passes_recreate_search_criteria(OrderRecordImmutable record) {
|
||||
return (record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error)
|
||||
&& !record.has_parent()
|
||||
&& record.State.Quantity > record.State.FilledQuantity;
|
||||
}
|
||||
|
||||
private void _vm_selected_order_changed(OrderRecordImmutable? e) {
|
||||
_modify_order_command.raiseCanExecuteChanged();
|
||||
_cancel_selected_orders_command.raiseCanExecuteChanged();
|
||||
_recreate_selected_orders_command.raiseCanExecuteChanged();
|
||||
}
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
|
||||
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
|
||||
_vm.is_attaching_algos = false;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,11 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace bts.shell {
|
||||
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookWindowFactory.OrderBookStartupData> {
|
||||
@@ -49,24 +50,57 @@ namespace bts.shell {
|
||||
|
||||
[ProtoContract]
|
||||
public class OrderBookStartupData {
|
||||
//[ProtoMember(5)]
|
||||
//public readonly List<SortingCriteria> sort_criteria;
|
||||
const bool _default_algo_editor_is_visible = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_fills_slave_grid_height = 138;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid;
|
||||
// algo editor is visible
|
||||
[ProtoMember(16)]
|
||||
[DefaultValue(_default_algo_editor_is_visible)]
|
||||
public bool algo_editor_is_visible { get; set; }
|
||||
// right panel width
|
||||
[ProtoMember(17)]
|
||||
[DefaultValue(_default_right_panel_width)]
|
||||
public double right_panel_width { get; set; }
|
||||
// fills slave grid height
|
||||
[ProtoMember(18)]
|
||||
[DefaultValue(_default_fills_slave_grid_height)]
|
||||
public double fills_slave_grid_height { get; set; }
|
||||
// fills slave grid is visible
|
||||
[ProtoMember(19)]
|
||||
public bool fills_slave_grid_is_visible { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
public OrderBookStartupData() { }
|
||||
public OrderBookStartupData() {
|
||||
algo_editor_is_visible = _default_algo_editor_is_visible;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
fills_slave_grid_height = _default_fills_slave_grid_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
|
||||
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, bool algo_editor_is_visible = false, double right_panel_width = 0, double fills_slave_grid_height = 0, bool fills_slave_grid_is_visible = false, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.algo_editor_is_visible = algo_editor_is_visible;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_slave_grid_height = fills_slave_grid_height;
|
||||
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
this.grid = grid;
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
@@ -83,9 +117,12 @@ namespace bts.shell {
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates {
|
||||
statuses = new[] { OrderStatus.Working }
|
||||
}, _default_general_grid, use_defaults: true);
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
_default_general_grid,
|
||||
_default_algo_editor_is_visible,
|
||||
_default_right_panel_width,
|
||||
_default_fills_slave_grid_height,
|
||||
use_defaults: true);
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid);
|
||||
|
||||
@@ -115,6 +152,7 @@ namespace bts.shell {
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
@@ -145,11 +183,12 @@ namespace bts.shell {
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key
|
||||
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
@@ -208,11 +247,17 @@ namespace bts.shell {
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
@@ -230,6 +275,32 @@ namespace bts.shell {
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols));
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -240,7 +311,7 @@ namespace bts.shell {
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
|
||||
public OrderBookGridSettings() { }
|
||||
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
|
||||
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, double right_panel_width = double.NaN) {
|
||||
this.general = general;
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookView {
|
||||
public class OrderBookAttachmentTargetAdorner : DIYGridRowHighlighterAdorner, IDisposable {
|
||||
IDisposable _attach_op;
|
||||
bool _is_active, _is_disposed;
|
||||
|
||||
#region IsActive
|
||||
public static readonly DependencyProperty IsActiveProperty =
|
||||
WPFHelper.create_dp<bool, OrderBookAttachmentTargetAdorner>(
|
||||
nameof(IsActive),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => {
|
||||
o._is_active = v;
|
||||
Disposable.dispose(ref o._attach_op);
|
||||
if (v && !o._is_disposed) {
|
||||
o.IsHitTestVisible = true;
|
||||
o._attach_op = Disposable.Create(
|
||||
o.SubscribePreviewMouseDown((s, e) => {
|
||||
if (e.ChangedButton == MouseButton.Left) {
|
||||
if (e.ClickCount > 1 && o.TargetRow is OrderBookRowVM r) {
|
||||
e.Handled = true;
|
||||
o._raise_target_row_selected(r);
|
||||
}
|
||||
}
|
||||
}),
|
||||
o.AdornedElement.SubscribeMouseHoverRow<OrderBookRowVM>().Subscribe(row => o.TargetRow = row));
|
||||
} else {
|
||||
o.IsHitTestVisible = false;
|
||||
o.TargetRow = null;
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
public bool IsActive {
|
||||
get => (bool)GetValue(IsActiveProperty);
|
||||
set => SetValue(IsActiveProperty, value);
|
||||
}
|
||||
|
||||
public event Action<OrderBookRowVM> TargetRowSelected;
|
||||
#endregion
|
||||
|
||||
public OrderBookAttachmentTargetAdorner(DIYGrid grid) : base(grid) { }
|
||||
|
||||
public void Dispose() {
|
||||
if (!_is_disposed) {
|
||||
_is_disposed = true;
|
||||
Disposable.dispose(ref _attach_op);
|
||||
}
|
||||
}
|
||||
|
||||
void _raise_target_row_selected(OrderBookRowVM row) => TargetRowSelected?.Invoke(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<c:DIYGrid
|
||||
x:Class="mbts.spark.OrderBookGridView"
|
||||
x:ClassModifier="internal"
|
||||
x:Name="_grid"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Resources (Brushes, Colors, etc.)-->
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<!--Converters-->
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
|
||||
<!--DataTemplates-->
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookGridView.SubTypeFilterTemplateKey}">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
|
||||
>
|
||||
<c:ListBoxEx.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:ListBoxEx.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
<!--Styles-->
|
||||
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
|
||||
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="RenderTransform" TargetName="ExpandPath">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToolTip}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="Content" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</c:DIYGrid.Resources>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.RowStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Working, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</c:DIYGrid.RowStyle>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<sp:OrderBookRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
+33
-9
@@ -1,11 +1,7 @@
|
||||
using mbts.ui.controls;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookView : UserControl {
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice }
|
||||
internal partial class OrderBookGridView : DIYGrid {
|
||||
public const string
|
||||
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
|
||||
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
|
||||
@@ -14,13 +10,41 @@ namespace mbts.spark {
|
||||
CVRowStyleKey = "CVRowStyle",
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum ColumnKeys {
|
||||
Security,
|
||||
Symbol,
|
||||
Time,
|
||||
LastChange,
|
||||
User,
|
||||
Account,
|
||||
Exchange,
|
||||
TimeInForce,
|
||||
OrderType,
|
||||
SubType,
|
||||
Side,
|
||||
Size,
|
||||
Price,
|
||||
Filled,
|
||||
AvgFilled,
|
||||
LastPrice,
|
||||
Status,
|
||||
LastMessage,
|
||||
SecurityKey,
|
||||
OrderKey,
|
||||
ParentKey,
|
||||
AttachedTargetOrderKey,
|
||||
Cancel,
|
||||
EditableQty,
|
||||
EditablePrice
|
||||
}
|
||||
|
||||
public OrderBookGridView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public OrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
<UserControl
|
||||
x:Class="mbts.spark.OrderBookView"
|
||||
x:ClassModifier="internal"
|
||||
x:Name="Root"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
Grid.Row="0"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderParamsPanel.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="GridSplitterWidth">5</sys:Double>
|
||||
<sys:Double x:Key="FillsSlaveGridMinHeight">64</sys:Double>
|
||||
<SolidColorBrush x:Key="AlgoAttachmentBackground" Color="#55009500"/>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
|
||||
<cv:ValueMap x:Key="AlgoAttachmentStateConverter" Default="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
|
||||
<cv:KeyValue Key="{u:True}" Value="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}"/>
|
||||
</cv:ValueMap>
|
||||
<Style TargetType="GridSplitter">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource Control.MediumGrayBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Control.MediumGrayBrush}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Bid Price ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Bid Size ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Ask Price ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Ask Size ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.is_attaching_algos, Source={u:DirectReference ContentRoot}, Converter={StaticResource AlgoAttachmentStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="AlgoAttachmentStates">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
|
||||
<Storyboard>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_AlgoAttachmentRoot_Border" Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0" Value="1"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Orders_DIYGrid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Pen}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsToggle_Hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_algos_button" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_algo_editor" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
|
||||
<Storyboard>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_AlgoAttachmentRoot_Border" Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0" Value="0"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Orders_DIYGrid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Arrow}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsToggle_Hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_algos_button" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_algo_editor" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding right_panel_width, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<sp:OrderBookGridView
|
||||
x:Name="PART_Orders_DIYGrid"
|
||||
Background="Transparent"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Rows="{Binding rows}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
Cursor="Arrow"
|
||||
/>
|
||||
<Border
|
||||
x:Name="PART_AlgoAttachmentRoot_Border"
|
||||
Width="{Binding ActualWidth, ElementName=PART_Orders_DIYGrid}"
|
||||
Height="{Binding ActualHeight, ElementName=PART_Orders_DIYGrid}"
|
||||
Background="{StaticResource AlgoAttachmentBackground}"
|
||||
Opacity="0"
|
||||
IsHitTestVisible="False"
|
||||
/>
|
||||
<GridSplitter
|
||||
Grid.Column="1"
|
||||
Panel.ZIndex="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
Width="{StaticResource GridSplitterWidth}"
|
||||
BorderThickness="1,0,0,0"
|
||||
/>
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="RightPanelRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference RightPanelRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.fills_slave_grid_is_visible, Source={u:DirectReference RightPanelRoot}, Converter={x:Static sp:OrderBookView.FillsPanelVisualStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Fills_DIYGrid" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsAlgo_GridSplitter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
|
||||
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
|
||||
</Int32AnimationUsingKeyFrames>
|
||||
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
|
||||
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
|
||||
</Int32AnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Fills_DIYGrid" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsAlgo_GridSplitter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
|
||||
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
|
||||
</Int32AnimationUsingKeyFrames>
|
||||
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
|
||||
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
|
||||
</Int32AnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource GridSplitterWidth}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition MinHeight="{StaticResource FillsSlaveGridMinHeight}" Height="{Binding fills_slave_grid_height, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}" MaxHeight="{Binding ActualHeight, ElementName=RightPanelRoot, Converter={x:Static sp:OrderBookView.FillsSlaveGridMaxHeightConverter}}"/>
|
||||
<RowDefinition MinHeight="{x:Static sp:OrderBookView.GridSplitterHeight}" Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="PART_FillsToggle_Hyperlink" Grid.Column="1">
|
||||
<TextBlock.Margin>
|
||||
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="4"/>
|
||||
</TextBlock.Margin>
|
||||
<Hyperlink Command="{Binding toggle_fills_slave_grid_is_visible_cmd}">
|
||||
<Hyperlink.Inlines>
|
||||
<Run Text="{Binding current_row, Converter={x:Static sp:OrderBookView.OrderDescriptionConverter}, Mode=OneWay}"/>
|
||||
</Hyperlink.Inlines>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<c:DIYGrid x:Name="PART_Fills_DIYGrid" Grid.Column="1" Grid.Row="1" Columns="{Binding fills_vm.columns}" Rows="{Binding fills_vm.rows}">
|
||||
<c:DIYGrid.Margin>
|
||||
<Thickness Bottom="{x:Static sp:OrderBookView.GridSplitterHeight}"/>
|
||||
</c:DIYGrid.Margin>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<GridSplitter
|
||||
x:Name="PART_FillsAlgo_GridSplitter"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Panel.ZIndex="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
|
||||
BorderThickness="0,1,0,0"
|
||||
/>
|
||||
<GroupBox
|
||||
x:Name="order_params_group_box"
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
Padding="12"
|
||||
BorderThickness="1"
|
||||
Header="Order Parameters"
|
||||
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
|
||||
>
|
||||
<usp:OrderParamsPanel
|
||||
x:Name="order_params_panel"
|
||||
DataContext="{Binding order_mod_vm}"
|
||||
Price="{Binding price}"
|
||||
Size="{Binding size_calculator.value}"
|
||||
Accounts="{Binding accounts}"
|
||||
SelectedAccount="{Binding selected_account}"
|
||||
BestBidPrice="{Binding bbo.bid}"
|
||||
BestBidSize="{Binding bbo.bid_size}"
|
||||
BestAskPrice="{Binding bbo.ask}"
|
||||
BestAskSize="{Binding bbo.ask_size}"
|
||||
SizePresets="{Binding size_calculator.buttons}"
|
||||
Security="{Binding security}"
|
||||
AlgoVM="{Binding algo_vm}"
|
||||
IsEnabled="{Binding IsEnabled, ElementName=modify_algos_button}"
|
||||
>
|
||||
<usp:OrderParamsPanel.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
|
||||
</usp:OrderParamsPanel.CommandBindings>
|
||||
</usp:OrderParamsPanel>
|
||||
</GroupBox>
|
||||
<Grid
|
||||
x:Name="attachment_algo_editor"
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
MinWidth="320"
|
||||
Margin="0,4,0,0"
|
||||
Visibility="Collapsed"
|
||||
>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<GroupBox
|
||||
Padding="12"
|
||||
Header="Attached Order Parameters"
|
||||
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
|
||||
>
|
||||
<usp:OrderParamsPanel
|
||||
x:Name="attached_order_params_panel"
|
||||
DataContext="{Binding attach_vm}"
|
||||
Price="{Binding price}"
|
||||
Size="{Binding size_calculator.value}"
|
||||
Accounts="{Binding accounts}"
|
||||
SelectedAccount="{Binding selected_account}"
|
||||
BestBidPrice="{Binding bbo.bid}"
|
||||
BestBidSize="{Binding bbo.bid_size}"
|
||||
BestAskPrice="{Binding bbo.ask}"
|
||||
BestAskSize="{Binding bbo.ask_size}"
|
||||
SizePresets="{Binding size_calculator.buttons}"
|
||||
Security="{Binding security}"
|
||||
AlgoVM="{Binding algo_vm}"
|
||||
IsEnabled="{Binding IsEnabled, ElementName=send_attached_orders_button}"
|
||||
>
|
||||
<usp:OrderParamsPanel.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
|
||||
</usp:OrderParamsPanel.CommandBindings>
|
||||
</usp:OrderParamsPanel>
|
||||
</GroupBox>
|
||||
<StackPanel
|
||||
x:Name="attach_orders_dialog_skirt"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="{StaticResource DialogSkirtPadding}"
|
||||
Orientation="Horizontal"
|
||||
Visibility="Collapsed"
|
||||
>
|
||||
<Button x:Name="send_attached_orders_button" Command="{Binding send_attached_orders_cmd}" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}">
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Send Attached Orders ({0})'}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
|
||||
<Setter Property="Text" Value="Send Attached Orders"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button u:Behavior.CloseWindow="False" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding cancel_attach_orders_cmd}" Style="{StaticResource CancelButton}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button x:Name="modify_algos_button" Grid.Row="1" HorizontalAlignment="Right" Margin="{StaticResource DialogSkirtPadding}" Command="{Binding modify_selected_algos_command}">
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Modify Algos ({0})'}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
|
||||
<Setter Property="Text" Value="Modify Algos"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Grid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Orders:"/>
|
||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,127 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookView : UserControl {
|
||||
public const double
|
||||
GridSplitterHeight = 5,
|
||||
MinAlgoEditorHeight = 64,
|
||||
MinAttachOrderParamsPanelWidth = 340,
|
||||
AttachOrderParamsPanelWidthRatio = 0.33;
|
||||
|
||||
public static readonly IValueConverter
|
||||
OrderDescriptionConverter = ValueConverter.create<OrderBookRowVM, string>(row => row == null ? string.Empty : row.order.short_description(row.security)),
|
||||
FillsSlaveGridMaxHeightConverter = ValueConverter.create<double, double>(right_panel_height => right_panel_height - (GridSplitterHeight + MinAlgoEditorHeight)),
|
||||
FillsPanelVisualStateConverter = ValueConverter.create<bool, string>(show => show ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive),
|
||||
AttachmentTargetCursorConverter = ValueConverter.create<bool, Cursor>(is_active => is_active ? Cursors.Pen : Cursors.Arrow);
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
const int delay_ms = 25;
|
||||
AttachOp? attach_op = null; ;
|
||||
vm.begin_attaching += on_begin_attaching;
|
||||
vm.end_attaching += on_end_attaching;
|
||||
vm.SubscribeDisposed(() => {
|
||||
if (attach_op.HasValue) {
|
||||
vm.right_panel_width = attach_op.Value.raw_right_panel_width;
|
||||
vm.fills_slave_grid_is_visible = attach_op.Value.fills_slave_grid_is_visible;
|
||||
}
|
||||
vm.begin_attaching -= on_begin_attaching;
|
||||
vm.end_attaching -= on_end_attaching;
|
||||
});
|
||||
async void on_begin_attaching() {
|
||||
if (attach_op.HasValue) {
|
||||
// begin-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
|
||||
return;
|
||||
}
|
||||
attach_op = new AttachOp(vm.algo_editor_is_visible, vm.fills_slave_grid_is_visible, vm.raw_right_panel_width);
|
||||
vm.fills_slave_grid_is_visible = false;
|
||||
vm.algo_editor_is_visible = true;
|
||||
double target_width = Math.Max(ActualWidth * AttachOrderParamsPanelWidthRatio, MinAttachOrderParamsPanelWidth);
|
||||
if (vm.right_panel_width < target_width) {
|
||||
foreach (double incr in _get_incremental_widths(attach_op.Value.right_panel_is_visible ? attach_op.Value.raw_right_panel_width : 0, target_width)) {
|
||||
vm.right_panel_width = incr;
|
||||
await Task.Delay(delay_ms);
|
||||
if (!attach_op.HasValue)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async void on_end_attaching() {
|
||||
if (!attach_op.HasValue) {
|
||||
// end-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
|
||||
return;
|
||||
}
|
||||
var incr_widths = _get_incremental_widths(vm.right_panel_width, attach_op.Value.raw_right_panel_width);
|
||||
vm.algo_editor_is_visible = attach_op.Value.right_panel_is_visible;
|
||||
vm.fills_slave_grid_is_visible = attach_op.Value.fills_slave_grid_is_visible;
|
||||
vm.right_panel_width = attach_op.Value.raw_right_panel_width;
|
||||
attach_op = null;
|
||||
foreach (double incr in incr_widths) {
|
||||
vm.right_panel_width = incr;
|
||||
await Task.Delay(delay_ms);
|
||||
if (attach_op.HasValue)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<double> _get_incremental_widths(double from, double to) {
|
||||
var list = new List<double> { to };
|
||||
int incr = 1;
|
||||
if (from < to) {
|
||||
do {
|
||||
list.Add(to - (incr * 0.6666));
|
||||
list.Add(to - incr);
|
||||
from += incr;
|
||||
incr += incr;
|
||||
} while (from < to);
|
||||
} else if (from > to) {
|
||||
do {
|
||||
list.Add(to + incr);
|
||||
incr += incr;
|
||||
to += incr;
|
||||
} while (from > to);
|
||||
}
|
||||
for (int i = list.Count - 1; i >= 0; i--) {
|
||||
yield return list[i];
|
||||
}
|
||||
}
|
||||
|
||||
struct AttachOp {
|
||||
public readonly bool right_panel_is_visible, fills_slave_grid_is_visible;
|
||||
public readonly double raw_right_panel_width;
|
||||
|
||||
public AttachOp(bool right_panel_is_visible, bool fills_slave_grid_is_visible, double raw_right_panel_width) {
|
||||
this.right_panel_is_visible = right_panel_is_visible;
|
||||
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
|
||||
this.raw_right_panel_width = raw_right_panel_width;
|
||||
}
|
||||
}
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class FillsPanel {
|
||||
public const string GroupName = nameof(FillsPanel);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class AlgoAttachment {
|
||||
public const string GroupName = nameof(AlgoAttachment);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<ControlTemplate x:Key="OrderParamsPanelTemplate" TargetType="usp:OrderParamsPanel">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:PriceEntryControl
|
||||
ContractSpec="{Binding ContractSpec, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Value="{Binding Price, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
ToolTip="Price"
|
||||
/>
|
||||
<Button
|
||||
x:Name="best_bid_button"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
|
||||
CommandParameter="{Binding BestBidPrice, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
FontWeight="Bold"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}}"
|
||||
>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="BestBidPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="best_ask_button"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
|
||||
CommandParameter="{Binding BestAskPrice, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
FontWeight="Bold"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}}"
|
||||
>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="BestAskPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:TickUpDownNullableUInt Value="{Binding Size, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Size"/>
|
||||
<Button
|
||||
x:Name="best_bid_size_button"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding BestBidSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}}"
|
||||
/>
|
||||
<Button
|
||||
x:Name="best_ask_size_button"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding BestAskSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}}"
|
||||
/>
|
||||
<ItemsControl Grid.Row="2" Grid.RowSpan="2" Margin="-2,-2,-2,0" ItemsSource="{Binding SizePresets, RelativeSource={RelativeSource TemplatedParent}}" Focusable="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="3" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="2,4,2,0"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Grid Grid.Row="2" Grid.RowSpan="2" Grid.Column="2" Grid.ColumnSpan="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ComboBox Margin="0,2,0,4" ItemsSource="{Binding Accounts, RelativeSource={RelativeSource TemplatedParent}}" SelectedItem="{Binding SelectedAccount, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Choose Account"/>
|
||||
<ComboBox Grid.Row="1">
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource DisabledPlaceholderComboBox}">
|
||||
<Setter Property="ItemsSource">
|
||||
<Setter.Value>
|
||||
<x:ArrayExtension Type="{x:Type sys:String}">
|
||||
<sys:String>Time In Force (N/A)</sys:String>
|
||||
</x:ArrayExtension>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<usp:AlgoEditorView x:Name="algo_editor" Grid.Row="4" DataContext="{Binding AlgoVM, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style TargetType="usp:OrderParamsPanel">
|
||||
<Setter Property="Template" Value="{StaticResource OrderParamsPanelTemplate}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,188 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
class OrderParamsPanel : Control {
|
||||
public const string
|
||||
BestBidPriceButtonStyleKey = nameof(BestBidPriceButtonStyleKey),
|
||||
BestBidSizeButtonStyleKey = nameof(BestBidSizeButtonStyleKey),
|
||||
BestAskPriceButtonStyleKey = nameof(BestAskPriceButtonStyleKey),
|
||||
BestAskSizeButtonStyleKey = nameof(BestAskSizeButtonStyleKey),
|
||||
SizeCalculatorButtonStyleKey = nameof(SizeCalculatorButtonStyleKey);
|
||||
|
||||
public static readonly RoutedCommand SetPriceCommand = new RoutedCommand();
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler SetPriceExecuted =
|
||||
(s, e) => {
|
||||
if (s is OrderParamsPanel opp)
|
||||
opp.SetCurrentValue(PriceProperty, ConvertUtils.try_convert(e.Parameter, out double d) ? d : double.NaN);
|
||||
};
|
||||
|
||||
public static readonly CanExecuteRoutedEventHandler
|
||||
SetPriceCanExecute = (s, e) => e.CanExecute = e.Parameter is double,
|
||||
SetSizeCanExecute = (s, e) => e.CanExecute = e.Parameter is uint;
|
||||
|
||||
static OrderParamsPanel() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(OrderParamsPanel), new FrameworkPropertyMetadata(typeof(OrderParamsPanel)));
|
||||
}
|
||||
|
||||
#region Price
|
||||
public static readonly DependencyProperty PriceProperty =
|
||||
WPFHelper.create_dp<double, OrderParamsPanel>(nameof(Price), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, double.NaN);
|
||||
|
||||
public double Price {
|
||||
get => (double)GetValue(PriceProperty);
|
||||
set => SetValue(PriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Size
|
||||
public static readonly DependencyProperty SizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(Size), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public uint? Size {
|
||||
get => GetValue(SizeProperty) as uint?;
|
||||
set => SetValue(SizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SizePresets
|
||||
public static readonly DependencyProperty SizePresetsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(SizePresets), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable SizePresets {
|
||||
get => GetValue(SizePresetsProperty) as IEnumerable;
|
||||
set => SetValue(SizePresetsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestBidPrice
|
||||
public static readonly DependencyProperty BestBidPriceProperty =
|
||||
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestBidPrice));
|
||||
|
||||
public double? BestBidPrice {
|
||||
get => GetValue(BestBidPriceProperty) as double?;
|
||||
set => SetValue(BestBidPriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestBidSize
|
||||
public static readonly DependencyProperty BestBidSizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestBidSize));
|
||||
|
||||
public uint? BestBidSize {
|
||||
get => GetValue(BestBidSizeProperty) as uint?;
|
||||
set => SetValue(BestBidSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestAskPrice
|
||||
public static readonly DependencyProperty BestAskPriceProperty =
|
||||
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestAskPrice));
|
||||
|
||||
public double? BestAskPrice {
|
||||
get => GetValue(BestAskPriceProperty) as double?;
|
||||
set => SetValue(BestAskPriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestAskSize
|
||||
public static readonly DependencyProperty BestAskSizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestAskSize));
|
||||
|
||||
public uint? BestAskSize {
|
||||
get => GetValue(BestAskSizeProperty) as uint?;
|
||||
set => SetValue(BestAskSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Accounts
|
||||
public static readonly DependencyProperty AccountsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(Accounts), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable Accounts {
|
||||
get => GetValue(AccountsProperty) as IEnumerable;
|
||||
set => SetValue(AccountsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedAccount
|
||||
public static readonly DependencyProperty SelectedAccountProperty =
|
||||
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedAccount));
|
||||
|
||||
public object SelectedAccount {
|
||||
get => GetValue(SelectedAccountProperty);
|
||||
set => SetValue(SelectedAccountProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TimeInForces
|
||||
public static readonly DependencyProperty TimeInForcesProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(TimeInForces), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable TimeInForces {
|
||||
get => GetValue(TimeInForcesProperty) as IEnumerable;
|
||||
set => SetValue(TimeInForcesProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedTimeInForce
|
||||
public static readonly DependencyProperty SelectedTimeInForceProperty =
|
||||
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedTimeInForce));
|
||||
|
||||
public object SelectedTimeInForce {
|
||||
get => GetValue(SelectedTimeInForceProperty);
|
||||
set => SetValue(SelectedTimeInForceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Security
|
||||
public static readonly DependencyProperty SecurityProperty =
|
||||
WPFHelper.create_dp<SecurityImmutable?, OrderParamsPanel>(
|
||||
nameof(Security),
|
||||
(o, v) => o.CoerceValue(ContractSpecProperty));
|
||||
|
||||
public SecurityImmutable? Security {
|
||||
get => GetValue(SecurityProperty) as SecurityImmutable?;
|
||||
set => SetValue(SecurityProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ContractSpec
|
||||
public static readonly DependencyProperty ContractSpecProperty =
|
||||
WPFHelper.create_dp<ContractSpecificationImmutable?, OrderParamsPanel>(
|
||||
nameof(ContractSpec),
|
||||
null,
|
||||
(d, base_value) => d.Security?.ContractSpec ?? base_value ?? SecurityDataHelper.Defaults.ContractSpecificationImmutable);
|
||||
|
||||
public ContractSpecificationImmutable? ContractSpec {
|
||||
get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?;
|
||||
set => SetValue(ContractSpecProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AlgoVM
|
||||
public static readonly DependencyProperty AlgoVMProperty =
|
||||
WPFHelper.create_dp<AlgoDefinitionVM, OrderParamsPanel>(
|
||||
nameof(AlgoVM),
|
||||
(o, v) => o._algo_editor?.SetCurrentValue(VisibilityProperty, v != null ? Visibility.Visible : Visibility.Collapsed));
|
||||
|
||||
public AlgoDefinitionVM AlgoVM {
|
||||
get => GetValue(AlgoVMProperty) as AlgoDefinitionVM;
|
||||
set => SetValue(AlgoVMProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
AlgoEditorView _algo_editor;
|
||||
public override void OnApplyTemplate() {
|
||||
_algo_editor = GetTemplateChild("algo_editor") as AlgoEditorView;
|
||||
_algo_editor?.SetCurrentValue(VisibilityProperty, AlgoVM != null ? Visibility.Visible : Visibility.Collapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,17 +84,17 @@ namespace bts.shell {
|
||||
static class DefaultColumns {
|
||||
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, false, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings security_type => new GridColumnSettings(col_id(Columns.SecurityType), DefaultColumnHeaders.SecurityType, width: DefaultColumnWidths.SecurityType);
|
||||
public static GridColumnSettings start_net_position => new GridColumnSettings(col_id(Columns.StartNetPos), DefaultColumnHeaders.StartNetPos, width: DefaultColumnWidths.Start_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, false, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_long_pnl => new GridColumnSettings(col_id(Columns.DayLong), DefaultColumnHeaders.DayLong, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_short_pnl => new GridColumnSettings(col_id(Columns.DayShort), DefaultColumnHeaders.DayShort, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_pnl => new GridColumnSettings(col_id(Columns.DayPNL), DefaultColumnHeaders.DayPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, false, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, false, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings pnl_price => new GridColumnSettings(col_id(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
@@ -158,8 +158,6 @@ namespace bts.shell {
|
||||
// ProtoMember 13 cannot be used further
|
||||
[ProtoMember(14)]
|
||||
public SecurityType[] security_types;
|
||||
[ProtoMember(15)]
|
||||
public bool[] has_day_trades;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using OrderTuple = System.ValueTuple<bts.api.SecurityImmutable?, bts.api.orderman.OrderRecordImmutable>;
|
||||
|
||||
namespace bts.shell {
|
||||
interface INotificationService {
|
||||
@@ -19,14 +20,40 @@ namespace bts.shell {
|
||||
INotificationServiceDataCache data_cache { get; }
|
||||
IDisposable link_to_activity(IPredicate<Activity> predicate, SoundNotification sound);
|
||||
IDisposable link_to_activity(INotificationPredicate<Activity> toast_notification_predicate);
|
||||
IDisposable link_to_order(IPredicate<OrderSecurity> predicate, SoundNotification sound);
|
||||
IDisposable link_to_order(INotificationPredicate<OrderSecurity> toast_notification_predicate);
|
||||
IDisposable link_to_order(IPredicate<OrderTuple> predicate, SoundNotification sound);
|
||||
IDisposable link_to_order(INotificationPredicate<OrderTuple> toast_notification_predicate);
|
||||
}
|
||||
|
||||
static class NotificationServiceExtensions {
|
||||
/// TODO: remove this extension and force callers to use the appropriate 'link' method in <see cref="INotificationService"/> (it is smelly, and is NOT ACTUALLY STRONGLY TYPED)
|
||||
public static IDisposable link<T>(this INotificationService notification_service, IPredicate<T> predicate, SoundNotification sound) {
|
||||
if (predicate is IPredicate<OrderTuple> orp)
|
||||
return notification_service.link_to_order(orp, sound);
|
||||
if (predicate is IPredicate<OrderSecurity> osp) {
|
||||
// Note: watch out for implicit types as generic type arguments (although OrderSecurity can be implicitly cast to OrderTuple, IPredicate<OrderSecurity> CANNOT be implicitly cast to IPredicate<OrderTuple>)
|
||||
return notification_service.link_to_order(PredicateBuilder.create<OrderTuple>(ot => osp.trigger(new OrderSecurity(ot.Item1, ot.Item2))), sound);
|
||||
}
|
||||
if (predicate is IPredicate<Activity> ap)
|
||||
return notification_service.link_to_activity(ap, sound);
|
||||
Log.Ui.error($"Unable to activate sound-trigger (unrecognized predicate type '{typeof(T)}').");
|
||||
return Disposable.empty;
|
||||
}
|
||||
|
||||
/// TODO: remove this extension and force callers to use the appropriate 'link' method in <see cref="INotificationService"/> (it is smelly, and is NOT ACTUALLY STRONGLY TYPED)
|
||||
public static IDisposable link<T>(this INotificationService notification_service, INotificationPredicate<T> toast_notification_predicate) {
|
||||
if (toast_notification_predicate is INotificationPredicate<OrderTuple> orp)
|
||||
return notification_service.link_to_order(orp);
|
||||
if (toast_notification_predicate is INotificationPredicate<Activity> ap)
|
||||
return notification_service.link_to_activity(ap);
|
||||
Log.Ui.error($"Unable to activate toast-notification-trigger (unrecognized predicate type '{typeof(T)}').");
|
||||
return Disposable.empty;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(INotificationService))]
|
||||
partial class NotificationService : INotificationService, IDisposable {
|
||||
readonly NotificationProcessor<Activity> _activity_notifications;
|
||||
readonly NotificationProcessor<OrderSecurity> _order_notifications;
|
||||
readonly NotificationProcessor<OrderTuple> _order_notifications;
|
||||
readonly DataCache _data_cache = new DataCache();
|
||||
|
||||
IDisposable _dispose;
|
||||
@@ -35,7 +62,7 @@ namespace bts.shell {
|
||||
public NotificationService(IBus bus, INotifier notifier, OrdersModel orders_model) {
|
||||
this.notifier = notifier;
|
||||
_activity_notifications = new NotificationProcessor<Activity>(notifier);
|
||||
_order_notifications = new NotificationProcessor<OrderSecurity>(notifier);
|
||||
_order_notifications = new NotificationProcessor<OrderTuple>(notifier);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.LogActivity).Subscribe(_on_activity),
|
||||
orders_model.on_orders.Subscribe(_on_orders)
|
||||
@@ -48,14 +75,14 @@ namespace bts.shell {
|
||||
|
||||
public IDisposable link_to_activity(IPredicate<Activity> predicate, SoundNotification sound) => _activity_notifications.link(predicate, sound);
|
||||
public IDisposable link_to_activity(INotificationPredicate<Activity> toast_notification_predicate) => _activity_notifications.link(toast_notification_predicate);
|
||||
public IDisposable link_to_order(IPredicate<OrderSecurity> predicate, SoundNotification sound) => _order_notifications.link(predicate, sound);
|
||||
public IDisposable link_to_order(INotificationPredicate<OrderSecurity> toast_notification_predicate) => _order_notifications.link(toast_notification_predicate);
|
||||
public IDisposable link_to_order(IPredicate<OrderTuple> predicate, SoundNotification sound) => _order_notifications.link(predicate, sound);
|
||||
public IDisposable link_to_order(INotificationPredicate<OrderTuple> toast_notification_predicate) => _order_notifications.link(toast_notification_predicate);
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
void _on_activity(Activity activity) =>
|
||||
_activity_notifications.process_item(activity);
|
||||
|
||||
void _on_orders(TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> update) {
|
||||
void _on_orders(TableUpdate<(SecurityImmutable?,OrderRecordImmutable),uint> update) {
|
||||
// must update notification_service_data_cache first, some of the predicate/triggers (esp. ToastNotifications) will use the notification_service_data_cache to lookup order-history
|
||||
_data_cache.update(update.as_iincremental());
|
||||
foreach (var o in update.added.Concat(update.changed))
|
||||
|
||||
@@ -79,10 +79,12 @@ namespace mbts.spark {
|
||||
RequestClose?.Invoke(this, new EventArgs());
|
||||
}
|
||||
});
|
||||
bbo = new BBOVM();
|
||||
_dispose = Disposable.Create(
|
||||
size_calculator,
|
||||
properties_vm,
|
||||
bbo = new BBOVM(security_md_source),
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
bbo,
|
||||
order_source.OnDispatcher().Subscribe(ort => {
|
||||
key = ort.order.Key;
|
||||
if (DoubleHelper.IsNaNOrInfinity(price))
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace mbts.spark {
|
||||
) {
|
||||
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
|
||||
bbo = new BBOVM(security_md_source);
|
||||
bbo = new BBOVM();
|
||||
send_order = new DelegateCommand<Side>(side => {
|
||||
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
|
||||
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
|
||||
@@ -59,6 +59,8 @@ namespace mbts.spark {
|
||||
});
|
||||
set_price_command = new DelegateCommand<double>(set_price);
|
||||
_dispose = Disposable.Create(
|
||||
bbo,
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
size_calculator = new CalculatorLogic(0, calculator_buttons),
|
||||
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
@@ -27,7 +28,7 @@ namespace bts.spark {
|
||||
static readonly IEqualityComparer<OrderRecordImmutable> _id_comparer =
|
||||
bts.utils.EqualityComparer.Create<OrderRecordImmutable, uint>(a => a.Key);
|
||||
|
||||
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint>> orders) {
|
||||
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<(SecurityImmutable? sec,OrderRecordImmutable order), uint>> orders) {
|
||||
_bus = bus;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(sec => _security = sec),
|
||||
@@ -83,25 +84,18 @@ namespace bts.spark {
|
||||
public static string modified_description(OrderRecordImmutable old_order, OrderRecordImmutable new_order, ContractSpecificationImmutable? spec) {
|
||||
double old_price = old_order.State.Price, new_price = new_order.State.Price;
|
||||
uint old_size = old_order.State.Quantity, new_size = new_order.State.Quantity;
|
||||
string modified_size_partial_description(int working_size) {
|
||||
return $"size from {old_size} to {new_size}, working {working_size}";
|
||||
uint old_attached_target_key = old_order.attached_target_order_key(), new_attached_target_key = new_order.attached_target_order_key();
|
||||
return StringBuilderEx.join(prefix, change_descs());
|
||||
//
|
||||
string prefix() => $"({old_order.Key}) Changed";
|
||||
IEnumerable<string> change_descs() {
|
||||
if (old_price != new_price)
|
||||
yield return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
|
||||
if (old_size != new_size)
|
||||
yield return $"size from {old_size} to {new_size}, working {new_order.working_quantity()}";
|
||||
if (old_attached_target_key != new_attached_target_key)
|
||||
yield return $"attached target from {old_attached_target_key} to {new_attached_target_key}";
|
||||
}
|
||||
string modified_price_partial_description() {
|
||||
return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
|
||||
}
|
||||
if (old_price != new_price || old_size != new_size) {
|
||||
StringBuilder sb = new StringBuilder($"({old_order.Key}) Changed ");
|
||||
if (old_price != new_price) {
|
||||
sb.Append(modified_price_partial_description());
|
||||
if (old_size != new_size)
|
||||
sb.Append($", {modified_size_partial_description(new_order.working_quantity())}");
|
||||
}
|
||||
else
|
||||
sb.Append(modified_size_partial_description(new_order.working_quantity()));
|
||||
return sb.ToString();
|
||||
}
|
||||
else
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
static string _header(uint key) => $"({key})";
|
||||
@@ -184,21 +178,31 @@ namespace bts.spark {
|
||||
format_cancel_error(order, security?.ContractSpec, error));
|
||||
}
|
||||
|
||||
internal void update_order(OrderRecordImmutable order, double price, uint size) {
|
||||
internal void update_order(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "change from");
|
||||
|
||||
internal void update_order_failed(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "failed to change from");
|
||||
|
||||
void _log_order_update(OrderRecordImmutable order, double price, uint size, bool algo_changed, string change_desc_prefix) {
|
||||
if (price == order.price() && size == order.working_quantity() && !algo_changed) {
|
||||
// nothing actually changed
|
||||
return;
|
||||
}
|
||||
const string algo_change_text = "change algo parameters";
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var spec = security?.ContractSpec;
|
||||
log_status(ActivityLogCategory.modify,
|
||||
security,
|
||||
$"({order.Key}) change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
|
||||
var desc_builder = new StringBuilderEx();
|
||||
if (price != order.price() || size != order.working_quantity()) {
|
||||
desc_builder.Add(order.State.Quantity.ToString(), "@", spec.format(order.State.Price), "to ", size.ToString(), "@", spec.format(price));
|
||||
if (algo_changed)
|
||||
desc_builder.Add("; ", algo_change_text);
|
||||
} else if (algo_changed) {
|
||||
desc_builder.Add(algo_change_text);
|
||||
}
|
||||
desc_builder.Insert(0, $"{change_desc_prefix} ");
|
||||
desc_builder.Insert(0, $"({order.Key}) ");
|
||||
log_status(ActivityLogCategory.modify, security, desc_builder.ToString());
|
||||
}
|
||||
|
||||
internal void update_order_failed(OrderRecordImmutable order, double price, uint size) {
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var spec = security?.ContractSpec;
|
||||
log_status(ActivityLogCategory.modify,
|
||||
security,
|
||||
$"({order.Key}) failed to change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -346,9 +346,9 @@ namespace mbts.spark {
|
||||
_time_in_force.observe_current(start_immediately);
|
||||
|
||||
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
|
||||
_model.algo_model.templates_enabled() ?
|
||||
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
|
||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||
_model.algo_model.templates_enabled() ?
|
||||
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
|
||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||
|
||||
public OrderDefinition create(Side side) {
|
||||
if (HasErrors)
|
||||
|
||||
@@ -7,31 +7,18 @@ namespace bts.api.orderman {
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
piq = null;
|
||||
}
|
||||
public OrderSecurity((SecurityImmutable? sec, OrderRecordImmutable order) t) {
|
||||
security = t.sec;
|
||||
order = t.order;
|
||||
piq = null;
|
||||
}
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
this.piq = piq;
|
||||
}
|
||||
//public OrderSecurity(OrderSecurity t) {
|
||||
// security = t.security;
|
||||
// order = t.order;
|
||||
// piq = t.piq;
|
||||
//}
|
||||
public static implicit operator OrderSecurity(ValueTuple<SecurityImmutable?, OrderRecordImmutable> x) => new OrderSecurity(x);
|
||||
public SecurityImmutable? security { get; }
|
||||
public OrderRecordImmutable order { get; }
|
||||
public uint? piq { get; }
|
||||
|
||||
public override bool Equals(object obj) => (obj is OrderSecurity os) && Equals(os);
|
||||
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security && piq == other.piq;
|
||||
public override int GetHashCode() => (security, order, piq).GetHashCode();
|
||||
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security;
|
||||
public override int GetHashCode() => (security, order).GetHashCode();
|
||||
|
||||
public static bool operator == (OrderSecurity left, OrderSecurity right) => left.Equals(right);
|
||||
public static bool operator != (OrderSecurity left, OrderSecurity right) => !left.Equals(right);
|
||||
|
||||
@@ -11,7 +11,7 @@ using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
|
||||
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel> {
|
||||
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel>, IObserver<AlgoEditingPermissions> {
|
||||
static readonly AlgoDefi _empty = new AlgoDefi(new AlgoDef());
|
||||
readonly VersionContainer<AlgoDefi> _definition;
|
||||
AlgoFormats _format;
|
||||
@@ -37,10 +37,7 @@ namespace mbts.spark {
|
||||
impl.is_expanded = value;
|
||||
}
|
||||
}
|
||||
public bool can_edit_values => impl?.can_edit_values ?? false;
|
||||
public bool can_edit_structure => impl?.can_edit_structure ?? false;
|
||||
public bool can_save_values => impl?.can_save_values ?? false;
|
||||
public bool can_save_structure => impl?.can_save_structure ?? false;
|
||||
public AlgoEditingPermissions editing_permissions => _editing_permissions;
|
||||
public string display_name => impl?.display_name ?? "(Default)";
|
||||
public AlgoDefi definition => _definition.value;
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors { get; } = new ObservableCollection<IAlgoSelector>();
|
||||
@@ -48,10 +45,6 @@ namespace mbts.spark {
|
||||
public void assign_to(AlgoDef value) => value.MergeFrom(definition.CloneToMutable());
|
||||
public bool key_match(KeyEventArgs e) => false;
|
||||
public void load_from(AlgoDefi value) => impl?.load_from(value);
|
||||
public void set_permissions(AlgoEditingPermissions editing_permissions) {
|
||||
_editing_permissions = editing_permissions;
|
||||
impl?.set_permissions(editing_permissions);
|
||||
}
|
||||
public Task<AlgoDefi?> load_from_async(AlgoDefi definition) =>
|
||||
impl?.load_from_async(definition) ?? Task.FromResult<AlgoDefi?>(definition);
|
||||
|
||||
@@ -98,6 +91,13 @@ namespace mbts.spark {
|
||||
_on_impl_definition_changed();
|
||||
}
|
||||
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) {
|
||||
Set(ref _editing_permissions, editing_permissions, nameof(this.editing_permissions));
|
||||
(impl as IObserver<AlgoEditingPermissions>)?.OnNext(editing_permissions);
|
||||
}
|
||||
|
||||
void _on_impl_definition_changed() {
|
||||
var orig_def = definition;
|
||||
_definition.increment_version();
|
||||
@@ -107,4 +107,4 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(nameof(order_type), nameof(meta));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -11,8 +12,9 @@ using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
public class DynamicOrderHelper
|
||||
{
|
||||
class DynamicOrderHelper {
|
||||
public readonly IMaskIndexInt32<string> dynamic_type_index;
|
||||
|
||||
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
|
||||
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
|
||||
|
||||
@@ -21,6 +23,7 @@ namespace mbts.spark {
|
||||
private DynamicOrderHelper(OrderService order_service)
|
||||
{
|
||||
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
|
||||
dynamic_type_index = new MaskIndexInt32<string>(_dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType).ToList());
|
||||
}
|
||||
|
||||
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
|
||||
@@ -42,20 +45,25 @@ namespace mbts.spark {
|
||||
|
||||
internal DynamicOrderDefinition create_def(string dynamic_type) {
|
||||
var desc = get_descriptor(dynamic_type);
|
||||
var def = new DynamicOrderDefinition();
|
||||
var def = new DynamicOrderDefinition { DynamicOrderType = dynamic_type };
|
||||
add_dynamic_params(ref def);
|
||||
return def;
|
||||
}
|
||||
|
||||
internal void add_dynamic_params(ref DynamicOrderDefinition dynamic_def) {
|
||||
var desc = get_descriptor(dynamic_def.DynamicOrderType);
|
||||
foreach (var p in desc.ParamDesc) {
|
||||
if (_is_child_algo_desc(p)) {
|
||||
def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
|
||||
dynamic_def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
|
||||
} else if (_is_value_param_desc(p)) {
|
||||
var value_case = _get_value_case(p.ParamType.Primitive);
|
||||
if (_descriptor_case_index.TryGetValue(value_case, out (FieldDescriptor fd, Type type) tup)) {
|
||||
var dop = new DynamicOrderParam();
|
||||
tup.fd.Accessor.SetValue(dop, ConvertUtils.get_default(tup.type));
|
||||
def.OrderParams[p.ParamName] = dop;
|
||||
dynamic_def.OrderParams[p.ParamName] = dop;
|
||||
}
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
static DynamicOrderParam.ValueOneofCase _get_value_case(DynamicOrderPrimitiveDescriptor primitive_descriptor_case) {
|
||||
|
||||
@@ -22,17 +22,13 @@ namespace mbts.spark {
|
||||
CustomOrderDefinition.CustomOneofCase order_type { get; }
|
||||
bool is_bts_type { get; }
|
||||
OrderMetadata meta { get; }
|
||||
AlgoEditingPermissions editing_permissions { get; }
|
||||
bool can_expand { get; }
|
||||
bool is_expanded { get; set; }
|
||||
bool can_edit_values { get; }
|
||||
bool can_edit_structure { get; }
|
||||
bool can_save_values { get; }
|
||||
bool can_save_structure { get; }
|
||||
string display_name { get; }
|
||||
CustomOrderDefinitionImmutable definition { get; }
|
||||
ObservableCollection<IAlgoSelector> child_algo_selectors { get; }
|
||||
bool key_match(KeyEventArgs e);
|
||||
void set_permissions(AlgoEditingPermissions editing_permissions);
|
||||
Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition);
|
||||
}
|
||||
|
||||
@@ -91,4 +87,4 @@ namespace mbts.spark {
|
||||
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, s => load_algo_data_recursive(s, algo_data, default_algo)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,38 +95,32 @@ namespace mbts.spark {
|
||||
public static FieldDescriptor find_descriptor<T>() =>
|
||||
_algo_type_index.TryGetValue(typeof(T), out FieldDescriptor fd) ?
|
||||
fd : throw new NotSupportedException($"No {nameof(AlgoDef)} {nameof(FieldDescriptor)} exists for type '{typeof(T)}.'");
|
||||
|
||||
public static Func<TMutable, TImmutable> immutable_factory<TMutable, TImmutable>() where TMutable : IMessage, new() where TImmutable : IImmutableMessage =>
|
||||
_ImmutableActivator<TMutable, TImmutable>.factory;
|
||||
|
||||
static class _ImmutableActivator<TMutable, TImmutable> where TMutable : IMessage, new() where TImmutable : IImmutableMessage {
|
||||
public static readonly Func<TMutable, TImmutable> factory =
|
||||
m => (TImmutable)Activator.CreateInstance(typeof(TImmutable), m);
|
||||
}
|
||||
}
|
||||
|
||||
#region OrderTypeVM Base Classes
|
||||
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM, IObserver<SecurityMD?>, IObserver<OrdersModel> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
|
||||
/// TODO: maybe use explicit delegates rather than rely on reflection <see cref="_descriptor"/> <see cref="_create_immutable"/>.
|
||||
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM,
|
||||
IObserver<SecurityMD?>,
|
||||
IObserver<SecurityImmutable?>,
|
||||
IObserver<OrdersModel>,
|
||||
IObserver<AlgoEditingPermissions> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
|
||||
static readonly FieldDescriptor _descriptor = OrderTypeVMUtils.find_descriptor<TMutable>();
|
||||
static readonly Func<TMutable, TImmutable> _create_immutable = OrderTypeVMUtils.immutable_factory<TMutable, TImmutable>();
|
||||
readonly IDictionary<string, object> _children_vm = new ExpandoObject();
|
||||
protected CompositeDisposable _resources = new CompositeDisposable();
|
||||
|
||||
readonly ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string> _auto_props = new ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string>(t => t.name);
|
||||
readonly ObservableKeyedCollection<AlgoSelectorData, string> _child_algo_selector_data = new ObservableKeyedCollection<AlgoSelectorData, string>(sd => sd.prop_name);
|
||||
readonly ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData> _child_algo_selectors;
|
||||
readonly Property<bool> _is_expanded, _can_save_values, _can_save_structure;
|
||||
readonly Property<bool> _is_expanded;
|
||||
readonly DeferrableActionWrapper _def_change_notifier;
|
||||
SecurityInput _security_input;
|
||||
AlgoEditingPermissions _editing_permissions;
|
||||
IDisposable _pending_load;
|
||||
|
||||
protected OrderTypeVMBase(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions) {
|
||||
this.id = id;
|
||||
this.display_name = display_name;
|
||||
this.editing_permissions = editing_permissions;
|
||||
_editing_permissions = editing_permissions;
|
||||
_child_algo_selectors = new ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData>(_child_algo_selector_data, sd => sd.selector);
|
||||
_can_save_values = backing(nameof(can_save_values), true);
|
||||
_can_save_structure = backing(nameof(can_save_structure), true);
|
||||
_is_expanded = backing<bool>(nameof(is_expanded));
|
||||
meta = OrderDataHelper.get_metadata(order_type);
|
||||
_def_change_notifier = new DeferrableActionWrapper(() => definition_changed?.Invoke());
|
||||
@@ -134,10 +128,6 @@ namespace mbts.spark {
|
||||
switch (e.PropertyName) {
|
||||
// ignore props that don't affect the resultant AlgoDef
|
||||
case nameof(is_expanded):
|
||||
case nameof(can_edit_values):
|
||||
case nameof(can_edit_structure):
|
||||
case nameof(can_save_values):
|
||||
case nameof(can_save_structure):
|
||||
return;
|
||||
default:
|
||||
_def_change_notifier.execute();
|
||||
@@ -148,10 +138,7 @@ namespace mbts.spark {
|
||||
CompositeDisposable def_sub = null;
|
||||
sd.selector.selected_algo_changed += subscribe_new_algo;
|
||||
return new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect_item_hooks(sd.selector.algos, a => {
|
||||
a.set_permissions(this.editing_permissions);
|
||||
return a as IDisposable ?? Disposable.empty;
|
||||
}, true)) {
|
||||
ObservableAutoWrapper.try_dispose_on_removed(sd.selector.algos, true)) {
|
||||
() => {
|
||||
sd.selector.selected_algo_changed -= subscribe_new_algo;
|
||||
Disposable.dispose(ref def_sub);
|
||||
@@ -170,6 +157,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public event Action definition_changed;
|
||||
event Action<SecurityImmutable?> _security_changed;
|
||||
// core
|
||||
public AlgoDefi definition => new AlgoDefi(this.created_assigned());
|
||||
public AlgoType order_type => OrderDataHelper.get_order_type(new TMutable());
|
||||
@@ -182,18 +170,14 @@ namespace mbts.spark {
|
||||
public dynamic children_vm => _children_vm;
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
|
||||
// editing permissions
|
||||
public AlgoEditingPermissions editing_permissions { get; private set; }
|
||||
public bool can_edit_values => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditValues);
|
||||
public bool can_edit_structure => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditStructure);
|
||||
public bool can_save_values => _can_save_values.Value;
|
||||
public bool can_save_structure => _can_save_structure.Value;
|
||||
// misc
|
||||
public AlgoEditingPermissions editing_permissions { get => _editing_permissions; private set => Set(ref _editing_permissions, value); }
|
||||
// expand
|
||||
public virtual bool can_expand => !meta.NoProps;
|
||||
public virtual bool is_expanded { get => _is_expanded.Value; set => _is_expanded.Value = value; }
|
||||
// protected (observed props)
|
||||
protected OrdersModel model { get; private set; }
|
||||
protected SecurityMD? security_md { get; private set; }
|
||||
protected SecurityImmutable? security => security_md?.security;
|
||||
protected SecurityMD? security_md => _security_input.security_md;
|
||||
protected SecurityImmutable? security => _security_input.security;
|
||||
|
||||
public void assign_to(AlgoDef value) {
|
||||
var algo = new TMutable();
|
||||
@@ -206,12 +190,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void set_permissions(AlgoEditingPermissions editing_permissions) {
|
||||
using (detect_property_changes(nameof(can_edit_values), nameof(can_edit_structure), nameof(can_save_values), nameof(can_save_structure)))
|
||||
this.editing_permissions = editing_permissions;
|
||||
on_permissions_changed();
|
||||
}
|
||||
|
||||
public bool key_match(KeyEventArgs key) =>
|
||||
!string.IsNullOrEmpty(display_name) && key.Key == (Key.A + display_name[0] - 'A');
|
||||
|
||||
@@ -219,22 +197,31 @@ namespace mbts.spark {
|
||||
if (value.CustomCase != order_type)
|
||||
return;
|
||||
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
|
||||
var security_src = Observable.Create<SecurityImmutable?>(o => {
|
||||
o.OnNext(security);
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.replace(ref _pending_load,
|
||||
new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), sd => {
|
||||
var inner_def = sd.get_order(value);
|
||||
if (inner_def.HasValue) {
|
||||
if (security.HasValue) {
|
||||
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
|
||||
if (!inner_def.HasValue)
|
||||
return;
|
||||
}
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
|
||||
vm.load_from(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
}),
|
||||
Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
var inner_def = sd.get_order(value);
|
||||
if (inner_def.HasValue) {
|
||||
if (security.HasValue) {
|
||||
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
|
||||
if (!inner_def.HasValue)
|
||||
return;
|
||||
}
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
|
||||
vm.load_from(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
|
||||
@@ -260,11 +247,6 @@ namespace mbts.spark {
|
||||
|
||||
protected virtual Task on_load_from_async(AlgoDefi defi) => Task.CompletedTask;
|
||||
|
||||
protected virtual void on_permissions_changed() {
|
||||
foreach (var vm in child_algo_selectors.SelectMany(s => s.algos.AsCollection()))
|
||||
vm.set_permissions(editing_permissions);
|
||||
}
|
||||
|
||||
protected void raise_definition_changed() =>
|
||||
_def_change_notifier.execute();
|
||||
|
||||
@@ -337,12 +319,11 @@ namespace mbts.spark {
|
||||
protected Property<TImmutableValue> add_property<TMutableValue, TImmutableValue>(string prop_name, int target_field_number, TImmutableValue init_value = default) where TMutableValue : class, IMessage, new() where TImmutableValue : IImmutableMessage {
|
||||
var prop = backing(prop_name, init_value);
|
||||
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
|
||||
var immutable_factory = OrderTypeVMUtils.immutable_factory<TMutableValue, TImmutableValue>();
|
||||
add_property<TMutableValue>(
|
||||
prop_name,
|
||||
mdef => field_desc.Accessor.GetValue(mdef) as TMutableValue,
|
||||
(mdef, mval) => field_desc.Accessor.SetValue(mdef, mval),
|
||||
mval => prop.set(immutable_factory(mval ?? new TMutableValue())),
|
||||
mval => prop.set(ProtoHelper.immutable_factory<TMutableValue, TImmutableValue>(mval ?? new TMutableValue())),
|
||||
() => {
|
||||
// TODO: perhaps CloneToMutable() should be a member of IImmutableMessage.
|
||||
// this method makes things so simple for the caller...but this dynamic cast is a little sketch.
|
||||
@@ -381,6 +362,7 @@ namespace mbts.spark {
|
||||
|
||||
protected virtual void on_model_internal() { }
|
||||
protected virtual void on_security_md_internal() { }
|
||||
protected virtual void on_security_internal() { }
|
||||
|
||||
// model
|
||||
void IObserver<OrdersModel>.OnNext(OrdersModel model) {
|
||||
@@ -400,14 +382,40 @@ namespace mbts.spark {
|
||||
void IObserver<OrdersModel>.OnCompleted() { }
|
||||
// security md
|
||||
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
|
||||
this.security_md = security_md;
|
||||
using (_def_change_notifier.defer_execution()) {
|
||||
on_security_md_internal();
|
||||
_populate_selectors();
|
||||
}
|
||||
using (_def_change_notifier.defer_execution())
|
||||
using (_update_sec_input())
|
||||
_security_input = _security_input.with_security_md(security_md);
|
||||
}
|
||||
void IObserver<SecurityMD?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityMD?>.OnCompleted() { }
|
||||
// security
|
||||
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? security) {
|
||||
using (_def_change_notifier.defer_execution())
|
||||
using (_update_sec_input())
|
||||
_security_input = _security_input.with_security(security);
|
||||
}
|
||||
|
||||
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityImmutable?>.OnCompleted() { }
|
||||
// editing permissions
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) => this.editing_permissions = editing_permissions;
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
|
||||
IDisposable _update_sec_input() {
|
||||
var old_sec = security;
|
||||
var old_sec_md = security_md;
|
||||
return Disposable.Create(() => {
|
||||
if (security != old_sec) {
|
||||
on_security_internal();
|
||||
if (_editing_permissions.HasFlag(AlgoEditingPermissions.AllowEditStructure))
|
||||
_populate_selectors();
|
||||
_security_changed?.Invoke(security);
|
||||
}
|
||||
if (security_md != old_sec_md)
|
||||
on_security_md_internal();
|
||||
});
|
||||
}
|
||||
|
||||
IDisposable _selector_data_hooks;
|
||||
void _populate_selectors() {
|
||||
@@ -426,6 +434,40 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
// security input may come in 2 forms:
|
||||
// 1) SecurityImmutable?
|
||||
// 2) SecurityMD?
|
||||
// This structure resolves conflicts that may arise from the different inputs
|
||||
struct SecurityInput {
|
||||
readonly SecurityMD? _raw_security_md;
|
||||
readonly SecurityImmutable? _raw_security;
|
||||
|
||||
public readonly SecurityMD? security_md;
|
||||
public readonly SecurityImmutable? security;
|
||||
|
||||
SecurityInput(SecurityImmutable? raw_security, SecurityMD? raw_security_md, SecurityImmutable? security, SecurityMD? security_md) {
|
||||
_raw_security = raw_security;
|
||||
_raw_security_md = raw_security_md;
|
||||
this.security_md = security_md;
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public SecurityInput with_security(SecurityImmutable? security) =>
|
||||
new SecurityInput(
|
||||
security,
|
||||
_raw_security_md,
|
||||
security,
|
||||
security.HasValue && _raw_security_md.HasValue && _raw_security_md.Value.security.Id != security.Value.Id ?
|
||||
null : _raw_security_md);
|
||||
|
||||
public SecurityInput with_security_md(SecurityMD? security_md) =>
|
||||
new SecurityInput(
|
||||
_raw_security,
|
||||
security_md,
|
||||
security_md?.security ?? _raw_security,
|
||||
security_md);
|
||||
}
|
||||
|
||||
struct AlgoSelectorData {
|
||||
public readonly string prop_name;
|
||||
public readonly AlgoSelector selector;
|
||||
@@ -445,14 +487,15 @@ namespace mbts.spark {
|
||||
IReadOnlyDictionary<AlgoKey, AlgoData> _pending_data;
|
||||
AlgoKey? _pending_default;
|
||||
|
||||
public AlgoSelector() =>
|
||||
public AlgoSelector() {
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(algos, AlgoCollectionHelper.algo_vm_comparer);
|
||||
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
|
||||
}
|
||||
|
||||
public void load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
|
||||
_pending_data = algo_data;
|
||||
_pending_default = default_algo;
|
||||
_sync_formats();
|
||||
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
|
||||
}
|
||||
|
||||
public event PropertyChange<IAlgoVM> selected_algo_changed;
|
||||
@@ -513,4 +556,4 @@ namespace mbts.spark {
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.utility;
|
||||
@@ -11,7 +12,7 @@ using SpreaderDefi = bts.api.orderman.SpreaderOrderDefinitionImmutable;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<SecurityMD?>, IObserver<OrdersModel> {
|
||||
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<OrdersModel> {
|
||||
readonly Property<SpreaderDefi?> _template;
|
||||
readonly StrategyEditorWrapper _strategy_editor_wrapper;
|
||||
IDisposable _strategy_sub;
|
||||
@@ -78,16 +79,27 @@ namespace mbts.spark {
|
||||
set_error(nameof(template), "Waiting for security and/or strategy.", true);
|
||||
else clear_error(nameof(template));
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
protected override void on_model_internal() => _reset_strategy_sub();
|
||||
protected override void on_security_internal() => _reset_strategy_sub();
|
||||
=======
|
||||
|
||||
protected override void on_security_md_internal() => _reset_strategy_sub();
|
||||
protected override void on_model_internal() => _reset_strategy_sub();
|
||||
>>>>>>> master
|
||||
|
||||
void _reset_strategy_sub() {
|
||||
Disposable.dispose(ref _strategy_sub);
|
||||
if (model != null) {
|
||||
<<<<<<< HEAD
|
||||
if (security.HasValue) {
|
||||
_strategy_sub = model.spreader.security_strategy(security.Value.Id)
|
||||
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security.Value, strategy)))
|
||||
=======
|
||||
if (security_md.HasValue) {
|
||||
_strategy_sub = model.spreader.security_strategy(security_md.Value.security.Id)
|
||||
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security_md.Value.security, strategy)))
|
||||
>>>>>>> master
|
||||
.OnDispatcher()
|
||||
.Subscribe(s => {
|
||||
is_loaded = true;
|
||||
|
||||
@@ -130,4 +130,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,9 @@ using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using bts.md;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.config;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -18,10 +16,8 @@ using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
using OrderUpdate = bts.api.core.TableUpdate<(bts.api.SecurityImmutable? sec, bts.api.orderman.OrderRecordImmutable order), uint>;
|
||||
using OrderUpdatePiq = bts.api.core.TableUpdate<bts.api.orderman.OrderSecurity, uint>;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
@@ -30,26 +26,21 @@ namespace mbts.spark {
|
||||
readonly OrderFactory _factory;
|
||||
readonly OrderService _service;
|
||||
readonly PositionService _position_service;
|
||||
readonly MBOService _mbo_service;
|
||||
readonly SpreaderModel _spreader;
|
||||
readonly ErrorReporter _error;
|
||||
readonly DynamicOrderHelper _dynamic_helper;
|
||||
readonly OrderActivitySummarizer _activity;
|
||||
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer;
|
||||
// we need a new one of these for the time being since the ladder is subscribing to orders with piq
|
||||
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_piq_demuxer;
|
||||
readonly TableUpdateDemuxer<PositionUnitImmutable, int, uint> _security_positions_demuxer;
|
||||
IDisposable _dispose;
|
||||
static readonly bool _piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
|
||||
|
||||
[ImportingConstructor]
|
||||
OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, MBOService mbo_service,
|
||||
OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service,
|
||||
SpreaderModel spreader, DynamicOrderHelper dynamic_helper, AdminModel admin_model, IAlgoStudioModel algo_model, SecurityStore sstore, ErrorReporter error) {
|
||||
_bus = bus;
|
||||
_factory = factory;
|
||||
_service = service;
|
||||
_position_service = position_service;
|
||||
_mbo_service = mbo_service;
|
||||
_spreader = spreader;
|
||||
_error = error;
|
||||
_dynamic_helper = dynamic_helper;
|
||||
@@ -57,34 +48,13 @@ namespace mbts.spark {
|
||||
this.algo_model = algo_model;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders =
|
||||
on_orders =
|
||||
_service
|
||||
.subscribei_orders()
|
||||
.Naggle(interval, scheduler)
|
||||
.PermissionFilter(admin_model.on_admin_data)
|
||||
.join(sstore, order => order.security_key())
|
||||
.Table()
|
||||
.Cache();
|
||||
|
||||
if (_piq_enabled) {
|
||||
on_orders_with_piq = on_orders
|
||||
.LeftJoin<
|
||||
(SecurityImmutable?, OrderRecordImmutable),
|
||||
OrderPIQImmutable,
|
||||
OrderSecurity,
|
||||
uint
|
||||
>(mbo_service.subscribei_piq().Table(),
|
||||
si_ort => si_ort.Item2.Key,
|
||||
p => p.OrderId,
|
||||
(si_ort, r, p) => new OrderSecurity(si_ort.Item1, si_ort.Item2, p.Position),
|
||||
(si_ort, r) => new OrderSecurity(si_ort.Item1, si_ort.Item2, null),
|
||||
osp => osp.order.Key);
|
||||
} else {
|
||||
on_orders_with_piq = on_orders
|
||||
.Select((ft) => new OrderSecurity(ft.Item1, ft.Item2, null), os => os.order.Key)
|
||||
.Replay(1);
|
||||
}
|
||||
|
||||
.Table().Cache();
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).Table().Cache();
|
||||
working_orders =
|
||||
on_orders.Scan(
|
||||
@@ -92,13 +62,6 @@ namespace mbts.spark {
|
||||
(workers, table) => _update_working_orders(workers, table))
|
||||
.Replay(1).RefCount();
|
||||
|
||||
working_orders_with_piq =
|
||||
on_orders_with_piq.Scan(
|
||||
ImmutableDictionary<uint, OrderSecurity>.Empty,
|
||||
(workers, table) => _update_working_orders(workers, table))
|
||||
.Replay(1).RefCount();
|
||||
|
||||
|
||||
on_order_securities =
|
||||
on_orders
|
||||
.as_iincremental()
|
||||
@@ -107,7 +70,6 @@ namespace mbts.spark {
|
||||
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
_security_orders_piq_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
_security_positions_demuxer = new TableUpdateDemuxer<PositionUnitImmutable, int, uint>(o => o.RecordId, p => p.SecurityId);
|
||||
var algo_meta_join = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
|
||||
algo_model.metadata_service.subscribe_metadata().Table(m => m.id),
|
||||
@@ -124,7 +86,7 @@ namespace mbts.spark {
|
||||
(os, okey, am, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, am.algo, am.meta),
|
||||
(os, okey, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, null, null),
|
||||
ot => ot.order.Key);
|
||||
on_order_tuples = order_secs_algo_meta_join;
|
||||
on_order_tuples = order_secs_algo_meta_join.Cache();
|
||||
var on_fills =
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
@@ -139,18 +101,14 @@ namespace mbts.spark {
|
||||
(f, fk, o, ok) => new FillTuple(f, o),
|
||||
(f, fk, ok) => new FillTuple(f, null),
|
||||
f => f.fill.FillKey);
|
||||
on_fill_tuples = fill_order_tuples_join;
|
||||
on_fill_tuples = fill_order_tuples_join.Cache();
|
||||
_dispose = Disposable.Create(
|
||||
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
|
||||
on_orders_with_piq.RefCount().Select(t => t.order, o => o.Key).Subscribe(_security_orders_piq_demuxer),
|
||||
on_positions.Subscribe(_security_positions_demuxer),
|
||||
order_secs_algo_meta_join.Connect(),
|
||||
algo_meta_join.Connect(),
|
||||
fill_order_tuples_join.Connect(),
|
||||
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
|
||||
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
|
||||
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.CrackOrders).Subscribe(x => crack_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.DetachOrders).Subscribe(x => detach_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.UpdateOrder).Subscribe(x => this.update_order(x.key, x.price, x.size).ignore())
|
||||
);
|
||||
}
|
||||
@@ -160,14 +118,11 @@ namespace mbts.spark {
|
||||
public SpreaderModel spreader => _spreader;
|
||||
public DynamicOrderHelper dynamic_helper => _dynamic_helper;
|
||||
public IObservable<OrderUpdate> on_orders { get; }
|
||||
public IConnectableObservable<OrderUpdatePiq> on_orders_with_piq { get; }
|
||||
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples { get; }
|
||||
public IObservable<IIncrementalUpdate<SecurityImmutable, uint>> on_order_securities { get; }
|
||||
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint,OrderSecurity>> working_orders { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders_with_piq { get; }
|
||||
public IObservable<TableUpdate<PositionUnitImmutable, int>> on_positions { get; }
|
||||
|
||||
public AdminModel admin_model { get; }
|
||||
public IAlgoStudioModel algo_model { get; }
|
||||
|
||||
@@ -208,27 +163,37 @@ namespace mbts.spark {
|
||||
return success;
|
||||
}
|
||||
|
||||
public async Task<bool> update_order(OrderRecordImmutable order, double price, uint size) {
|
||||
if (order.working_quantity() <= 0) return false;
|
||||
_activity.update_order(order, price, size);
|
||||
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) {
|
||||
// order already filled to completion
|
||||
return false;
|
||||
}
|
||||
var size = new_size ?? (uint)order.working_quantity();
|
||||
var price = new_price ?? order.price();
|
||||
if (size == order.working_quantity() && price == order.price() && !new_algo.HasValue) {
|
||||
// no parameters would be changed by this update
|
||||
return false;
|
||||
}
|
||||
_activity.update_order(order, price, size, new_algo.HasValue);
|
||||
var result = await _service.update_order_async(new Update() {
|
||||
OrderKey = order.Key,
|
||||
Price = price,
|
||||
Quantity = size
|
||||
Quantity = size,
|
||||
Custom = new_algo?.CloneToMutable()
|
||||
});
|
||||
(var key, string msg) = _handle_error(result, "Failed to update order: {0}");
|
||||
bool success = key.HasValue;
|
||||
if (!success) {
|
||||
_activity.update_order_failed(order, price, size);
|
||||
_activity.update_order_failed(order, price, size, new_algo.HasValue);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public Task<bool[]> crack_orders(IEnumerable<uint> orders) =>
|
||||
Task.WhenAll(orders.Select(crack_order));
|
||||
public Task<bool[]> detach_orders(IEnumerable<uint> orders) =>
|
||||
Task.WhenAll(orders.Select(detach_order));
|
||||
|
||||
public async Task<bool> crack_order(uint order) {
|
||||
var result = await _service.crack_order_async(new Crack() { OrderKey = order });
|
||||
public async Task<bool> detach_order(uint order) {
|
||||
var result = await _service.detach_order_async(new Detach() { OrderKey = order });
|
||||
return _handle_error(result, "Failed to break order: {0}").key.HasValue;
|
||||
}
|
||||
|
||||
@@ -239,30 +204,8 @@ namespace mbts.spark {
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
static ImmutableDictionary<uint, OrderSecurity> _update_working_orders(ImmutableDictionary<uint, OrderSecurity> target, OrderUpdate table) {
|
||||
var clone = target.ToBuilder();
|
||||
foreach (var o in table.removed) {
|
||||
if (o.order.is_active()) {
|
||||
clone.Remove(o.order.Key);
|
||||
}
|
||||
}
|
||||
foreach (var o in table.changes) {
|
||||
if (o.Old.order.is_active()) {
|
||||
clone.Remove(o.Old.order.Key);
|
||||
}
|
||||
if (o.New.order.is_active()) {
|
||||
clone.Add(o.New.order.Key, o.New);
|
||||
}
|
||||
}
|
||||
foreach (var o in table.added) {
|
||||
if (o.order.is_active()) {
|
||||
clone.Add(o.order.Key, o);
|
||||
}
|
||||
}
|
||||
return clone.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
static ImmutableDictionary<uint, OrderSecurity> _update_working_orders(ImmutableDictionary<uint, OrderSecurity> target, OrderUpdatePiq table) {
|
||||
static ImmutableDictionary<uint, OrderSecurity>
|
||||
_update_working_orders(ImmutableDictionary<uint, OrderSecurity> target, OrderUpdate table) {
|
||||
var clone = target.ToBuilder();
|
||||
foreach (var o in table.removed) {
|
||||
if (o.order.is_active()) {
|
||||
@@ -286,7 +229,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// usinig new OrdersModel extensions to reduce the amount of code change needed to switch uis over to the new OrdersModel,
|
||||
// without changing any code in the actual new OrdersModel class.
|
||||
static class OrdersModelExtensions {
|
||||
|
||||
@@ -4,7 +4,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class DiscreteStringPredicate : IDiscreteValuesPredicateData<string>, IEquatable<DiscreteStringPredicate> {
|
||||
class DiscreteStringPredicate : IDiscreteValuesPredicateBuilder<string>, IEquatable<DiscreteStringPredicate> {
|
||||
[ProtoMember(1)]
|
||||
string[] _values;
|
||||
[ProtoMember(2)]
|
||||
|
||||
@@ -4,7 +4,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class DiscreteUIntPredicate : IDiscreteValuesPredicateData<uint, uint?, uint[]>, IEquatable<DiscreteUIntPredicate> {
|
||||
class DiscreteUIntPredicate : IDiscreteValuesPredicateBuilder<uint, uint?, uint[]>, IEquatable<DiscreteUIntPredicate> {
|
||||
[ProtoMember(1)]
|
||||
uint[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -16,7 +16,7 @@ namespace mbts {
|
||||
public uint[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<uint> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<uint> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(DiscreteUIntPredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as DiscreteUIntPredicate);
|
||||
|
||||
@@ -8,14 +8,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts {
|
||||
interface IDiscreteValuesPredicateData<T, TSingle, TMulti> {
|
||||
interface IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> : IPredicateBuilder<T> {
|
||||
TSingle is_value { get; set; }
|
||||
TSingle not_value { get; set; }
|
||||
TMulti in_set { get; set; }
|
||||
TMulti not_in_set { get; set; }
|
||||
DiscreteValuesOneofCase predicate_type_case { get; }
|
||||
}
|
||||
interface IDiscreteValuesPredicateData<T> : IDiscreteValuesPredicateData<T, T, T[]> { }
|
||||
interface IDiscreteValuesPredicateBuilder<T> : IDiscreteValuesPredicateBuilder<T, T, T[]> { }
|
||||
|
||||
public enum DiscreteValuesOneofCase {
|
||||
None,
|
||||
@@ -32,8 +32,8 @@ namespace mbts {
|
||||
|
||||
namespace mbts.ui.settings {
|
||||
static class DiscreteValuesPredicateUtils {
|
||||
public static bool equals<T>(IDiscreteValuesPredicateData<T> a, IDiscreteValuesPredicateData<T> b) => a.safe_equals(b, _equals);
|
||||
static bool _equals<T>(IDiscreteValuesPredicateData<T> a, IDiscreteValuesPredicateData<T> b) {
|
||||
public static bool equals<T>(IDiscreteValuesPredicateBuilder<T> a, IDiscreteValuesPredicateBuilder<T> b) => a.safe_equals(b, _equals);
|
||||
static bool _equals<T>(IDiscreteValuesPredicateBuilder<T> a, IDiscreteValuesPredicateBuilder<T> b) {
|
||||
var dvoc = a.predicate_type_case;
|
||||
if (dvoc != b.predicate_type_case)
|
||||
return false;
|
||||
@@ -47,8 +47,8 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static bool equals<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a, IDiscreteValuesPredicateData<T, TSingle, TMulti> b) => a.safe_equals(b, _equals);
|
||||
public static bool _equals<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a, IDiscreteValuesPredicateData<T, TSingle, TMulti> b) {
|
||||
public static bool equals<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a, IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> b) => a.safe_equals(b, _equals);
|
||||
public static bool _equals<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a, IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> b) {
|
||||
var dvoc = a.predicate_type_case;
|
||||
if (dvoc != b.predicate_type_case)
|
||||
return false;
|
||||
@@ -62,7 +62,7 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static int hash<T>(IDiscreteValuesPredicateData<T> a) {
|
||||
public static int hash<T>(IDiscreteValuesPredicateBuilder<T> a) {
|
||||
switch (a.predicate_type_case) {
|
||||
case DiscreteValuesOneofCase.IsValue: return a.is_value.GetHashCode() % MathUtils.Primes.P_17191;
|
||||
case DiscreteValuesOneofCase.NotValue: return a.not_value.GetHashCode() % MathUtils.Primes.P_83987;
|
||||
@@ -72,7 +72,7 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static int hash<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a) {
|
||||
public static int hash<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a) {
|
||||
switch (a.predicate_type_case) {
|
||||
case DiscreteValuesOneofCase.IsValue: return a.is_value.GetHashCode() % MathUtils.Primes.P_17191;
|
||||
case DiscreteValuesOneofCase.NotValue: return a.not_value.GetHashCode() % MathUtils.Primes.P_83987;
|
||||
@@ -88,7 +88,7 @@ namespace mbts.ui.settings {
|
||||
(negated ? DiscreteValuesOneofCase.NotValue : DiscreteValuesOneofCase.IsValue) :
|
||||
(negated ? DiscreteValuesOneofCase.NotInSet : DiscreteValuesOneofCase.IsInSet);
|
||||
|
||||
public static DiscreteValuesOneofCase get_predicate_type_case<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> predicate_builder) {
|
||||
public static DiscreteValuesOneofCase get_predicate_type_case<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> predicate_builder) {
|
||||
if (predicate_builder.is_value != null) return DiscreteValuesOneofCase.IsValue;
|
||||
if (predicate_builder.not_value != null) return DiscreteValuesOneofCase.NotValue;
|
||||
if (predicate_builder.in_set != null) return DiscreteValuesOneofCase.IsInSet;
|
||||
@@ -96,7 +96,7 @@ namespace mbts.ui.settings {
|
||||
return DiscreteValuesOneofCase.None;
|
||||
}
|
||||
|
||||
public static IPredicate<T> create<T>(IDiscreteValuesPredicateData<T> predicate_builder) {
|
||||
public static IPredicate<T> create<T>(IDiscreteValuesPredicateBuilder<T> predicate_builder) {
|
||||
switch (predicate_builder.predicate_type_case) {
|
||||
case DiscreteValuesOneofCase.IsValue: return PredicateBuilder.equals(predicate_builder.is_value);
|
||||
case DiscreteValuesOneofCase.NotValue: return PredicateBuilder.not_equals(predicate_builder.not_value);
|
||||
@@ -106,7 +106,7 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static IPredicate<T> create<T>(IDiscreteValuesPredicateData<T, T?, T[]> predicate_builder) where T : struct {
|
||||
public static IPredicate<T> create<T>(IDiscreteValuesPredicateBuilder<T, T?, T[]> predicate_builder) where T : struct {
|
||||
switch (predicate_builder.predicate_type_case) {
|
||||
case DiscreteValuesOneofCase.IsValue: return PredicateBuilder.equals(predicate_builder.is_value.Value);
|
||||
case DiscreteValuesOneofCase.NotValue: return PredicateBuilder.not_equals(predicate_builder.not_value.Value);
|
||||
@@ -118,13 +118,13 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
static class DiscreteValuesPredicateVMFactory {
|
||||
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs_value_type<T, TPredicate>(IEnumerable<T> items)
|
||||
where T : struct where TPredicate : IDiscreteValuesPredicateData<T, T?, T[]> =>
|
||||
where T : struct where TPredicate : IDiscreteValuesPredicateBuilder<T, T?, T[]> =>
|
||||
//
|
||||
get_oneofs<T, T?, T[], TPredicate>(identity_view_value_collection(items), val => val.Value, val => val, vals => vals ?? Enumerable.Empty<T>(), vals => vals?.ToArray() ?? new T[0], get_identity_view);
|
||||
|
||||
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TSingle, TMulti, TPredicate>(
|
||||
IObservableCollection<IViewValue<T>> items, Func<TSingle, T> from_single, Func<T, TSingle> to_single, Func<TMulti,IEnumerable<T>> from_multi, Func<IEnumerable<T>, TMulti> to_multi, Func<T, object> get_view)
|
||||
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> {
|
||||
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> {
|
||||
//
|
||||
return new Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> {
|
||||
{ DiscreteValuesOneofCase.IsValue, PropertyWrapperVMUtils.create_selector_vm(
|
||||
@@ -153,14 +153,14 @@ namespace mbts.ui.settings {
|
||||
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(
|
||||
IEnumerable<T> items, Func<T, TKey> key, Func<TSingle, TKey> from_single, Func<TKey, TSingle> to_single, Func<TMulti, IEnumerable<TKey>> from_multi, Func<IEnumerable<TKey>, TMulti> to_multi)
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> =>
|
||||
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> =>
|
||||
//
|
||||
get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(RXCollection.from_enumerable(items.Select<T, IViewValue<T>>(v => new ViewValueTuple<T>(v, v))), key, from_single, to_single, from_multi, to_multi);
|
||||
|
||||
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(
|
||||
IObservableCollection<IViewValue<T>> items, Func<T, TKey> key, Func<TSingle, TKey> from_single, Func<TKey, TSingle> to_single, Func<TMulti, IEnumerable<TKey>> from_multi, Func<IEnumerable<TKey>, TMulti> to_multi)
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> {
|
||||
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> {
|
||||
//
|
||||
return new Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> {
|
||||
{ DiscreteValuesOneofCase.IsValue, PropertyWrapperVMUtils.create_keyed_selector_vm<TPredicate, IViewValue<T>, TKey>(
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class ExchangePredicate : IDiscreteValuesPredicateData<Venue, Venue?, Venue[]>, IEquatable<ExchangePredicate> {
|
||||
class ExchangePredicate : IDiscreteValuesPredicateBuilder<Venue, Venue?, Venue[]>, IEquatable<ExchangePredicate> {
|
||||
[ProtoMember(1)]
|
||||
Venue[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public Venue[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<Venue> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<Venue> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(ExchangePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as ExchangePredicate);
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class OrderStatusPredicate : IDiscreteValuesPredicateData<OrderStatus, OrderStatus?, OrderStatus[]>, IEquatable<OrderStatusPredicate> {
|
||||
class OrderStatusPredicate : IDiscreteValuesPredicateBuilder<OrderStatus, OrderStatus?, OrderStatus[]>, IEquatable<OrderStatusPredicate> {
|
||||
[ProtoMember(1)]
|
||||
OrderStatus[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public OrderStatus[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<OrderStatus> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<OrderStatus> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(OrderStatusPredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderStatusPredicate);
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class OrderSubTypePredicate : IDiscreteValuesPredicateData<OrderSubType, OrderSubType?, OrderSubType[]>, IEquatable<OrderSubTypePredicate> {
|
||||
class OrderSubTypePredicate : IDiscreteValuesPredicateBuilder<OrderSubType, OrderSubType?, OrderSubType[]>, IEquatable<OrderSubTypePredicate> {
|
||||
[ProtoMember(1)]
|
||||
OrderSubType[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public OrderSubType[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<OrderSubType> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<OrderSubType> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(OrderSubTypePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderSubTypePredicate);
|
||||
|
||||
@@ -5,7 +5,7 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class OrderTypePredicate : IDiscreteValuesPredicateData<OrderType, OrderType?, OrderType[]>, IEquatable<OrderTypePredicate> {
|
||||
class OrderTypePredicate : IDiscreteValuesPredicateBuilder<OrderType, OrderType?, OrderType[]>, IEquatable<OrderTypePredicate> {
|
||||
[ProtoMember(1)]
|
||||
OrderType[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public OrderType[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<OrderType> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<OrderType> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(OrderTypePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderTypePredicate);
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class TimeInForcePredicate : IDiscreteValuesPredicateData<TimeInForce, TimeInForce?, TimeInForce[]>, IEquatable<TimeInForcePredicate> {
|
||||
class TimeInForcePredicate : IDiscreteValuesPredicateBuilder<TimeInForce, TimeInForce?, TimeInForce[]>, IEquatable<TimeInForcePredicate> {
|
||||
[ProtoMember(1)]
|
||||
TimeInForce[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public TimeInForce[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negate);
|
||||
public IPredicate<TimeInForce> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<TimeInForce> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(TimeInForcePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as TimeInForcePredicate);
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,7 +14,7 @@ namespace mbts {
|
||||
static class OrderCanceledEventPredicateVMFactory {
|
||||
public static Dictionary<OrderCanceledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCanceledEventPredicate>> get_oneofs(IBus bus) =>
|
||||
new Dictionary<OrderCanceledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCanceledEventPredicate>> {
|
||||
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderCanceledEventPredicate>(p => p.security = null) },
|
||||
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderCanceledEventPredicate>(p => p.security = null) },
|
||||
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderCanceledEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
|
||||
(p, s) => {
|
||||
if (p.security == null)
|
||||
@@ -25,7 +26,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderCanceledEventPredicate : IEquatable<OrderCanceledEventPredicate> {
|
||||
class OrderCanceledEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderCanceledEventPredicate> {
|
||||
public enum PredicateTypeOneofCase {
|
||||
[Description("(any order)")]
|
||||
AnyOrder,
|
||||
@@ -51,7 +52,7 @@ namespace mbts {
|
||||
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Canceled, security, data_cache);
|
||||
|
||||
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
|
||||
new MyToaster(security?.create(), data_cache);
|
||||
new MyToaster(security?.create(data_cache), data_cache);
|
||||
|
||||
public bool Equals(OrderCanceledEventPredicate other) => other != null && other.security.safe_equals(other.security);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderCanceledEventPredicate);
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,7 +14,7 @@ namespace mbts {
|
||||
static class OrderCompletedEventPredicateVMFactory {
|
||||
public static Dictionary<OrderCompletedEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCompletedEventPredicate>> get_oneofs(IBus bus) =>
|
||||
new Dictionary<OrderCompletedEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCompletedEventPredicate>> {
|
||||
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderCompletedEventPredicate>(p => p.security = null) },
|
||||
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderCompletedEventPredicate>(p => p.security = null) },
|
||||
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderCompletedEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
|
||||
(p, s) => {
|
||||
if (p.security == null)
|
||||
@@ -25,7 +26,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderCompletedEventPredicate : IEquatable<OrderCompletedEventPredicate> {
|
||||
class OrderCompletedEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderCompletedEventPredicate> {
|
||||
public enum PredicateTypeOneofCase {
|
||||
[Description("(any order)")]
|
||||
AnyOrder,
|
||||
@@ -51,7 +52,7 @@ namespace mbts {
|
||||
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Completed, security, data_cache);
|
||||
|
||||
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
|
||||
new MyToaster(security?.create(), data_cache);
|
||||
new MyToaster(security?.create(data_cache), data_cache);
|
||||
|
||||
public bool Equals(OrderCompletedEventPredicate other) => other != null && other.security.safe_equals(other.security);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderCompletedEventPredicate);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
|
||||
namespace mbts {
|
||||
@@ -37,7 +39,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
public static IPredicate<OrderSecurity> create_status_transition_predicate(OrderStatus status, SecurityPredicate security_predicate, INotificationServiceDataCache data_cache) =>
|
||||
new OrderStatusTransitionPredicate(status, security_predicate?.create(), data_cache);
|
||||
new OrderStatusTransitionPredicate(status, security_predicate?.create(data_cache), data_cache);
|
||||
|
||||
class OrderStatusTransitionPredicate : IPredicate<OrderSecurity>, IEquatable<OrderStatusTransitionPredicate> {
|
||||
readonly IPredicate<SecurityImmutable> _security_predicate;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace mbts {
|
||||
static class OrderFilledEventPredicateVMFactory {
|
||||
public static Dictionary<OrderFilledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderFilledEventPredicate>> get_oneofs(IBus bus) =>
|
||||
new Dictionary<OrderFilledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderFilledEventPredicate>> {
|
||||
{ OrderFilledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderFilledEventPredicate>(p => p.security = null) },
|
||||
{ OrderFilledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderFilledEventPredicate>(p => p.security = null) },
|
||||
{ OrderFilledEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderFilledEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
|
||||
(p, s) => {
|
||||
if (p.security == null) {
|
||||
@@ -24,7 +24,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderFilledEventPredicate : IEquatable<OrderFilledEventPredicate> {
|
||||
class OrderFilledEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderFilledEventPredicate> {
|
||||
public enum PredicateTypeOneofCase {
|
||||
[PredicateCase("(any order)")]
|
||||
AnyOrder,
|
||||
@@ -59,7 +59,7 @@ namespace mbts {
|
||||
public override int GetHashCode() => security == null ? 0 : security.GetHashCode();
|
||||
|
||||
FillTriggerModel _create_model(INotificationServiceDataCache nsdc) =>
|
||||
new FillTriggerModel(nsdc, security?.create());
|
||||
new FillTriggerModel(nsdc, security?.create(nsdc));
|
||||
|
||||
class FillTriggerModel {
|
||||
readonly IPredicate<SecurityImmutable> _security_predicate;
|
||||
@@ -82,7 +82,7 @@ namespace mbts {
|
||||
// old order will be a copy of new order, except with filledQuantity set to 0
|
||||
var old_state = ort.order.State.CloneToMutable();
|
||||
old_state.FilledQuantity = 0;
|
||||
return (new OrderSecurity(ort.security, ort.order.with(state: old_state), ort.piq), ort);
|
||||
return (new OrderSecurity(ort.security, ort.order.with(state: old_state)), ort);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -56,16 +56,16 @@ namespace mbts {
|
||||
Func<TChildPredicate, TChildPredicateOneof> key,
|
||||
Dictionary<TChildPredicateOneof, ITemplatedPropertyWrapperVM<TChildPredicate>> oneofs) where TChildPredicateOneof : struct where TChildPredicate : new() =>
|
||||
//
|
||||
PredicateVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
|
||||
PredicateBuilderVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderRecordTuplePredicate : IEquatable<OrderRecordTuplePredicate> {
|
||||
class OrderRecordTuplePredicate : INotificationPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderRecordTuplePredicate> {
|
||||
SecurityPredicate _security;
|
||||
SidePredicate _side;
|
||||
DoublePredicate _price, _last_price, _avg_price;
|
||||
UIntPredicate _quantity, _filled_quantity, _security_key, _order_key, _parent_key;
|
||||
UIntPredicate _quantity, _filled_quantity, _security_key, _order_key, _parent_key, _attached_target_order_key;
|
||||
OrderStatusPredicate _order_status;
|
||||
OrderTypePredicate _order_type;
|
||||
OrderSubTypePredicate _order_sub_type;
|
||||
@@ -125,7 +125,9 @@ namespace mbts {
|
||||
[PredicateCase("When order is completed", PredicateCategory.Event)]
|
||||
CompletedEvent,
|
||||
[PredicateCase("When order is canceled", PredicateCategory.Event)]
|
||||
CanceledEvent
|
||||
CanceledEvent,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
AttachedTargetOrderKey
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
@@ -176,6 +178,8 @@ namespace mbts {
|
||||
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
|
||||
[ProtoMember(24)]
|
||||
public bool is_toast_enabled { get; set; }
|
||||
[ProtoMember(25)]
|
||||
public UIntPredicate attached_order_target_key { get => _attached_target_order_key; set => _set(ref _attached_target_order_key, value); }
|
||||
|
||||
public PTOC predicate_type_case {
|
||||
get {
|
||||
@@ -202,42 +206,47 @@ namespace mbts {
|
||||
if (_filled_event != null) return PTOC.FilledEvent;
|
||||
if (_completed_event != null) return PTOC.CompletedEvent;
|
||||
if (_canceled_event != null) return PTOC.CanceledEvent;
|
||||
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
|
||||
return PTOC.None;
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) {
|
||||
switch (predicate_type_case) {
|
||||
case PTOC.Security: return wrap(_security.create(), r => r.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
case PTOC.Time: return wrap(_time_created.create(), r => r.order.created());
|
||||
case PTOC.LastChange: return wrap(_last_change.create(), r => r.order.last_change());
|
||||
case PTOC.User: return wrap(_user.create(), r => r.order.user_id());
|
||||
case PTOC.Account: return wrap(_account.create(), r => r.order.account_id());
|
||||
case PTOC.Exchange: return wrap(_exchange.create(), r => r.security.exchange_or_default());
|
||||
case PTOC.TimeInForce: return wrap(_time_in_force.create(), r => r.order.time_in_force());
|
||||
case PTOC.OrderType: return wrap(_order_type.create(), r => r.order.order_type());
|
||||
case PTOC.SubType: return wrap(_order_sub_type.create(), r => r.order.sub_type());
|
||||
case PTOC.Side: return wrap(_side.create(), r => r.order.side());
|
||||
case PTOC.Size: return wrap(_quantity.create(), r => r.order.quantity());
|
||||
case PTOC.Price: return wrap(_price.create(), r => r.order.price());
|
||||
case PTOC.Filled: return wrap(_filled_quantity.create(), r => r.order.filled());
|
||||
case PTOC.AvgFilled: return wrap(_avg_price.create(), r => r.order.avg_price());
|
||||
case PTOC.LastPrice: return wrap(_last_price.create(), r => r.order.last_price());
|
||||
case PTOC.Status: return wrap(_order_status.create(), r => r.order.status());
|
||||
case PTOC.LastMessage: return wrap(_last_message.create(), r => r.order.last_msg());
|
||||
case PTOC.SecurityKey: return wrap(_security_key.create(), r => r.order.security_key());
|
||||
case PTOC.OrderKey: return wrap(_order_key.create(), r => r.order.Key);
|
||||
case PTOC.ParentKey: return wrap(_parent_key.create(), r => r.order.ParentKey);
|
||||
case PTOC.Security: return wrap(_security, r => r.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
case PTOC.Time: return wrap(_time_created, r => r.order.created());
|
||||
case PTOC.LastChange: return wrap(_last_change, r => r.order.last_change());
|
||||
case PTOC.User: return wrap(_user, r => r.order.user_id());
|
||||
case PTOC.Account: return wrap(_account, r => r.order.account_id());
|
||||
case PTOC.Exchange: return wrap(_exchange, r => r.security.exchange_or_default());
|
||||
case PTOC.TimeInForce: return wrap(_time_in_force, r => r.order.time_in_force());
|
||||
case PTOC.OrderType: return wrap(_order_type, r => r.order.order_type());
|
||||
case PTOC.SubType: return wrap(_order_sub_type, r => r.order.sub_type());
|
||||
case PTOC.Side: return wrap(_side, r => r.order.side());
|
||||
case PTOC.Size: return wrap(_quantity, r => r.order.quantity());
|
||||
case PTOC.Price: return wrap(_price, r => r.order.price());
|
||||
case PTOC.Filled: return wrap(_filled_quantity, r => r.order.filled());
|
||||
case PTOC.AvgFilled: return wrap(_avg_price, r => r.order.avg_price());
|
||||
case PTOC.LastPrice: return wrap(_last_price, r => r.order.last_price());
|
||||
case PTOC.Status: return wrap(_order_status, r => r.order.status());
|
||||
case PTOC.LastMessage: return wrap(_last_message, r => r.order.last_msg());
|
||||
case PTOC.SecurityKey: return wrap(_security_key, r => r.order.security_key());
|
||||
case PTOC.OrderKey: return wrap(_order_key, r => r.order.Key);
|
||||
case PTOC.ParentKey: return wrap(_parent_key, r => r.order.ParentKey);
|
||||
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
|
||||
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
|
||||
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
|
||||
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key, r => r.order.attached_target_order_key());
|
||||
default: return null;
|
||||
}
|
||||
//
|
||||
IPredicate<OrderSecurity> wrap<T>(IPredicate<T> trigger, Func<OrderSecurity, T> select) =>
|
||||
trigger.wrap<OrderSecurity, T>(select);
|
||||
IPredicate<OrderSecurity> wrap<T>(IPredicateBuilder<T> trigger, Func<OrderSecurity, T> select) =>
|
||||
trigger.create(data_cache).wrap<OrderSecurity,T>(select);
|
||||
}
|
||||
|
||||
IPredicate<OrderSecurity> IPredicateBuilder<OrderSecurity>.create(object obj) =>
|
||||
create_predicate((INotificationServiceDataCache)obj);
|
||||
|
||||
public INotificationPredicate<OrderSecurity> create_notification_predicate(INotificationServiceDataCache notification_service_data_cache) {
|
||||
if (!is_toast_enabled)
|
||||
return null;
|
||||
@@ -249,6 +258,9 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
INotificationPredicate<OrderSecurity> INotificationPredicateBuilder<OrderSecurity>.create_notification_predicate(object obj) =>
|
||||
create_notification_predicate((INotificationServiceDataCache)obj);
|
||||
|
||||
public bool Equals(OrderRecordTuplePredicate other) {
|
||||
if (other == null)
|
||||
return false;
|
||||
@@ -279,6 +291,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
|
||||
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
|
||||
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
|
||||
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
|
||||
case PTOC.None: return true;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
}
|
||||
@@ -313,6 +326,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
|
||||
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
|
||||
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
|
||||
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
|
||||
case PTOC.None: raw_hash = 0; break;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
}
|
||||
@@ -347,6 +361,7 @@ namespace mbts {
|
||||
_filled_event = null;
|
||||
_completed_event = null;
|
||||
_canceled_event = null;
|
||||
_attached_target_order_key = null;
|
||||
}
|
||||
field = value;
|
||||
}
|
||||
@@ -372,9 +387,9 @@ namespace mbts {
|
||||
_sound_animations = new List<SoundNotification> ( sound_animations );
|
||||
}
|
||||
|
||||
public OrderRecordTuplePredicate predicate_data => _predicate;
|
||||
public OrderRecordTuplePredicate predicate_builder => _predicate;
|
||||
public IReadOnlyList<CellAnimation> cell_animations => _cell_animations ?? (_cell_animations = new List<CellAnimation>());
|
||||
public IReadOnlyList<RowAnimation> row_animations => _row_animations ?? (_row_animations = new List<RowAnimation>());
|
||||
public IReadOnlyList<SoundNotification> sound_notifications => _sound_animations ?? (_sound_animations = new List<SoundNotification>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -23,7 +24,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class DoublePredicate : IEquatable<DoublePredicate> {
|
||||
class DoublePredicate : IPredicateBuilder<double>, IEquatable<DoublePredicate> {
|
||||
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
|
||||
// greater than vs less than for specific values, or inside vs outside for value-ranges
|
||||
// trading a little more code for more clarity
|
||||
@@ -61,7 +62,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<double> create() {
|
||||
public IPredicate<double> create(object obj) {
|
||||
switch (predicate_type_case) {
|
||||
case PredicateTypeOneofCase.GreaterThan: return PredicateBuilder.greater_than(_greater_than.Value);
|
||||
case PredicateTypeOneofCase.LessThan: return PredicateBuilder.less_than(_less_than.Value);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using ProtoBuf;
|
||||
@@ -22,7 +23,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class SidePredicate : IEquatable<SidePredicate> {
|
||||
class SidePredicate : IPredicateBuilder<Side>, IEquatable<SidePredicate> {
|
||||
Side? _is_value;
|
||||
Side? _not_value;
|
||||
|
||||
@@ -47,7 +48,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<Side> create() {
|
||||
public IPredicate<Side> create(object obj) {
|
||||
switch (predicate_type_case) {
|
||||
case PredicateTypeOneofCase.IsValue: return PredicateBuilder.equals(_is_value.Value);
|
||||
case PredicateTypeOneofCase.NotValue: return PredicateBuilder.not_equals(_not_value.Value);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -25,7 +27,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class StringPredicate : IEquatable<StringPredicate> {
|
||||
class StringPredicate : IPredicateBuilder<string>, IEquatable<StringPredicate> {
|
||||
StringIgnoreCase? _is_string, _not_string, _contains_string, _not_contains_string;
|
||||
|
||||
public enum PredicateTypeOneofCase {
|
||||
@@ -59,7 +61,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<string> create() {
|
||||
public IPredicate<string> create(object obj) {
|
||||
switch (predicate_type_case) {
|
||||
case PTOC.IsMatch: return PredicateBuilder.equals(_is_string.Value.text, _get_eq_comparer(_is_string));
|
||||
case PTOC.NotMatch: return PredicateBuilder.not_equals(_not_string.Value.text, _get_eq_comparer(_not_string));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -20,7 +21,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class TimePredicate : IEquatable<TimePredicate> {
|
||||
class TimePredicate : IPredicateBuilder<DateTime>, IEquatable<TimePredicate> {
|
||||
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
|
||||
// before vs after for specific times, or inside vs outside for time-ranges
|
||||
// trading a little more code for clarity
|
||||
@@ -58,7 +59,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<DateTime> create() {
|
||||
public IPredicate<DateTime> create(object obj) {
|
||||
switch (predicate_type_case) {
|
||||
case PTOC.Before: return PredicateBuilder.less_than(_before.Value.get_date_time());
|
||||
case PTOC.After: return PredicateBuilder.greater_than(_after.Value.get_date_time());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -23,7 +24,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class UIntPredicate : IEquatable<UIntPredicate> {
|
||||
class UIntPredicate : IPredicateBuilder<uint>, IEquatable<UIntPredicate> {
|
||||
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
|
||||
// greater than vs less than for specific values, or inside vs outside for value-ranges
|
||||
// trading a little more code for more clarity
|
||||
@@ -61,7 +62,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<uint> create() {
|
||||
public IPredicate<uint> create(object obj) {
|
||||
switch (predicate_type_case) {
|
||||
case PredicateTypeOneofCase.GreaterThan: return PredicateBuilder.greater_than(_greater_than.Value);
|
||||
case PredicateTypeOneofCase.LessThan: return PredicateBuilder.less_than(_less_than.Value);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -23,7 +24,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class UShortPredicate : IEquatable<UShortPredicate> {
|
||||
class UShortPredicate : IPredicateBuilder<ushort>, IEquatable<UShortPredicate> {
|
||||
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
|
||||
// greater than vs less than for specific values, or inside vs outside for value-ranges
|
||||
// trading a little more code for more clarity
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class SecurityPredicate : IEquatable<SecurityPredicate> {
|
||||
class SecurityPredicate : IPredicateBuilder<SecurityImmutable>, IEquatable<SecurityPredicate> {
|
||||
[ProtoMember(1)]
|
||||
SecurityCriteria _criteria;
|
||||
[ProtoMember(2)]
|
||||
@@ -43,8 +43,7 @@ namespace mbts {
|
||||
_criteria == null ? PTOC.None :
|
||||
_negate ? PTOC.NotInCriteria : PTOC.InCriteria;
|
||||
|
||||
public IPredicate<SecurityImmutable> create() => _criteria.is_defined() ? new MyPredicate(_criteria, _negate) : null;
|
||||
|
||||
public IPredicate<SecurityImmutable> create(object obj) => _criteria.is_defined() ? new MyPredicate(_criteria, _negate) : null;
|
||||
public bool Equals(SecurityPredicate other) =>
|
||||
other != null &&
|
||||
_negate == other._negate &&
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using bts.utils;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -12,6 +14,23 @@ namespace mbts {
|
||||
bool trigger(T value);
|
||||
}
|
||||
|
||||
interface IPredicateBuilder<T> {
|
||||
IPredicate<T> create(object obj);
|
||||
}
|
||||
|
||||
interface INotificationPredicateBuilder<T> : IPredicateBuilder<T> {
|
||||
bool is_toast_enabled { get; set; }
|
||||
INotificationPredicate<T> create_notification_predicate(object obj);
|
||||
}
|
||||
|
||||
interface IPredicateBuilder<T, TArg> : IPredicateBuilder<T> {
|
||||
IPredicate<T> create_predicate(TArg arg);
|
||||
}
|
||||
|
||||
interface INotificationPredicateBuilder<T, TArg> : IPredicateBuilder<T, TArg>, INotificationPredicateBuilder<T> {
|
||||
INotificationPredicate<T> create_notification_predicate(TArg arg);
|
||||
}
|
||||
|
||||
public enum PredicateCategory {
|
||||
Normal,
|
||||
Event
|
||||
|
||||
+41
-36
@@ -13,23 +13,25 @@ using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.settings {
|
||||
interface IPredicateVM<TRowData, TPredicateData, TPredicateDataOneof> : ITemplatedPropertyWrapperVM<TPredicateData> where TPredicateDataOneof : struct {
|
||||
IReadOnlyCollection<TPredicateDataOneof> predicate_types { get; }
|
||||
interface IPredicateBuilderVM<TRowData, TPredicateBuilder, TPredicateBuilderOneof> : ITemplatedPropertyWrapperVM<TPredicateBuilder>
|
||||
where TPredicateBuilder : IPredicateBuilder<TRowData>
|
||||
where TPredicateBuilderOneof : struct {
|
||||
IReadOnlyCollection<TPredicateBuilderOneof> predicate_types { get; }
|
||||
ICollectionView predicate_types_view { get; }
|
||||
TPredicateDataOneof selected_predicate_type { get; set; }
|
||||
ITemplatedPropertyWrapperVM<TPredicateData> selected_predicate { get; }
|
||||
TPredicateBuilderOneof selected_predicate_type { get; set; }
|
||||
ITemplatedPropertyWrapperVM<TPredicateBuilder> selected_predicate { get; }
|
||||
bool can_enable_toast_notifications { get; }
|
||||
bool toast_notifications_enabled { get; }
|
||||
}
|
||||
|
||||
interface IChildPredicateVM<TSource, TChildPredicate, TChildPredicateOneof> : ITemplatedPropertyWrapperVM<TSource> where TChildPredicateOneof : struct {
|
||||
IReadOnlyCollection<TChildPredicateOneof> predicate_types { get; }
|
||||
interface IChildPredicateBuilderVM<TSource, TChildPredicateBuilder, TChildPredicateBuilderOneof> : ITemplatedPropertyWrapperVM<TSource> where TChildPredicateBuilderOneof : struct {
|
||||
IReadOnlyCollection<TChildPredicateBuilderOneof> predicate_types { get; }
|
||||
ICollectionView predicate_types_view { get; }
|
||||
TChildPredicateOneof selected_predicate_type { get; set; }
|
||||
ITemplatedPropertyWrapperVM<TChildPredicate> selected_predicate { get; }
|
||||
TChildPredicateBuilderOneof selected_predicate_type { get; set; }
|
||||
ITemplatedPropertyWrapperVM<TChildPredicateBuilder> selected_predicate { get; }
|
||||
}
|
||||
|
||||
static class PredicateVMWrapper {
|
||||
static class PredicateBuilderVMWrapper {
|
||||
static readonly bool _toast_notifications_enabled_global = Configuration.get<bts.config.toggles.ToastNotifications>().enabled;
|
||||
|
||||
public static ITemplatedPropertyWrapperVM<TSource> wrap_child_oneof<TSource, TChild, TChildOneof>(
|
||||
@@ -37,17 +39,14 @@ namespace mbts.ui.settings {
|
||||
where TChildOneof : struct
|
||||
where TChild : new() =>
|
||||
//
|
||||
new ChildImpl<TSource, TChild, TChildOneof>(get, set, get_child_oneof, oneof_vms);
|
||||
new PredicateChildWrapperVM<TSource, TChild, TChildOneof>(get, set, get_child_oneof, oneof_vms);
|
||||
|
||||
public static IPredicateVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
|
||||
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key) where TPredicateOneof : struct =>
|
||||
public static IPredicateBuilderVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
|
||||
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key)
|
||||
where TPredicateOneof : struct
|
||||
where TPredicate : IPredicateBuilder<TRowData>, new() =>
|
||||
//
|
||||
new Impl<TRowData, TPredicate, TPredicateOneof>(oneofs, key, _ => false, (p, b) => { });
|
||||
|
||||
public static IPredicateVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
|
||||
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key, Func<TPredicate, bool> get_is_toast_enabled, Action<TPredicate, bool> set_is_toast_enabled) where TPredicateOneof : struct =>
|
||||
//
|
||||
new Impl<TRowData, TPredicate, TPredicateOneof>(oneofs, key, get_is_toast_enabled, set_is_toast_enabled);
|
||||
new PredicateWrapperVM<TRowData, TPredicate, TPredicateOneof>(oneofs, key);
|
||||
|
||||
public static ITemplatedPropertyWrapperVM<TSource> create<TSource>(Action<TSource> set = null) =>
|
||||
new PredicatePlaceholderVM<TSource>(set);
|
||||
@@ -64,30 +63,35 @@ namespace mbts.ui.settings {
|
||||
return cv;
|
||||
}
|
||||
|
||||
class Impl<TRowData, TPredicate, TPredicateOneof> : DisposableViewModelBase, IPredicateVM<TRowData, TPredicate, TPredicateOneof>, ITemplatedPropertyWrapperVM<TPredicate>
|
||||
where TPredicateOneof : struct {
|
||||
class PredicateWrapperVM<TRowData, TPredicate, TPredicateOneof> : DisposableViewModelBase, IPredicateBuilderVM<TRowData, TPredicate, TPredicateOneof>, ITemplatedPropertyWrapperVM<TPredicate>
|
||||
where TPredicateOneof : struct
|
||||
where TPredicate : IPredicateBuilder<TRowData>, new() {
|
||||
readonly Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> _oneofs;
|
||||
readonly Property<TPredicateOneof> _selected_predicate_type;
|
||||
readonly Property<ITemplatedPropertyWrapperVM<TPredicate>> _selected_predicate;
|
||||
readonly DelegateProperty<bool> _can_enable_toast_notifications;
|
||||
readonly Property<bool> _toast_notifications_enabled;
|
||||
readonly Func<TPredicate, TPredicateOneof> _key_selector;
|
||||
readonly Func<TPredicate, bool> _get_is_toast_enabled;
|
||||
readonly Action<TPredicate, bool> _set_is_toast_enabeled;
|
||||
|
||||
public Impl(Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key_selector, Func<TPredicate, bool> get_is_toast_enabled, Action<TPredicate, bool> set_is_toast_enabled) {
|
||||
public PredicateWrapperVM(Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key_selector) {
|
||||
_oneofs = oneofs;
|
||||
_key_selector = key_selector;
|
||||
_get_is_toast_enabled = get_is_toast_enabled;
|
||||
_set_is_toast_enabeled = set_is_toast_enabled;
|
||||
_selected_predicate = backing<ITemplatedPropertyWrapperVM<TPredicate>>(nameof(selected_predicate));
|
||||
_selected_predicate_type = backing<TPredicateOneof>(nameof(selected_predicate_type));
|
||||
predicate_types_view = _create_predicate_types_view(predicate_types);
|
||||
var toastable_predicates = new HashSet<TPredicateOneof>(oneofs.Keys.Where(k => {
|
||||
var pca = EnumHelper.get_attribute<TPredicateOneof, PredicateCaseAttribute>(k);
|
||||
return pca != null && pca.is_toastable;
|
||||
}));
|
||||
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => toastable_predicates.Contains(selected_predicate_type), nameof(selected_predicate_type));
|
||||
if (typeof(TPredicate).GetInterfaces().Contains(typeof(INotificationPredicateBuilder<TRowData>))) {
|
||||
// simple_predicates are only referred to when TPredicate is an INotificationPredicateBuilder, but does not support INotificationPredicates for every predicate-case
|
||||
// (this way we can gradually add notifications as-needed beasis)
|
||||
// for ex: we may release a cut that supports fill notifications without having to create a notification-predicate for every predicate-case in OrderRecordTuplePredicate.
|
||||
// Note: it is assumed that all TPredicateOneof cases DO SUPPORT notification-predicates UNLESS THEY SPECIFY OTHERWISE IN A PREDICATECASEATTRIBUTE.
|
||||
var simple_predicates = new HashSet<TPredicateOneof>(oneofs.Keys.Where(k => {
|
||||
var pca = EnumHelper.get_attribute<TPredicateOneof, PredicateCaseAttribute>(k);
|
||||
return pca != null && !pca.is_toastable;
|
||||
}));
|
||||
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => !simple_predicates.Contains(selected_predicate_type), nameof(selected_predicate_type));
|
||||
} else {
|
||||
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => false);
|
||||
}
|
||||
_toast_notifications_enabled = backing<bool>(nameof(toast_notifications_enabled));
|
||||
selected_predicate_type = predicate_types.FirstOrDefault();
|
||||
}
|
||||
@@ -114,18 +118,19 @@ namespace mbts.ui.settings {
|
||||
public void load_from(TPredicate value) {
|
||||
selected_predicate_type = _key_selector(value);
|
||||
selected_predicate?.load_from(value);
|
||||
if (can_enable_toast_notifications)
|
||||
_toast_notifications_enabled.set(_get_is_toast_enabled(value));
|
||||
if (can_enable_toast_notifications) {
|
||||
_toast_notifications_enabled.set(((INotificationPredicateBuilder<TRowData>)value).is_toast_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public void assign_to(TPredicate value) {
|
||||
selected_predicate?.assign_to(value);
|
||||
if (can_enable_toast_notifications)
|
||||
_set_is_toast_enabeled(value, toast_notifications_enabled);
|
||||
if (toast_notifications_enabled && value is INotificationPredicateBuilder<TRowData> tp)
|
||||
tp.is_toast_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
class ChildImpl<TPredicate, TChild, TChildKey> : DisposableViewModelBase, IChildPredicateVM<TPredicate, TChild, TChildKey> where TChildKey : struct where TChild : new() {
|
||||
class PredicateChildWrapperVM<TPredicate, TChild, TChildKey> : DisposableViewModelBase, IChildPredicateBuilderVM<TPredicate, TChild, TChildKey> where TChildKey : struct where TChild : new() {
|
||||
readonly Property<ITemplatedPropertyWrapperVM<TChild>> _selected_predicate;
|
||||
readonly Property<TChildKey> _selected_predicate_type;
|
||||
readonly Dictionary<TChildKey, ITemplatedPropertyWrapperVM<TChild>> _children;
|
||||
@@ -134,7 +139,7 @@ namespace mbts.ui.settings {
|
||||
readonly Func<TChild, TChildKey> _key;
|
||||
readonly string _template_key;
|
||||
|
||||
public ChildImpl(Func<TPredicate, TChild> get, Action<TPredicate, TChild> set, Func<TChild, TChildKey> key,
|
||||
public PredicateChildWrapperVM(Func<TPredicate, TChild> get, Action<TPredicate, TChild> set, Func<TChild, TChildKey> key,
|
||||
Dictionary<TChildKey, ITemplatedPropertyWrapperVM<TChild>> oneofs, string template = PredicateTemplateKeys.Recursive) {
|
||||
_get = get;
|
||||
_set = set;
|
||||
@@ -4,8 +4,7 @@
|
||||
<Authors>Blue Trading Systems</Authors>
|
||||
<Company>Blue Trading Systems</Company>
|
||||
<Product>Spark</Product>
|
||||
<Description>Advanced Derivatives Trading</Description>
|
||||
<ApplicationManifest>spark.manifest</ApplicationManifest>
|
||||
<Description>The game has changed. Make your Move.</Description>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x64</Platforms>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:control="clr-namespace:mbts.ui.controls">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:bv="clr-namespace:bts.view"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:control="clr-namespace:mbts.ui.controls"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Images/Vector/Icons.xaml" />
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml" />
|
||||
@@ -516,9 +519,16 @@
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<control:SparkMenuItem
|
||||
Content="{Binding title}" Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
|
||||
Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
|
||||
Collapsed="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type control:SparkMenu}}, Path=ShowText, Converter={StaticResource NotConverter}}"
|
||||
/>
|
||||
>
|
||||
<control:SparkMenuItem.Content>
|
||||
<MultiBinding Converter="{x:Static bv:SparkWindowView.HeaderConverter}">
|
||||
<Binding Path="title"/>
|
||||
<Binding Path="key_gesture"/>
|
||||
</MultiBinding>
|
||||
</control:SparkMenuItem.Content>
|
||||
</control:SparkMenuItem>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -11,13 +12,15 @@ using static mbts.spark.SparkTags;
|
||||
|
||||
namespace bts.spark {
|
||||
public abstract class SparkCommand {
|
||||
public readonly KeyGesture key_gesture;
|
||||
string _title;
|
||||
bool _show_in_toolbar, _show_in_context_menu;
|
||||
public SparkCommand(string title, ImageSource icon, bool show_in_toolbar = true, bool show_in_context_menu = true) {
|
||||
public SparkCommand(string title, ImageSource icon, bool show_in_toolbar = true, bool show_in_context_menu = true, KeyGesture key_gesture = null) {
|
||||
_title = title;
|
||||
this.icon = icon;
|
||||
_show_in_toolbar = show_in_toolbar;
|
||||
_show_in_context_menu = show_in_context_menu;
|
||||
this.key_gesture = key_gesture;
|
||||
}
|
||||
|
||||
public event Action title_changed, show_in_toolbar_changed, show_in_context_menu_changed;
|
||||
@@ -78,15 +81,11 @@ namespace bts.spark {
|
||||
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, Func<T, bool> can_execute) =>
|
||||
create(title, icon, tag, parameter, parameter.Select(can_execute));
|
||||
|
||||
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, Func<T, bool> can_execute, Func<bool> is_confirmation_affirmed) =>
|
||||
create(title, icon, tag, parameter, parameter.Select(can_execute), is_confirmation_affirmed);
|
||||
|
||||
public static SparkCommandParamObserver<T> create<T, TSource>(string title, ImageSource icon, ITag<T> tag, IObservable<TSource> parameter_source, Func<TSource, bool> parameter_source_where, Func<TSource, T> parameter_selector) =>
|
||||
create(title, icon, tag, parameter_source.Select(parameter_selector), parameter_source.Select(parameter_source_where));
|
||||
|
||||
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute, Func<bool> is_confirmation_affirmed = null) =>
|
||||
new SparkCommandParamObserver<T>(title, icon, tag, parameter, can_execute, is_confirmation_affirmed);
|
||||
|
||||
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) =>
|
||||
new SparkCommandParamObserver<T>(title, icon, tag, parameter, can_execute);
|
||||
}
|
||||
|
||||
public static class SparkCommandExtensions {
|
||||
@@ -136,15 +135,11 @@ namespace bts.spark {
|
||||
public SparkCommandParamObserver<T> complete(IObservable<T> value, Func<T, bool> can_execute) =>
|
||||
SparkCommand.create(title, icon, tag, value, can_execute);
|
||||
|
||||
public SparkCommandParamObserver<T> complete(IObservable<T> value, Func<T, bool> can_execute, Func<bool> is_confirmation_affirmed) =>
|
||||
SparkCommand.create(title, icon, tag, value, can_execute, is_confirmation_affirmed);
|
||||
|
||||
public SparkCommandParamObserver<T> complete<TSource>(IObservable<TSource> value_source, Func<TSource, bool> value_source_where, Func<TSource, T> value_selector) =>
|
||||
SparkCommand.create(title, icon, tag, value_source, value_source_where, value_selector);
|
||||
|
||||
public SparkCommandParamObserver<T> complete(IObservable<T> parameter, IObservable<bool> can_execute) =>
|
||||
SparkCommand.create(title, icon, tag, parameter, can_execute);
|
||||
|
||||
}
|
||||
|
||||
public class SparkCommandWrapper : SparkCommand, IDisposable {
|
||||
@@ -207,7 +202,8 @@ namespace bts.spark {
|
||||
public class SparkCommandCommand : SparkCommand {
|
||||
readonly ICommand _a;
|
||||
|
||||
public SparkCommandCommand(string title, ImageSource icon, ICommand a) : base(title, icon) {
|
||||
public SparkCommandCommand(string title, ImageSource icon, ICommand a, KeyGesture key_gesture = null)
|
||||
: base(title, icon, key_gesture: key_gesture) {
|
||||
_a = a;
|
||||
}
|
||||
|
||||
@@ -230,9 +226,8 @@ namespace bts.spark {
|
||||
}
|
||||
|
||||
public class SparkCommandParamObserver<T> : SparkCommand, IDisposable {
|
||||
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute, Func<bool> is_confirmation_affirmed = null) : base(title, icon) {
|
||||
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) : base(title, icon) {
|
||||
_tag = tag;
|
||||
_is_confirmation_affirmed = is_confirmation_affirmed;
|
||||
_subs = Disposable.Create(
|
||||
parameter.OnDispatcher().Subscribe(p => _parameter = p),
|
||||
can_execute.OnDispatcher().Subscribe(ce => {
|
||||
@@ -246,12 +241,10 @@ namespace bts.spark {
|
||||
IDisposable _subs;
|
||||
T _parameter;
|
||||
bool _can_execute;
|
||||
Func<bool> _is_confirmation_affirmed;
|
||||
|
||||
|
||||
public override bool canExecute() => _can_execute;
|
||||
public override void execute(IBus bus) {
|
||||
if (_can_execute && (_is_confirmation_affirmed == null || _is_confirmation_affirmed())) {
|
||||
if (_can_execute) {
|
||||
try {
|
||||
bus.post(_tag, _parameter);
|
||||
} catch (Exception e) {
|
||||
@@ -325,6 +318,7 @@ namespace bts.spark {
|
||||
}
|
||||
|
||||
public string title => _cmd.title;
|
||||
public KeyGesture key_gesture => _cmd.key_gesture;
|
||||
public ImageSource icon => _cmd.icon;
|
||||
public bool show_in_toolbar => _cmd.show_in_toolbar;
|
||||
public bool show_in_context_menu => _cmd.show_in_context_menu;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark
|
||||
public static ITag<IEnumerable<(OrderDefinitionImmutable definition, double price, uint size)>> RecreateOrder { get; } = new SparkTag<IEnumerable<(OrderDefinitionImmutable, double, uint)>>();
|
||||
|
||||
public static ITag<IEnumerable<uint>> CancelOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<IEnumerable<uint>> CrackOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<IEnumerable<uint>> DetachOrders { get; } = new SparkTag<IEnumerable<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));
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace mbts.ui.controls {
|
||||
var rv = new ObservableAggregateCollection<TInner>();
|
||||
var aggregator = new Aggregator<TInner>(rv);
|
||||
foreach (var o in source)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
source.CollectionChanged += _mk_outter_collection_changed_handler(aggregator, select_inner);
|
||||
return rv;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace mbts.ui.controls {
|
||||
aggregate.Clear();
|
||||
var aggregator = new Aggregator<TInner>(aggregate);
|
||||
foreach (var o in source)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
var handler = _mk_outter_collection_changed_handler(aggregator, select_inner);
|
||||
source.CollectionChanged += handler;
|
||||
return Disposable.Create(() => source.CollectionChanged -= handler);
|
||||
@@ -37,23 +37,23 @@ namespace mbts.ui.controls {
|
||||
add_collections(e.NewStartingIndex, e.NewItems);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
aggregator.clear_collections();
|
||||
aggregator.Clear();
|
||||
foreach (TOutter o in (IEnumerable<TOutter>)s)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
break;
|
||||
}
|
||||
};
|
||||
void add_collections(int new_item_index, IList new_items) {
|
||||
foreach (TOutter o in new_items)
|
||||
aggregator.insert_collection(new_item_index++, select_inner(o));
|
||||
aggregator.Insert(new_item_index++, select_inner(o));
|
||||
}
|
||||
void remove_collections(int old_item_index, IList old_items) {
|
||||
foreach (TOutter o in old_items)
|
||||
aggregator.remove_collection_at(old_item_index);
|
||||
aggregator.RemoveAt(old_item_index);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Aggregator<T> : IDisposable {
|
||||
internal class Aggregator<T> : IDisposable, IList<ObservableCollection<T>>, IReadOnlyList<ObservableCollection<T>> {
|
||||
readonly ObservableCollection<T> _target;
|
||||
readonly List<ObservableCollection<T>> _collections = new List<ObservableCollection<T>>();
|
||||
bool _is_disposed;
|
||||
@@ -61,28 +61,39 @@ namespace mbts.ui.controls {
|
||||
public Aggregator(ObservableCollection<T> target, params ObservableCollection<T>[] collections) {
|
||||
_target = target;
|
||||
foreach (var c in collections)
|
||||
add_collection(c);
|
||||
Add(c);
|
||||
}
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) {
|
||||
public ObservableCollection<T> this[int index] {
|
||||
get => _collections[index];
|
||||
set => Insert(index, value);
|
||||
}
|
||||
|
||||
public int Count => _collections.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(ObservableCollection<T> collection) {
|
||||
_check_disposed();
|
||||
_target.AddRange(collection);
|
||||
_collections.Add(collection);
|
||||
collection.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
|
||||
public void remove_collection(ObservableCollection<T> collection) {
|
||||
public bool Remove(ObservableCollection<T> collection) {
|
||||
int num_before = _collections.Count;
|
||||
_check_disposed();
|
||||
_remove_collection_items(collection, out int _, out int collection_index);
|
||||
_collections.RemoveAt(collection_index);
|
||||
collection.CollectionChanged -= _on_collection_changed;
|
||||
return _collections.Count < num_before;
|
||||
}
|
||||
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) {
|
||||
public void Insert(int index, ObservableCollection<T> collection) {
|
||||
_check_disposed();
|
||||
if (index < 0 || index > _collections.Count) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (index == _collections.Count)
|
||||
add_collection(collection);
|
||||
Add(collection);
|
||||
else {
|
||||
_collections.Insert(index, collection);
|
||||
_on_collection_changed(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, 0));
|
||||
@@ -90,12 +101,12 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_collection_at(int index) {
|
||||
public void RemoveAt(int index) {
|
||||
_check_disposed();
|
||||
remove_collection(_collections[index]);
|
||||
Remove(_collections[index]);
|
||||
}
|
||||
|
||||
public void clear_collections() {
|
||||
public void Clear() {
|
||||
_check_disposed();
|
||||
foreach (var c in _collections)
|
||||
c.CollectionChanged -= _on_collection_changed;
|
||||
@@ -103,6 +114,15 @@ namespace mbts.ui.controls {
|
||||
_target.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(ObservableCollection<T> item) =>
|
||||
_collections.Contains(item);
|
||||
|
||||
public void CopyTo(ObservableCollection<T>[] array, int arrayIndex) =>
|
||||
_collections.CopyTo(array, arrayIndex);
|
||||
|
||||
public int IndexOf(ObservableCollection<T> item) =>
|
||||
_collections.IndexOf(item);
|
||||
|
||||
public void Dispose() {
|
||||
if (!_is_disposed) {
|
||||
foreach (var c in _collections)
|
||||
@@ -111,6 +131,9 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<ObservableCollection<T>> GetEnumerator() => _collections.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => _collections.GetEnumerator();
|
||||
|
||||
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
_check_disposed();
|
||||
int offset = _get_collection_start(sender);
|
||||
@@ -166,10 +189,12 @@ namespace mbts.ui.controls {
|
||||
public ObservableAggregateCollection(params ObservableCollection<T>[] collections) =>
|
||||
_aggregator = new ObservableAggregateCollection.Aggregator<T>(this, collections);
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) => _aggregator.add_collection(collection);
|
||||
public void remove_collection(ObservableCollection<T> collection) => _aggregator.remove_collection(collection);
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) => _aggregator.insert_collection(index, collection);
|
||||
public void remove_collection_at(int index) => _aggregator.remove_collection_at(index);
|
||||
public void clear_collections() => _aggregator.clear_collections();
|
||||
public IReadOnlyList<ObservableCollection<T>> source_collections => _aggregator;
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) => _aggregator.Add(collection);
|
||||
public void remove_collection(ObservableCollection<T> collection) => _aggregator.Remove(collection);
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) => _aggregator.Insert(index, collection);
|
||||
public void remove_collection_at(int index) => _aggregator.RemoveAt(index);
|
||||
public void clear_collections() => _aggregator.Clear();
|
||||
}
|
||||
}
|
||||
@@ -192,16 +192,16 @@ namespace mbts.ui.utility {
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) {
|
||||
foreach (T v in source)
|
||||
action(v);
|
||||
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
|
||||
}
|
||||
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) =>
|
||||
connect_item_actions(source, source, action);
|
||||
|
||||
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) {
|
||||
foreach (T v in source)
|
||||
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) =>
|
||||
connect_item_actions(source, source, action);
|
||||
|
||||
public static IDisposable connect_item_actions<T>(INotifyCollectionChanged notifier, IEnumerable<T> collection, Action<T> action) {
|
||||
foreach (T v in collection)
|
||||
action(v);
|
||||
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
|
||||
return notifier.Subscribe((s, e) => CollectionHelper.process_new_items(collection, action, e));
|
||||
}
|
||||
|
||||
public static IDisposable connect_item_hooks<T>(IReadOnlyObservableCollection<T> source, Func<T, IDisposable> get_item_hook) {
|
||||
@@ -218,6 +218,18 @@ namespace mbts.ui.utility {
|
||||
source.Subscribe((s, e) => CollectionHelper.reflect_change(item_hooks, source, get_item_hook, d => d.Dispose(), e)));
|
||||
}
|
||||
|
||||
public static IDisposable dispose_on_removed<T>(IReadOnlyObservableCollection<T> source) where T : IDisposable =>
|
||||
connect_item_hooks(source, v => v);
|
||||
|
||||
public static IDisposable dispose_on_removed<T>(ObservableCollection<T> source, bool clear_source_on_dispose) where T : IDisposable =>
|
||||
connect_item_hooks(source, v => v, clear_source_on_dispose);
|
||||
|
||||
public static IDisposable try_dispose_on_removed<T>(IReadOnlyObservableCollection<T> source) =>
|
||||
connect_item_hooks(source, v => v as IDisposable ?? Disposable.empty);
|
||||
|
||||
public static IDisposable try_dispose_on_removed<T>(ObservableCollection<T> source, bool clear_source_on_dispose = false) =>
|
||||
connect_item_hooks(source, v => v as IDisposable ?? Disposable.empty, clear_source_on_dispose);
|
||||
|
||||
class SortedWrapper<T> : IReadOnlyObservableCollection<T> {
|
||||
const int _min_capacity = 4;
|
||||
readonly IComparer<T> _comparer;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace mbts.ui.utilities
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
readonly TSourceCollection _source;
|
||||
readonly IList<T> _target;
|
||||
IDictionary<T, int> _cache = new Dictionary<T, int>();
|
||||
IDictionary<T, int> _cache;
|
||||
IDisposable _sub;
|
||||
|
||||
public DistinctCollectionResolver(TSourceCollection source, IList<T> target, Func<TSource, T> get_value)
|
||||
@@ -38,6 +38,7 @@ namespace mbts.ui.utilities
|
||||
_target = target;
|
||||
_get_value = get_value;
|
||||
_comparer = comparer;
|
||||
_cache = new Dictionary<T, int>(comparer);
|
||||
_sync();
|
||||
_sub = _source.Subscribe(_source_CollectionChanged);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,11 @@ namespace mbts.ui.utility {
|
||||
foreach (var value in source_collection.OfType<INotifyPropertyChanged>())
|
||||
value.PropertyChanged += on_item_PropertyChanged;
|
||||
}
|
||||
CollectionHelper.sync_with_never_duplicate(filtered_collection, source_collection.Where(filter));
|
||||
if (source_collection.Count == 0) {
|
||||
filtered_collection.Clear();
|
||||
} else {
|
||||
CollectionHelper.sync_with_never_duplicate(filtered_collection, source_collection.Where(filter));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,4 +217,4 @@ namespace mbts.ui.utility {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,5 +24,8 @@ namespace mbts {
|
||||
return (uint)value;
|
||||
}
|
||||
}
|
||||
|
||||
public static double? nan_nullable(double value) =>
|
||||
double.IsNaN(value) ? (double?)null : value;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,17 @@ namespace System.Collections.Generic {
|
||||
// yield return item;
|
||||
//}
|
||||
|
||||
public static T SafeSingleOrDefault<T>(this IEnumerable<T> source) {
|
||||
using (var en = source.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
var rv_candidate = en.Current;
|
||||
if (!en.MoveNext())
|
||||
return rv_candidate;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
|
||||
TAccumulate a = seed;
|
||||
foreach (T el in source) {
|
||||
@@ -86,6 +97,28 @@ namespace System.Collections.Generic {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetSingle<T>(this IEnumerable<T> source, out T result) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
using (var en = source.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
result = en.Current;
|
||||
if (!en.MoveNext())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetSingle<T, TResult>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T, TResult> convert, out TResult result) {
|
||||
if (TryGetSingle(source.Where(predicate), out T v)) {
|
||||
result = convert(v);
|
||||
return true;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, T default_rv) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
foreach (T element in source)
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Reactive.Linq;
|
||||
using System.Collections;
|
||||
using mbts.utils;
|
||||
using System.Threading.Tasks;
|
||||
using bts;
|
||||
using mbts;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using System.Collections.Specialized;
|
||||
using mbts.spark;
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
namespace mbts
|
||||
{
|
||||
namespace mbts {
|
||||
public static class GlobalExtensions
|
||||
{
|
||||
#region AddRange
|
||||
@@ -105,8 +98,8 @@ namespace mbts
|
||||
return items.GroupBy(key_selector).ToDictionary(g => g.Key, g => g.AsEnumerable());
|
||||
}
|
||||
|
||||
public static IDictionary<K, V> MapAggregate<K, V, T>(this IEnumerable<T> items, Func<T, K> key_selector, Func<T, V> value_selector, V seed, Func<V, V, V> aggregate, IEqualityComparer<K> key_equality_comparer) {
|
||||
return items.GroupBy(key_selector, key_equality_comparer).ToDictionary(g => g.Key, g => g.Select(value_selector).Aggregate(seed, aggregate));
|
||||
public static IDictionary<K, V> MapAggregate<K, V, T>(this IEnumerable<T> items, Func<T, K> key_selector, Func<T, V> value_selector, V seed, Func<V, V, V> aggregate, IEqualityComparer<K> key_comparer) {
|
||||
return items.GroupBy(key_selector, key_comparer).ToDictionary(g => g.Key, g => g.Select(value_selector).Aggregate(seed, aggregate), key_comparer);
|
||||
}
|
||||
|
||||
public static int aggregate_hash<T>(this IEnumerable<T> series) where T : class {
|
||||
@@ -238,8 +231,7 @@ namespace mbts
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Collections.Specialized
|
||||
{
|
||||
namespace System.Collections.Specialized {
|
||||
public static class NotifyCollectionChangedExtensions
|
||||
{
|
||||
public static IDisposable SuspendHandler(this INotifyCollectionChanged source, NotifyCollectionChangedEventHandler handler) {
|
||||
@@ -258,8 +250,7 @@ namespace System.Collections.Specialized
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Reactive.Linq
|
||||
{
|
||||
namespace System.Reactive.Linq {
|
||||
public static class ObservableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -397,4 +388,4 @@ namespace System.Reactive.Linq
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,6 @@ namespace mbts.ui.native
|
||||
[DllImport("libspark")]
|
||||
public static extern void enable_limits();
|
||||
|
||||
[DllImport("libspark")]
|
||||
public static extern uint setup_process_job_object();
|
||||
|
||||
[DllImport("libspark", CharSet = CharSet.Unicode)]
|
||||
public static extern void set_window_app_id(IntPtr hwnd, string stack_id);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui.shell;
|
||||
using bts.utils;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -98,8 +99,12 @@ namespace mbts.ui {
|
||||
}
|
||||
}
|
||||
|
||||
public static string diagnostic_name(this IShellWindow window) =>
|
||||
$"[{window.window_type}]{window.Title}";
|
||||
public static string diagnostic_name(this IShellWindow window) {
|
||||
var sb = new StringBuilderEx { "[", window.window_type, "]", window.Title };
|
||||
if (window is ISharedWindow shared_win)
|
||||
sb.Add("(thread: ", shared_win.thread_id, ")");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static IObservable<string> observe_title(this IShellWindow window) {
|
||||
var src = Observable.FromEvent<Action, string>(cb => () => cb(window.Title), cb => window.TitleChanged += cb, cb => window.TitleChanged -= cb);
|
||||
|
||||
Loaded 100 of 404 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user