Compare commits
64
Commits
fix2
..
excel_algos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0abb668a0 | ||
|
|
6a0f45ed72 | ||
|
|
9baa81b14f | ||
|
|
a43e649c06 | ||
|
|
6bf39b43f8 | ||
|
|
851a34449a | ||
|
|
5609b8d80d | ||
|
|
dffb9018da | ||
|
|
aec1d1ee6e | ||
|
|
fa4b4fc934 | ||
|
|
49c584047a | ||
|
|
773ea38ffa | ||
|
|
81b7770151 | ||
|
|
e480b8951f | ||
|
|
781cf3ea6d | ||
|
|
93f86ecb94 | ||
|
|
f360b79d27 | ||
|
|
7bf3c51380 | ||
|
|
1192182bc8 | ||
|
|
419cac3e3d | ||
|
|
2e8dbd8478 | ||
|
|
938db6b616 | ||
|
|
d6bc9298cc | ||
|
|
d7ea71de84 | ||
|
|
2881e5ea32 | ||
|
|
4fd8530bc7 | ||
|
|
ffd5915c52 | ||
|
|
70f1552ec6 | ||
|
|
f3114d1664 | ||
|
|
88494eb0fc | ||
|
|
d5427f6c24 | ||
|
|
106788c8c8 | ||
|
|
6968312a04 | ||
|
|
4c9dc9d83b | ||
|
|
ff193b13f0 | ||
|
|
fc9582850b | ||
|
|
f1ae9030d7 | ||
|
|
fc5439e92e | ||
|
|
908b50a92a | ||
|
|
eb7aa17f2e | ||
|
|
f92722c849 | ||
|
|
ebedd17129 | ||
|
|
0f8ed5dacb | ||
|
|
14f4452599 | ||
|
|
22002c43cf | ||
|
|
7551e23877 | ||
|
|
22c92c3e17 | ||
|
|
613823e7c5 | ||
|
|
f0a9a2415d | ||
|
|
708e63e36e | ||
|
|
53990297e8 | ||
|
|
9570be8bfa | ||
|
|
0b15570d00 | ||
|
|
5fbc1397b5 | ||
|
|
38bd2ef321 | ||
|
|
59c00203a9 | ||
|
|
706e5bc73b | ||
|
|
6cf395199e | ||
|
|
1707c3268f | ||
|
|
9b73ad2486 | ||
|
|
205eebb7da | ||
|
|
ef4dc1fc65 | ||
|
|
474401cddc | ||
|
|
3ebcf9a270 |
No files matched your search
+2
-2
@@ -32,5 +32,5 @@ trace_data/
|
||||
/src/lib/orderman/perf_analysis/last_pretty_log.csv
|
||||
/src/lib/orderman/perf_analysis/log.csv
|
||||
/doc/user/public/*
|
||||
.ionide/*
|
||||
/.ignore
|
||||
.ionide/*
|
||||
src/web/ui/.ionide/symbolCache.db
|
||||
Vendored
+5
-4
@@ -44,6 +44,7 @@
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/build/ninja_debug/sdk/examples" : "${workspaceRoot}/src/sdk/examples",
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
@@ -104,7 +105,7 @@
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": [
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
@@ -118,7 +119,7 @@
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"test",
|
||||
"ApiUpdateTests"
|
||||
"test_filter"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
@@ -135,13 +136,13 @@
|
||||
"name": "Spreader Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"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",
|
||||
|
||||
@@ -59,6 +59,7 @@ else ()
|
||||
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/
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/excel_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
@@ -53,25 +53,3 @@ If exposed_qty is set to 0 the splitter will continue to exist in the Order Book
|
||||
##### Parameters
|
||||
* exposed_qty - The total quantity to expose on the market.
|
||||
* max_order_qty - The maximum size of a single order.
|
||||
|
||||
|
||||
## Scratch
|
||||
The scratch algo is similar to Lean, but instead of watching the opposite inside market, it watches the same side market and pays up when this quantity recedes below the scratch_threshold
|
||||
|
||||
##### Parameters
|
||||
* scratch_threshold - The quantity on the same side market below which to scratch
|
||||
* chase_ticks - The number of ticks through the opposite market to pay up
|
||||
|
||||
|
||||
## ProRata
|
||||
The prorata algo is a special case hedging algo for pro-rata markets. Users can specify two hedge types-- normal hedge and overfill hedge, the latter is spawned when you receive more fills than your fill_limit. Further, the algo attempts to scratch when in an overfilled state AND optionally the same side size falls below stay_in_threshold
|
||||
|
||||
##### Parameters
|
||||
* hedge_ticks - The amount of ticks better hedges are placed on the opposite side market as the fill
|
||||
* chase_ticks - How many ticks through the opposite market on a scratch
|
||||
* fill_limit - The limit after which the algo considers further fills to be 'overfills'
|
||||
* stay_in_threshold - A same side size threshold that enables the algo to 'stay in' even if overfilled
|
||||
* enable_scratch_on_overfill - When true, overfills are immediately scratched upon the algo exiting
|
||||
* enable_stay_in - When true, the algo will 'stay in' so long as the same side size is above stay_in_threshold, when false the algo attempts to 'get out' (and potentially scratch overfills) as soon as it reaches overfill status
|
||||
* hedge - Child order type to send for normal hedges
|
||||
* overfill_hedge - Child order type to send for overfill hedges
|
||||
@@ -4,82 +4,6 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.10.0
|
||||
### User Visible Changes
|
||||
- Fills now include information about the algo that originated the fill
|
||||
- A new, more powerful PnL form is now available
|
||||
### API Changes
|
||||
- Added security state information (pre open, halted, normal, closed, etc)
|
||||
### Bug Fixes
|
||||
- Brought back the "select all" check box in the mini order book
|
||||
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
|
||||
### Internal Changes
|
||||
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
|
||||
## 2.9.3
|
||||
### Internal Changes
|
||||
- Fixed system test scripts
|
||||
## 2.9.2
|
||||
### Bug Fixes
|
||||
- Fixed an issue with the exchange simulator that could lead to crossed markets
|
||||
## 2.9.1
|
||||
### Bug Fixes
|
||||
- Fixed an issue with trade sound alerts, where in some cases the use of a "In Between" condition could lead to a UI failure.
|
||||
- Fixed an issue with the display of the new raw position column in the market view window.
|
||||
- Fixed the popup label that describes the raw position information in the ladder
|
||||
### Internal Changes
|
||||
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
|
||||
### Api
|
||||
- C# API sample now uses synchronous calls.
|
||||
|
||||
## 2.9.0
|
||||
### User Visible Changes
|
||||
- Improved Workspace management User Interface
|
||||
- Added the ability to change grid background colors
|
||||
- The Spark UI Window group now has a title in the Windows task bar
|
||||
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
|
||||
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
|
||||
- Added import facilities for GMI position files
|
||||
- Added the ability for risk managers to enter manual fills
|
||||
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
|
||||
### Bug Fixes
|
||||
- Fixed the settings action menu (Load/Save/Reset to Factory)
|
||||
- Fixed the method used to determine if a security is expired to take into account the start of day time
|
||||
### API Changes
|
||||
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
|
||||
- Added the ability for algo orders to distinguish fills existing prior to launch
|
||||
### Internal Changes
|
||||
- Changed how we packaged certain third-party components
|
||||
- Added logging of entitlement status
|
||||
- Tagged outgoing orders with sequence number of marketdata or FIX event that triggered them
|
||||
## 2.8.3
|
||||
### Bug Fixes
|
||||
- Fixed a bug that could cause an OrderBook's Security column filter to be blank
|
||||
- Fixed a bug that could cause a UI failure when clearing a filter selection on a filtering widget
|
||||
## 2.8.2
|
||||
### Bug Fixes
|
||||
- Added proper display factors for SOM spreads
|
||||
- Fixed a bug that could cause a UI failure when trying to change settings in a synthetic ladder
|
||||
## 2.8.1
|
||||
### Bug fixes
|
||||
- Fixed an error in the UI grids filtering widget
|
||||
## 2.8.0
|
||||
### User Visible Changes
|
||||
- Added ProRata and Scratch algos
|
||||
- Ladder badge now grows/shrinks to fill the containing cell
|
||||
- Attached algo parameters are now displayed in the update panel
|
||||
- Fixed a bug where the update panel would show '-' values when only
|
||||
1 order was selected
|
||||
- Fixed a bug that cause cause a failure on startup if the user
|
||||
loaded a workspace with a Ladder that had a dynamic order type
|
||||
selected by default
|
||||
- Increased width of login panel to better show login error messages
|
||||
- Added optional 'Maker/Taker' pricing to Spreader algo
|
||||
- Algo buttons in ladder can now be made to deactivate after launching an algo or remain 'sticky'
|
||||
### Internal changes
|
||||
- Made it easier to generate simple UI's for new order types
|
||||
### API Changes
|
||||
- Dynamic orders can now use QuantityMeasure parameter types
|
||||
|
||||
## 2.7.2
|
||||
### User Visible Changes
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
@@ -423,8 +347,7 @@ insert_anchor_links = "right"
|
||||
- The zoom slider can be enabled from Ladder Form -> Settings button -> Left Pane tab -> check Zoom Slider checkbox
|
||||
- The zoom functionality can also be performed via hotkey and mouse wheel combinations
|
||||
- Zoom In = CTRL + Mouse wheel up
|
||||
- Zoom Out = CTRL + Mouse wheel down
|
||||
|
||||
- Zoom Out = CTRL + Mouse wheel down␍
|
||||
- Reset zoom = CTRL + Mouse wheel click or double click on the zoom slider
|
||||
### Exchange simulator
|
||||
- Improved the exchange simulator to track real market data more closely
|
||||
|
||||
+1
-1
Submodule externals updated: b5ca264286...67a79263ba.
@@ -70,6 +70,7 @@ namespace mbts.ui.shell
|
||||
{
|
||||
Log.Gen.info("Application.OnExit");
|
||||
if (_runtime != null) _runtime.Dispose();
|
||||
utils.BTSNativeMethods.finit_spark_lib();
|
||||
base.OnExit(e);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using mbts;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utilities;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using Disposable = mbts.Disposable;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
class GlobalAlertsDaemon : IShellDaemon, IDisposable {
|
||||
const string
|
||||
// used for logging/diagnostic purposes
|
||||
_window_name = "Global Alerts Dialog",
|
||||
// actual window title
|
||||
_window_title = "Alerts";
|
||||
|
||||
readonly ThreadedWindowLauncher _launcher;
|
||||
readonly UserSettingsHub _hub;
|
||||
readonly IBus _bus;
|
||||
readonly INotificationService _notification_service;
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public GlobalAlertsDaemon(IBus bus, UserSettingsHub hub, INotificationService notification_service) {
|
||||
_bus = bus;
|
||||
_hub = hub;
|
||||
_notification_service = notification_service;
|
||||
_launcher = new ThreadedWindowLauncher(_window_name);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowGlobalAlertsTag)
|
||||
.Subscribe(_ => _launcher.show(_view_factory)),
|
||||
_hub.subscribe_default_settings()
|
||||
.SubscribeEach(s => new CompositeDisposable(
|
||||
s.alerts.trade_alerts.Where(t => t.is_enabled).Select(
|
||||
a => _notification_service.link_to_trades(
|
||||
a.predicate.security_criteria, a.predicate.create(), a.sound)))));
|
||||
}
|
||||
|
||||
Window _view_factory() {
|
||||
var vm = new GlobalAlertsVM();
|
||||
vm.load_from(_hub.current.alerts);
|
||||
vm.request_commit += commit;
|
||||
var sec_sub = _bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => vm.security_map = m);
|
||||
var win = new GlobalAlertsDialog {
|
||||
Title = "Alerts",
|
||||
DataContext = vm
|
||||
};
|
||||
win.Closed += (s, e) => Disposable.dispose(ref sec_sub);
|
||||
return win;
|
||||
void commit() {
|
||||
var s = _hub.current.with(alerts: vm.create());
|
||||
_hub.save_default(s).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using bts.api.core;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.api.orderman;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.api.persistence;
|
||||
using bts.api.position;
|
||||
using bts.api.synthetic;
|
||||
@@ -46,11 +45,41 @@ namespace Spark {
|
||||
|
||||
[Export]
|
||||
public MarketDataRelay md_replay => context.locate<MarketDataRelay>();
|
||||
//[Export]
|
||||
//public AlgoService algo_service => context.locate<AlgoService>();
|
||||
|
||||
AlgoServiceWrapper _algo_service_wrapper;
|
||||
[Export]
|
||||
public AlgoDefinitionService algo_definition_service => context.locate<AlgoDefinitionService>();
|
||||
public AlgoServiceWrapper algo_service_wrapper => _algo_service_wrapper ?? (_algo_service_wrapper = new AlgoServiceWrapper(context));
|
||||
|
||||
[Export]
|
||||
public MBOService mbo_service => context.locate<MBOService>();
|
||||
}
|
||||
|
||||
class AlgoServiceWrapper {
|
||||
public enum Mode {
|
||||
None,
|
||||
Hierarchical,
|
||||
Flat
|
||||
}
|
||||
|
||||
public AlgoServiceWrapper(ServiceContext context) {
|
||||
var flat_model_options = mbts.config.Configuration.get<bts.config.AlgoStudioFlatDefinitionModelOptions>();
|
||||
if (flat_model_options.enabled) {
|
||||
flat = context.locate<bts.api.orderman.algo.AlgoDefinitionService>();
|
||||
} else {
|
||||
hierarhical = context.locate<AlgoService>();
|
||||
}
|
||||
}
|
||||
|
||||
public AlgoService hierarhical { get; }
|
||||
public bts.api.orderman.algo.AlgoDefinitionService flat { get; }
|
||||
public Mode mode {
|
||||
get {
|
||||
if (hierarhical != null) return Mode.Hierarchical;
|
||||
if (flat != null) return Mode.Flat;
|
||||
return Mode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,18 +40,14 @@ namespace mbts.ui {
|
||||
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
|
||||
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
|
||||
GearGeometryKey = nameof(GearGeometryKey),
|
||||
HornGeometryKey = nameof(HornGeometryKey),
|
||||
KeyGeometryKey = nameof(KeyGeometryKey),
|
||||
LetterAKey = nameof(LetterAKey),
|
||||
LetterDKey = nameof(LetterDKey),
|
||||
OrdersGeometryKey = nameof(OrdersGeometryKey),
|
||||
PauseGeometryKey = nameof(PauseGeometryKey),
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
PencilGeometryKey = nameof(PencilGeometryKey),
|
||||
PlayGeometryKey = nameof(PlayGeometryKey),
|
||||
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
|
||||
RubberStampGeometryKey = nameof(RubberStampGeometryKey),
|
||||
SpeakerGeometryKey = nameof(SpeakerGeometryKey),
|
||||
StarGeometryKey = nameof(StarGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
TrashGeometryKey = nameof(TrashGeometryKey),
|
||||
@@ -153,7 +149,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 CopyWF => create_icon(CopyWFGeometryKey);
|
||||
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
|
||||
public static ImageSource DataEdit => create_icon(DataEditGeometryKey, Brushes.White, new Pen(Brushes.Black, 1).frozen());
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
|
||||
public static ImageSource Disk => create_icon(DiskGeometryKey);
|
||||
@@ -172,19 +168,14 @@ namespace mbts.ui {
|
||||
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
|
||||
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
|
||||
public static ImageSource Gear => create_icon(GearGeometryKey);
|
||||
public static ImageSource Horn => create_icon(HornGeometryKey);
|
||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||
public static ImageSource Key => create_icon(KeyGeometryKey);
|
||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||
public static ImageSource Pause => create_icon(PauseGeometryKey);
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
||||
public static ImageSource Play => create_icon(PlayGeometryKey);
|
||||
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
|
||||
public static ImageSource RubberStamp => create_icon(RubberStampGeometryKey);
|
||||
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 Speaker => create_icon(SpeakerGeometryKey);
|
||||
public static ImageSource Star => create_icon(StarGeometryKey);
|
||||
public static ImageSource Target { get { return load_resource_and_freeze("Target"); } }
|
||||
public static ImageSource Tetris { get { return load_resource_and_freeze("Tetris"); } }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<ResourceDictionary
|
||||
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
@@ -18,7 +18,9 @@
|
||||
Ttile: Add free icon
|
||||
Credit: Icon made by Gregor Cresnar from www.flaticon.com-->
|
||||
<sys:String x:Key="{x:Static mbts:Icons.AddBoxesGeometryKey}">
|
||||
M 87.5 10 C 80.313542 10 74.400391 15.913151 74.400391 23.099609 V 397.90039 C 74.400391 405.08685 80.313541 411 87.5 411 h 374.80078 c 7.18646 0 13.09961 -5.91315 13.09961 -13.09961 V 23.099609 C 475.40039 15.913151 469.48724 10 462.30078 10 Z m 13 26.199219 H 449.19922 V 384.90039 H 100.5 Z M 21.099609 76.400391 C 13.912943 76.400391 8 82.313333 8 89.5 v 374.80078 c 0 7.18667 5.912943 13.09961 13.099609 13.09961 H 395.90039 c 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 0 -7.18667 -5.91294 -13.10156 -13.09961 -13.10156 H 34.199219 V 89.5 c 0 -7.186667 -5.912943 -13.099609 -13.09961 -13.099609 z M 274.90039 116.5 c -7.18667 0 -13.09961 5.91294 -13.09961 13.09961 V 197.5 h -67.90039 c -7.18667 0 -13.09961 5.91294 -13.09961 13.09961 0 7.18667 5.91294 13.09961 13.09961 13.09961 h 67.90039 v 67.90039 c 0 7.18667 5.91294 13.09961 13.09961 13.09961 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 v -67.90039 h 67.90039 c 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 v -0.0391 -0.0391 C 368.85982 203.37217 363.03372 197.5 355.90039 197.5 H 288 V 129.59961 C 288 122.41294 282.08706 116.5 274.90039 116.5 Z
|
||||
M393,480.3c0-5-4.1-9.1-9.1-9.1H18.2V105.5c0-5-4.1-9.1-9.1-9.1S0,100.5,0,105.5v374.8c0,5,4.1,9.1,9.1,9.1h374.8 C388.9,489.4,393,485.3,393,480.3z
|
||||
M373.9,187.5H302v-71.9c0-5-4.1-9.1-9.1-9.1s-9.1,4.1-9.1,9.1v71.9h-71.9c-5,0-9.1,4.1-9.1,9.1s4.1,9.1,9.1,9.1h71.9v71.9 c0,5,4.1,9.1,9.1,9.1s9.1-4.1,9.1-9.1v-71.9h71.9c5,0,9.1-4.1,9.1-9.1C382.9,191.5,378.9,187.5,373.9,187.5z
|
||||
M105.5,393h374.8c5,0,9.1-4.1,9.1-9.1V9.1c0-5-4.1-9.1-9.1-9.1H105.5c-5,0-9.1,4.1-9.1,9.1v374.8 C96.4,388.9,100.5,393,105.5,393z M114.5,18.2h356.7v356.7H114.5V18.2z
|
||||
</sys:String>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
@@ -114,7 +116,7 @@
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DataEditGeometryKey}">
|
||||
M 22.589844 14.470703 H 0.82421875 C 0.36896661 14.470586 -1.1747368e-4 14.83967 0 15.294922 v 4.669922 c -1.1747368e-4 0.455252 0.36896661 0.824336 0.82421875 0.824218 H 16.940231 l 0.425003 -1.09375 0.554688 -0.554687 H 1.6484375 V 16.119141 H 20.941406 Z M 27.462891 9.5976562 V 8.0742188 A 0.8240064 0.8240064 0 0 0 26.638672 7.25 H 0.82421875 A 0.8240064 0.8240064 0 0 0 0 8.0742188 v 4.6699222 a 0.8240064 0.8240064 0 0 0 0.82421875 0.824218 H 23.492188 l 1.648437 -1.648437 H 1.6484375 V 8.8984375 H 25.814453 V 11.246094 Z M 0.82421878 2.736881e-8 A 0.8240064 0.8240064 0 0 0 2.7433281e-8 0.82421879 V 5.4941406 A 0.8240064 0.8240064 0 0 0 0.82421878 6.3183594 H 26.638672 A 0.8240064 0.8240064 0 0 0 27.46289 5.4941406 V 0.82421879 A 0.8240064 0.8240064 0 0 0 26.638672 2.736881e-8 Z M 1.6484375 1.6484375 H 25.814453 V 4.6699219 H 1.6484375 Z M 31.628051 7.2558423 19.165575 19.718635 23.70324 24.256618 36.166032 11.793823 Z m -0.0013 3.5965607 0.941422 0.94142 -8.866233 8.866232 -0.94142 -0.94142 z m -13.240122 9.708458 -1.636234 6.110706 6.109329 -1.637606 z
|
||||
M17.997977,17.396268C18.197928,17.396268,18.297904,17.496276,18.497975,17.596284L19.497972,18.295283C19.99797,18.595292,20.697919,19.195308,21.197917,19.495299L22.196937,20.195323C22.696935,20.495315,22.596959,20.995322,22.096961,21.195338L20.997966,21.695346C20.497968,21.89434,19.597947,22.294355,19.097949,22.494355L17.997977,22.994363C17.497979,23.194362,17.097957,22.894355,17.197932,22.394347L17.297908,21.195338C17.398005,20.595323,17.497979,19.695315,17.497979,19.195308L17.597955,17.995277C17.497979,17.695283,17.69793,17.396268,17.997977,17.396268z M1.799981,13.597213L19.697923,13.597213 17.997977,15.896244C17.797906,16.096259 17.69793,16.396253 17.69793,16.69626 17.197932,16.796268 16.79791,17.296275 16.697934,17.895284L16.597959,19.195308 1.799981,19.195308C0.79998477,19.195308,0,18.395291,0,17.396268L0,15.396237C0,14.396221,0.79998477,13.597213,1.799981,13.597213z M1.799981,6.7981106L21.796914,6.7981106C22.696935,6.7981106,23.49698,7.498118,23.596955,8.3981257L20.697919,12.397197 1.799981,12.397197C0.79998477,12.397197,0,11.597181,0,10.597165L0,8.5981418C0,7.598126,0.79998477,6.7981106,1.799981,6.7981106z M27.796891,4.3990708C27.996963,4.3990708,28.096938,4.3990708,28.296889,4.5990711L31.695924,7.0981179C31.995851,7.3981105,32.095948,7.7981258,31.895875,8.0981341L23.296908,19.795308C23.096957,20.095315,22.596959,20.195323,22.196937,19.895314L18.797902,17.396268C18.497975,17.096274,18.398002,16.69626,18.597951,16.396253L27.196918,4.6990786C27.396869,4.4990784,27.59694,4.3990708,27.796891,4.3990708z M1.799981,0L21.796914,0C22.79691,4.9549271E-08,23.596955,0.8000156,23.596955,1.800031L23.596955,3.7990554C23.596955,4.7990709,22.79691,5.5990864,21.796914,5.5990864L1.799981,5.5990864C0.79998477,5.4990941,0,4.6990786,0,3.6990629L0,1.7000236C0,0.8000156,0.79998477,4.9549271E-08,1.799981,0z
|
||||
</sys:String>
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DiskGeometryKey}">
|
||||
M403.213,20H0v472h512V128.787L403.213,20z M131,50h180v100h10h20h10V50h30v130H131V50z M381,462H131V315
|
||||
@@ -272,22 +274,10 @@
|
||||
M16.000015,9.6000061C12.500012,9.6000061 9.5999844,12.5 9.5999844,16 9.5999844,19.5 12.500012,22.399994 16.000015,22.399994 19.500019,22.399994 22.400015,19.5 22.400015,16 22.400015,12.5 19.500019,9.6000061 16.000015,9.6000061z M14.199995,0L17.800005,0C18.199999,0,18.599993,0.29998779,18.599993,0.79998779L18.599993,4.7999878C19.900013,5.1000061,21.000019,5.6000061,22.099997,6.2999878L25.000023,3.3999939C25.099999,3.2999878 25.300011,3.1999817 25.500025,3.1999817 25.700007,3.1999817 25.900019,3.2999878 26.000025,3.3999939L28.600033,6C28.900021,6.2999878,28.900021,6.7999878,28.600033,7.1000061L25.700007,10C26.400019,11.100006,26.900019,12.299988,27.200007,13.5L31.20001,13.5C31.600037,13.5,31.999998,13.799988,31.999998,14.299988L31.999998,17.899994C31.999998,18.299988,31.700012,18.699982,31.20001,18.699982L27.200007,18.699982C26.900019,20,26.400019,21.100006,25.700007,22.199982L28.600033,25.100006C28.700009,25.199982 28.799984,25.399994 28.799984,25.600006 28.799984,25.799988 28.700009,26 28.600033,26.100006L26.000025,28.600006C25.900019,28.699982 25.700007,28.799988 25.500025,28.799988 25.300011,28.799988 25.099999,28.699982 25.000023,28.600006L22.099997,25.699982C21.000019,26.399994,19.800007,26.899994,18.599993,27.199982L18.599993,31.199982C18.599993,31.600006,18.300005,32,17.800005,32L14.199995,32C13.8,32,13.400006,31.699982,13.400006,31.199982L13.400006,27.199982C12.099987,26.899994,11.000011,26.399994,9.9000027,25.699982L7.0000064,28.600006C6.9000003,28.699982 6.6999881,28.799988 6.5000059,28.799988 6.2999937,28.799988 6.099981,28.699982 6.0000054,28.600006L3.3999967,26C3.0999784,25.699982,3.0999784,25.199982,3.3999967,24.899994L6.2999937,22C5.5999805,20.899994,5.0999801,19.699982,4.7999923,18.5L0.79998828,18.5C0.39999409,18.5,-1.933513E-07,18.199982,0,17.699982L0,14.100006C-1.933513E-07,13.699982,0.2999879,13.299988,0.79998828,13.299988L4.7999923,13.299988C5.0999801,12,5.5999805,10.899994,6.2999937,9.7999878L3.3999967,7C3.2999908,6.8999939 3.1999845,6.6999817 3.1999845,6.5 3.1999845,6.2999878 3.2999908,6.1000061 3.3999967,6L6.0000054,3.3999939C6.099981,3.2999878 6.2999937,3.1999817 6.5000059,3.1999817 6.6999881,3.1999817 6.9000003,3.2999878 7.0000064,3.3999939L9.9000027,6.2999878C11.000011,5.6000061,12.199993,5.1000061,13.400006,4.7999878L13.400006,0.79998779C13.400006,0.29998779,13.8,0,14.199995,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.HornGeometryKey}">
|
||||
M4.0499969,7.0870913C2.9519978,7.0870913 2.0249984,8.4780906 2.0249987,10.125091 2.0249984,11.771091 2.9519978,13.162091 4.0499969,13.162091 5.1479961,13.162091 6.0749954,11.771091 6.0749955,10.125091 6.0749954,8.4780906 5.1479961,7.0870913 4.0499969,7.0870913z M29.975016,3.316155C23.883831,8.2016557,11.829888,8.9691842,8.1184387,9.0896781L8.0151367,9.0928462 8.0175586,9.1059306C8.0716033,9.4352315 8.0999939,9.7760909 8.0999942,10.125091 8.0999939,10.473966 8.0716033,10.814716 8.0175586,11.143921L8.0098858,11.185364 8.193316,11.195448C12.163719,11.420399,23.994581,12.458921,29.975016,17.005057z M31.176758,0.00019051835C31.280359,-0.0022594665 31.391857,0.01889403 31.514975,0.065146674 31.908983,0.21413228 31.999985,0.59114302 31.999985,1.0121596L31.999985,19.236969C31.999985,19.653958 31.913988,20.028956 31.525991,20.179955 31.405998,20.226952 31.450981,20.250023 31.32699,20.250023 31.050991,20.250023 30.778992,20.13601 30.583986,19.925928 25.229074,14.140572 9.6001122,13.269665 7.2865356,13.172559L7.2813506,13.172345 7.1739591,13.343056C6.4304952,14.468533 5.3060585,15.187091 4.0499969,15.187091 1.8169985,15.187091 0,12.916091 0,10.125091 0,7.3330911 1.8169985,5.0620912 4.0499969,5.0620912 5.3060585,5.0620912 6.4304952,5.78065 7.1739591,6.9063042L7.2852713,7.0832756 7.3087114,7.0828188C9.6077337,7.036146 25.141474,6.4939421 30.567997,0.34315929 30.75913,0.12521249 30.948835,0.005580858 31.176758,0.00019051835z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.KeyGeometryKey}">
|
||||
M22.399981,2.4000006C22.099982,2.4000006 21.899982,2.5 21.599982,2.6999998 21.199983,3.1000004 21.199983,3.8000002 21.599982,4.2000003L26.199979,8.9000001C26.599978,9.3000002 27.299977,9.3000002 27.699977,8.9000001 28.099977,8.5000002 28.099977,7.8000002 27.699977,7.4000006L23.099981,2.6999998C22.899981,2.5,22.699981,2.4000006,22.399981,2.4000006z M21.499982,0C22.299982,0,23.199981,0.30000019,23.79998,0.90000057L29.299976,6.4000001C30.599975,7.7000003,30.599975,9.7000003,29.299976,11L22.599981,17.7C21.299983,19,19.299984,19,17.999985,17.7L17.299985,17 12.699989,21.6 10.399991,20.900001 10.899991,23.400001 8.4999928,22.8 8.9999924,25.5 6.2999945,25.1 6.7999945,27.6 4.2999964,27 4.7999954,29.6 2.4999971,32 0,26.6 13.499989,13.1 16.999986,16.6 12.699989,12.3C11.399991,11,11.399991,9.0000002,12.699989,7.7000003L19.399984,1C19.899983,0.30000019,20.699983,0,21.499982,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
|
||||
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.PauseGeometryKey}">
|
||||
M17.94,0L29.150001,0 29.150001,32 17.94,32z M0,0L11.210002,0 11.210002,32 0,32z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.PaymentGeometryKey}">
|
||||
M28.000001,6.0000026L28.000001,15 6.0010006,15 6.0010006,17.000003 30.001,17.000003 30.001,6.0000026z M14.231,2.9540056L14.979999,2.9540056 14.979999,3.987005C15.704998,4.009005,16.200997,4.178005,16.550998,4.3600049L16.248998,5.0620046C15.994998,4.9340047 15.499998,4.7010047 14.725999,4.7010047 13.796,4.7010047 13.445001,5.1900046 13.445001,5.6160046 13.445001,6.1710043 13.893,6.4470041 14.943999,6.8310037 16.187998,7.2780037 16.816998,7.8320036 16.816998,8.7800031 16.816998,9.6210027 16.151998,10.409003 14.907999,10.591002L14.907999,11.698002 14.146,11.698002 14.146,10.622003C13.445001,10.611002,12.720001,10.420002,12.297002,10.154003L12.587001,9.4400029C13.022001,9.696003 13.651,9.8980026 14.328,9.8980026 15.185999,9.8980026 15.765998,9.4620028 15.765998,8.8540034 15.765998,8.2680035 15.293999,7.9060035 14.4,7.5870035 13.167001,7.1610038 12.406001,6.6710041 12.406001,5.7440041 12.406001,4.8600047 13.119,4.189005 14.231,4.0290052z M2,2.0000002L2,13 26.000001,13 26.000001,2.0000002z M0,0L28.000001,0 28.000001,4.0000031 32.001,4.0000031 32.001,19.000003 4.0010006,19.000003 4.0010006,15 0,15z
|
||||
</sys:String>
|
||||
@@ -337,11 +327,6 @@
|
||||
M33.5,4h-6v10h6V4z M31.5,12h-2V6h2V12z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.SpeakerGeometryKey}">
|
||||
M18,12.839666L18,19.159448 18.029955,19.131576C18.848108,18.314159 19.332083,17.192702 19.332083,15.999873 19.332083,14.806357 18.848108,13.684685 18.029955,12.867527z M21.738388,4.748316C21.868353,4.7515661 21.999973,4.7804414 22.126221,4.8376924 26.511095,6.8177201 29.344013,11.199782 29.344013,15.999849 29.344013,20.799915 26.511095,25.181977 22.126221,27.162004 21.992224,27.223004 21.853228,27.251006 21.715233,27.251006 21.333244,27.251006 20.970255,27.032003 20.803259,26.662997 20.575266,26.15899 20.799259,25.566981 21.302244,25.339978 24.973139,23.681956 27.34407,20.015905 27.34407,15.999849 27.34407,11.983792 24.973139,8.3177406 21.302244,6.6597177 20.799259,6.4327146 20.575266,5.8407065 20.803259,5.3366992 20.973503,4.9594442 21.348494,4.7385656 21.738388,4.748316z M16,3.1854247L6.6530151,11.257323C6.4719848,11.414303,6.2399902,11.500301,6,11.500301L1.9999998,11.500301 1.9999998,21.500148 6,21.500148C6.2219848,21.500148,6.4370117,21.573144,6.6110229,21.709129L16,28.964023z M17.000252,1.186163E-08C17.141373,-2.1778678E-05 17.283264,0.02997845 17.416016,0.091477852 17.770996,0.25450047 18,0.60843792 18,1.0004608L18,10.368904 18.170259,10.465408C20.111607,11.612996 21.331976,13.716092 21.331976,15.999873 21.331976,18.282748 20.111607,20.38576 18.170259,21.53334L18,21.629842 18,31.000003C18,31.382016 17.78302,31.728995 17.440002,31.896962 17.301025,31.965991 17.150024,31.99999 17,31.99999 16.78302,31.99999 16.567017,31.929982 16.388977,31.791007L5.65802,23.500117 0.99999966,23.500117C0.44702145,23.500117,-3.9348969E-08,23.053104,3.5527137E-15,22.500133L3.5527137E-15,10.500316C-3.9348969E-08,9.9473459,0.44702145,9.5003311,0.99999966,9.5003311L5.6279907,9.5003311 16.346985,0.24345344C16.531998,0.083467316,16.765053,3.6392792E-05,17.000252,1.186163E-08z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.StarGeometryKey}">
|
||||
M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z
|
||||
</sys:String>
|
||||
@@ -351,7 +336,7 @@
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.TrashGeometryKey}">
|
||||
M15.000001,8.0000272L17,8.0000272 17,27.000028 15.000001,27.000028z M11.000001,8.0000272L13.000001,8.0000272 13.000001,27.000028 11.000001,27.000028z M7.0000005,8.0000272L9.000001,8.0000272 9.000001,27.000028 7.0000005,27.000028z M3.0790101,5.0000274L4.9959994,29.922999 19.000006,30.000026 20.918949,5.0000274z M13.771989,1.9959999L10.198989,2.0000001C10.158989,2.025,9.9989892,2.241,9.9989892,2.6000001L9.9989892,3.0000258 13.998989,3.0000258 13.998989,2.6000001C13.998989,2.241,13.838988,2.025,13.771989,1.9959999z M10.198989,0L13.798988,0C15.031989,0,15.998989,1.142,15.998989,2.6000001L15.998989,3.0000258 23.07898,3.0000258 24,3.0000272 24,5.0000274 22.925121,5.0000274 20.995976,30.076991C20.999027,31.102992,20.100956,32.000026,18.999029,32.000026L4.9990512,32.000026C3.8960255,32.000026,2.9990543,31.102992,2.999054,30.000026L1.073059,5.0000274 0,5.0000274 0,3.0000272 0.91897895,3.0000272 7.9989887,3.0000258 7.9989887,2.6000001C7.9989887,1.142,8.9659892,0,10.198989,0z
|
||||
M0,12.568994L23.662999,12.568994 20.910989,31.999999 2.7520113,31.999999z M8.0689915,1.6269979L8.0689915,4.6679968 15.594984,4.6679968 15.594984,1.6269979z M6.4420097,0L17.221997,0 17.221997,4.6679968 23.662999,4.6679968 23.662999,8.7949938 0,8.7949938 0,4.6679968 6.4420097,4.6679968z
|
||||
</sys:String>
|
||||
|
||||
<!--UndoCircular (the icon is free to use if we include this information in our credit/about section)
|
||||
@@ -411,10 +396,10 @@
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="
|
||||
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
|
||||
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
|
||||
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
|
||||
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
|
||||
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
|
||||
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
|
||||
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
|
||||
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
|
||||
M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
@@ -430,10 +415,10 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Brush="Black" Geometry="
|
||||
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
|
||||
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
|
||||
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
|
||||
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
|
||||
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
|
||||
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
|
||||
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
|
||||
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
|
||||
M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
@@ -849,7 +834,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M23,14C24.10498,14 25,14.89502 25,16 25,17.10498 24.10498,18 23,18 21.89502,18 21,17.10498 21,16 21,14.89502 21.89502,14 23,14z M16,14C17.105,14 18,14.895 18,16 18,17.105 17.105,18 16,18 14.895,18 14,17.105 14,16 14,14.895 14.895,14 16,14z M9,14C10.105,14 11,14.895 11,16 11,17.105 10.105,18 9,18 7.8950005,18 7,17.105 7,16 7,14.895 7.8950005,14 9,14z M16,2C8.2799997,2 2,8.2800002 2,16 2,23.72 8.2799997,30 16,30 23.72,30 30,23.72 30,16 30,8.2800002 23.72,2 16,2z M16,0C24.822,0 32,7.178 32,16 32,24.822 24.822,32 16,32 7.1780005,32 0,24.822 0,16 0,7.178 7.1780005,0 16,0z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -862,7 +847,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M299.152955025434,0L496.426178902388,0 268.890046089888,280.247772216797 198.190201729536,367.407897949219 199.87503144145,369.495361328125 271.502137154341,457.706451416016 498.999970406294,738 301.717621773481,738 101.234124153852,491.008697509766 99.5304498374462,488.936553955078 98.6409082114697,490.003173828125 0,368.488952636719 98.6409082114697,246.945526123047z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -874,7 +859,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="{mbts:BTSColor MarketData.bid}"
|
||||
Geometry="m 24.706673 31.196264 -3.086182 3.086182 5.965481 5.963348 -5.965481 5.96548 3.086182 3.084049 5.963348 -5.963348 5.96548 5.963348 3.084049 -3.084049 -5.963347 -5.96548 5.963347 -5.963348 -3.084049 -3.086182 -5.96548 5.965481 z M 5.3507926 32.045363 H 17.816449 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132994 v 2.401501 H 5.3507926 Z m 0 9.874466 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z M 10.662215 24.759265 V 5.1202697 h 7.148923 q 3.286862 0 5.05355 1.2051818 1.766687 1.2051838 1.766687 3.3964241 0 1.5886494 -1.081924 2.7801354 -1.068231 1.191488 -2.739053 1.657126 v 0.05479 q 2.095375 0.26021 3.341644 1.547564 1.259962 1.287354 1.259962 3.136214 0 2.697964 -1.93103 4.286613 -1.931032 1.574956 -5.272673 1.574956 z M 15.085783 8.3797408 v 4.6563852 h 1.944726 q 1.369526 0 2.150155 -0.657372 0.794325 -0.671068 0.794325 -1.835164 0 -2.1638492 -3.23208 -2.1638492 z m 0 7.9432472 v 5.176805 h 2.39667 q 1.533868 0 2.39667 -0.712152 0.876495 -0.712155 0.876495 -1.944728 0 -1.17779 -0.862802 -1.848858 -0.849105 -0.671067 -2.382974 -0.671067 z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -886,7 +871,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="{mbts:BTSColor MarketData.ask}"
|
||||
Geometry="m 19.060265 26.797642 v -2.144583 q 0.583058 0.489233 1.266644 0.737201 0.683586 0.241265 1.380576 0.241265 0.408811 0 0.710393 -0.07372 0.308284 -0.07372 0.509339 -0.201055 0.207756 -0.134036 0.308284 -0.308283 0.100527 -0.18095 0.100527 -0.388706 0 -0.281477 -0.160844 -0.502637 -0.160844 -0.22116 -0.44232 -0.408811 -0.274775 -0.187651 -0.656779 -0.361898 -0.382004 -0.174248 -0.824324 -0.355197 -1.125906 -0.469128 -1.682158 -1.146012 -0.549549 -0.676884 -0.549549 -1.635245 0 -0.750604 0.301582 -1.28675 0.301582 -0.542847 0.817622 -0.891342 0.522743 -0.348495 1.206328 -0.509339 0.683586 -0.167545 1.447594 -0.167545 0.750604 0 1.326961 0.09383 0.583059 0.08712 1.072292 0.274775 v 2.003845 q -0.241266 -0.167545 -0.529444 -0.29488 -0.281477 -0.127335 -0.583059 -0.207757 -0.301582 -0.08712 -0.603164 -0.127334 -0.29488 -0.04021 -0.562953 -0.04021 -0.3686 0 -0.670182 0.07372 -0.301582 0.06702 -0.509339 0.194353 -0.207756 0.127334 -0.321687 0.308284 -0.113931 0.174247 -0.113931 0.395407 0 0.241266 0.127334 0.435619 0.127335 0.187651 0.361899 0.361898 0.234564 0.167546 0.569655 0.335091 0.335091 0.160844 0.757306 0.335091 0.576356 0.241266 1.03208 0.516041 0.462426 0.268073 0.790816 0.609866 0.328389 0.341793 0.502636 0.784113 0.174248 0.435618 0.174248 1.018677 0 0.804219 -0.308284 1.353768 -0.301582 0.542848 -0.824324 0.884641 -0.522743 0.335091 -1.219732 0.482531 -0.690288 0.14744 -1.460998 0.14744 -0.790815 0 -1.50791 -0.134036 -0.710393 -0.134037 -1.233135 -0.40211 z m 6.620223 3.788289 -1.556212 1.556212 3.008102 3.007026 -3.008102 3.008101 1.556212 1.555136 3.007026 -3.007026 3.008101 3.007026 1.555136 -1.555136 -3.007026 -3.008101 3.007026 -3.007026 -1.555136 -1.556212 -3.008101 3.008102 z m -9.76023 2.917766 h 2.792621 v 1.21096 h -2.792621 z m 0 -2.489606 h 6.285825 v 1.21096 h -6.285825 z m 0 -2.489607 h 9.470951 v 1.210961 H 15.920258 Z M 13.914475 14.855173 a 0.69291063 0.69291063 0 0 0 -0.692361 0.692361 v 22.317061 a 0.69291063 0.69291063 0 0 0 0.692361 0.692361 h 7.806047 a 9.0291392 9.0291392 0 0 1 -0.02954 -0.692361 9.0291392 9.0291392 0 0 1 0.03447 -0.693345 H 14.60782 V 16.24088 h 15.419061 v 12.624011 a 9.0291392 9.0291392 0 0 1 0.693346 -0.02954 9.0291392 9.0291392 0 0 1 0.692361 0.03447 V 15.547534 a 0.69291063 0.69291063 0 0 0 -0.692361 -0.692361 z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -899,7 +884,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M 5.3507926 32.045363 H 17.81645 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132995 v 2.401501 H 5.3507926 Z M 18.799228 22.222041 v 2.32929 h -1.567173 v -2.264886 q -2.179014 -0.01073 -4.057475 -0.987533 v -2.973336 q 0.622575 0.504501 1.856993 0.933864 1.234417 0.429362 2.200482 0.493766 v -3.907198 q -2.511771 -0.933863 -3.4349 -2.018003 -0.912395 -1.094874 -0.912395 -2.662047 0 -1.685248 1.191482 -2.8659947 1.19148 -1.1914807 3.155813 -1.3846939 V 4.918734 h 1.567173 v 1.9535992 q 2.264887 0.1073411 3.38123 0.7299162 V 10.500446 Q 20.677689 9.5880506 18.799228 9.3841034 v 4.0682086 q 2.35076 0.847992 3.370495 1.942866 1.019736 1.08414 1.019736 2.629845 0 1.781854 -1.137811 2.876728 -1.13781 1.08414 -3.25242 1.32029 z M 17.232055 12.829737 V 9.4270397 q -1.492034 0.2683516 -1.492034 1.5564393 0 1.105608 1.492034 1.846258 z m 1.567173 3.628113 v 3.25242 q 1.53497 -0.236149 1.53497 -1.53497 0 -1.051939 -1.53497 -1.71745 z m 13.258587 13.65074 h -3.954117 l 0.0014 7.641804 -7.643169 -10e-7 0.0014 3.952751 h 7.640439 l 0.0014 7.641803 h 3.951385 l 0.0014 -7.641802 7.640437 -10e-7 0.0014 -3.952751 -7.643169 10e-7 z M 5.3507926 36.982596 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -911,7 +896,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="m 26.463403 27.963318 h -2.359042 l -0.683586 -2.137882 h -3.417929 l -0.676884 2.137882 h -2.345638 l 3.498351 -9.610414 h 2.566798 z M 22.92484 24.163384 21.89276 20.933106 q -0.113931 -0.361899 -0.160844 -0.864535 h -0.05361 q -0.03351 0.422214 -0.167545 0.837727 l -1.045485 3.257086 z m 2.065112 7.055953 -1.51223 1.51223 2.923087 2.922042 -2.923087 2.923086 1.51223 1.511185 2.922042 -2.922042 2.923086 2.922042 1.511185 -1.511185 -2.922041 -2.923086 2.922041 -2.922042 -1.511185 -1.51223 -2.923086 2.923087 z m -9.484387 0.416058 h 6.108175 v 1.176737 h -6.108175 z m 0 -2.419244 h 9.203284 v 1.176735 h -9.203284 z m 0 4.83849 h 2.713696 v 1.176736 H 15.505565 Z M 13.556469 15.93316 a 0.67332766 0.67332766 0 0 0 -0.672794 0.672793 v 21.686339 a 0.67332766 0.67332766 0 0 0 0.672794 0.672793 h 7.585433 a 8.7739586 8.7739586 0 0 1 -0.02871 -0.672793 8.7739586 8.7739586 0 0 1 0.0335 -0.673751 H 14.230219 V 17.279703 h 14.983289 v 12.267233 a 8.7739586 8.7739586 0 0 1 0.67375 -0.02871 8.7739586 8.7739586 0 0 1 0.672794 0.0335 V 16.605953 A 0.67332766 0.67332766 0 0 0 29.887258 15.93316 Z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -923,7 +908,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="m 24.706673 31.196264 -3.086181 3.086182 5.96548 5.963348 -5.96548 5.96548 3.086181 3.084049 5.963348 -5.963348 5.96548 5.963348 3.084049 -3.084049 -5.963347 -5.96548 5.963347 -5.963348 -3.084049 -3.086182 -5.96548 5.965481 z M 5.3507926 32.045363 H 17.81645 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132994 v 2.401501 H 5.3507926 Z M 26.434166 6.0031559 14.993666 17.443656 9.6399578 12.089946 6.0100982 15.719802 c 2.9622322 2.959826 6.0772448 6.092263 8.9969908 8.970151 L 30.064121 9.6329208 Z M 5.3507926 36.982596 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -946,7 +931,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="{mbts:BTSColor MarketData.bid}"
|
||||
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -958,7 +943,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="{mbts:BTSColor MarketData.ask}"
|
||||
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -970,7 +955,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -983,7 +968,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="m 69.641861 87.129943 a 36.813842 36.813842 0 0 0 -36.813776 36.813767 36.813842 36.813842 0 0 0 31.044618 36.31355 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.828154 39.50509 41.092964 41.092964 0 0 0 41.09258 41.0931 41.092964 41.092964 0 0 0 41.093104 -41.0931 41.092964 41.092964 0 0 0 -0.10284 -2.9068 41.092964 41.092964 0 0 0 -38.048841 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150946 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.12557 2.49907 27.542416 27.542416 0 0 0 27.54251 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 l -31.44563 -14.89935 a 36.813842 36.813842 0 0 0 1.55701 -10.53786 36.813842 36.813842 0 0 0 -36.813769 -36.813767 z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -995,7 +980,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M 36.50044 86.999995 A 36.813842 36.813842 0 0 0 -0.3133356 123.81377 36.813842 36.813842 0 0 0 30.731282 160.12732 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.8281546 39.50509 41.092964 41.092964 0 0 0 41.0925806 41.0931 41.092964 41.092964 0 0 0 41.093099 -41.0931 41.092964 41.092964 0 0 0 -0.102836 -2.9068 41.092964 41.092964 0 0 0 -38.04884 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150948 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.125574 2.49907 27.542416 27.542416 0 0 0 27.542513 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 L 71.757201 134.35163 A 36.813842 36.813842 0 0 0 73.314213 123.81377 36.813842 36.813842 0 0 0 36.50044 86.999995 Z m 116.60981 115.628085 0.007 37.48967 h -37.49716 l 0.007 19.39257 h 37.48344 L 153.11751 297 h 19.38493 l 0.007 -37.48968 h 37.48342 L 210 240.11775 h -37.49727 l 0.007 -37.48967 z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -1020,7 +1005,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M 37.03498 86.999995 A 36.813842 36.813842 0 0 0 0.2212037 123.81377 36.813842 36.813842 0 0 0 31.265822 160.12732 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.8281553 39.50509 41.092964 41.092964 0 0 0 41.0925813 41.0931 41.092964 41.092964 0 0 0 41.093099 -41.0931 41.092964 41.092964 0 0 0 -0.102836 -2.9068 41.092964 41.092964 0 0 0 -38.04884 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150948 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.125574 2.49907 27.542416 27.542416 0 0 0 27.542503 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 L 72.291741 134.35163 A 36.813842 36.813842 0 0 0 73.848753 123.81377 36.813842 36.813842 0 0 0 37.03498 86.999995 Z m 145.03189 90.486735 -76.71431 76.71435 27.93311 27.93293 L 210 205.41986 Z m -0.008 22.13769 5.79522 5.79544 -54.57645 54.57644 -5.7952 -5.79522 z m -81.50016 59.76108 -10.071854 37.61451 37.606034 -10.0803 z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -1032,7 +1017,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="m 139.80129 180.78831 a 20.783495 20.783495 0 0 0 -20.78345 20.78346 20.783495 20.783495 0 0 0 17.52643 20.50105 l 0.68209 12.05803 1.01877 18.01482 a 23.1993 23.1993 0 0 0 -16.83968 22.30284 23.1993 23.1993 0 0 0 23.1991 23.19938 23.1993 23.1993 0 0 0 23.19935 -23.19938 23.1993 23.1993 0 0 0 -0.058 -1.64105 23.1993 23.1993 0 0 0 -21.4807 -21.47518 l -1.67225 -29.56198 a 20.783495 20.783495 0 0 0 11.34383 -7.12437 l 18.71558 8.86752 v 3.1e-4 a 15.549251 15.549251 0 0 0 -0.0708 1.41086 15.549251 15.549251 0 0 0 15.5493 15.54927 15.549251 15.549251 0 0 0 15.54931 -15.54927 15.549251 15.549251 0 0 0 -15.54931 -15.54932 15.549251 15.549251 0 0 0 -12.67213 6.5572 l -17.75282 -8.41152 a 20.783495 20.783495 0 0 0 0.87902 -5.94921 20.783495 20.783495 0 0 0 -20.78345 -20.78346 z M 31.983713 88.265691 A 30.676234 30.676234 0 0 0 1.3075339 118.94187 30.676234 30.676234 0 0 0 27.17639 149.20122 l 1.006764 17.79752 1.503689 26.58969 A 34.241941 34.241941 0 0 0 4.8316412 226.50722 34.241941 34.241941 0 0 0 39.073263 260.74928 34.241941 34.241941 0 0 0 73.315316 226.50722 34.241941 34.241941 0 0 0 73.229625 224.08505 34.241941 34.241941 0 0 0 41.524291 192.3879 L 39.05604 148.75469 A 30.676234 30.676234 0 0 0 55.799422 138.2392 l 27.624018 13.08837 v 4.4e-4 a 22.950541 22.950541 0 0 0 -0.104638 2.08242 22.950541 22.950541 0 0 0 22.950618 22.9506 22.950541 22.950541 0 0 0 22.95062 -22.9506 22.950541 22.950541 0 0 0 -22.95062 -22.95061 22.950541 22.950541 0 0 0 -18.703946 9.67837 L 61.362463 127.72286 A 30.676234 30.676234 0 0 0 62.65989 118.94187 30.676234 30.676234 0 0 0 31.983713 88.265691 Z" />
|
||||
</DrawingGroup.Children>
|
||||
@@ -1156,7 +1141,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing
|
||||
Brush="Black"
|
||||
Geometry="M 0.814453 2e-6 C 0.365092 -7.56e-4 3.21e-4 0.36314 0 0.812502 v 23.945313 c 0.788895 1.126154 1.626953 -0.02112 1.626953 -0.02112 V 1.625002 h 16.241843 c 0 0 0.80538 -0.934678 -0.124651 -1.625002 z M 3.750875 3.43624 C 3.301514 3.435482 2.936743 3.799378 2.936422 4.24874 v 23.945313 c -7.6e-4 0.450124 0.364329 0.815213 0.814453 0.814453 h 7.439453 C 11.042745 28.716852 10.90913 28.418337 10.789937 28.113975 10.699431 27.872971 10.617994 27.628659 10.545796 27.381553 H 4.563375 V 5.06124 h 15.304687 v 7.039063 c 0.173002 -0.01023 0.346234 -0.01609 0.519531 -0.01758 0.343731 -2.6e-4 0.68726 0.01669 1.029297 0.05078 0.02606 0.0038 0.0521 0.0077 0.07813 0.01172 V 4.248738 C 21.494699 3.799376 21.129928 3.43548 20.680567 3.436238 Z m 13.332031 6.884766 c -1.226661 0 -2.230469 1.002456 -2.230469 2.228516 v 9.572265 l -0.824219 -0.8125 v 0.002 c -0.991917 -0.9815 -2.60964 -0.982302 -3.603515 0 -0.48293 0.477155 -0.751953 1.119962 -0.751953 1.796875 0 0.67607 0.269215 1.317958 0.751953 1.794922 l 4.841797 4.78711 0.0078 0.0078 c 2.115591 1.851014 4.144077 2.322266 5.615234 2.322266 0.547268 0 1.017683 -0.06414 1.388672 -0.142578 1.701281 -0.360558 3.108561 -1.372782 3.751954 -2.75586 0.419048 -0.9011 0.978455 -2.062137 0.923828 -3.261718 4.09e-4 -0.01025 0.0011 -0.0075 0.002 -0.02344 0.0015 -0.02655 0.0039 -0.01047 0.0039 -0.07813 v -5.113281 c 0 -1.161114 -0.955476 -2.109375 -2.121094 -2.109375 -0.04803 0 -0.09044 0.0088 -0.136719 0.01172 -0.207784 -1.015547 -1.021531 -1.826172 -2.095703 -1.826172 -0.343265 0 -0.63914 0.145124 -0.93164 0.285156 -0.397916 -0.616533 -1.045784 -1.0625 -1.830078 -1.0625 -0.189487 0 -0.354735 0.07615 -0.53125 0.121094 v -3.515625 c 0 -1.22606 -1.003809 -2.228516 -2.230469 -2.228516 z m 0 1.699219 c 0.148476 0 0.278642 0.05997 0.375 0.15625 0.09685 0.09678 0.15625 0.225142 0.15625 0.373047 v 5.625 1.740234 c 0 0.464846 0.384763 0.849609 0.849609 0.849609 0.464527 0 0.84961 -0.383921 0.84961 -0.849609 v -1.740234 c 0 -0.148598 0.05896 -0.275762 0.15625 -0.373047 0.09729 -0.09729 0.228277 -0.158203 0.375 -0.158203 0.146722 0 0.277709 0.06091 0.375 0.158203 0.09729 0.09729 0.15625 0.224449 0.15625 0.373047 v 0.77539 0.4375 c 0 0.465373 0.384236 0.84961 0.849609 0.84961 0.465373 0 0.849609 -0.384239 0.849609 -0.84961 v -0.425781 l 0.0059 -0.06836 c 0.0286 -0.265146 0.249414 -0.472657 0.52539 -0.472657 0.148238 0 0.278481 0.05996 0.375 0.15625 0.09649 0.09627 0.15625 0.225145 0.15625 0.373047 v 1.935547 c 0 0.465371 0.384237 0.84961 0.84961 0.84961 0.465372 0 0.849609 -0.384237 0.849609 -0.84961 v -0.65039 c 0.0016 -9e-6 2.95e-4 0 0.002 0 0.238806 0 0.421875 0.185008 0.421875 0.410156 l 0.002 5.0625 c -4.64e-4 0.0084 -0.0023 0.0401 -0.0039 0.07227 -0.0015 0.03041 -0.0059 0.02687 -0.002 0.101563 0.03253 0.757506 -0.372777 1.677807 -0.765625 2.521484 h -0.002 c -0.194342 0.418116 -0.532821 0.810028 -0.976563 1.126953 -0.443595 0.316821 -0.990203 0.55925 -1.585937 0.685547 -0.515602 0.10823 -1.298166 0.179402 -2.248047 -0.03125 -0.47546 -0.105441 -0.991926 -0.281044 -1.539063 -0.560547 -0.547306 -0.279588 -1.127683 -0.66381 -1.724609 -1.185547 L 11.620061 23.69601 c -0.162198 -0.160763 -0.248047 -0.365899 -0.248047 -0.587891 0 -0.223284 0.08648 -0.426518 0.248047 -0.585937 0.168295 -0.166151 0.386826 -0.249911 0.607422 -0.25 0.220595 -9e-5 0.437432 0.08144 0.605469 0.248047 l 2.240234 2.207031 0.01367 0.02148 0.04687 0.03516 0.03711 0.0332 0.02539 0.01758 0.01563 0.01367 0.01563 0.0098 c 0.114637 0.07747 0.248043 0.126397 0.388672 0.140625 l -0.04883 -0.0078 0.04102 0.0098 0.08594 0.0039 h 0.0078 c 0.06844 0 0.126002 -0.0085 0.169922 -0.01758 l 0.0059 -0.002 0.01953 -0.0039 h 0.02539 l 0.06641 -0.02734 0.02734 -0.0098 0.0098 -0.0039 c 0.102105 -0.04223 0.198238 -0.105586 0.28125 -0.189454 h 0.002 c 0.163006 -0.165519 0.243191 -0.385955 0.242188 -0.599609 v -0.002 -0.01953 -11.580078 c 0 -0.147904 0.0594 -0.276271 0.15625 -0.373047 0.09636 -0.09628 0.226522 -0.15625 0.375 -0.15625 z"
|
||||
Geometry="M17.740002,11.999007C17.254002,11.999007,16.859001,12.394008,16.859001,12.879008L16.859001,24.458791 16.860127,24.479386C16.861003,24.607261 16.813003,24.735511 16.716002,24.834012 16.667502,24.883012 16.611378,24.91995 16.551394,24.944762L16.521562,24.955436 16.507633,24.960536 16.506465,24.960837 16.50574,24.961097 16.49194,24.964574 16.459728,24.972863C16.42719,24.979527,16.393502,24.983027,16.359002,24.983027L16.316763,24.980893 16.315716,24.980851 16.307903,24.980445C16.223895,24.971904,16.146014,24.942538,16.07951,24.897597L16.067752,24.887895 16.047276,24.873078 16.015234,24.844558 16.005502,24.836528 16.001236,24.831357 13.737002,22.600011C13.267003,22.134011 12.502003,22.135011 12.031003,22.600011 11.805003,22.82301 11.680003,23.120011 11.680003,23.436011 11.680003,23.751012 11.805003,24.048011 12.031003,24.272012L16.842003,29.031013C19.311003,31.189013 21.546002,31.119014 22.656003,30.886014 23.939002,30.614013 25.015002,29.846013 25.464002,28.880013 25.857001,28.036012 26.301002,27.080012 26.263001,26.195012 26.262002,26.178012 26.268003,26.074012 26.269002,26.056012L26.268003,20.974011C26.268003,20.55601 25.922002,20.215011 25.496001,20.215011 25.335502,20.215011 25.239001,20.246511 25.179877,20.283135L25.144,20.309762 25.144,21.215013C25.144,21.491012 24.919999,21.715013 24.644,21.715013 24.367999,21.715013 24.144,21.491012 24.144,21.215013L24.144,19.278017C24.144,18.793018 23.749,18.399019 23.263,18.399019 22.808312,18.399019 22.432727,18.745308 22.387555,19.18822L22.383004,19.277944 22.383004,19.716007C22.383004,19.992006 22.159003,20.216004 21.883002,20.216004 21.607003,20.216004 21.383002,19.992006 21.383002,19.716007L21.383002,19.278054 21.383002,18.504013C21.383002,18.018016 20.988002,17.623017 20.502002,17.623017 20.016002,17.623017 19.621001,18.018016 19.621001,18.504013L19.621001,20.24402C19.621001,20.52102 19.397001,20.74402 19.121001,20.74402 18.844001,20.74402 18.621001,20.52102 18.621001,20.24402L18.621001,18.504035 18.621001,12.879008C18.621001,12.394008,18.226001,11.999007,17.740002,11.999007z M17.740002,10.999005C18.777001,10.999005,19.621001,11.843007,19.621001,12.879008L19.621001,16.842839 19.687315,16.808824C19.933955,16.689783 20.210345,16.623022 20.502002,16.623022 21.247346,16.623022 21.892987,17.059031 22.197202,17.689331L22.211675,17.721288 22.212884,17.720384C22.513,17.517568 22.874499,17.399021 23.263,17.399021 24.267594,17.399021 25.091062,18.190155 25.141547,19.181478L25.143222,19.247425 25.174827,19.241074C25.276639,19.223799 25.383876,19.21501 25.496001,19.21501 26.473002,19.21501 27.268003,20.00401 27.268003,20.974011L27.268003,26.087012C27.268003,26.098012 27.262002,26.194013 27.262002,26.205012 27.311002,27.281013 26.790002,28.401013 26.371001,29.302012 25.783002,30.566012 24.472001,31.523013 22.863002,31.864013 22.513001,31.938014 22.067002,31.999013 21.547002,31.999013 20.149003,31.999013 18.216002,31.561013 16.161002,29.763013L11.328003,24.983012C10.910003,24.570012 10.680003,24.020011 10.680003,23.436011 10.680003,22.85101 10.910003,22.302011 11.328003,21.88901 12.187003,21.040011 13.583003,21.041011 14.440002,21.88901L15.859002,23.287391 15.859002,12.879008C15.859002,11.843007,16.703002,10.999005,17.740002,10.999005z M6.8659983,4.0500388L19.083997,4.0500388C20.668996,4.0500388,21.957997,5.3460296,21.957997,6.9390478L21.957997,14.653042C21.957997,14.93005 21.734998,15.153042 21.457997,15.153042 21.181996,15.153042 20.957997,14.93005 20.957997,14.653042L20.957997,6.9390478C20.957997,5.8980318,20.116998,5.0500394,19.083997,5.0500394L6.8659983,5.0500394C5.8319979,5.0500394,4.9909983,5.8980318,4.9909984,6.9390478L4.9909984,26.116061C4.9909983,27.157046,5.8319979,28.00504,6.8659983,28.00504L11.696996,28.00504C11.972997,28.00504 12.196996,28.229039 12.196996,28.50504 12.196996,28.781041 11.972997,29.00504 11.696996,29.00504L6.8659983,29.00504C5.2809954,29.00504,3.9909983,27.70905,3.9909983,26.116061L3.9909983,6.9390478C3.9909983,5.3460296,5.2809954,4.0500388,6.8659983,4.0500388z M6.4360008,0L17.467001,0C17.743001,0 17.967001,0.22300029 17.967001,0.50000024 17.967001,0.77600098 17.743001,1.0000012 17.467001,1.0000012L6.4360008,1.0000012C3.4380004,1.0000012,1.0000002,3.4210042,1.0000002,6.3970079L1.0000002,20.617026C1.0000002,20.893025 0.77600026,21.117026 0.50000048,21.117026 0.22400022,21.117026 2.3841858E-07,20.893025 0,20.617026L0,6.3970079C2.3841858E-07,2.8700035,2.8870004,0,6.4360008,0z"
|
||||
/>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
@@ -1164,10 +1149,10 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
|
||||
</DrawingImage>
|
||||
|
||||
|
||||
<!-- Pin
|
||||
<!-- Pin
|
||||
<Path Data="F1M3,11L3,7 0,7 0,6 1,6 1,1 1,0 6,0 6,1 6,6 7,6 7,7 4,7 4,11 3,11z M2,1L2,6 4,6 4,1 2,1z" />
|
||||
Unpin
|
||||
Unpin
|
||||
<Path Data="F1M0,3L4,3 4,0 5,0 5,1 10,1 11,1 11,6 10,6 5,6 5,7 4,7 4,4 0,4 0,3z M10,2L5,2 5,4 10,4 10,2z" />
|
||||
-->
|
||||
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -36,7 +36,6 @@ namespace mbts.spark {
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
|
||||
@@ -90,10 +89,6 @@ namespace mbts.spark {
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
raise_invalidated();
|
||||
}),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
@@ -107,7 +102,6 @@ namespace mbts.spark {
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public double last_price => _last_price.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
|
||||
@@ -10,6 +10,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
@@ -41,13 +42,12 @@ namespace mbts.spark {
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid_brush, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask_brush, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
@@ -77,18 +77,18 @@ namespace mbts.spark {
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Brush background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, DefaultSelectionHighlight, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, null, h_alignment);
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Brush background, Brush background_overlay, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
@@ -96,7 +96,8 @@ namespace mbts.spark {
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
background = background,
|
||||
background_overlay = background_overlay,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
@@ -110,8 +111,14 @@ namespace mbts.spark {
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.BidImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.BidDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.BidMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.AskImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.AskDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.AskMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
@@ -144,7 +151,6 @@ namespace mbts.spark {
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
@@ -156,7 +162,7 @@ namespace mbts.spark {
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
session_low_filter, session_high_filter, position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
@@ -224,7 +230,6 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
@@ -256,7 +261,6 @@ namespace mbts.spark {
|
||||
session_low = session_low_filter.get_state(),
|
||||
session_high = session_high_filter.get_state(),
|
||||
position = position_filter.get_state(),
|
||||
raw_day_position = raw_day_position_filter.get_state(),
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
|
||||
@@ -8,34 +8,8 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for MarketSheetsView.xaml
|
||||
/// </summary>
|
||||
partial class MarketViewView : UserControl {
|
||||
public enum Columns {
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
LastSize,
|
||||
Position,
|
||||
RawDayPosition,
|
||||
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,
|
||||
MarketDescription, ClearedAlias, ProductDescription, HubAlias, StripName, MicCode
|
||||
}
|
||||
|
||||
public MarketViewView() {
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace mbts.spark {
|
||||
_selected_security_criteria_backing,
|
||||
_sorted_selected_security_criteria_backing;
|
||||
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using bts.api;
|
||||
using bts.proto;
|
||||
using bts.settings;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -9,7 +7,6 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -38,8 +35,13 @@ namespace bts.shell {
|
||||
void _show(SecurityCriteria criteria) =>
|
||||
_bus.post(SparkTags.ShowWindow, _create(StartupData.Default(criteria)));
|
||||
|
||||
IShellWindow _create(StartupData state) =>
|
||||
new MarketViewWindow(this, _bus, _orders_model, state, _settings_hub.create_mview_settings_manager(state.grid));
|
||||
IShellWindow _create(StartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.market_view_grid,
|
||||
set: (us, gs) => us.with(provided_market_view_gridsettings: gs));
|
||||
return new MarketViewWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
@@ -66,29 +68,7 @@ namespace bts.shell {
|
||||
_sorting_criteria = sort;
|
||||
}
|
||||
|
||||
public static GridSettings sanitize(GridSettings s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// column upgrade
|
||||
var columns = s.columns.ToList();
|
||||
var bid_col_index = columns.FindIndex(x => x.id == col_id(Columns.Bid));
|
||||
var ask_col_index = columns.FindIndex(x => x.id == col_id(Columns.Ask));
|
||||
if (bid_col_index > -1 && ask_col_index > -1) {
|
||||
if (!columns[bid_col_index].background_color_override.HasValue) {
|
||||
columns[bid_col_index] = columns[bid_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.bid.ToUint()));
|
||||
}
|
||||
if (!columns[ask_col_index].background_color_override.HasValue) {
|
||||
columns[ask_col_index] = columns[ask_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.ask.ToUint()));
|
||||
}
|
||||
}
|
||||
// and enable background color upgrade
|
||||
return s.with(columns, enable_background_color: true);
|
||||
}
|
||||
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
@@ -102,13 +82,12 @@ namespace bts.shell {
|
||||
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
|
||||
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings raw_day_position => new GridColumnSettings(col_id(Columns.RawDayPosition), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
|
||||
public static GridColumnSettings working_bids => new GridColumnSettings(col_id(Columns.WorkingBids), DefaultColumnHeaders.Working, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.Working, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings bid_implied_size => new GridColumnSettings(col_id(Columns.BidImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings bid_direct_size => new GridColumnSettings(col_id(Columns.BidDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings bid_merged_size => new GridColumnSettings(col_id(Columns.BidMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings bid => new GridColumnSettings(col_id(Columns.Bid), DefaultColumnHeaders.Bid, font_color_override: BTSColors.header_text, width:DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right, background_color_override: BTSColors.MarketData.bid);
|
||||
public static GridColumnSettings ask => new GridColumnSettings(col_id(Columns.Ask), DefaultColumnHeaders.Ask, font_color_override: BTSColors.header_text, width: DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right, background_color_override: BTSColors.MarketData.ask);
|
||||
public static GridColumnSettings bid => new GridColumnSettings(col_id(Columns.Bid), DefaultColumnHeaders.Bid, font_color_override: BTSColors.header_text, width:DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask => new GridColumnSettings(col_id(Columns.Ask), DefaultColumnHeaders.Ask, font_color_override: BTSColors.header_text, width: DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask_merged_size => new GridColumnSettings(col_id(Columns.AskMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask_direct_size => new GridColumnSettings(col_id(Columns.AskDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask_implied_size => new GridColumnSettings(col_id(Columns.AskImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
@@ -140,7 +119,6 @@ namespace bts.shell {
|
||||
yield return last;
|
||||
yield return last_size;
|
||||
yield return position;
|
||||
yield return raw_day_position;
|
||||
yield return working_bids;
|
||||
yield return bid_implied_size;
|
||||
yield return bid_direct_size;
|
||||
@@ -195,8 +173,6 @@ namespace bts.shell {
|
||||
public DoubleMinMaxState session_high;
|
||||
[ProtoMember(12)]
|
||||
public IntMinMaxState position;
|
||||
[ProtoMember(27)]
|
||||
public DoubleMinMaxState raw_day_position;
|
||||
[ProtoMember(13)]
|
||||
public IntMinMaxState working_bid_volume;
|
||||
[ProtoMember(14)]
|
||||
@@ -226,46 +202,5 @@ namespace bts.shell {
|
||||
[ProtoMember(26)]
|
||||
public string mic_code;
|
||||
}
|
||||
|
||||
// migration rules
|
||||
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : IMarketViewGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
settings.add_missing_columns(StartupData.DefaultGridSettings.columns, out new_settings);
|
||||
}
|
||||
|
||||
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
|
||||
class InitBidAskBackgroundOverrideMigrationRule : IMarketViewGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings s, out GridSettings new_settings) {
|
||||
var columns = s.columns.ToList();
|
||||
var bid_col_index = columns.FindIndex(x => x.id == col_id(Columns.Bid));
|
||||
var ask_col_index = columns.FindIndex(x => x.id == col_id(Columns.Ask));
|
||||
bool applied = false;
|
||||
if (bid_col_index > -1 && ask_col_index > -1) {
|
||||
if (!columns[bid_col_index].background_color_override.HasValue) {
|
||||
columns[bid_col_index] = columns[bid_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.bid.ToUint()));
|
||||
applied = true;
|
||||
}
|
||||
if (!columns[ask_col_index].background_color_override.HasValue) {
|
||||
columns[ask_col_index] = columns[ask_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.ask.ToUint()));
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
new_settings = applied ? s.with(columns) : default;
|
||||
return applied;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
|
||||
class InitBackgroundOverrideMigrationRule : IMarketViewGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) {
|
||||
if (!settings.enable_background_color) {
|
||||
new_settings = settings.with(enable_background_color: true);
|
||||
return true;
|
||||
}
|
||||
new_settings = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,16 @@ using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
struct TradeInfo {
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly TradeMessageImmutable trade;
|
||||
|
||||
public TradeInfo(SecurityImmutable security, TradeMessageImmutable trade) {
|
||||
this.security = security;
|
||||
this.trade = trade;
|
||||
}
|
||||
}
|
||||
|
||||
class TimeAndSalesModel : IDisposable {
|
||||
internal readonly SecurityCriteria security_criteria;
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace mbts.spark {
|
||||
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
|
||||
_trade_key = trade.Key;
|
||||
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
|
||||
price = trade.price();
|
||||
price = trade.Mantissa * Math.Pow(10, trade.Exponent);
|
||||
size = trade.Size;
|
||||
aggressor_side = trade.AggressorSide;
|
||||
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
|
||||
|
||||
@@ -26,10 +26,7 @@ namespace bts.ui {
|
||||
public SettingsShellModule(IBus bus, UserSettingsHub settings_hub, WorkspacesModel ws) {
|
||||
// todo: settings dialog
|
||||
ws_model = ws;
|
||||
var actions_list = new List<SparkCommand>(2) {
|
||||
SparkCommands.ShowGlobalAlerts,
|
||||
SparkCommands.ShowWorkspaces
|
||||
};
|
||||
var actions_list = new List<SparkCommand>(2) { SparkCommands.ShowWorkspaces };
|
||||
if (Configuration.get<bts.config.toggles.ToastNotifications>().enabled) {
|
||||
actions_list.Add(SparkCommands.ShowSettings);
|
||||
}
|
||||
|
||||
@@ -374,72 +374,6 @@
|
||||
Height="{TemplateBinding Height}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<Border.Resources>
|
||||
<!--Leg Count States-->
|
||||
<sys:String x:Key="LegCountStates">LegCountStates</sys:String>
|
||||
<sys:String x:Key="LegCountStates.TwoOrFewer">TwoOrFewerLegsState</sys:String>
|
||||
<sys:String x:Key="LegCountStates.ThreeOrMore">ThreeOrMoreLegsState</sys:String>
|
||||
<cv:ValueConverterPipe x:Key="LegCountStateConverter">
|
||||
<cv:IntGreaterThanConverter Value="2"/>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{StaticResource LegCountStates.TwoOrFewer}" ValueForTrue="{StaticResource LegCountStates.ThreeOrMore}"/>
|
||||
</cv:ValueConverterPipe>
|
||||
<!--Persistance States-->
|
||||
<sys:String x:Key="PersistanceStates">PersistanceStates</sys:String>
|
||||
<sys:String x:Key="PersistanceStates.Unpersisted">UnpersistedState</sys:String>
|
||||
<sys:String x:Key="PersistanceStates.Persisted">PersistedState</sys:String>
|
||||
<cv:BooleanToObjectConverter x:Key="PersistanceStateConverter" ValueForFalse="{StaticResource PersistanceStates.Persisted}" ValueForTrue="{StaticResource PersistanceStates.Unpersisted}"/>
|
||||
</Border.Resources>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.LegTuples.Count, RelativeSource={RelativeSource Self}, Converter={StaticResource LegCountStateConverter}}"/>
|
||||
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.IsNew, RelativeSource={RelativeSource Self}, Converter={StaticResource PersistanceStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{StaticResource LegCountStates}">
|
||||
<VisualState Name="{StaticResource LegCountStates.TwoOrFewer}"/>
|
||||
<VisualState Name="{StaticResource LegCountStates.ThreeOrMore}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_Label" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_DropDown" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{StaticResource PersistanceStates}">
|
||||
<VisualState Name="{StaticResource PersistanceStates.Unpersisted}"/>
|
||||
<VisualState Name="{StaticResource PersistanceStates.Persisted}">
|
||||
<Storyboard>
|
||||
<!--Name-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_TextBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<!--Tick Factors-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Picker" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<!--Pricing-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_DropDown" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -454,9 +388,8 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="PART_Name_Label" VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
|
||||
<TextBlock VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
|
||||
<TextBox
|
||||
x:Name="PART_Name_TextBox"
|
||||
Grid.Column="1"
|
||||
@@ -475,37 +408,20 @@
|
||||
</u:Behavior.BindingUpdateGroups>
|
||||
</TextBox>
|
||||
<TextBlock
|
||||
x:Name="PART_TickFactors_Label"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks"
|
||||
IsEnabled="True"
|
||||
Grid.Column="2" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<c:TickFactorsPicker
|
||||
x:Name="PART_TickFactors_Picker"
|
||||
Grid.Column="3"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="True"
|
||||
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<TextBlock
|
||||
x:Name="PART_Pricing_Label"
|
||||
Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
IsEnabled="True"
|
||||
Text="Pricing"
|
||||
/>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" >Pricing</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="PART_Pricing_DropDown"
|
||||
Grid.Column="5"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Width="90"
|
||||
IsEnabled="True"
|
||||
IsEnabled="{TemplateBinding IsNew}"
|
||||
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}"
|
||||
SelectedItem="{Binding price_calc}"
|
||||
>
|
||||
@@ -515,24 +431,6 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock
|
||||
Grid.Column="6"
|
||||
x:Name="PART_HedgePricing_Label"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Hedge"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_HedgePricing_DropDown"
|
||||
Grid.Column="7"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
ItemsSource="{TemplateBinding HedgePricingItemsSource}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding HedgePricing, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
</Grid>
|
||||
<c:DataGridEx
|
||||
x:Name="PART_DataGrid"
|
||||
|
||||
@@ -8,7 +8,6 @@ using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -184,29 +183,6 @@ namespace mbts.ui.spark {
|
||||
public mbts.spark.ContractPickerModel ContractPickerModel { get => GetValue(ContractPickerModelProperty) as mbts.spark.ContractPickerModel; set => SetValue(ContractPickerModelProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region HedgePricingItemsSource
|
||||
public static readonly DependencyProperty HedgePricingItemsSourceProperty =
|
||||
WPFHelper.create_dp<IEnumerable, ExecutionStrategyEditor>(
|
||||
nameof(HedgePricingItemsSource));
|
||||
|
||||
public IEnumerable HedgePricingItemsSource {
|
||||
get => GetValue(HedgePricingItemsSourceProperty) as IEnumerable;
|
||||
set => SetValue(HedgePricingItemsSourceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HedgePricing
|
||||
public static readonly DependencyProperty HedgePricingProperty =
|
||||
WPFHelper.create_dp<object, ExecutionStrategyEditor>(
|
||||
nameof(HedgePricing),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public object HedgePricing {
|
||||
get => GetValue(HedgePricingProperty);
|
||||
set => SetValue(HedgePricingProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void OnApplyTemplate() =>
|
||||
_connect_leg_tuples();
|
||||
|
||||
@@ -275,7 +251,7 @@ namespace mbts.ui.spark {
|
||||
|
||||
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
|
||||
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactors_Picker");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawing_context) {
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
/>
|
||||
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
|
||||
</Grid>
|
||||
|
||||
@@ -17,7 +17,6 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HedgePricing = bts.spark.ExecutionStrategy.Types.HedgePricing;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
@@ -33,7 +32,6 @@ namespace mbts.spark {
|
||||
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
|
||||
TickFactors _tick_factors;
|
||||
ExecutionStrategyImmutable _strategy;
|
||||
HedgePricing _hedge_pricing;
|
||||
|
||||
/// <summary>
|
||||
/// Inititalizes a new instance of SyntheticEditorVM class. This constructor may only be used for already-existing synthetic securities (id must be non-zero).
|
||||
@@ -296,9 +294,6 @@ namespace mbts.spark {
|
||||
public IRaiseCanExecuteChangedCommand commit { get; }
|
||||
public ICommand remove_leg { get; }
|
||||
public ContractPickerModel contract_picker_model { get; }
|
||||
// hedge pricing
|
||||
public HedgePricing hedge_pricing { get => _hedge_pricing; set => Set(ref _hedge_pricing, value); }
|
||||
public IEnumerable<HedgePricing> hedge_pricing_options => EnumHelper.get_values<HedgePricing>();
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
@@ -306,7 +301,6 @@ namespace mbts.spark {
|
||||
foreach (var lt in leg_tuples) {
|
||||
mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
|
||||
}
|
||||
mutable_strategy.HedgePricing = hedge_pricing;
|
||||
return new ExecutionStrategyImmutable(mutable_strategy);
|
||||
}
|
||||
|
||||
@@ -314,7 +308,6 @@ namespace mbts.spark {
|
||||
_synthetic = synthetic.CloneToMutable();
|
||||
_synthetic.Exchange = Venue.Bts;
|
||||
_strategy = strategy;
|
||||
hedge_pricing = strategy.HedgePricing;
|
||||
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
|
||||
RaisePropertyChanged(nameof(name), nameof(tick_factors));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using mbts.collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// This class's primary purpose is to handle async AlgoService persistence calls for the AlgoStudioVM/OrderPropertiesEditorVM.
|
||||
/// When a new definition/template item is persisted for the first time, it's corresponding viewmodel (<see cref="AlgoDefinitionVM"/>, <see cref="AlgoDefinitionTemplateVM"/>)
|
||||
/// must be removed and replaced with a new one that has the brand new persisted id (<see cref="AlgoKey"/>). As part of its duty, this class makes sure that the new persisted items are inserted into the same index as their unpersisted predecessor.
|
||||
/// </summary>
|
||||
static class AlgoPersistenceHelper {
|
||||
public static (int? def_index, int? tmp_index) selection_snapshot(IList<AlgoDefinitionVM> definitions, AlgoDefinitionVM selected_definition, AlgoDefinitionTemplateVM selected_template) {
|
||||
var def = selected_definition;
|
||||
var tmp = selected_template;
|
||||
int? def_index = def == null ? (int?)null : definitions.IndexOf(def);
|
||||
int? tmp_index = tmp == null ? (int?)null : tmp.owner.templates.IndexOf(tmp);
|
||||
return (def_index, tmp_index);
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_definition(OrdersModel model, IList<AlgoDefinitionVM> definitions, AlgoDefinitionVM definition) {
|
||||
int def_index = definitions.IndexOf(definition);
|
||||
return await persist(model, definitions.WrapSubRange(def_index, 1).WrapSelector(def => (def, definition.templates.AsList()), t => t.def));
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_template(OrdersModel model, IList<AlgoDefinitionVM> definitions, AlgoDefinitionTemplateVM template) {
|
||||
switch (template.owner.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return await persist_definition(model, definitions, template.owner);
|
||||
case AlgoKey.Mode.Persisted:
|
||||
int def_index = definitions.IndexOf(template.owner);
|
||||
int tmp_index = template.owner.templates.IndexOf(template);
|
||||
return await persist(model, definitions.WrapSubRange(def_index, 1).WrapSelector(def => (def, def.templates.WrapSubRange(tmp_index, 1)), t => t.def));
|
||||
default: return DictionaryHelper.empty<AlgoKey, AlgoKey>();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_all(OrdersModel model, ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> definitions) =>
|
||||
await persist(model, definitions.WrapSelector(def => (def, def.templates.AsList()), t => t.def));
|
||||
|
||||
/// <summary>
|
||||
/// Persists each pair of definition/templates, then returns a map of non-persisted keys to persisted ones (<see cref="AlgoKey"/>, <see cref="AlgoDefinitionVM.key"/>, <see cref="AlgoDefinitionTemplateVM.key"/>).
|
||||
/// The key-map gives callers the ability to replace unpersisted items with persisted ones before the next update arrives.
|
||||
/// <para>Note: use a <see cref="mbts.utils.TableUpdateBuffer{T, TKey}"/> to suspend updates while awaiting this task; don't resume them until all unpersisted items have been replaced.
|
||||
/// <see cref="AlgoDefinitionCollection"/> caters to this pre-emptive adding behavior, in it's <see cref="TableUpdate{T, TKey}"/> callback, it checks if added items are already present, and if so, treats them as changes instead.</para>
|
||||
/// <para>PS: the reason for all this madness is so newly persisted items are inserted in the same index as their placeholder counterpart. The only way to connect the non-persisted keys with their persisted counterparts
|
||||
/// is to await each save-op individually, and build a map with the results..hence the returned key-map.</para>
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="definition_templates"></param>
|
||||
/// <returns>An AlgoKey->AlgoKey map of old placeholder ids to newly persisted ones.</returns>
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist(OrdersModel model, IList<(AlgoDefinitionVM definition, IList<AlgoDefinitionTemplateVM> templates)> definition_templates) {
|
||||
var placeholder_to_persisted_keys = new Dictionary<AlgoKey, AlgoKey>();
|
||||
for (int i = 0; i < definition_templates.Count; i++) {
|
||||
(var def, var templates) = definition_templates[i];
|
||||
var persisted_def = def;
|
||||
if (def.is_dirty) {
|
||||
var def_record = await model.algo_model.save_definition(def.create_algo_def());
|
||||
if (def_record.HasValue) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
persisted_def = def.to_persisted(def_record.Value);
|
||||
foreach (var tmp in templates) {
|
||||
AlgoDefinitionTemplateVM persisted_tmp;
|
||||
var tmp_record = await model.algo_model.save_template(tmp.create(def_record.Value));
|
||||
if (tmp_record.HasValue) {
|
||||
persisted_def.templates.Add(persisted_tmp = tmp.to_persisted(persisted_def, new AlgoData(tmp_record.Value.value, tmp_record.Value.name, tmp_record.Value.key)));
|
||||
} else {
|
||||
/// If the save-op fails, the unpersisted template is cloned and added to the persisted def's template items, so the user may try to save it again without having to re-enter all the templated-values.
|
||||
/// Q: "why don't we just re-add the same template to the persisted def, the save-op failed, so nothing changed...why do we need a new one?"
|
||||
/// A: "A new template vm must be created because its owner has changed (it's a readonly prop, <see cref="AlgoDefinitionTemplateVM.owner"/>)."
|
||||
persisted_def.templates.Add(persisted_tmp = new AlgoDefinitionTemplateVM(persisted_def, model, tmp.create_def_data()));
|
||||
}
|
||||
placeholder_to_persisted_keys[tmp.key] = persisted_tmp.key;
|
||||
}
|
||||
persisted_def.connect();
|
||||
if (def.key.mode == AlgoKey.Mode.Unpersisted) {
|
||||
definition_templates[i] = (persisted_def, persisted_def.templates);
|
||||
} else {
|
||||
definition_templates.Add((persisted_def, persisted_def.templates));
|
||||
}
|
||||
placeholder_to_persisted_keys[def.key] = persisted_def.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// so the def isn't dirty, but it still might have dirty templates
|
||||
using (persisted_def.buffer_updates()) {
|
||||
for (int j = 0; j < templates.Count; j++) {
|
||||
var tmp = templates[j];
|
||||
if (!tmp.is_dirty)
|
||||
continue;
|
||||
var tmp_record = await model.algo_model.save_template(tmp.create());
|
||||
if (tmp_record.HasValue) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
var persisted_tmp = tmp.to_persisted(persisted_def, new AlgoData(tmp_record.Value.value, tmp_record.Value.name, tmp_record.Value.key));
|
||||
templates[j] = persisted_tmp;
|
||||
placeholder_to_persisted_keys[tmp.key] = persisted_tmp.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return placeholder_to_persisted_keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,14 @@
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:StateManager.VisualState="{Binding selected_definition.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
|
||||
u:StateManager.VisualState="{Binding content_source.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
@@ -30,6 +33,9 @@
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
|
||||
<!--Converters-->
|
||||
<cv:ValueMap x:Key="AlgoTemplateFavoriteToggleButtonToolTipConverter" Default="Add to Favorites">
|
||||
<cv:KeyValue Key="{u:True}" Value="Remove from Favorites"/>
|
||||
</cv:ValueMap>
|
||||
<sys:String x:Key="AlgoAbbreviationActiveState">AbbreviationActive</sys:String>
|
||||
<sys:String x:Key="AlgoAbbreviationInactiveState">AbbreviationInactive</sys:String>
|
||||
<cv:ValueMap x:Key="AlgoAbbreviationVisualStateConverter" Default="{StaticResource AlgoAbbreviationActiveState}">
|
||||
@@ -124,8 +130,70 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoTemplateFavoriteToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="LightGray"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="IsThreeState" Value="False"/>
|
||||
<Setter Property="IsChecked" Value="{Binding is_favorite}"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="UseLayoutRounding" Value="True"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="ToolTip" Value="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static uag:AlgoTemplateToolBar.FavoriteToggleButtonToolTipConverter}}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid UseLayoutRounding="{TemplateBinding UseLayoutRounding}">
|
||||
<Ellipse x:Name="ellipse" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}" Width="0" Height="0"/>
|
||||
<Image SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" UseLayoutRounding="{TemplateBinding UseLayoutRounding}" MaxHeight="13" MaxWidth="13">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Geometry="{Binding Source={StaticResource ResourceKey={x:Static u:Icons.StarGeometryKey}}}" Brush="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" >
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen
|
||||
Brush="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Thickness="{Binding BorderThickness.Left, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
/>
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Foreground" Value="Gold"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Width" To="16" Duration="0:0:0.03" />
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Height" To="16" Duration="0:0:0.03" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Width" To="0" Duration="0:0:0.0" />
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0.0" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
|
||||
<Setter Property="DropDownStyleKey" Value="{x:Static usp:AlgoContentTemplateSelector.DefinitionDropDownStyleKey}"/>
|
||||
<Setter Property="DropDownStyleKey" Value="{Binding DataContext.content_source, RelativeSource={RelativeSource AncestorType=usp:AlgoStudioView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
@@ -153,26 +221,94 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ListBox
|
||||
<TreeView
|
||||
x:Name="_tree_view"
|
||||
u:TreeViewEx.SelectedItemChangedHandler="{Binding}"
|
||||
ItemsSource="{Binding definitions_view}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
MinWidth="180"
|
||||
Background="Transparent"
|
||||
SelectedItem="{Binding selected_definition}"
|
||||
Style="{StaticResource AlgoTemplateTreeViewStyle}"
|
||||
>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionVM}">
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}"/>
|
||||
<DataTemplate.Triggers>
|
||||
<TreeView.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:EditableCollectionViewEx.AddNewItemCommand}" Executed="{x:Static u:EditableCollectionViewEx.AddNewItemExecuted}" CanExecute="{x:Static u:EditableCollectionViewEx.AddNewItemCanExecute}"/>
|
||||
<CommandBinding Command="{x:Static u:EditableCollectionViewEx.CommitAnyCommand}" Executed="{x:Static u:EditableCollectionViewEx.CommitAnyExecuted}"/>
|
||||
</TreeView.CommandBindings>
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type sp:AlgoDefinitionVM}" ItemsSource="{Binding templates_view}">
|
||||
<Grid u:StateManager.VisualStateCollection="{Binding tree_view_visual_states}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="badge" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="badge"
|
||||
Fill="{Binding badge_fill}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
Visibility="{DynamicResource ResourceKey={x:Static usp:AlgoStudioView.BadgesInTreeViewVisibilityKey}}"
|
||||
/>
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}"/>
|
||||
</Grid>
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding is_dirty}" Value="True">
|
||||
<Setter TargetName="text_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
|
||||
</DataTrigger>
|
||||
</HierarchicalDataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionTemplateVM}">
|
||||
<Grid u:StateManager.VisualStateCollection="{Binding tree_view_visual_states}" Margin="-6,0,0,0">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="favorite_toggle" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton x:Name="favorite_toggle" Grid.Column="1" Margin="4,0,0,0" Style="{StaticResource AlgoTemplateFavoriteToggleButtonStyle}"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}" ToolTip="Double click to set as default"/>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Value="{u:True}">
|
||||
<DataTrigger.Binding>
|
||||
<MultiBinding Converter="{StaticResource OrConverter}">
|
||||
<Binding Path="is_dirty"/>
|
||||
<Binding Path="is_persisted" Converter="{StaticResource NotConverter}"/>
|
||||
</MultiBinding>
|
||||
</DataTrigger.Binding>
|
||||
<Setter TargetName="text_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</TreeView.Resources>
|
||||
</TreeView>
|
||||
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
|
||||
<Grid x:Name="edit_pane">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -294,7 +430,7 @@
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
Text="{Binding content_source.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
/>
|
||||
<Grid x:Name="abbreviation_root" Grid.Column="2">
|
||||
<Grid.ToolTip>
|
||||
@@ -316,7 +452,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
|
||||
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
|
||||
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding content_source.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
|
||||
@@ -342,8 +478,8 @@
|
||||
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
|
||||
<usp:OrderTypeItem
|
||||
Margin="2,0"
|
||||
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
DisplayName="{Binding selected_definition.algo_root.display_name}"
|
||||
BadgeFill="{Binding content_source.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
DisplayName="{Binding content_source.algo_root.display_name}"
|
||||
/>
|
||||
<TextBlock>).</TextBlock>
|
||||
</StackPanel>
|
||||
@@ -360,7 +496,7 @@
|
||||
x:Name="default_badge"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Fill="{Binding content_source.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
/>
|
||||
</Grid>
|
||||
@@ -370,7 +506,7 @@
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="{StaticResource Margin.LP2}"
|
||||
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
|
||||
IsChecked="{Binding content_source.custom_badge_color.enabled}"
|
||||
GroupName="Badge"
|
||||
>
|
||||
<RadioButton.ToolTip>
|
||||
@@ -390,7 +526,7 @@
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="3,0"
|
||||
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Fill="{Binding content_source.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
|
||||
@@ -405,7 +541,7 @@
|
||||
<Button
|
||||
x:Name="custom_badge_color_picker_button"
|
||||
Grid.Column="1"
|
||||
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Background="{Binding content_source.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
|
||||
/>
|
||||
@@ -413,8 +549,8 @@
|
||||
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
|
||||
<cp:ColorPickerControl
|
||||
Background="Transparent"
|
||||
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||
Palettes="{Binding DataContext.content_source.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||
SelectedColor="{Binding DataContext.content_source.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||
/>
|
||||
</Border>
|
||||
</Popup>
|
||||
@@ -423,7 +559,7 @@
|
||||
</Grid>
|
||||
<usp:AlgoEditorView
|
||||
Grid.Row="2"
|
||||
DataContext="{Binding selected_definition}"
|
||||
DataContext="{Binding content_source}"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
@@ -433,10 +569,10 @@
|
||||
<StackPanel.Margin>
|
||||
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
|
||||
</StackPanel.Margin>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
|
||||
</Button>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
@@ -444,4 +580,4 @@
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -1,24 +1,43 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoStudioView : UserControl {
|
||||
public const string BadgesInTreeViewVisibilityKey = nameof(BadgesInTreeViewVisibilityKey);
|
||||
public static readonly IValueConverter EditPaneVisualStateConverter =
|
||||
ValueConverter.create(obj => obj == null ? VisualStates.Inactive : VisualStates.Active);
|
||||
|
||||
IAlgoStudioViewController _controller;
|
||||
|
||||
public AlgoStudioView() {
|
||||
InitializeComponent();
|
||||
PopupViewController.attach(custom_badge_color_picker_popup, custom_badge_color_picker_button);
|
||||
this.ConnectDataContext<AlgoStudioVM>(vm => vm.request_view_focus_name += focus_name, vm => vm.request_view_focus_name -= focus_name);
|
||||
void focus_name(AlgoDefinitionVM target) =>
|
||||
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
|
||||
DataContextChanged += (s, e) => set_controller();
|
||||
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));
|
||||
}
|
||||
|
||||
public new AlgoStudioVM DataContext {
|
||||
get => base.DataContext as AlgoStudioVM;
|
||||
set => base.DataContext = value;
|
||||
}
|
||||
|
||||
public void add_definition() => _controller.add_definition();
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) => _controller.add_definition_clone(src_definition);
|
||||
public void add_template(AlgoDefinitionVM definition) => _controller.add_template(definition);
|
||||
public void rename_definition(AlgoDefinitionVM definition) => _controller.rename_definition(definition);
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) => _controller.rename_template(template);
|
||||
public void select_definition(AlgoDefinitionVM definition) => _controller.select_definition(definition);
|
||||
public void select_template(AlgoDefinitionTemplateVM template) => _controller.select_template(template);
|
||||
public IDisposable preserve_selected_definition() => _controller.preserve_selected_definition();
|
||||
|
||||
void _end_edit_executed(object s, ExecutedRoutedEventArgs e) =>
|
||||
_controller.end_edit(e);
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class EditPane {
|
||||
public const string GroupName = nameof(EditPane);
|
||||
@@ -56,6 +75,14 @@ namespace mbts.ui.spark {
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class TreeViewBadge {
|
||||
public const string GroupName = nameof(TreeViewBadge);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class FavoriteToggleButton {
|
||||
public const string GroupName = nameof(FavoriteToggleButton);
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
interface IAlgoStudioViewController : IDisposable {
|
||||
void add_definition();
|
||||
void add_definition_clone(AlgoDefinitionVM src_definition);
|
||||
void add_template(AlgoDefinitionVM definition);
|
||||
void rename_definition(AlgoDefinitionVM definition);
|
||||
void rename_template(AlgoDefinitionTemplateVM template);
|
||||
void end_edit(ExecutedRoutedEventArgs e);
|
||||
void select_definition(AlgoDefinitionVM definition);
|
||||
void select_template(AlgoDefinitionTemplateVM template);
|
||||
IDisposable preserve_selected_definition();
|
||||
}
|
||||
|
||||
class AlgoStudioViewControllerFactory {
|
||||
static readonly IAlgoStudioViewController _empty = new EmptyController();
|
||||
|
||||
public static IAlgoStudioViewController create(AlgoStudioVM vm, TreeView tree_view, TextBox name_text_box, Button color_picker_button, Popup color_picker_popup) =>
|
||||
vm == null || tree_view == null || name_text_box == null || color_picker_button == null || color_picker_popup == null ?
|
||||
_empty : new Controller(vm, tree_view, name_text_box, color_picker_button, color_picker_popup);
|
||||
|
||||
/// <summary>
|
||||
/// This wrapper class is returned by <see cref="create(AlgoStudioVM, TreeView)"/> when the vm or tree_view is null.
|
||||
/// This further separates the view from the controller-logic; it doesn't even need to check for null when accessing <see cref="IAlgoStudioViewController"/> methods.
|
||||
/// </summary>
|
||||
class EmptyController : IAlgoStudioViewController {
|
||||
public void add_definition() { }
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) { }
|
||||
public void add_template(AlgoDefinitionVM definition) { }
|
||||
public void rename_definition(AlgoDefinitionVM definition) { }
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) { }
|
||||
public void end_edit(ExecutedRoutedEventArgs e) { }
|
||||
public void select_definition(AlgoDefinitionVM definition) { }
|
||||
public void select_template(AlgoDefinitionTemplateVM template) { }
|
||||
public IDisposable preserve_selected_definition() => Disposable.empty;
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
class Controller : IAlgoStudioViewController {
|
||||
readonly AlgoStudioVM _vm;
|
||||
readonly TreeView _tree_view;
|
||||
readonly TextBox _name_text_box;
|
||||
IDisposable _dispose;
|
||||
|
||||
public Controller(AlgoStudioVM vm, TreeView tree_view, TextBox name_text_box, Button color_picker_button, Popup color_picker_popup) {
|
||||
_vm = vm;
|
||||
_tree_view = tree_view;
|
||||
_name_text_box = name_text_box;
|
||||
_dispose = PopupViewController.attach(color_picker_popup, color_picker_button);
|
||||
}
|
||||
|
||||
public void add_definition() {
|
||||
_vm.add_new_definition();
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) {
|
||||
_vm.add_new_definition_clone(src_definition);
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void add_template(AlgoDefinitionVM def) {
|
||||
var new_tmp = def.template_factory();
|
||||
def.templates.Add(new_tmp);
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void end_edit(ExecutedRoutedEventArgs e) {
|
||||
if (e.OriginalSource is EditableTextBlock etb) {
|
||||
etb.end_edit();
|
||||
_get_container(etb.DataContext)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void rename_definition(AlgoDefinitionVM definition) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(definition) is TreeViewItem tvi) {
|
||||
var etb = tvi.descendants_of_type<EditableTextBlock>().FirstOrDefault();
|
||||
etb.SetCurrentValue(EditableTextBlock.IsEditingProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(template.owner) is TreeViewItem tvi) {
|
||||
if (tvi.ItemContainerGenerator.ContainerFromItem(template) is TreeViewItem ttvi) {
|
||||
var etb = ttvi.descendants_of_type<EditableTextBlock>().FirstOrDefault();
|
||||
etb.SetCurrentValue(EditableTextBlock.IsEditingProperty, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void select_definition(AlgoDefinitionVM definition) =>
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(definition)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
|
||||
public void select_template(AlgoDefinitionTemplateVM template) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(template.owner) is TreeViewItem def_view) {
|
||||
def_view.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
def_view.ItemContainerGenerator.ContainerFromItem(template)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable preserve_selected_definition() =>
|
||||
Disposable.Create(() => {
|
||||
var name = _vm.selected_definition.name;
|
||||
if (_vm.definitions.TryGetFirst(d => d.name == name, out AlgoDefinitionVM def))
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(def)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
});
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
void _begin_edit() =>
|
||||
WPFHelper.push_frame_until(() => Keyboard.Focus(_name_text_box) == _name_text_box);
|
||||
|
||||
TreeViewItem _get_container(object obj) =>
|
||||
obj is AlgoDefinitionTemplateVM t ? _get_container(t) :
|
||||
obj is AlgoDefinitionVM d ? _get_container(d) : null;
|
||||
|
||||
TreeViewItem _get_container(AlgoDefinitionVM d) =>
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(d) as TreeViewItem;
|
||||
|
||||
TreeViewItem _get_container(AlgoDefinitionTemplateVM t) =>
|
||||
_get_container(t.owner) is TreeViewItem dview ? dview.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,19 +62,35 @@ namespace mbts.spark {
|
||||
_vm = new AlgoStudioVM(_model);
|
||||
_vm.load_from(_state);
|
||||
_view = new AlgoStudioView { DataContext = _vm };
|
||||
_view.Resources[AlgoStudioView.BadgesInTreeViewVisibilityKey] = _ui_configuration.show_badges_in_tree_view ? Visibility.Visible : Visibility.Collapsed;
|
||||
_resources = new CompositeDisposable();
|
||||
// observable sources
|
||||
var content_src = _vm.observe_selected().Select(_ => _vm.content_source);
|
||||
// add definition
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
||||
// clone definition
|
||||
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
|
||||
_commands.Add(clone_selected_definition_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
|
||||
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, new DelegateCommand(_view.add_definition)));
|
||||
// add definition clone
|
||||
var add_definition_clone_spark_cmd = new ObservantSparkCommandCommand<AlgoDefinitionVM>(Icons.DocumentCopy, d => $"Clone '{d?.name}'", d => d != null, _view.add_definition_clone);
|
||||
_commands.Add(add_definition_clone_spark_cmd);
|
||||
// add template
|
||||
var add_template_spark_cmd = new ObservantSparkCommandCommand<AlgoDefinitionVM>(Icons.AddFile, d => $"Add '{d?.name}' Template", d => d != null, _view.add_template) {
|
||||
show_in_toolbar = (_ui_configuration.add_template_button & bts.config.AlgoStudioOptions.AddTemplateButton.MainToolBar) == bts.config.AlgoStudioOptions.AddTemplateButton.MainToolBar,
|
||||
show_in_context_menu = (_ui_configuration.add_template_button & bts.config.AlgoStudioOptions.AddTemplateButton.DefinitionContextMenu) == bts.config.AlgoStudioOptions.AddTemplateButton.DefinitionContextMenu
|
||||
};
|
||||
_commands.Add(add_template_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(add_definition_clone_spark_cmd, add_template_spark_cmd));
|
||||
// save
|
||||
if (_ui_configuration.can_save_specific) {
|
||||
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
|
||||
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||
var content_is_dirty_src = _vm.observe_selected().SelectManyInSeries(t => {
|
||||
if (t.template != null) {
|
||||
return t.template.observe_is_dirty();
|
||||
} else if (t.definition != null) {
|
||||
return t.definition.subscribe_is_definition_dirty();
|
||||
} else return Observable.Return(false);
|
||||
});
|
||||
_commands.Add(new ReactiveSparkCommand(content_src.OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_template is AlgoDefinitionTemplateVM t)
|
||||
_vm.save_template(t).ignore();
|
||||
else if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||
_vm.save_definition(d).ignore();
|
||||
}, content_is_dirty_src));
|
||||
}
|
||||
@@ -85,39 +101,48 @@ namespace mbts.spark {
|
||||
// undo/redo
|
||||
if (_ui_configuration.can_undo_redo) {
|
||||
// undo
|
||||
var undo_cmd = new ObservantCommand(() => _vm.selected_definition?.try_undo());
|
||||
var undo_cmd = new ObservantCommand(() => _vm.content_source?.try_undo());
|
||||
var undo_spark_cmd = new SparkCommandCommand("Undo", Icons.UndoCircular, undo_cmd);
|
||||
var can_undo_source = _vm.subscribe_definition().SelectManyInSeries(def =>
|
||||
def != null ?
|
||||
var can_undo_source = _vm.observe_selected().SelectManyInSeries(tup => {
|
||||
IAlgoStudioContentVM content = (IAlgoStudioContentVM)tup.template ?? tup.definition;
|
||||
return content != null ?
|
||||
Observable.Create<bool>(o => {
|
||||
def.can_undo_changed += notify;
|
||||
content.can_undo_changed += notify;
|
||||
notify();
|
||||
return Disposable.Create(() => def.can_undo_changed -= notify);
|
||||
void notify() => o.OnNext(def.can_undo);
|
||||
return Disposable.Create(() => content.can_undo_changed -= notify);
|
||||
void notify() => o.OnNext(content.can_undo);
|
||||
}) :
|
||||
Observable.Return(false));
|
||||
Observable.Return(false);
|
||||
});
|
||||
_commands.Add(undo_spark_cmd);
|
||||
_resources.Add(can_undo_source.Subscribe(undo_cmd));
|
||||
// redo
|
||||
var redo_cmd = new ObservantCommand(() => _vm.selected_definition?.try_redo());
|
||||
var redo_cmd = new ObservantCommand(() => _vm.content_source?.try_redo());
|
||||
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
|
||||
var can_redo_source = _vm.subscribe_definition().SelectManyInSeries(def =>
|
||||
def != null ?
|
||||
var can_redo_source = _vm.observe_selected().SelectManyInSeries(tup => {
|
||||
IAlgoStudioContentVM content = (IAlgoStudioContentVM)tup.template ?? tup.definition;
|
||||
return content != null ?
|
||||
Observable.Create<bool>(o => {
|
||||
def.can_redo_changed += notify;
|
||||
content.can_redo_changed += notify;
|
||||
notify();
|
||||
return Disposable.Create(() => def.can_redo_changed -= notify);
|
||||
void notify() => o.OnNext(def.can_redo);
|
||||
return Disposable.Create(() => content.can_redo_changed -= notify);
|
||||
void notify() => o.OnNext(content.can_redo);
|
||||
}) :
|
||||
Observable.Return(false));
|
||||
Observable.Return(false);
|
||||
});
|
||||
_commands.Add(redo_spark_cmd);
|
||||
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
||||
}
|
||||
// remove
|
||||
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
|
||||
if (value is AlgoDefinitionVM d) {
|
||||
if (value is AlgoDefinitionTemplateVM t) {
|
||||
_vm.remove_template(t).ignore();
|
||||
} else if (value is AlgoDefinitionVM d) {
|
||||
string prompt; MessageBoxImage icon;
|
||||
if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||
if (_model.algo_model.templates_enabled() && d.templates.Count > 0) {
|
||||
prompt = $"Deleting this definition ({d.name}) will also delete its templates ({d.templates.Count}).\n\nDo you wan to Continue?";
|
||||
icon = MessageBoxImage.Warning;
|
||||
} else if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||
prompt = $"Are you sure you want to delete '{d.name}?'";
|
||||
icon = MessageBoxImage.Question;
|
||||
} else {
|
||||
@@ -137,7 +162,7 @@ namespace mbts.spark {
|
||||
}
|
||||
});
|
||||
_commands.Add(remove_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
|
||||
_resources.Add(content_src.OnDispatcher().Subscribe(remove_spark_cmd));
|
||||
}
|
||||
|
||||
void _dispose_core() {
|
||||
|
||||
@@ -1,59 +1,31 @@
|
||||
using bts.api.orderman;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
struct AlgoData : IEquatable<AlgoData> {
|
||||
public readonly CustomOrderDefinitionImmutable value;
|
||||
public readonly string name;
|
||||
public readonly AlgoKey key;
|
||||
|
||||
AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
|
||||
public AlgoData(CustomOrderDefinitionImmutable value)
|
||||
: this(value, value.display_name(), OrderDataHelper.get_shallow_factory_key(value)) { }
|
||||
|
||||
public AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public static AlgoData empty => from_custom_def(new CustomOrderDefinitionImmutable(new CustomOrderDefinition()));
|
||||
|
||||
/// <summary>
|
||||
/// This is the only <see cref="AlgoData"/> factory method that allows callers to explicitly specify a name.
|
||||
/// It should only be used within the context of creating, or renaming, studio algos.
|
||||
/// </summary>
|
||||
public static AlgoData create(CustomOrderDefinitionImmutable def, string name, AlgoKey key) =>
|
||||
new AlgoData(def, name, key);
|
||||
|
||||
/// <summary>
|
||||
/// Reconstructs <see cref="AlgoData"/> from a <see cref="CustomOrderDefinitionImmutable"/>, with a special caveat in the case of <see cref="CompositeOrderDefinition"/>:
|
||||
/// a fallback/default name is used instead of the composite algo's true name, which must be obtained from the <see cref="IAlgoStudioModel.definitions"/> stream.
|
||||
/// </summary>
|
||||
public static AlgoData from_custom_def(CustomOrderDefinitionImmutable def) {
|
||||
switch (def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite:
|
||||
string name = $"({OrderDataHelper.abbreviate_algo_type(def.CustomCase)}){OrderDataHelper.abbreviate_algo_type(def.Composite.Definition.CustomCase)}";
|
||||
return new AlgoData(def.Composite.Definition, name, AlgoKey.from_composite_key(def.Composite.AlgoTemplateKey));
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic:
|
||||
return new AlgoData(def,def.Dynamic.DynamicOrderType, AlgoKey.from_dynamic_type(def.Dynamic.DynamicOrderType));
|
||||
default:
|
||||
return new AlgoData(def, def.CustomCase.display_name(), AlgoKey.from_known_type(def.CustomCase));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData from_record(AlgoDefinitionRecordImmutable record) =>
|
||||
new AlgoData(record.Algo.Definition, record.Algo.Name, AlgoKey.from_composite_key(record.Key));
|
||||
|
||||
public static AlgoData create_unpersisted(CustomOrderDefinitionImmutable custom_def, string name) =>
|
||||
new AlgoData(custom_def, name, AlgoKey.create_unpersisted());
|
||||
public AlgoData with(CustomOrderDefinitionImmutable? value = null, string name = null) =>
|
||||
new AlgoData(value ?? this.value, name ?? this.name, key);
|
||||
|
||||
public bool Equals(AlgoData other) =>
|
||||
key == other.key &&
|
||||
name == other.name &&
|
||||
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer.Equals(value, other.value);
|
||||
OrderDataHelper.EqualsHelpers.custom_order_def_compareri.Equals(value, other.value);
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
public override int GetHashCode() =>
|
||||
@@ -67,8 +39,6 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
struct AlgoDataVM : IEquatable<AlgoDataVM> {
|
||||
public static readonly Func<AlgoDataVM, AlgoID> id_fn = vm => AlgoID.from_algo_key(vm.data.key);
|
||||
|
||||
public readonly AlgoData data;
|
||||
public readonly AlgoDefinitionMetadata? meta;
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using mbts.ui.utility;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
[ProtoContract]
|
||||
struct AlgoID : IEquatable<AlgoID> {
|
||||
[ProtoMember(1)]
|
||||
@@ -48,23 +47,24 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoID from_algo_key(AlgoKey key) {
|
||||
public static AlgoID from_algo_key(AlgoKey key, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory: return new AlgoID(key.factory);
|
||||
case AlgoKey.Mode.Dynamic: return new AlgoID(key.dynamic_type);
|
||||
case AlgoKey.Mode.Persisted: return new AlgoID(key.persisted);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
case AlgoKey.Mode.Factory:
|
||||
return AlgoStructureSerializer.to_algo_id(key.factory.structure, dynamic_type_index);
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return new AlgoID(key.persisted.id_uint);
|
||||
}
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
}
|
||||
|
||||
public static AlgoKey to_algo_key(AlgoID id) {
|
||||
public static AlgoKey to_algo_key(AlgoID id, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (id._factory_key.HasValue)
|
||||
return AlgoKey.from_known_type(id._factory_key.Value);
|
||||
return new AlgoKey(id._factory_key.Value);
|
||||
if (id._persisted_key.HasValue)
|
||||
return AlgoKey.from_composite_key(id._persisted_key.Value);
|
||||
return new AlgoKey(new AlgoPersistedKey(id._persisted_key.Value));
|
||||
if (id._dynamic_key != null)
|
||||
return AlgoKey.from_dynamic_type(id._dynamic_key);
|
||||
throw new ArgumentException("ID is empty.", nameof(id));
|
||||
return new AlgoKey(new AlgoNameStructureKey(id._dynamic_key, AlgoStructureSerializer.structure_key(dynamic_type_index.to_int(id._dynamic_key))));
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoID other) =>
|
||||
@@ -85,93 +85,55 @@ namespace mbts.spark {
|
||||
public static bool operator !=(AlgoID a, AlgoID b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
/// TODO: Remove <see cref="AlgoKey"/>, and redirect references to <see cref="AlgoID"/>.
|
||||
struct AlgoKey : IEquatable<AlgoKey> {
|
||||
readonly uint? _composite_key;
|
||||
readonly AlgoPersistedKey? _persisted_key;
|
||||
readonly AlgoNameStructureKey? _factory_key;
|
||||
readonly Guid? _unpersisted;
|
||||
readonly string _dynamic_type;
|
||||
readonly CustomOrderDefinition.CustomOneofCase? _factory_type;
|
||||
|
||||
AlgoKey(Guid id_guid) {
|
||||
_composite_key = null;
|
||||
public AlgoKey(Guid id_guid) {
|
||||
_persisted_key = null;
|
||||
_factory_key = null;
|
||||
_unpersisted = id_guid;
|
||||
_dynamic_type = null;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(uint composite_key) {
|
||||
_composite_key = composite_key;
|
||||
public AlgoKey(CustomOrderDefinition.CustomOneofCase factory_root_algo_type)
|
||||
: this(new AlgoNameStructureKey(factory_root_algo_type)) { }
|
||||
|
||||
public AlgoKey(AlgoNameStructureKey factory_key) {
|
||||
_persisted_key = null;
|
||||
_factory_key = factory_key;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = null;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(string dynamic_type) {
|
||||
_composite_key = null;
|
||||
public AlgoKey(AlgoPersistedKey persisted_key) {
|
||||
_persisted_key = persisted_key;
|
||||
_factory_key = null;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = dynamic_type;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(CustomOrderDefinition.CustomOneofCase factory_type) {
|
||||
_composite_key = null;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = null;
|
||||
_factory_type = factory_type;
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
None,
|
||||
Persisted,
|
||||
Unpersisted,
|
||||
Dynamic,
|
||||
Factory
|
||||
}
|
||||
|
||||
public bool is_persisted => mode == Mode.Persisted;
|
||||
|
||||
public uint persisted => _composite_key.Value;
|
||||
public AlgoPersistedKey persisted => _persisted_key.Value;
|
||||
public AlgoNameStructureKey factory => _factory_key ?? new AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase.None);
|
||||
public Guid unpersisted => _unpersisted.Value;
|
||||
public CustomOrderDefinition.CustomOneofCase factory => _factory_type.Value;
|
||||
public string dynamic_type => _dynamic_type;
|
||||
|
||||
public Mode mode {
|
||||
get {
|
||||
if (_factory_type.HasValue) return Mode.Factory;
|
||||
if (_composite_key.HasValue) return Mode.Persisted;
|
||||
if (_dynamic_type != null) return Mode.Dynamic;
|
||||
if (_persisted_key.HasValue) return Mode.Persisted;
|
||||
if (_unpersisted.HasValue) return Mode.Unpersisted;
|
||||
if (_factory_key.HasValue) return Mode.Factory;
|
||||
return Mode.None;
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoKey create_unpersisted() =>
|
||||
new AlgoKey(Guid.NewGuid());
|
||||
|
||||
public static AlgoKey from_custom_def(CustomOrderDefinitionImmutable custom_def) {
|
||||
switch (custom_def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoKey(custom_def.Dynamic.DynamicOrderType);
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoKey(custom_def.Composite.AlgoTemplateKey);
|
||||
default: return new AlgoKey(custom_def.CustomCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoKey from_known_type(CustomOrderDefinition.CustomOneofCase known_type) {
|
||||
if (known_type == CustomOrderDefinition.CustomOneofCase.Dynamic)
|
||||
throw new ArgumentException(nameof(known_type), "Potential loss of data: no dynamic type name specified.");
|
||||
if (known_type == CustomOrderDefinition.CustomOneofCase.Composite)
|
||||
throw new ArgumentException(nameof(known_type), "Potential loss of data: no composite id specified.");
|
||||
return new AlgoKey(known_type);
|
||||
}
|
||||
|
||||
public static AlgoKey from_dynamic_type(string dynamic_type) {
|
||||
if (string.IsNullOrEmpty(dynamic_type))
|
||||
throw ExceptionBuilder.Argument.null_or_empty(nameof(dynamic_type));
|
||||
return new AlgoKey(dynamic_type);
|
||||
}
|
||||
|
||||
public static AlgoKey from_composite_key(uint composite_key) =>
|
||||
new AlgoKey(composite_key);
|
||||
|
||||
public bool Equals(AlgoKey other) {
|
||||
Mode m = mode;
|
||||
if (m == other.mode) {
|
||||
@@ -179,7 +141,6 @@ namespace mbts.spark {
|
||||
case Mode.Persisted: return persisted == other.persisted;
|
||||
case Mode.Unpersisted: return unpersisted == other.unpersisted;
|
||||
case Mode.Factory: return factory == other.factory;
|
||||
case Mode.Dynamic: return dynamic_type == other.dynamic_type;
|
||||
case Mode.None: return true;
|
||||
}
|
||||
}
|
||||
@@ -194,7 +155,6 @@ namespace mbts.spark {
|
||||
case Mode.Persisted: hash = persisted.GetHashCode(); phase_mult = 2; break;
|
||||
case Mode.Unpersisted: hash = unpersisted.GetHashCode(); phase_mult = 4; break;
|
||||
case Mode.Factory: hash = factory.GetHashCode(); phase_mult = 3; break;
|
||||
case Mode.Dynamic: hash = dynamic_type.GetHashCode(); phase_mult = 5; break;
|
||||
default: return 0;
|
||||
}
|
||||
return EqualityComparer.phase_hash(hash, phase_mult);
|
||||
@@ -205,4 +165,4 @@ namespace mbts.spark {
|
||||
|
||||
InvalidOperationException _unexpected_mode_ex() => new InvalidOperationException($"Unexpected Mode '{mode}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace bts.persist.algo {
|
||||
public bool is_persisted => mode != Mode.None;
|
||||
|
||||
public mbts.spark.AlgoKey to_algo_key() =>
|
||||
new mbts.spark.AlgoKey(_id_uint ?? 0);
|
||||
new mbts.spark.AlgoKey(this);
|
||||
|
||||
public bool Equals(AlgoPersistedKey other) {
|
||||
Mode m = mode;
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
struct AlgoNameStructureKey : IEquatable<AlgoNameStructureKey> {
|
||||
public readonly string name;
|
||||
public readonly AlgoStructureKey structure;
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
|
||||
|
||||
public AlgoNameStructureKey(string name, AlgoStructureKey structure) {
|
||||
this.name = name;
|
||||
this.structure = structure;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoNameStructureKey other) => name == other.name && structure == other.structure;
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(EqualityComparer.safe_get_hash_code(name), structure.GetHashCode());
|
||||
public static bool operator ==(AlgoNameStructureKey a, AlgoNameStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
|
||||
readonly int[] _structure;
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(new[] { (int)base_type }) { }
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
|
||||
|
||||
public AlgoStructureKey(int[] structure) =>
|
||||
_structure = structure;
|
||||
|
||||
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
|
||||
|
||||
public bool Equals(AlgoStructureKey other) {
|
||||
if (_structure == null) {
|
||||
return other._structure == null;
|
||||
} else {
|
||||
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
|
||||
return other._structure != null && _structure.Length == other._structure.Length && _structure.SequenceEqual(other._structure);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AlgoStructureKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_structure == null ? 0 : EqualityComparer.aggregate_hash(_structure);
|
||||
|
||||
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static class AlgoStructureSerializer {
|
||||
const int
|
||||
_dynamic_mask = 1 << 31,
|
||||
_child_mask_offset = 30;
|
||||
|
||||
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 CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
|
||||
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
public static AlgoStructureKey structure_key(int dynamic_type) =>
|
||||
new AlgoStructureKey(new[] { dynamic_type | _dynamic_mask });
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static AlgoID to_algo_id(AlgoStructureKey key, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (key.structure.Count == 0)
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
int i = key.structure[0];
|
||||
return BitHelper.remove_flags(ref i, _dynamic_mask) ? new AlgoID(dynamic_type_index.from_int(i)) : new AlgoID((CustomOrderDefinition.CustomOneofCase)i);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoStudioDefinitionTemplateModel : IAlgoStudioModel, IDisposable {
|
||||
readonly AlgoService _service;
|
||||
readonly AdminModel _admin_model;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
readonly ObservableDemuxer<AlgoKey, AlgoDefinitionMetadata> _metadata_demuxer = new ObservableDemuxer<AlgoKey, AlgoDefinitionMetadata>();
|
||||
readonly TableUpdateDemuxer<AlgoTemplateData, AlgoKey, AlgoKey> _definition_templates_demuxer;
|
||||
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
|
||||
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
|
||||
IDisposable _dispose;
|
||||
|
||||
public AlgoStudioDefinitionTemplateModel(AlgoService service, IAlgoDefinitionMetadataService meta, AdminModel admin_model, ErrorReporter error_reporter, bts.config.AlgoStudioOptions ui_configuration) {
|
||||
_service = service;
|
||||
metadata_service = meta;
|
||||
_admin_model = admin_model;
|
||||
_error_reporter = error_reporter;
|
||||
this.ui_configuration = ui_configuration;
|
||||
_definition_templates_demuxer = new TableUpdateDemuxer<AlgoTemplateData, AlgoKey, AlgoKey>(t => t.key, t => t.definition_key);
|
||||
definitions = _service.subscribei_algos()
|
||||
.Select(u => new Update<AlgoData, AlgoKey>(
|
||||
u.all.Select(_Utils.definition_data).ToList(),
|
||||
u.added.Select(_Utils.definition_data).ToList(),
|
||||
u.changes.Select(ch => new Change<AlgoData>(_Utils.definition_data(ch.Old), _Utils.definition_data(ch.New))).ToList(),
|
||||
u.removed.Select(d => _Utils.definition_data(d)).ToList(),
|
||||
def => def.key))
|
||||
.Table()
|
||||
.Cache();
|
||||
templates = _service.subscribei_algo_templates()
|
||||
.Select(u => new Update<AlgoTemplateData, AlgoKey>(
|
||||
u.all.Select(_Utils.template_data).ToList(),
|
||||
u.added.Select(_Utils.template_data).ToList(),
|
||||
u.changes.Select(ch => new Change<AlgoTemplateData>(_Utils.template_data(ch.Old), _Utils.template_data(ch.New))).ToList(),
|
||||
u.removed.Select(_Utils.template_data).ToList(),
|
||||
def => def.key))
|
||||
.Table()
|
||||
.Cache();
|
||||
_dispose = new CompositeDisposable(
|
||||
definitions.OnDispatcher().Subscribe(t => _algos = t.state),
|
||||
templates.OnDispatcher().Subscribe(t => _algo_templates = t.state),
|
||||
templates.Subscribe(_definition_templates_demuxer),
|
||||
metadata_service.subscribe_metadata().Subscribe(_metadata_demuxer));
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
|
||||
public IAlgoDefinitionMetadataService metadata_service { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition) =>
|
||||
_definition_templates_demuxer.subscribe(definition);
|
||||
|
||||
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
||||
if (definition_data.key.is_persisted) {
|
||||
// TODO: implement update op when backend ready
|
||||
} else {
|
||||
// create op
|
||||
return await _create_definition(new CompositeAlgoDefinitionImmutable(_Utils.to_definition(definition_data)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public async Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) {
|
||||
if (template_data.key.is_persisted) {
|
||||
// update op
|
||||
return await _update_template(_Utils.to_recordi(template_data));
|
||||
} else {
|
||||
// create op
|
||||
return await _create_template(_Utils.to_templatei(template_data));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The api is not used here (there are some unresolved dependency issues with definitions on the backend).
|
||||
/// The definition is marked as hidden in the AlgoMetadata stored on the server.
|
||||
/// <para/>
|
||||
/// For implementation details, see: <see cref="IAlgoDefinitionMetadataService"/>, <see cref="AlgoTemplateMetadataService"/>
|
||||
/// </summary>
|
||||
public async Task<bool> remove_definition(AlgoKey key) {
|
||||
if (_algos.TryGetValue(key, out AlgoData curr)) {
|
||||
if (_definition_templates_demuxer.try_get_values(key, out IReadOnlyDictionary<AlgoKey, AlgoTemplateData> tmps)) {
|
||||
foreach (var t in tmps.Values)
|
||||
await remove_template(t.key);
|
||||
}
|
||||
await metadata_service.hide_definition(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> remove_template(AlgoKey template_key) {
|
||||
var req = new AlgoServiceRemoveRequest();
|
||||
_Utils.set_key(ref req, template_key);
|
||||
var reply = await _safe_execute(() => _service.remove_template_async(req), ActionMetadata.remove_template);
|
||||
return reply.success && reply.value.Success;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) =>
|
||||
await _create_definition(definition, _admin_model.user.id);
|
||||
|
||||
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
|
||||
new TemplateCreateRequest { UserId = _admin_model.user.id, NewTemplate = template.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(value.Record);
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
|
||||
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(template);
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition, uint user_id) {
|
||||
(var result, var success) = await _error_reporter.safe_execute(() => _service.create_composite_algo_async(
|
||||
new CompositeAlgoCreateRequest {
|
||||
NewAlgo = definition.CloneToMutable(),
|
||||
UserId = user_id
|
||||
}), r => r.Success, r => r.ErrMsg, "create definition");
|
||||
return success ? _Utils.definition_data(new CompositeAlgoRecordImmutable(result.Record)) : (AlgoData?)null;
|
||||
}
|
||||
|
||||
async Task<Result<T>> _safe_execute<T>(Func<Task<Result<T>>> execute, IActionMetadata<T> am) {
|
||||
try {
|
||||
var result = await execute();
|
||||
if (!result.success) {
|
||||
_report_error(am.present_progressive_desc, result.error_message);
|
||||
} else if (!am.get_success(result.value)) {
|
||||
_report_error(am.present_progressive_desc, am.get_error_msg(result.value));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
_report_error(am.present_progressive_desc, ex.ToString());
|
||||
}
|
||||
return new Result<T>(bts.api.ApiError.UnexpectedResponse);
|
||||
}
|
||||
|
||||
void _report_error(string present_progressive_desc, string msg) =>
|
||||
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
|
||||
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
|
||||
// used for error handling/reporting
|
||||
interface IActionMetadata {
|
||||
string desc { get; }
|
||||
string present_progressive_desc { get; }
|
||||
}
|
||||
|
||||
interface IActionMetadata<TReply> : IActionMetadata {
|
||||
bool get_success(TReply reply);
|
||||
string get_error_msg(TReply reply);
|
||||
}
|
||||
|
||||
static class ActionMetadata {
|
||||
public const string
|
||||
CreateDefinition = "create definition",
|
||||
UpdateDefinition = "update definition",
|
||||
CreateTemplate = "create template",
|
||||
UpdateTemplate = "update template";
|
||||
public static readonly IActionMetadata<AlgoServiceReply> update_template = new Impl<AlgoServiceReply>(_update, _template, r => r.Success, r => r.ErrMsg.ToString());
|
||||
public static readonly IActionMetadata<AlgoServiceReply> remove_template = new Impl<AlgoServiceReply>(_delete, _template, r => r.Success, r => r.ErrMsg.ToString());
|
||||
public static readonly IActionMetadata<TemplateCreateResult> create_template = new Impl<TemplateCreateResult>(_create, _template, r => r.Success, r => r.ErrMsg);
|
||||
public static readonly IActionMetadata<CompositeAlgoCreateResult> create_definition = new Impl<CompositeAlgoCreateResult>(_create, _definition, r => r.Success, r => r.ErrMsg);
|
||||
|
||||
static readonly DescTuple _create = new DescTuple("create", "creating"), _update = new DescTuple("update", "updating"), _delete = new DescTuple("delete", "deleting");
|
||||
static readonly string _definition = "definition", _template = "template";
|
||||
|
||||
class Impl<T> : IActionMetadata<T> {
|
||||
readonly DescTuple _desc;
|
||||
readonly string _subject;
|
||||
readonly Func<T, bool> _get_success;
|
||||
readonly Func<T, string> _get_error_msg;
|
||||
|
||||
public Impl(DescTuple desc, string subject, Func<T, bool> get_success, Func<T, string> get_error_msg) {
|
||||
_desc = desc;
|
||||
_subject = subject;
|
||||
_get_success = get_success;
|
||||
_get_error_msg = get_error_msg;
|
||||
}
|
||||
public string desc => _desc.desc;
|
||||
public string present_progressive_desc => _desc.present_progressive_desc;
|
||||
public bool get_success(T reply) => _get_success(reply);
|
||||
public string get_error_msg(T reply) => _get_error_msg(reply);
|
||||
}
|
||||
|
||||
struct DescTuple {
|
||||
public readonly string desc, present_progressive_desc;
|
||||
public DescTuple(string desc, string present_progressive_desc) {
|
||||
this.desc = desc;
|
||||
this.present_progressive_desc = present_progressive_desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AlgoService protobuf types have been extracted out of the viewmodels (<see cref="AlgoStudioVM"/>, <see cref="OrderPropertiesEditorVM"/>, <see cref="AlgoDefinitionVM"/>, <see cref="AlgoDefinitionTemplateVM"/>).
|
||||
/// The viewmodels instead use these types to represent the data: <see cref="AlgoData"/> is used in place of <see cref="CompositeAlgoRecordImmutable"/>, and <see cref="AlgoTemplateData"/> is used in place of <see cref="AlgoTemplateRecordImmutable"/>.
|
||||
/// This thin abstraction isolates the server-side from the client-side, so if one day the server side types change, we won't need to change any client-side code except the methods below which handle the conversion from server type to corresponding client type.
|
||||
/// There are a couple other methods in here as well (this is an extensions class afterall), but [John]I am going out of town soon (1/6/18) and did not get a chance to organize these methods beyond containing them in <see cref="Utils"/>.
|
||||
/// </summary>
|
||||
static class _Utils {
|
||||
// AlgoTemplateImmutable custom_order_definition(i)
|
||||
public static CustomOrderDefinitionImmutable custom_order_definitioni(AlgoTemplateImmutable templatei) {
|
||||
switch (templatei.AlgoParamsCase) {
|
||||
case AlgoTemplate.AlgoParamsOneofCase.CompositeAlgoParams: return templatei.CompositeAlgoParams.Algo;
|
||||
case AlgoTemplate.AlgoParamsOneofCase.SystemAlgoParams: return templatei.SystemAlgoParams;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(templatei.AlgoParamsCase, nameof(templatei));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData definition_data(AlgoTemplateImmutable templatei) {
|
||||
switch (templatei.AlgoParamsCase) {
|
||||
case AlgoTemplate.AlgoParamsOneofCase.CompositeAlgoParams: return definition_data(templatei.CompositeAlgoParams);
|
||||
case AlgoTemplate.AlgoParamsOneofCase.SystemAlgoParams:
|
||||
return new AlgoData(
|
||||
templatei.SystemAlgoParams,
|
||||
templatei.SystemAlgoParams.CustomCase.display_name(),
|
||||
get_key(templatei.SystemAlgoParams));
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(templatei.AlgoParamsCase, nameof(templatei));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData definition_data(CompositeAlgoRecordImmutable recordi) => new AlgoData(recordi.AlgoDefinition.Algo, recordi.AlgoDefinition.Name, get_key(recordi));
|
||||
|
||||
public static AlgoData definition_data(CompositeAlgoDefinitionImmutable definitioni) =>
|
||||
new AlgoData(definitioni.Algo, definitioni.Name, get_key(definitioni));
|
||||
|
||||
public static AlgoData? definition_data(Result<CompositeAlgoCreateResult> result) {
|
||||
if (result.success && result.value.Success) {
|
||||
var r = result.value.Record;
|
||||
return new AlgoData(new CustomOrderDefinitionImmutable(r.AlgoDefinition.Algo), r.AlgoDefinition.Name, new AlgoKey(new AlgoPersistedKey(r.Key)));
|
||||
} else return null;
|
||||
}
|
||||
|
||||
// AlgoTemplateRecord(Immutable) template data
|
||||
public static AlgoTemplateData template_data(AlgoTemplateRecord record) => template_data(new AlgoTemplateRecordImmutable(record));
|
||||
public static AlgoTemplateData template_data(AlgoTemplateRecordImmutable recordi) => new AlgoTemplateData(custom_order_definitioni(recordi.AlgoTemplate), recordi.AlgoTemplate.Name, get_key(recordi), definition_data(recordi.AlgoTemplate));
|
||||
|
||||
public static CompositeAlgoDefinition to_definition(AlgoData definition_data) =>
|
||||
new CompositeAlgoDefinition {
|
||||
Name = definition_data.name,
|
||||
Algo = definition_data.value.CloneToMutable()
|
||||
};
|
||||
|
||||
// AlgoTemplateData to_record(i)
|
||||
public static AlgoTemplateRecord to_record(AlgoTemplateData template) {
|
||||
var record = new AlgoTemplateRecord {
|
||||
AlgoTemplate = new AlgoTemplate {
|
||||
Name = template.name,
|
||||
CompositeAlgoParams = new CompositeAlgoDefinition {
|
||||
Name = template.definition.name,
|
||||
Algo = template.value.CloneToMutable()
|
||||
}
|
||||
}
|
||||
};
|
||||
set_key(ref record, template.key);
|
||||
return record;
|
||||
}
|
||||
public static AlgoTemplateRecordImmutable to_recordi(AlgoTemplateData template) => new AlgoTemplateRecordImmutable(to_record(template));
|
||||
|
||||
// AlgoTemplateData to_template(i)
|
||||
public static AlgoTemplate to_template(AlgoTemplateData template) =>
|
||||
new AlgoTemplate {
|
||||
Name = template.name,
|
||||
CompositeAlgoParams = new CompositeAlgoDefinition {
|
||||
Name = template.definition.name,
|
||||
Algo = template.value.CloneToMutable()
|
||||
}
|
||||
};
|
||||
public static AlgoTemplateImmutable to_templatei(AlgoTemplateData template) => new AlgoTemplateImmutable(to_template(template));
|
||||
|
||||
public static AlgoKey get_key(CompositeAlgoRecordImmutable recordi) => new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
public static AlgoKey get_key(AlgoTemplateRecordImmutable recordi) => new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
public static AlgoKey get_key(CompositeAlgoDefinitionImmutable definitioni) => new AlgoKey(new AlgoPersistedKey(definitioni.Name));
|
||||
public static AlgoKey get_key(CustomOrderDefinitionImmutable custom_order_definitioni) => new AlgoKey(custom_order_definitioni.CustomCase);
|
||||
|
||||
public static void set_key(ref AlgoTemplateRecord record, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
switch (key.persisted.mode) {
|
||||
case AlgoPersistedKey.Mode.String:
|
||||
record.Key = key.persisted.id_string; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
public static void set_key(ref AlgoServiceRemoveRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
switch (key.persisted.mode) {
|
||||
case AlgoPersistedKey.Mode.String:
|
||||
request.Key = key.persisted.id_string; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.persist.algo;
|
||||
using bts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export(typeof(IAlgoStudioModel))]
|
||||
class AlgoStudioFlatDefinitionModel : IAlgoStudioModel, IDisposable {
|
||||
readonly AlgoDefinitionService _service;
|
||||
readonly AdminModel _admin_model;
|
||||
@@ -19,12 +18,12 @@ namespace mbts.spark {
|
||||
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter) {
|
||||
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter, bts.config.AlgoStudioOptions ui_configuration) {
|
||||
_service = service;
|
||||
this.metadata_service = metadata_service;
|
||||
_admin_model = admin_model;
|
||||
_error_reporter = error_reporter;
|
||||
this.ui_configuration = ui_configuration;
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
|
||||
@@ -36,7 +35,7 @@ namespace mbts.spark {
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates => Observable.Empty<TableUpdate<AlgoTemplateData, AlgoKey>>();
|
||||
public IAlgoDefinitionMetadataService metadata_service { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
|
||||
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
|
||||
@@ -72,10 +71,10 @@ namespace mbts.spark {
|
||||
new AlgoDefinitionImmutable(_template(definition_data));
|
||||
|
||||
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
|
||||
AlgoData.from_record(recordi);
|
||||
new AlgoData(recordi.Algo.Definition, recordi.Algo.Name, _key(recordi));
|
||||
|
||||
static AlgoKey _key(AlgoDefinitionRecordImmutable recordi) =>
|
||||
AlgoKey.from_composite_key(recordi.Key);
|
||||
new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
|
||||
static AlgoDefinitionUpdateRequest _with_key(AlgoDefinitionUpdateRequest request, AlgoKey key) {
|
||||
_set_key(ref request, key);
|
||||
@@ -84,10 +83,9 @@ namespace mbts.spark {
|
||||
|
||||
static void _set_key(ref AlgoDefinitionUpdateRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted; break;
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted.id_uint; break;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Factory:
|
||||
case AlgoKey.Mode.Dynamic:
|
||||
case AlgoKey.Mode.None: request.Key = 0; break;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
@@ -95,7 +93,7 @@ namespace mbts.spark {
|
||||
|
||||
static void _set_key(ref AlgoDefinitionRemoveRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted; break;
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted.id_uint; break;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -17,13 +20,34 @@ namespace mbts.spark {
|
||||
bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
}
|
||||
|
||||
class AlgoStudioModelExporter {
|
||||
[ImportingConstructor]
|
||||
AlgoStudioModelExporter(Spark.AlgoServiceWrapper service_wrapper, IAlgoDefinitionMetadataService meta, AdminModel admin_model, ErrorReporter error_reporter) {
|
||||
var ui_configuration = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
||||
switch (service_wrapper.mode) {
|
||||
case Spark.AlgoServiceWrapper.Mode.Flat: model = new AlgoStudioFlatDefinitionModel(service_wrapper.flat, meta, admin_model, error_reporter, ui_configuration); break;
|
||||
case Spark.AlgoServiceWrapper.Mode.Hierarchical: model = new AlgoStudioDefinitionTemplateModel(service_wrapper.hierarhical, meta, admin_model, error_reporter, ui_configuration); break;
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public IAlgoStudioModel model { get; }
|
||||
}
|
||||
|
||||
static class AlgoStudioModelUtils {
|
||||
public static bool templates_enabled(this IAlgoStudioModel model) =>
|
||||
get_templates_enabled(model.ui_configuration);
|
||||
|
||||
public static bool get_templates_enabled(bts.config.AlgoStudioOptions ui_configuration) =>
|
||||
ui_configuration.add_template_button != bts.config.AlgoStudioOptions.AddTemplateButton.None;
|
||||
|
||||
public static IReadOnlyDictionary<object, object> get_dynamic_resources(bts.config.AlgoStudioOptions ui_configuration) =>
|
||||
new Dictionary<object, object> {
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarRenameDefinitionKey, ui_configuration.can_modify_definition_name && ui_configuration.show_algo_toolbar_rename },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarSaveDefinitionKey, ui_configuration.can_modify_definition_values && ui_configuration.show_algo_toolbar_save },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey, ui_configuration.show_algo_toolbar_revert },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey, ui_configuration.show_algo_toolbar_undo_redo }
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey, ui_configuration.show_algo_toolbar_undo_redo },
|
||||
{ AlgoStudioWindow.TemplatesEnabledKey, get_templates_enabled(ui_configuration) }
|
||||
};
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -35,9 +35,9 @@ namespace mbts.spark {
|
||||
internal AlgoDefinitionPersistedMetadata to_persisted() =>
|
||||
id.mode != AlgoKey.Mode.Persisted ?
|
||||
throw new InvalidOperationException("Metadata does not have a persisted key.") :
|
||||
new AlgoDefinitionPersistedMetadata(new AlgoPersistedKey(id.persisted),
|
||||
default_template.mode == AlgoKey.Mode.Persisted ? new AlgoPersistedKey(default_template.persisted) : default,
|
||||
favorite_templates.Where(t => t.mode == AlgoKey.Mode.Persisted).Select(t => new AlgoPersistedKey(t.persisted)).ToArray(),
|
||||
new AlgoDefinitionPersistedMetadata(id.persisted,
|
||||
default_template.mode == AlgoKey.Mode.Persisted ? default_template.persisted : default,
|
||||
favorite_templates.Where(t => t.mode == AlgoKey.Mode.Persisted).Select(t => t.persisted).ToArray(),
|
||||
is_favorite, is_hidden, abbreviation, new NullableColorProto(badge_color));
|
||||
|
||||
public AlgoDefinitionMetadata with(AlgoKey? id = null, AlgoKey? default_template = null, IEnumerable<AlgoKey> favorite_templates = null, bool? is_favorite = null, bool? is_hidden = null, string abbreviation = null, NullableColorProto? badge_color = null) =>
|
||||
|
||||
@@ -83,8 +83,7 @@ namespace mbts.spark {
|
||||
|
||||
public static AlgoDisplayGroup display_group(this AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory: return display_group(key.factory);
|
||||
case AlgoKey.Mode.Dynamic: return AlgoDisplayGroup.API;
|
||||
case AlgoKey.Mode.Factory: return display_group(key.factory.structure.order_type());
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using VisualStateNames = mbts.ui.spark.AlgoStudioView.VisualStateNames;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoDefinitionTemplateVM : DisposableViewModelBase, IAlgoStudioContentVM, IEquatable<AlgoDefinitionTemplateVM> {
|
||||
/// <see cref="CustomType"/> is referenced by an x:Static extension in OrderDetailsPopupControl.xaml <see cref="mbts.ui.spark.algo.OrderDetailsPopupControl"/>.
|
||||
/// In most cases, <see cref="mbts.ui.NestedExtension"/> works just like <see cref="System.Windows.Markup.TypeExtension"/>, but for nested types, such as <see cref="CustomTemplatePlaceholderVM"/>.
|
||||
/// However, when the former is used to denote the DataType of a DataTemplate, a strange XamlReader exception is thrown at runtime.
|
||||
/// After spending ~15 minutes investigating what was happening, [John]I decided it was more trouble than it was worth and opted for this workaround.
|
||||
public static readonly Type CustomType = typeof(CustomTemplatePlaceholderVM);
|
||||
readonly OrdersModel _model;
|
||||
readonly VisualStateModel _edit_pane_vs_model = new VisualStateModel(), _tree_view_vs_model = new VisualStateModel();
|
||||
/// Note: the difference between <see cref="EditStack{T}"/> and <see cref="VersionContainer{T}"/> is that the former implements push/pop functionality so the user may undo/redo (like ctrl+z/ctrl+y in text editors),
|
||||
/// while the latter is a lazy delegate-based container for the most recent value (<see cref="VersionContainer{T}.increment_version"/>).
|
||||
readonly EditStack<Snapshot> _edit_stack = new EditStack<Snapshot>();
|
||||
readonly VersionContainer<Snapshot> _version;
|
||||
readonly DeferrableActionWrapper _refresh_on_local_change;
|
||||
readonly Property<IAlgoVM> _algo_root;
|
||||
readonly DelegateProperty<bool> _can_undo, _can_redo, _is_dirty;
|
||||
readonly Property<bool> _is_default, _is_favorite, _is_selected;
|
||||
readonly Property<string> _name, _abbreviation;
|
||||
// maintains the state of each algo-root, so the user may change the definition root back and forth without any loss of state amongst the templates
|
||||
Snapshot _committed;
|
||||
|
||||
public AlgoDefinitionTemplateVM(AlgoDefinitionVM owner, OrdersModel model, AlgoData data, AlgoEditingPermissions editing_permissions = AlgoEditingPermissions.AllowEditValues) {
|
||||
// init core data
|
||||
this.owner = owner;
|
||||
_model = model;
|
||||
key = data.key;
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
// maintain states for different algo roots, so they aren't lost when the user changes the root algo type
|
||||
var algo_map = new Dictionary<AlgoKey, IAlgoVM> {
|
||||
{ data.key, algo_vm_factory(data.value) }
|
||||
};
|
||||
// init backing props
|
||||
_name = backing(nameof(this.name), name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
_is_default = backing(nameof(is_default), false);
|
||||
_is_favorite = backing(nameof(is_favorite), false);
|
||||
_is_selected = backing(nameof(is_selected), false);
|
||||
_algo_root = backing(nameof(algo_root), get_algo_root(owner.algo_root));
|
||||
_committed = _version.value;
|
||||
_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));
|
||||
_is_dirty = backing(nameof(is_dirty), () => !_committed.Equals(_version.value));
|
||||
_can_redo.Changed += on_can_redo_changed;
|
||||
_can_undo.Changed += on_can_undo_changed;
|
||||
set_as_default_cmd = new DelegateCommand(() => {
|
||||
if (!is_default && key.mode == AlgoKey.Mode.Persisted)
|
||||
_model.algo_model.metadata_service.set_as_default(owner.key, key).ignore();
|
||||
});
|
||||
// init visual states
|
||||
// TODO: implement these features in IAlgoMetadataService; until then, set the appropriate visual states.
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Abbreviation.GroupName, VisualStateNames.Abbreviation.NotSupportedForTemplates);
|
||||
_tree_view_vs_model.set_state(VisualStateNames.FavoriteToggleButton.GroupName, VisualStateNames.FavoriteToggleButton.Inactive);
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_edit_stack.push(_committed);
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Active);
|
||||
break;
|
||||
case AlgoKey.Mode.Unpersisted: _edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Active); break;
|
||||
case AlgoKey.Mode.Factory: _edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive); break;
|
||||
}
|
||||
// set hooks/subscriptions
|
||||
_refresh_on_local_change = new DeferrableActionWrapper(on_local_changed);
|
||||
_dispose = new System.Reactive.Disposables.CompositeDisposable(
|
||||
as_demuxer().subscribe_many(_refresh_on_local_change.execute, nameof(name), nameof(abbreviation), nameof(is_default), nameof(is_favorite)),
|
||||
owner.observe_algo_root().Subscribe(r => _algo_root.set(get_algo_root(r))),
|
||||
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).Subscribe(_ => _refresh_on_local_change.execute()),
|
||||
Disposable.wrap_collection(algo_map.Values)) {
|
||||
() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}
|
||||
};
|
||||
// local methods
|
||||
IAlgoVM algo_vm_factory(CustomOrderDefinitionImmutable def) => OrderTypeVMFactory.create(new AlgoData(def, def.CustomCase.display_name(), OrderDataHelper.get_shallow_factory_key(def)), editing_permissions);
|
||||
IAlgoVM get_algo_root(IAlgoVM owner_root) {
|
||||
var key = new AlgoKey(owner_root.order_type);
|
||||
if (algo_map.TryGetValue(key, out IAlgoVM vm)) {
|
||||
vm.load_structure(owner_root.definition);
|
||||
} else {
|
||||
vm = algo_map.GetOrAdd(key, ot => algo_vm_factory(owner_root.definition));
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
void on_local_changed() {
|
||||
_version.increment_version();
|
||||
_edit_stack.push_distinct(_version.value);
|
||||
_is_dirty.refresh();
|
||||
_refresh_undo_redo();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action can_redo_changed, can_undo_changed;
|
||||
public event PropertyChange<IAlgoVM> algo_root_changed { add => _algo_root.Changed += value; remove => _algo_root.Changed -= value; }
|
||||
|
||||
public AlgoDefinitionVM owner { get; }
|
||||
public AlgoKey owner_key => owner.key;
|
||||
public AlgoKey key { get; }
|
||||
public string name { get => _name.Value; set => _name.set(value); }
|
||||
public string abbreviation { get => _abbreviation.Value; set => _abbreviation.set(value); }
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool can_redo => _can_redo.Value;
|
||||
public bool can_undo => _can_undo.Value;
|
||||
public bool is_default => _is_default.Value;
|
||||
public bool is_favorite { get => _is_favorite.Value; set => _is_favorite.set(value); }
|
||||
public IAlgoVM algo_root => _algo_root.Value;
|
||||
public virtual bool is_custom_placeholder => false;
|
||||
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
|
||||
public bool is_expanded => false;
|
||||
public bool is_persisted => key.mode == AlgoKey.Mode.Persisted;
|
||||
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
|
||||
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
|
||||
public ICommand set_as_default_cmd { get; }
|
||||
|
||||
public IObservable<bool> observe_is_dirty() => _is_dirty.observe_current(true);
|
||||
public void undo() => _restore(_edit_stack.try_undo);
|
||||
public void redo() => _restore(_edit_stack.try_redo);
|
||||
|
||||
public void on_meta(bool is_default, bool is_favorite) {
|
||||
_is_default.set(is_default);
|
||||
_is_favorite.set(is_favorite);
|
||||
}
|
||||
|
||||
public AlgoData create_def_data() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
|
||||
public AlgoTemplateData create() =>
|
||||
create(owner.create_algo_def());
|
||||
|
||||
public AlgoTemplateData create(AlgoData record) {
|
||||
if (!OrderDataHelper.EqualsHelpers.structure_equals(algo_root.definition, record.value))
|
||||
throw new InvalidOperationException("AlgoTemplate's algo structure does not match that of its parent.");
|
||||
return new AlgoTemplateData(algo_root.definition, name, key, record);
|
||||
}
|
||||
|
||||
public AlgoDefinitionTemplateVM to_persisted(AlgoDefinitionVM persisted_owner, AlgoData template) =>
|
||||
new AlgoDefinitionTemplateVM(persisted_owner, _model, template);
|
||||
|
||||
public void load_committed() => _load_from(_committed.name, _committed.definition, true);
|
||||
|
||||
public void load_from(AlgoTemplateData value) {
|
||||
if (key.mode != AlgoKey.Mode.Persisted)
|
||||
throw new InvalidOperationException("AlgoStructure has not been saved yet.");
|
||||
else if (value.key != key)
|
||||
throw new InvalidOperationException("Value id does not match this id.");
|
||||
_load_from(value.name, value.value, true);
|
||||
}
|
||||
|
||||
public bool Equals(AlgoDefinitionTemplateVM other) => other != null && key == other.key;
|
||||
public override bool Equals(object obj) => Equals(obj as AlgoDefinitionTemplateVM);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
void _load_from(string new_name, CustomOrderDefinitionImmutable new_def, bool update_committed) {
|
||||
using (_refresh_on_local_change.defer_execution()) {
|
||||
name = new_name;
|
||||
algo_root.load_from(new_def);
|
||||
}
|
||||
if (update_committed) {
|
||||
_committed = _version.value;
|
||||
_is_dirty.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_undo_redo() {
|
||||
_can_redo.refresh();
|
||||
_can_undo.refresh();
|
||||
}
|
||||
|
||||
void _restore(TryGetValue<Snapshot> try_get_snapshot) {
|
||||
while (try_get_snapshot(out Snapshot next_state)) {
|
||||
if (!_version.value.Equals(next_state)) {
|
||||
_load_from(next_state.name, next_state.definition, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Snapshot _snapshot() =>
|
||||
new Snapshot(name, abbreviation, algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()), is_default, is_favorite);
|
||||
|
||||
struct Snapshot : IEquatable<Snapshot> {
|
||||
public readonly string name, abbreviation;
|
||||
public readonly CustomOrderDefinitionImmutable definition;
|
||||
public readonly bool is_default, is_favorite;
|
||||
|
||||
public Snapshot(string name, string abbreviation, CustomOrderDefinitionImmutable definition, bool is_default, bool is_favorite) {
|
||||
this.name = name;
|
||||
this.abbreviation = abbreviation;
|
||||
this.definition = definition;
|
||||
this.is_default = is_default;
|
||||
this.is_favorite = is_favorite;
|
||||
}
|
||||
|
||||
public bool Equals(Snapshot other) =>
|
||||
name == other.name &&
|
||||
abbreviation == other.abbreviation &&
|
||||
is_default == other.is_default &&
|
||||
is_favorite == other.is_favorite &&
|
||||
OrderDataHelper.EqualsHelpers.equals(definition, other.definition);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
EqualityComparer.safe_aggregate_hash(name),
|
||||
EqualityComparer.safe_aggregate_hash(abbreviation),
|
||||
is_default.GetHashCode(),
|
||||
is_favorite.GetHashCode(),
|
||||
OrderDataHelper.EqualsHelpers.custom_order_def_compareri.GetHashCode(definition));
|
||||
|
||||
public static bool operator ==(Snapshot a, Snapshot b) => a.Equals(b);
|
||||
public static bool operator !=(Snapshot a, Snapshot b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
public class CustomTemplatePlaceholderVM : AlgoDefinitionTemplateVM {
|
||||
const string _custom_name = "(Custom)";
|
||||
|
||||
public CustomTemplatePlaceholderVM(AlgoDefinitionVM owner, OrdersModel model, CustomOrderDefinitionImmutable definition)
|
||||
: base(owner, model, new AlgoData(definition, _custom_name, new AlgoKey(Guid.NewGuid())), AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues) {
|
||||
_edit_stack.reset(_snapshot());
|
||||
}
|
||||
|
||||
public override bool is_custom_placeholder => true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
@@ -6,6 +6,7 @@ using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
@@ -17,6 +18,7 @@ using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using VisualStateNames = mbts.ui.spark.AlgoStudioView.VisualStateNames;
|
||||
|
||||
@@ -34,27 +36,40 @@ namespace mbts.spark {
|
||||
public static readonly CustomOrderDefinitionImmutable new_algo_def = OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg);
|
||||
readonly OrdersModel _model;
|
||||
readonly VisualStateModel _edit_pane_vs_model = new VisualStateModel(), _tree_view_vs_model = new VisualStateModel();
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey> _templates = new ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey>(t => t.key);
|
||||
readonly EditStack<Snapshot> _edit_stack = new EditStack<Snapshot>();
|
||||
readonly VersionContainer<Snapshot> _version;
|
||||
readonly Property<bool> _is_expanded, _is_selected;
|
||||
readonly Property<IAlgoVM> _algo_root, _visible_algo_root;
|
||||
readonly Property<AlgoDefinitionTemplateVM> _selected_template;
|
||||
readonly DelegateProperty<bool> _is_favorite, _is_dirty, _can_undo, _can_redo;
|
||||
readonly Property<string> _name, _abbreviation;
|
||||
readonly ObservableKeyedCollection<IAlgoVM, AlgoKey> _algo_roots = new ObservableKeyedCollection<IAlgoVM, AlgoKey>(vm => vm.id);
|
||||
readonly ObservableAppendCollection<AlgoDefinitionTemplateVM> _templates_with_custom;
|
||||
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;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
mbts.ui.spark.AlgoContentTemplateSelector.OrderKind IAlgoSelector.order_kind =>
|
||||
new mbts.ui.spark.AlgoContentTemplateSelector.OrderKind(string.Empty, string.Empty);
|
||||
|
||||
int? _favorite_index;
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
AlgoDefinitionMetadata _metadata;
|
||||
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
|
||||
bool _connected;
|
||||
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
|
||||
new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
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, AlgoData.create_unpersisted(def, name), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
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, AlgoEditingPermissions init_permissions) {
|
||||
// init core data
|
||||
@@ -66,8 +81,20 @@ namespace mbts.spark {
|
||||
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)));
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
var conf = _model.algo_model.ui_configuration;
|
||||
@@ -75,80 +102,87 @@ namespace mbts.spark {
|
||||
_name = backing(nameof(name), def_data.name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
_selected_format = backing(nameof(selected_format), init_format);
|
||||
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
|
||||
_is_favorite = backing(nameof(is_favorite), () => _favorite_index.HasValue);
|
||||
_is_expanded = backing(nameof(is_expanded), true);
|
||||
_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 : AlgoData.from_custom_def(def_data.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)));
|
||||
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
|
||||
// only composite/studio algos can change root
|
||||
if (key.mode == AlgoKey.Mode.Factory || key.mode == AlgoKey.Mode.Dynamic) {
|
||||
_algo_root.Changing += (oldval, newval) =>
|
||||
Log.assert(newval != null && newval.id == key);
|
||||
}
|
||||
_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));
|
||||
_can_redo.Changed += on_can_redo_changed;
|
||||
_can_undo.Changed += on_can_undo_changed;
|
||||
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
|
||||
// load initial state
|
||||
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
||||
// - this behavior is configurable in 'AlgoStudioOptions' in UI.proto (01/28/2018)
|
||||
resources.Add(_algo_root.observe_current(true).Subscribe(_visible_algo_root.set));
|
||||
// set hooks/subscriptions
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
resources.AddRange(
|
||||
custom_badge_color.observe_nullable(true).Subscribe(v => Set(ref _badge_fill, (v ?? algo_root.safe_get_badge_color()).to_brush(), nameof(badge_fill))),
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||
vm == null ? null :
|
||||
Disposable.Create(
|
||||
vm.subscribe_is_dirty().Subscribe(v => {
|
||||
if (!v) {
|
||||
_version.increment_version();
|
||||
_on_committed_changed();
|
||||
}
|
||||
}),
|
||||
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||
Disposable.Create(() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}));
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_metadata = new AlgoDefinitionMetadata(key);
|
||||
resources.Add(model.algo_model.metadata_service.subscribe_metadata(key).OnDispatcher().Subscribe(_on_metadata));
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, conf.can_modify_definition_name ? VisualStateNames.Name.Active : VisualStateNames.Name.CannotRenameDefinition);
|
||||
break;
|
||||
case AlgoKey.Mode.Factory:
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
init_async();
|
||||
async void init_async() {
|
||||
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
||||
// - this behavior is configurable in 'AlgoStudioOptions' in UI.proto (01/28/2018)
|
||||
if (_model.algo_model.templates_enabled()) {
|
||||
resources.Add(_selected_template.observe_current(true).Subscribe(t => _visible_algo_root.set(t?.algo_root)));
|
||||
} else {
|
||||
resources.Add(_algo_root.observe_current(true).Subscribe(_visible_algo_root.set));
|
||||
}
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeEach(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 = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
// init tree view item visual state
|
||||
_tree_view_vs_model.set_state(VisualStateNames.TreeViewBadge.GroupName, conf.show_badges_in_tree_view ? VisualStateNames.TreeViewBadge.Active : VisualStateNames.TreeViewBadge.Inactive);
|
||||
// set hooks/subscriptions
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
resources.AddRange(
|
||||
custom_badge_color.observe_nullable(true).Subscribe(v => Set(ref _badge_fill, (v ?? algo_root.safe_get_badge_color()).to_brush(), nameof(badge_fill))),
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||
vm == null ? null :
|
||||
Disposable.Create(
|
||||
vm.subscribe_is_dirty().Subscribe(v => {
|
||||
if (!v) {
|
||||
_version.increment_version();
|
||||
_on_committed_changed();
|
||||
}
|
||||
}),
|
||||
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||
ObservableAutoWrapper.connect_item_hooks(templates, t => new CompositeDisposable(_any_templates_dirty_monitor.join(t), t), true),
|
||||
Disposable.Create(() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}));
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_metadata = new AlgoDefinitionMetadata(key);
|
||||
resources.Add(_template_update_buffer.OnDispatcher().Subscribe(_on_templates));
|
||||
resources.Add(model.algo_model.metadata_service.subscribe_metadata(key).OnDispatcher().Subscribe(_on_metadata));
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, conf.can_modify_definition_name ? VisualStateNames.Name.Active : VisualStateNames.Name.CannotRenameDefinition);
|
||||
break;
|
||||
case AlgoKey.Mode.Factory:
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeEach(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)));
|
||||
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
|
||||
_dispose = resources;
|
||||
algo_root.load_from(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
_on_committed_changed();
|
||||
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)));
|
||||
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
|
||||
_dispose = resources;
|
||||
await algo_root.load_from_async(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
_on_committed_changed();
|
||||
}
|
||||
}
|
||||
// local methods
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
@@ -175,6 +209,7 @@ namespace mbts.spark {
|
||||
public IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
|
||||
public IAlgoVM algo_root { get => _algo_root.Value; set => _algo_root.set(value); }
|
||||
public IAlgoVM visible_algo_root => _visible_algo_root.Value;
|
||||
public AlgoDefinitionTemplateVM selected_template { get => _selected_template.Value; set => _selected_template.set(value); }
|
||||
public int? favorite_index {
|
||||
get => _favorite_index;
|
||||
set {
|
||||
@@ -187,12 +222,34 @@ namespace mbts.spark {
|
||||
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool is_factory_definition => key.mode == AlgoKey.Mode.Factory;
|
||||
public AlgoDisplayGroup group => key.display_group();
|
||||
public AlgoDisplayGroup group {
|
||||
get {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (algo_root != null)
|
||||
return algo_root.group;
|
||||
break;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
}
|
||||
return AlgoDisplayGroup.BTS;
|
||||
}
|
||||
}
|
||||
public bool can_redo => _can_redo.Value;
|
||||
public bool can_undo => _can_undo.Value;
|
||||
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
|
||||
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
|
||||
public Brush badge_fill => _badge_fill;
|
||||
public ObservableCollection<AlgoDefinitionTemplateVM> templates => _templates;
|
||||
/// <see cref="templates_with_custom"/> has the same elements as <see cref="templates"/>, plus the custom-placeholder-item, which is always at index 0.
|
||||
/// The custom-placeholder supports legacy behavior in the ladder/ticket algo popup by allowing the user to manipulate the algo-structure (e.g. change an iceberg-limit order to iceberg-lean-cover-offset etc.).
|
||||
/// However, the custom-placeholder item cannot be used to create/save algo-templates.
|
||||
/// Algo-templates are just a set of values used to populate a concrete algo-structure; the 'structures' can be created in the AlgoStudio (<see cref="AlgoStudioWindow"/>).
|
||||
/// If we were to strictly follow this paradigm, the user would no longer be able to freely change the algo-structure in the ladder/ticket popup (they would be forced to open up a new <see cref="AlgoStudioWindow"/>, and we know how much they hate more clicks!).
|
||||
/// Some users might consider this a regression. The custom-placeholder-item addresses that concern.
|
||||
public ObservableAppendCollection<AlgoDefinitionTemplateVM> templates_with_custom => _templates_with_custom;
|
||||
public ListCollectionView templates_view { get; }
|
||||
// format
|
||||
public AlgoFormats selected_format { get => _selected_format.Value; set => _selected_format.set(value); }
|
||||
public ObservableCollection<AlgoFormats> formats { get; } = new ObservableCollection<AlgoFormats> { AlgoFormats.Exchange, AlgoFormats.Spreader, AlgoFormats.Exchange | AlgoFormats.Spreader };
|
||||
@@ -267,7 +324,24 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
// idempotent
|
||||
public void prepend_custom_template() {
|
||||
if (_templates_with_custom.appended_front.Count == 0)
|
||||
_templates_with_custom.insert_front(new AlgoDefinitionTemplateVM.CustomTemplatePlaceholderVM(this, _model, algo_root.definition));
|
||||
}
|
||||
|
||||
public AlgoDefinitionTemplateVM template_factory() =>
|
||||
new AlgoDefinitionTemplateVM(this, _model, algo_root.create_def_data().with(name: new_template_name)) {
|
||||
is_selected = true
|
||||
};
|
||||
|
||||
public IObservable<bool> subscribe_is_definition_dirty() => _is_dirty.observe_current(true);
|
||||
public IObservable<bool> subscribe_any_templates_dirty() => _any_templates_dirty_monitor;
|
||||
public IObservable<bool> subscribe_is_definition_or_any_templates_dirty() =>
|
||||
Observable.Merge(
|
||||
subscribe_is_definition_dirty().Select(v => v || _any_templates_dirty_monitor.is_above_threshold),
|
||||
subscribe_any_templates_dirty().Select(v => v || is_dirty))
|
||||
.DistinctUntilChanged();
|
||||
|
||||
public void undo() => _restore(_edit_stack.try_undo);
|
||||
public void redo() => _restore(_edit_stack.try_redo);
|
||||
@@ -276,9 +350,20 @@ namespace mbts.spark {
|
||||
_assign_from_data(_committed.Value);
|
||||
_on_committed_changed();
|
||||
}
|
||||
/// <summary>
|
||||
/// Subscribes to the templates source for this definition if it has been persisted (subscription requires a persistant id); does nothing if this definition has not been persisted, or if it is already subscribed (idempotent).
|
||||
/// </summary>
|
||||
public void connect() {
|
||||
if (!_connected) {
|
||||
_connected = true;
|
||||
concat_disposable(_model.algo_model.subscribe_definition_templates(key).OnDispatcher().Subscribe(_template_update_buffer.OnNext));
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
|
||||
|
||||
public void load_def(CustomOrderDefinitionImmutable def) {
|
||||
var key = AlgoKey.from_custom_def(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)) {
|
||||
@@ -302,7 +387,7 @@ namespace mbts.spark {
|
||||
case 1: load_def(def_list[0]); break;
|
||||
default:
|
||||
var def = def_list[0];
|
||||
var key = AlgoKey.from_custom_def(def);
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_many(def_list);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
@@ -332,7 +417,7 @@ namespace mbts.spark {
|
||||
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
|
||||
|
||||
public AlgoData create_algo_def() =>
|
||||
AlgoData.create(algo_root == null ? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()) : algo_root.definition, name, key);
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
|
||||
public AlgoDefinitionMetadata create_metadata() =>
|
||||
_metadata.with(abbreviation: abbreviation, badge_color: new NullableColorProto(custom_badge_color.get_nullable()));
|
||||
@@ -361,7 +446,7 @@ namespace mbts.spark {
|
||||
|
||||
void _assign_from_data(string name, CustomOrderDefinitionImmutable definition) {
|
||||
this.name = name;
|
||||
algo_root = _algo_roots.GetOrAdd(AlgoKey.from_custom_def(definition), _ => _mk_algo_root(definition));
|
||||
algo_root = _algo_roots.GetOrAdd(OrderDataHelper.get_shallow_factory_key(definition), _ => _mk_algo_root(definition));
|
||||
algo_root.load_from(definition);
|
||||
}
|
||||
|
||||
@@ -376,12 +461,18 @@ namespace mbts.spark {
|
||||
public Snapshot current_value => _version.value;
|
||||
|
||||
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
|
||||
OrderTypeVMFactory.create(AlgoData.from_custom_def(definition), is_expanded: true);
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
|
||||
|
||||
void _on_metadata(AlgoDefinitionMetadata metadata) {
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
_abbreviation.set(metadata.abbreviation);
|
||||
custom_badge_color.set_nullable(metadata.badge_color);
|
||||
foreach (var t in templates) {
|
||||
if (t.key.mode == AlgoKey.Mode.Persisted)
|
||||
t.on_meta(t.key == metadata.default_template, metadata.favorite_templates.Contains(t.key));
|
||||
else
|
||||
t.on_meta(false, false);
|
||||
}
|
||||
_version.increment_version();
|
||||
if (_edit_stack.size == 1 && _edit_stack.pos == 1) {
|
||||
// this is a hack to account for the metadata being part of the committed state,
|
||||
@@ -394,6 +485,22 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _on_templates(TableUpdate<AlgoTemplateData, AlgoKey> table) {
|
||||
foreach (var rec in table.removed)
|
||||
_templates.Remove(rec.key);
|
||||
foreach (var rec in table.changed)
|
||||
_templates[rec.key].load_from(rec);
|
||||
foreach (var rec in table.added) {
|
||||
if (_templates.TryGetValue(rec.key, out AlgoDefinitionTemplateVM tmp)) {
|
||||
tmp.load_from(rec);
|
||||
} else {
|
||||
tmp = new AlgoDefinitionTemplateVM(this, _model, new AlgoData(rec.value, rec.name, rec.key));
|
||||
tmp.on_meta(tmp.key == _metadata.default_template, _metadata.favorite_templates.Contains(tmp.key));
|
||||
_templates.Add(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_undo_redo() {
|
||||
_can_undo.refresh();
|
||||
_can_redo.refresh();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
@@ -13,7 +14,6 @@ using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
@@ -37,30 +37,29 @@ namespace mbts.spark {
|
||||
});
|
||||
}
|
||||
|
||||
class AlgoStudioVM : DisposableViewModelBase {
|
||||
class AlgoStudioVM : DisposableViewModelBase, IHandleSelectedItemChanged {
|
||||
static readonly AlgoFormats _studio_formats = AlgoFormats.Exchange;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<AlgoDefinitionVM> _selected_definition;
|
||||
readonly Property<AlgoDefinitionTemplateVM> _selected_template;
|
||||
readonly Property<IAlgoVM> _editable_content;
|
||||
readonly DelegateProperty<string> _save_cmd_tooltip, _revert_cmd_tooltip;
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly ListCollectionView<AlgoDefinitionVM> _definitions_view;
|
||||
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_update_buffer;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionVM> _any_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionVM>(d => d.subscribe_is_definition_dirty());
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionVM> _any_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionVM>(d => d.subscribe_is_definition_or_any_templates_dirty());
|
||||
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _persisted_meta = new Dictionary<AlgoKey, AlgoDefinitionMetadata>();
|
||||
|
||||
public AlgoStudioVM(OrdersModel model) {
|
||||
_model = model;
|
||||
_selected_definition = backing<AlgoDefinitionVM>(nameof(selected_definition));
|
||||
_editable_content = backing(nameof(editable_content), subscribe_definition().SelectManyInSeries(def => def?.observe_algo_root()).OnDispatcher());
|
||||
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
|
||||
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).OnDispatcher());
|
||||
_definitions_update_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(def => def.key);
|
||||
_definitions_view = new ListCollectionView<AlgoDefinitionVM>(definitions) {
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
|
||||
};
|
||||
definitions_view.Filter = d => !(d.key.mode == AlgoKey.Mode.Persisted && _persisted_meta.TryGetValue(d.key, out AlgoDefinitionMetadata meta) && meta.is_hidden);
|
||||
// add/clone
|
||||
add_definition_cmd = new DelegateCommand(add_definition);
|
||||
clone_selected_definition_cmd = new DelegateCommand(clone_selected_definition);
|
||||
// save
|
||||
var save_cmd = new ObservantAsyncCommand<IAlgoStudioContentVM>(save_async);
|
||||
this.save_cmd = save_cmd;
|
||||
@@ -72,18 +71,13 @@ namespace mbts.spark {
|
||||
//
|
||||
_dispose = new CompositeDisposable(
|
||||
_model.algo_model.definitions.Subscribe(_definitions_update_buffer),
|
||||
_definitions_update_buffer.OnDispatcher().Subscribe(u => {
|
||||
var orig_selected_def = selected_definition;
|
||||
AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, _studio_formats));
|
||||
if (selected_definition == null)
|
||||
selected_definition = definitions.FirstOrDefault();
|
||||
}),
|
||||
_definitions_update_buffer.OnDispatcher().Subscribe(u => AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, true, _studio_formats))),
|
||||
_model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(meta => {
|
||||
_persisted_meta = meta;
|
||||
definitions_view.Refresh();
|
||||
}),
|
||||
ObservableAutoWrapper.connect(subscribe_definition().Select(def => def?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
ObservableAutoWrapper.connect(observe_selected().Select(t => t.template?.edit_pane_visual_states ?? t.definition?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
|
||||
_save_cmd_tooltip.refresh();
|
||||
_revert_cmd_tooltip.refresh();
|
||||
@@ -91,64 +85,77 @@ namespace mbts.spark {
|
||||
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
|
||||
// local methods
|
||||
string get_revert_tooltip() {
|
||||
var def = selected_definition;
|
||||
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
|
||||
(def.is_dirty ? $"Revert changes to '{def.name}' templates" : "No changes to revert") :
|
||||
null;
|
||||
}
|
||||
async Task save_async(IAlgoStudioContentVM content) {
|
||||
if (content is AlgoDefinitionVM def)
|
||||
await save_definition(def);
|
||||
}
|
||||
string get_save_tooltip() {
|
||||
var def = selected_definition;
|
||||
if (def != null) {
|
||||
if (content_source is AlgoDefinitionTemplateVM tmp) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Persisted: return tmp.is_dirty ? $"Revert changes to '{tmp.name}'" : "No changes to revert";
|
||||
}
|
||||
} else if (content_source is AlgoDefinitionVM def) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}' templates" : "No changes to save";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Revert changes to '{def.name}' templates" : "No changes to revert";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void add_definition() =>
|
||||
add_definition_common(AlgoDefinitionVM.new_algo_def, AlgoDefinitionVM.new_algo_name);
|
||||
|
||||
void clone_selected_definition() {
|
||||
if (selected_definition == null || selected_definition.algo_root == null) {
|
||||
add_definition();
|
||||
} else {
|
||||
add_definition_common(
|
||||
selected_definition.algo_root.definition,
|
||||
string.IsNullOrEmpty(selected_definition.name) ? AlgoDefinitionVM.new_algo_name : $"{selected_definition.name} - Copy");
|
||||
async Task save_async(IAlgoStudioContentVM content) {
|
||||
if (content is AlgoDefinitionTemplateVM tmp) {
|
||||
await save_template(tmp);
|
||||
} else if (content is AlgoDefinitionVM def) {
|
||||
await save_definition(def);
|
||||
}
|
||||
}
|
||||
|
||||
void add_definition_common(CustomOrderDefinitionImmutable def, string name) {
|
||||
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats);
|
||||
_definitions.Add(new_def);
|
||||
selected_definition = new_def;
|
||||
request_view_focus_name?.Invoke(new_def);
|
||||
string get_save_tooltip() {
|
||||
if (content_source is AlgoDefinitionTemplateVM tmp) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return tmp.is_dirty ? $"Save '{tmp.name}'" : $"Cannot Save '{tmp.name}'";
|
||||
case AlgoKey.Mode.Persisted: return tmp.is_dirty ? $"Save changes to '{tmp.name}'" : "No changes to save";
|
||||
}
|
||||
} else if (content_source is AlgoDefinitionVM def) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Save changes to '{def.name}' templates" : "No changes to save";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<AlgoDefinitionVM> request_view_focus_name;
|
||||
event Action<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)> _selected_changed;
|
||||
|
||||
public ObservableCollection<AlgoDefinitionVM> definitions => _definitions;
|
||||
public ObservableCollection<string> content_visual_states { get; } = new ObservableCollection<string>();
|
||||
public AlgoDefinitionVM selected_definition { get => _selected_definition.Value; set => _selected_definition.set(value); }
|
||||
public AlgoDefinitionTemplateVM selected_template { get => _selected_template.Value; set => _selected_template.set(value); }
|
||||
public IAlgoStudioContentVM content_source => (IAlgoStudioContentVM)selected_template ?? selected_definition;
|
||||
public IAlgoVM editable_content => _editable_content.Value;
|
||||
public ICollectionView<AlgoDefinitionVM> definitions_view => _definitions_view;
|
||||
public ICommand add_definition_cmd { get; }
|
||||
public ICommand clone_selected_definition_cmd { get; }
|
||||
public ICommand save_cmd { get; }
|
||||
public ICommand revert_cmd { get; }
|
||||
public string save_cmd_tooltip => _save_cmd_tooltip.Value;
|
||||
public string revert_cmd_tooltip => _revert_cmd_tooltip.Value;
|
||||
|
||||
public void add_new_definition() =>
|
||||
_add_new_definition(AlgoDefinitionVM.new_algo_def, AlgoDefinitionVM.new_algo_name);
|
||||
|
||||
public void add_new_definition_clone(AlgoDefinitionVM src_definition) {
|
||||
if (src_definition == null || src_definition.algo_root == null) {
|
||||
add_new_definition();
|
||||
} else {
|
||||
_add_new_definition(src_definition.algo_root.definition, string.IsNullOrEmpty(src_definition.name) ? AlgoDefinitionVM.new_algo_name : $"{src_definition.name} - Copy");
|
||||
}
|
||||
}
|
||||
|
||||
void _add_new_definition(CustomOrderDefinitionImmutable def, string name) =>
|
||||
_definitions.Add(AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats));
|
||||
|
||||
public StartupData create() => new StartupData();
|
||||
public void load_from(StartupData state) { }
|
||||
public IObservable<AlgoDefinitionVM> subscribe_definition() => _selected_definition.observe_current(true);
|
||||
public IObservable<AlgoDefinitionTemplateVM> subscribe_template() => _selected_template.observe_current(true);
|
||||
public IObservable<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)> observe_selected() =>
|
||||
Observable.Create<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)>(o => {
|
||||
_selected_changed += o.OnNext;
|
||||
return Disposable.Create(() => _selected_changed -= o.OnNext);
|
||||
});
|
||||
public IObservable<bool> subscribe_dirty() => _any_dirty_monitor;
|
||||
|
||||
public async Task remove_definition(AlgoDefinitionVM definition) {
|
||||
@@ -156,43 +163,87 @@ namespace mbts.spark {
|
||||
await _model.algo_model.remove_definition(definition.key);
|
||||
} else {
|
||||
// This is a new definition, which hasn't been saved/persisted yet. No need to call the server.
|
||||
definition.templates.Clear();
|
||||
definitions.Remove(definition);
|
||||
}
|
||||
}
|
||||
|
||||
public Task save_definition(AlgoDefinitionVM definition) =>
|
||||
_save(new[] { definition });
|
||||
|
||||
public Task save_all() =>
|
||||
_save(definitions);
|
||||
|
||||
async Task _save(IEnumerable<AlgoDefinitionVM> definitions) {
|
||||
var orig_selected = selected_definition;
|
||||
var orig_selected_index = orig_selected == null ? -1 : this.definitions.IndexOf(orig_selected);
|
||||
using (_definitions_update_buffer.buffer_updates()) {
|
||||
var def_list = definitions as IList<AlgoDefinitionVM> ?? definitions.ToList();
|
||||
await Task.WhenAll(definitions.Select(async d => {
|
||||
var def = await _model.algo_model.save_definition(d.create_algo_def());
|
||||
if (def.HasValue) {
|
||||
// don't forget to save metadata
|
||||
await _model.algo_model.metadata_service.update_metadata(def.Value.key, _ => d.create_metadata().with(def.Value.key));
|
||||
}
|
||||
return def;
|
||||
})).ContinueOnDispatcher(t => {
|
||||
if (t.Status != TaskStatus.RanToCompletion) {
|
||||
Log.Ui.debug_fatal($"Algo Studio error saving definitions: unexpected Task.Status '{t.Status}'.");
|
||||
return;
|
||||
}
|
||||
var persisted_data = t.Result;
|
||||
Log.assert(persisted_data.Length == def_list.Count);
|
||||
for (int i = 0; i < persisted_data.Length; i++) {
|
||||
if (!def_list[i].key.is_persisted && persisted_data[i].HasValue)
|
||||
_definitions.Replace(def_list[i].key, def_list[i].to_persisted(persisted_data[i].Value));
|
||||
}
|
||||
if (selected_definition != orig_selected && orig_selected_index >= 0 && orig_selected_index < this.definitions.Count)
|
||||
selected_definition = this.definitions[orig_selected_index];
|
||||
});
|
||||
public async Task remove_template(AlgoDefinitionTemplateVM template) {
|
||||
if (template.key.mode == AlgoKey.Mode.Persisted) {
|
||||
await _model.algo_model.remove_template(template.key);
|
||||
} else {
|
||||
// This is a new template, which hasn't been saved/persisted yet. No need to call the server.
|
||||
template.owner.templates.Remove(template);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task save_definition(AlgoDefinitionVM definition) {
|
||||
using (_save_op()) {
|
||||
var newly_persisted_keys = await AlgoPersistenceHelper.persist_definition(_model, _definitions, definition);
|
||||
AlgoKey pkey;
|
||||
switch (definition.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
if (!newly_persisted_keys.TryGetValue(definition.key, out pkey))
|
||||
return;
|
||||
break;
|
||||
case AlgoKey.Mode.Persisted:
|
||||
pkey = definition.key;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
await _model.algo_model.metadata_service.update_metadata(pkey, _ => definition.create_metadata().with(id: pkey));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task save_template(AlgoDefinitionTemplateVM template) {
|
||||
using (_save_op())
|
||||
await AlgoPersistenceHelper.persist_template(_model, _definitions, template);
|
||||
}
|
||||
|
||||
public async Task save_all() =>
|
||||
await _save(definitions.WrapSelector(def => (def, def.templates.AsList()), t => t.def));
|
||||
|
||||
async Task _save(IList<(AlgoDefinitionVM definition, IList<AlgoDefinitionTemplateVM> templates)> definition_templates) {
|
||||
using (_save_op())
|
||||
await AlgoPersistenceHelper.persist(_model, definition_templates);
|
||||
}
|
||||
|
||||
IDisposable _save_op() {
|
||||
int? def_index = selected_definition == null ? (int?)null : _definitions.IndexOf(selected_definition);
|
||||
int? tmp_index = selected_template == null ? (int?)null : selected_definition.templates.IndexOf(selected_template);
|
||||
return new CompositeDisposable(_definitions_update_buffer.buffer_updates()) {
|
||||
() => {
|
||||
if (def_index.HasValue) {
|
||||
var def = _definitions[def_index.Value];
|
||||
if (tmp_index.HasValue) {
|
||||
var tmp = def.templates[tmp_index.Value];
|
||||
tmp.is_selected = true;
|
||||
} else def.is_selected = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Despite the existence of these properties: <see cref="AlgoDefinitionVM.is_selected"/>, and <see cref="AlgoDefinitionTemplateVM.is_selected"/>,
|
||||
/// <see cref="AlgoStudioVM"/> still implements <see cref="IHandleSelectedItemChanged"/> to update the properties that depend on the <see cref="System.Windows.Controls.TreeView.SelectedItem"/>.
|
||||
/// [John]I am going to Uganda in 3 days (today is 1/3/2019), and decided against removing this legacy-dependency at the last minute.
|
||||
/// Fwiw, <see cref="AlgoDefinitionVM.is_selected"/> and <see cref="AlgoDefinitionTemplateVM.is_selected"/> are bound to <see cref="System.Windows.Controls.TreeViewItem.IsSelected"/>.
|
||||
/// They serve to prevent a quirky default behavior in TreeView: double clicking any TreeViewItem collapses the entire TreeView.
|
||||
/// This behavior wasn't just quirky either, it actually conflicted with the double-click-to-set-template-as-default behavior.
|
||||
/// (for details, <see cref="mbts.ui.spark.AlgoStudioView.EnableToggleDefaultOnDoubleClickProperty"/>)
|
||||
void IHandleSelectedItemChanged.on_selected_item_changed(object old_value, object new_value) {
|
||||
if (new_value is AlgoDefinitionVM def) {
|
||||
selected_definition = def;
|
||||
selected_template = null;
|
||||
} else if (new_value is AlgoDefinitionTemplateVM templ) {
|
||||
selected_definition = templ.owner;
|
||||
selected_template = templ;
|
||||
} else {
|
||||
selected_definition = null;
|
||||
selected_template = null;
|
||||
}
|
||||
_selected_changed?.Invoke((selected_definition, selected_template));
|
||||
RaisePropertyChanged(nameof(content_source));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,7 @@ namespace mbts.spark {
|
||||
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
|
||||
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
|
||||
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
|
||||
public Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition) => _algo_root is IAlgoVM vm ? vm.load_from_async(definition) : Task.FromResult<CustomOrderDefinitionImmutable?>(null);
|
||||
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
|
||||
|
||||
IDisposable _set_hooks(AlgoDefinitionVM vm) {
|
||||
|
||||
-3
@@ -513,9 +513,6 @@
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
IsEnabled="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}, Converter={StaticResource NotConverter}}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
/>
|
||||
</AdornerDecorator>
|
||||
</DataTemplate>
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ using System.Reactive.Disposables;
|
||||
namespace mbts.spark {
|
||||
static class AlgoParamRowsHelper {
|
||||
public static IDisposable connect(AlgoDefinitionVM algo, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) =>
|
||||
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().OnDispatcher().SubscribeEach(a => {
|
||||
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().SubscribeEach(a => {
|
||||
rows.clear_collections();
|
||||
if (a == null)
|
||||
return Disposable.empty;
|
||||
@@ -77,7 +77,7 @@ namespace mbts.spark {
|
||||
include_algo_type_rows ?
|
||||
(ObservableCollection<AlgoParamRowVM>)new ObservableAppendCollection<AlgoParamRowVM>(child_rows, selector_wrapper) :
|
||||
child_rows,
|
||||
s.subscribe_selected_algo().OnDispatcher().SubscribeEach(a => {
|
||||
s.subscribe_selected_algo().SubscribeEach(a => {
|
||||
s.value = a;
|
||||
child_rows.clear_collections();
|
||||
return _wrap_rows(a, selector_wrapper, child_rows, include_algo_type_rows);
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ namespace mbts.ui.spark {
|
||||
class AlgoParamTemplateSelector : DataTemplateSelector {
|
||||
public static readonly Type generic_algo_param_itype = typeof(IAlgoParamVM<>);
|
||||
|
||||
KeyedCollection<TemplateSelectorKeyValue, string> _keyed_templates = new KeyedCollection<TemplateSelectorKeyValue, string>(tsk => tsk.Key.ToString());
|
||||
KeyedCollection<TemplateSelectorKeyValue, string> _keyed_templates = new KeyedCollection<TemplateSelectorKeyValue, string>(tsk => tsk.Key);
|
||||
|
||||
public DataTemplate BoolTemplate { get; set; }
|
||||
public DataTemplate NullableBoolTemplate { get; set; }
|
||||
|
||||
@@ -68,51 +68,13 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.symbol)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
DefaultColumnWidths.Symbol);
|
||||
// algo items view (common resources)
|
||||
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
|
||||
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
|
||||
// order type
|
||||
var order_type_filter = DiscreteValuesFilterVM.CreateTableUpdate<FillTuple, AlgoDataVM, AlgoID, OrderType[]>(
|
||||
ft => new AlgoDataVM(ft.algo ?? AlgoData.empty),
|
||||
AlgoDataVM.id_fn,
|
||||
ots => Array.ConvertAll(ots ?? Array.Empty<OrderType>(), ot => AlgoID.from_algo_key(AlgoKey.from_known_type(ot))),
|
||||
ids => ids.empty_if_null().Select(AlgoID.to_algo_key).Where(k => k.mode == AlgoKey.Mode.Factory).Select(k => k.factory).ToArray(),
|
||||
out var order_type_observer,
|
||||
out var order_type_items_view);
|
||||
|
||||
order_type_items_view.CustomSort = algo_items_comp;
|
||||
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
|
||||
order_type_observer.OnNext(TableUpdate<AlgoDataVM, AlgoID>.mk_empty(AlgoDataVM.id_fn)
|
||||
.with_added(OrderDataHelper.all_order_types
|
||||
.Select(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable)
|
||||
.Select(AlgoData.from_custom_def)
|
||||
.Select(d => new AlgoDataVM(d))));
|
||||
|
||||
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
|
||||
add_col_expl(Columns.OrderType,
|
||||
new OrderTypeColumn<Columns>(Columns.OrderType, new Binding(nameof(Row.badge_fill)), new Binding(nameof(Row.display_name)), order_type_filter) {
|
||||
id = col_id(Columns.OrderType),
|
||||
sort_member_path = nameof(Row.order_type)
|
||||
}, order_type_filter);
|
||||
// root algo type
|
||||
var root_algo_type_filter = DiscreteValuesFilterVM.CreateTableUpdate<FillTuple, AlgoDataVM, AlgoID>(
|
||||
ft => new AlgoDataVM(ft.root_algo ?? AlgoData.empty),
|
||||
AlgoDataVM.id_fn,
|
||||
out var root_algo_type_observer,
|
||||
out var root_algo_type_items_view);
|
||||
|
||||
root_algo_type_items_view.CustomSort = algo_items_comp;
|
||||
root_algo_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
|
||||
|
||||
add_col_expl(Columns.RootAlgoType,
|
||||
new OrderTypeColumn<Columns>(
|
||||
Columns.RootAlgoType,
|
||||
new Binding(nameof(Row.root_algo_badge_color)) { Converter = new ui.converters.ColorToBrushConverter() },
|
||||
new Binding(nameof(Row.root_algo_display_name)),
|
||||
root_algo_type_filter,
|
||||
DefaultColumnHeaders.RootAlgoType) {
|
||||
id = col_id(Columns.RootAlgoType),
|
||||
sort_member_path = nameof(Row.root_algo_order_type)
|
||||
}, root_algo_type_filter);
|
||||
// side
|
||||
var side_filter = dv_filter(new[] { Side.Buy, Side.Sell }, r => r.fill.Side);
|
||||
add_col_expl(Columns.Side, new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side) }, side_filter);
|
||||
@@ -166,7 +128,6 @@ namespace mbts.spark {
|
||||
};
|
||||
_rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher().Subscribe(root_algo_type_observer),
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
// local methods
|
||||
@@ -214,8 +175,8 @@ namespace mbts.spark {
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
|
||||
if (!state.orders.is_null_or_empty()) {
|
||||
var orders_hash = new HashSet<uint>(state.orders);
|
||||
return model.on_fill_tuples_with_root.Filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
} else return model.on_fill_tuples_with_root;
|
||||
return model.on_fill_tuples.Filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
} else return model.on_fill_tuples;
|
||||
}
|
||||
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
|
||||
@@ -230,7 +191,6 @@ namespace mbts.spark {
|
||||
case Columns.FillTime: return wrap(ft => ft.fill.Timestamp.to_local_time());
|
||||
case Columns.OrderKey: return wrap(ft => ft.fill.OrderKey);
|
||||
case Columns.OrderType: return wrap_default(ft => ft.order?.order_type() ?? OrderType.None);
|
||||
case Columns.RootAlgoType: return wrap_default(ft => ft.root_algo.HasValue ? ft.root_algo.Value.value.CustomCase : OrderType.None);
|
||||
case Columns.Price: return wrap(ft => ft.fill.LastPrice);
|
||||
case Columns.Security: return wrap(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
case Columns.SecurityKey: return wrap(ft => ft.fill.SecurityKey);
|
||||
@@ -264,10 +224,7 @@ namespace mbts.spark {
|
||||
readonly OrdersModel _model;
|
||||
readonly FillImmutable _fill;
|
||||
readonly OrderRecordImmutable _order;
|
||||
AlgoData? _root_algo => fill_tuple.root_algo;
|
||||
AlgoDefinitionMetadata? _root_algo_meta => fill_tuple.root_algo_meta;
|
||||
Brush _badge_fill;
|
||||
Color? _root_algo_badge_color;
|
||||
OrderMetadata _order_metadata;
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) :
|
||||
@@ -282,7 +239,6 @@ namespace mbts.spark {
|
||||
public DateTime fill_time => _fill.Timestamp.to_local_time();
|
||||
public TimeInForce time_in_force => _order.time_in_force();
|
||||
public OrderType order_type => _order.order_type();
|
||||
//public OrderType root_algo_type => _or
|
||||
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
|
||||
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
|
||||
@@ -296,12 +252,6 @@ namespace mbts.spark {
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
|
||||
// Root Algo
|
||||
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
|
||||
public OrderMetadata root_algo_order_metadata => _model.factory.get_metadata(root_algo_order_type);
|
||||
public Color root_algo_badge_color => _root_algo_badge_color ?? (_root_algo_badge_color = OrderUtils.badge_color(_root_algo_meta, root_algo_order_metadata)).Value;
|
||||
public string root_algo_display_name => _root_algo?.name ?? root_algo_order_type.display_name();
|
||||
|
||||
public bool Equals(Row other) => other != null && other._fill.Equals(_fill) && other._order.Equals(_order);
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace mbts.ui.spark {
|
||||
Symbol,
|
||||
TimeInForce,
|
||||
OrderType,
|
||||
RootAlgoType,
|
||||
Side,
|
||||
Price,
|
||||
Size,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
using bts.api;
|
||||
using bts.settings;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.FillsView;
|
||||
|
||||
namespace bts.shell {
|
||||
@@ -30,7 +34,6 @@ namespace bts.shell {
|
||||
{ nameof(FillsVM.Row.fill_time), Columns.FillTime },
|
||||
{ nameof(FillsVM.Row.order_key), Columns.OrderKey },
|
||||
{ nameof(FillsVM.Row.order_type), Columns.OrderType },
|
||||
{ nameof(FillsVM.Row.root_algo_order_type), Columns.RootAlgoType },
|
||||
{ nameof(FillsVM.Row.price), Columns.Price },
|
||||
{ nameof(FillsVM.Row.security), Columns.Security },
|
||||
{ nameof(FillsVM.Row.security_key), Columns.SecurityKey },
|
||||
@@ -64,12 +67,5 @@ namespace bts.shell {
|
||||
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
|
||||
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
// migration rules
|
||||
[Export(typeof(IFillsGridSettingsMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : IFillsGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
settings.add_missing_columns(FillsSettings.DefaultGridSettings.columns, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,6 @@ namespace bts.shell.fills {
|
||||
[DIYGridColumnID(nameof(Columns.OrderType))]
|
||||
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
|
||||
|
||||
[ProtoMember(16)]
|
||||
[DIYGridColumnID(nameof(Columns.RootAlgoType))]
|
||||
public AlgoID[] root_algo_type { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
[DIYGridColumnID(nameof(Columns.Account))]
|
||||
public string[] account { get; set; }
|
||||
@@ -92,7 +88,6 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.exchange, ft => ft.exchange());
|
||||
pb.add_discrete_values(fs.time_in_force, ft => ft.time_in_force());
|
||||
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
|
||||
pb.add_discrete_values(fs.root_algo_type, ft => AlgoID.from_algo_key((ft.root_algo ?? AlgoData.empty).key));
|
||||
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
|
||||
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
|
||||
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
|
||||
|
||||
@@ -47,7 +47,6 @@ namespace bts.shell.fills {
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(col_id(Columns.RootAlgoType), DefaultColumnHeaders.RootAlgoType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
@@ -71,7 +70,6 @@ namespace bts.shell.fills {
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
|
||||
@@ -194,27 +194,20 @@ namespace mbts.spark {
|
||||
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
|
||||
[ProtoMember(2)]
|
||||
public readonly int column_count;
|
||||
[ProtoMember(3)]
|
||||
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
|
||||
|
||||
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos)
|
||||
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)),
|
||||
column_count,
|
||||
ArrayHelper.safe_convert(stickied_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted))) { }
|
||||
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count)
|
||||
: this(ArrayHelper.safe_convert(favorite_algos, k => k.persisted), column_count) { }
|
||||
|
||||
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
|
||||
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count) {
|
||||
_favorite_algos = favorite_algos;
|
||||
this.column_count = column_count;
|
||||
_stickied_algos = stickied_algos;
|
||||
}
|
||||
|
||||
public IReadOnlyList<AlgoKey> favorite_algos => ArrayHelper.safe_convert(_favorite_algos, k => k.to_algo_key());
|
||||
public IReadOnlyList<AlgoKey> stickied_algos => ArrayHelper.safe_convert(_stickied_algos, k => k.to_algo_key());
|
||||
|
||||
public bool Equals(LadderFavoriteAlgosSettings other) =>
|
||||
column_count == other.column_count &&
|
||||
favorite_algos.SequenceEqual(other.favorite_algos) &&
|
||||
stickied_algos.SequenceEqual(other.stickied_algos);
|
||||
favorite_algos.SequenceEqual(other.favorite_algos);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
EqualityComparer.equals_obj(this, obj);
|
||||
@@ -222,8 +215,7 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
column_count,
|
||||
EqualityComparer.aggregate_hash_limited(favorite_algos),
|
||||
EqualityComparer.aggregate_hash_limited(stickied_algos));
|
||||
EqualityComparer.aggregate_hash_limited(favorite_algos));
|
||||
|
||||
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
|
||||
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
|
||||
@@ -281,7 +273,7 @@ namespace mbts.spark {
|
||||
order_entry_panel_initial_width = _default_order_entry_panel_width;
|
||||
zoom_factor = _default_zoom_factor;
|
||||
use_defaults = true;
|
||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
|
||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2);
|
||||
show_warn_on_cross = _default_show_warn_on_cross;
|
||||
}
|
||||
|
||||
@@ -450,8 +442,6 @@ namespace mbts.spark {
|
||||
public bool account_position_visible { get; set; }
|
||||
[ProtoMember(50), DefaultValue(true)]
|
||||
public bool day_position_visible { get; set; }
|
||||
[ProtoMember(56)]
|
||||
public bool raw_day_position_visible { get; set; }
|
||||
[ProtoMember(37), DefaultValue(true)]
|
||||
public bool size_presets_visible { get; set; }
|
||||
[ProtoMember(38), DefaultValue(true)]
|
||||
@@ -572,7 +562,6 @@ namespace mbts.spark {
|
||||
order_type_visible == other.order_type_visible &&
|
||||
account_position_visible == other.account_position_visible &&
|
||||
day_position_visible == other.day_position_visible &&
|
||||
raw_day_position_visible == other.raw_day_position_visible &&
|
||||
size_presets_visible == other.size_presets_visible &&
|
||||
cancel_buys_visible == other.cancel_buys_visible &&
|
||||
cancel_sells_visible == other.cancel_sells_visible &&
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using bts.api;
|
||||
using bts.api;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -11,10 +12,10 @@ using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using LadderSettings = mbts.spark.LadderSettings;
|
||||
@@ -22,31 +23,28 @@ using LadderSettings = mbts.spark.LadderSettings;
|
||||
namespace bts.shell {
|
||||
class LadderWindowFactory : ShellWindowFactory<LadderSaveState> {
|
||||
const string _window_type = "ladder";
|
||||
const int _version_where_stickied_algos_introduced = 2;
|
||||
readonly LadderConfig _ladder_conf;
|
||||
readonly IBus _bus;
|
||||
readonly IWindowManager _wm;
|
||||
readonly MBOService _mbo;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IShellWindowThreadIDProvider _thread_id_provider;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
public LadderWindowFactory(
|
||||
IShellModule owner,
|
||||
IBus bus,
|
||||
IWindowManager wm,
|
||||
UserSettingsHub settings_hub,
|
||||
IShellModule owner,
|
||||
IBus bus,
|
||||
IWindowManager wm,
|
||||
UserSettingsHub settings_hub,
|
||||
OrdersModel orders_model,
|
||||
MBOService mbo)
|
||||
|
||||
: base(owner, "SparkLadder", 2) {
|
||||
: base(owner, "SparkLadder") {
|
||||
_bus = bus;
|
||||
_wm = wm;
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
|
||||
_thread_id_provider = ShellWindowThreadIDFactory.create("Ladder", _ladder_conf.ladders_on_threads);
|
||||
_mbo = mbo;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
@@ -63,16 +61,6 @@ namespace bts.shell {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override Task<IShellWindow> try_upgrade_state(Stream load_stream, int version) {
|
||||
var state = ProtoBuf.Serializer.DeserializeWithLengthPrefix<LadderSaveState>(load_stream, ProtoBuf.PrefixStyle.Fixed32);
|
||||
if (version < _version_where_stickied_algos_introduced) {
|
||||
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
|
||||
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
|
||||
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(favorite_algos_array, state.settings.favorite_algos.column_count, favorite_algos_array);
|
||||
}
|
||||
return load(state);
|
||||
}
|
||||
|
||||
LadderSaveState _get_state(LadderVM vm, uint id) {
|
||||
LadderSettings s = vm.get_save_state();
|
||||
if (id != 0) {
|
||||
@@ -80,7 +68,8 @@ namespace bts.shell {
|
||||
// profile was deleted
|
||||
id = 0;
|
||||
} else {
|
||||
if (!s.Equals(result.value)) {
|
||||
var curr_profile = _fit_to_sec(result.value, vm.security.ContractCase);
|
||||
if (!s.Equals(curr_profile)) {
|
||||
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
|
||||
id = 0;
|
||||
}
|
||||
@@ -89,8 +78,10 @@ namespace bts.shell {
|
||||
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
|
||||
}
|
||||
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings last_value, uint settings_id = 0) {
|
||||
int _ladder_count = 0;
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
|
||||
IDisposable active_settings_dialog = null;
|
||||
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
// on_shell_window_vm should behave like an event (do not set to null)
|
||||
Action<ShellWindowVM> on_shell_window_vm = _ => { };
|
||||
ShellWindowVM shell_window_vm = null;
|
||||
@@ -104,11 +95,12 @@ namespace bts.shell {
|
||||
lp => lp.id,
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, LadderSettings.sanitize(lp.value), lp.security_criteria))),
|
||||
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
|
||||
last_value?.sanitize();
|
||||
state_settings?.sanitize();
|
||||
/// When settings are adjusted outside of the settings dialog, the vm should raise the <see cref="LadderVM.settings_invalidated"/> event.
|
||||
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
|
||||
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
|
||||
bool initialized = false, needs_resync = false;
|
||||
LadderSettings last_value = fit_to_sec(state_settings);
|
||||
LadderVM vm = null;
|
||||
var settings_subject = new Subject<LadderSettings>();
|
||||
var settings_source = Observable.Create<LadderSettings>(o => {
|
||||
@@ -138,6 +130,9 @@ namespace bts.shell {
|
||||
lv.Resources[kv.Key] = kv.Value;
|
||||
return lv;
|
||||
// local methods
|
||||
LadderSettings fit_to_sec(LadderSettings s) =>
|
||||
_fit_to_sec(s, md.security.ContractCase);
|
||||
|
||||
void on_settings_invalidated() =>
|
||||
needs_resync = true;
|
||||
|
||||
@@ -164,7 +159,7 @@ namespace bts.shell {
|
||||
if (!last_value.Equals(vm.get_save_state())) {
|
||||
settings_id = 0;
|
||||
} else {
|
||||
settings_subject.OnNext(last_value = profile.value);
|
||||
settings_subject.OnNext(last_value = fit_to_sec(profile.value));
|
||||
}
|
||||
} else {
|
||||
// profile was deleted; keep the current settings and switch to the 'Local' profile.
|
||||
@@ -174,7 +169,7 @@ namespace bts.shell {
|
||||
initialized = true;
|
||||
if (settings_id != 0) {
|
||||
if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
last_value = profile.value;
|
||||
last_value = fit_to_sec(profile.value);
|
||||
} else {
|
||||
settings_id = 0;
|
||||
}
|
||||
@@ -184,13 +179,13 @@ namespace bts.shell {
|
||||
// TODO: Use a kind of 'match priority' to resolve conflicts when the security is included in more than 1 profile's security criteria.
|
||||
// In the meantime, simply use the first match.
|
||||
if (lp.security_criteria.includes(md.security)) {
|
||||
last_value = lp.value;
|
||||
last_value = fit_to_sec(lp.value);
|
||||
settings_id = lp.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (last_value == null)
|
||||
last_value = _settings_hub.current.ladder;
|
||||
last_value = fit_to_sec(_settings_hub.current.ladder);
|
||||
}
|
||||
settings_subject.OnNext(last_value);
|
||||
}
|
||||
@@ -206,7 +201,7 @@ namespace bts.shell {
|
||||
svm.load(s);
|
||||
return svm;
|
||||
},
|
||||
_ => _,
|
||||
fit_to_sec,
|
||||
vm.get_save_state(),
|
||||
UserSettings.FileExtensions.LADDER,
|
||||
settings_id);
|
||||
@@ -262,11 +257,12 @@ namespace bts.shell {
|
||||
Log.Ui.warn($"LadderSettingsDialog error {action_desc}: {error}.");
|
||||
}
|
||||
});
|
||||
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{thread_id:00}" : "";
|
||||
return shell_window = ShellWindow.create(
|
||||
module, md.security.ToString(), _window_type, view,
|
||||
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
|
||||
icon: Icons.Ladder,
|
||||
thread_id: _thread_id_provider.next_id(),
|
||||
thread_id: thread,
|
||||
vm => {
|
||||
shell_window_vm = vm;
|
||||
title_source.connect();
|
||||
@@ -287,6 +283,20 @@ namespace bts.shell {
|
||||
}
|
||||
}
|
||||
|
||||
LadderSettings _fit_to_sec(LadderSettings s, Security.ContractOneofCase sec_type) {
|
||||
if (s == null) return null;
|
||||
var algo_key = AlgoID.to_algo_key(s.default_algo, _orders_model.dynamic_helper.dynamic_type_index);
|
||||
if (algo_key.mode == AlgoKey.Mode.Factory) {
|
||||
var fitted_algo = OrderDataHelper.fit_to_type(algo_key.factory.structure.build_algo(_orders_model.dynamic_helper), sec_type, true);
|
||||
if (fitted_algo.HasValue) {
|
||||
// be sure to clone ('s' may be shared)
|
||||
s = SparkUtils.clone_proto(s);
|
||||
s.default_algo = AlgoID.from_custom_def(fitted_algo.Value);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
readonly struct LadderConfig {
|
||||
public readonly uint max_open_ladders;
|
||||
public readonly bool ladders_on_threads, open_algo_popup_on_select, use_algo_param_grid;
|
||||
@@ -311,4 +321,4 @@ namespace bts.shell {
|
||||
[ProtoMember(3)]
|
||||
public uint settings_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
readonly Property<double> _font_size, _grid_row_height;
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<Brush> _foreground, _background, _hoverbackground, _border;
|
||||
readonly Property<bool> _has_multiple_orders_at_same_level, _is_hedge_order;
|
||||
readonly Property<CustomOrderDefinition.CustomOneofCase> _algo_type;
|
||||
@@ -64,7 +64,6 @@ namespace mbts.spark {
|
||||
_font_weight = backing(nameof(font_weight), owner.settings, get_font_weight, get_font_weight(fs));
|
||||
_font_size = backing(nameof(font_size), owner.settings, get_font_size, get_font_size(fs));
|
||||
_foreground = backing(nameof(foreground), owner.settings, get_foreground, get_foreground(fs));
|
||||
_grid_row_height = backing(nameof(grid_row_height), owner.settings, get_row_height, get_row_height(fs));
|
||||
_background = backing(nameof(background), _color_bg_brush);
|
||||
_hoverbackground = backing(nameof(hoverbackground), _color_bg_brush);
|
||||
_border = backing(nameof(border), _color_bg_brush);
|
||||
@@ -76,7 +75,6 @@ namespace mbts.spark {
|
||||
FontWeight get_font_weight(LadderSettings s) => ui.utility.text.TextUtils.from_open_type_weight_safe(s.orders_column.font_weight);
|
||||
double get_font_size(LadderSettings s) => s.orders_column.font_size;
|
||||
Brush get_foreground(LadderSettings s) => s.orders_column.foreground.rgb_brush();
|
||||
double get_row_height(LadderSettings s) => s.row_height;
|
||||
//
|
||||
add_order(op);
|
||||
|
||||
@@ -248,7 +246,6 @@ namespace mbts.spark {
|
||||
public FontFamily font_family => _font_family.Value;
|
||||
public FontWeight font_weight => _font_weight.Value;
|
||||
public double font_size => _font_size.Value;
|
||||
public double grid_row_height => _grid_row_height.Value;
|
||||
public Brush foreground => _foreground.Value;
|
||||
public Brush background { get => _background.Value; private set => _background.Value = value; }
|
||||
public Brush hoverbackground { get => _hoverbackground.Value; private set => _hoverbackground.Value = value; }
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.persist.algo;
|
||||
using bts.shell.orderbook;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
@@ -72,7 +71,6 @@ namespace mbts.spark {
|
||||
_order_type_visibility = new OrderEntryPanelItemVisibilityVM("Order Type"),
|
||||
_account_position_visibility = new OrderEntryPanelItemVisibilityVM("Account Position"),
|
||||
_day_position_visibility = new OrderEntryPanelItemVisibilityVM("Day Position"),
|
||||
_raw_day_position_visibility = new OrderEntryPanelItemVisibilityVM("Raw Day Position"),
|
||||
_size_preset_visibility = new OrderEntryPanelItemVisibilityVM("Size Presets"),
|
||||
_auto_clear_visibility = new OrderEntryPanelItemVisibilityVM("Auto Clear"),
|
||||
_clear_visibility = new OrderEntryPanelItemVisibilityVM("Clear"),
|
||||
@@ -137,8 +135,8 @@ namespace mbts.spark {
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged -= on_prop_changed;
|
||||
}
|
||||
AccountTuple? account_factory(uint key, string name) => new AccountTuple(key, name);
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, is_synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange);
|
||||
AccountTuple? account_factory(KeyValuePair<uint, string> kv) => new AccountTuple(kv.Key, kv.Value);
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, false);
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
@@ -240,7 +238,6 @@ namespace mbts.spark {
|
||||
yield return _order_type_visibility;
|
||||
yield return _account_position_visibility;
|
||||
yield return _day_position_visibility;
|
||||
yield return _raw_day_position_visibility;
|
||||
yield return _size_preset_visibility;
|
||||
yield return _clear_visibility;
|
||||
yield return _auto_clear_visibility;
|
||||
@@ -283,7 +280,6 @@ namespace mbts.spark {
|
||||
yield return _order_type_visibility;
|
||||
yield return _account_position_visibility;
|
||||
yield return _day_position_visibility;
|
||||
yield return _raw_day_position_visibility;
|
||||
yield return _size_preset_visibility;
|
||||
yield return _auto_clear_visibility;
|
||||
yield return _clear_visibility;
|
||||
@@ -346,7 +342,6 @@ namespace mbts.spark {
|
||||
_order_type_visibility.show = settings.order_type_visible;
|
||||
_account_position_visibility.show = settings.account_position_visible;
|
||||
_day_position_visibility.show = settings.day_position_visible;
|
||||
_raw_day_position_visibility.show = settings.raw_day_position_visible;
|
||||
_size_preset_visibility.show = settings.size_presets_visible;
|
||||
_clear_visibility.show = settings.clear_visible;
|
||||
_auto_clear_visibility.show = settings.auto_clear_visible;
|
||||
@@ -401,7 +396,7 @@ namespace mbts.spark {
|
||||
clone.default_account = default_account.Value.id;
|
||||
// - algo
|
||||
clone.default_algo = default_algo != null ?
|
||||
AlgoID.from_algo_key(default_algo.key) :
|
||||
AlgoID.from_algo_key(default_algo.key, _model.dynamic_helper.dynamic_type_index) :
|
||||
(_desired_algo ?? LadderSettings.Default.default_algo);
|
||||
// - components
|
||||
clone.auto_clear = auto_clear;
|
||||
@@ -419,7 +414,6 @@ namespace mbts.spark {
|
||||
clone.order_type_visible = _order_type_visibility.show;
|
||||
clone.account_position_visible = _account_position_visibility.show;
|
||||
clone.day_position_visible = _day_position_visibility.show;
|
||||
clone.raw_day_position_visible = _raw_day_position_visibility.show;
|
||||
clone.size_presets_visible = _size_preset_visibility.show;
|
||||
clone.auto_clear_visible = _auto_clear_visibility.show;
|
||||
clone.clear_visible = _clear_visibility.show;
|
||||
@@ -459,7 +453,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _try_select_desired_algo(bool bypass_setter = false) {
|
||||
if (_desired_algo.HasValue && (!_default_algo.setter_called || bypass_setter) && _algo_definitions.TryGetValue(AlgoID.to_algo_key(_desired_algo.Value), out var vm)) {
|
||||
if (_desired_algo.HasValue && (!_default_algo.setter_called || bypass_setter) && _algo_definitions.TryGetValue(AlgoID.to_algo_key(_desired_algo.Value, _model.dynamic_helper.dynamic_type_index), out var vm)) {
|
||||
_desired_algo = null;
|
||||
_default_algo.bypass_setter(vm);
|
||||
}
|
||||
@@ -729,15 +723,12 @@ namespace mbts.spark {
|
||||
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
|
||||
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
|
||||
int _column_count = 2;
|
||||
HashSet<AlgoPersistedKey> _sticky_algo_keys;
|
||||
IDisposable _sticky_algos_hook;
|
||||
|
||||
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
|
||||
_algo_definitions = algo_definitions;
|
||||
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
|
||||
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
|
||||
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
|
||||
_dispose = Disposable.Create(() => Disposable.dispose(ref _sticky_algos_hook));
|
||||
}
|
||||
|
||||
public ObservableCollection<FavoriteAlgoItem> items { get; }
|
||||
@@ -754,17 +745,6 @@ namespace mbts.spark {
|
||||
fai.selector.try_select(k);
|
||||
return fai;
|
||||
}));
|
||||
_sticky_algo_keys = new HashSet<AlgoPersistedKey>(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
|
||||
Disposable.replace(ref _sticky_algos_hook,
|
||||
ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
|
||||
x.selector.subscribe_value().Subscribe(algo => {
|
||||
if (algo != null &&
|
||||
algo.key.is_persisted &&
|
||||
(x.is_sticky = _sticky_algo_keys.Remove(new AlgoPersistedKey(algo.key.persisted))) &&
|
||||
_sticky_algo_keys.Count == 0)
|
||||
//
|
||||
Disposable.dispose(ref _sticky_algos_hook);
|
||||
})));
|
||||
}
|
||||
|
||||
public LadderFavoriteAlgosSettings create() =>
|
||||
@@ -773,13 +753,7 @@ namespace mbts.spark {
|
||||
.Where(k => k.HasValue && k.Value.is_persisted)
|
||||
.Select(k => k.Value)
|
||||
.ToArray(),
|
||||
column_count,
|
||||
_raw_items
|
||||
.Where(fai => fai.is_sticky)
|
||||
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
||||
.Where(k => k.HasValue && k.Value.is_persisted)
|
||||
.Select(k => k.Value)
|
||||
.ToArray());
|
||||
column_count);
|
||||
|
||||
FavoriteAlgoItem _item_factory() =>
|
||||
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
|
||||
@@ -792,7 +766,6 @@ namespace mbts.spark {
|
||||
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
|
||||
|
||||
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
|
||||
bool _is_sticky;
|
||||
|
||||
protected FavoriteAlgoItem() { }
|
||||
|
||||
@@ -803,7 +776,6 @@ namespace mbts.spark {
|
||||
|
||||
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
|
||||
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
|
||||
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
|
||||
|
||||
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
|
||||
public string key => LadderSettingsView.FavoriteAlgoAddButtonTemplateKey;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
@@ -34,9 +32,8 @@ namespace mbts.spark {
|
||||
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom;
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
|
||||
double _position, _day_position, _raw_day_position;
|
||||
bool _show_account_position, _show_day_position, _show_raw_day_position;
|
||||
double _position, _day_position;
|
||||
bool _show_account_position, _show_day_position;
|
||||
ulong _misc_time = 0;
|
||||
ulong _last_trade_time = 0;
|
||||
IEqualityComparer<double> _equality;
|
||||
@@ -109,10 +106,7 @@ namespace mbts.spark {
|
||||
(_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible)).set,
|
||||
(_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible)).set,
|
||||
s => {
|
||||
if (
|
||||
Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) |
|
||||
Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position)) |
|
||||
Set(ref _show_raw_day_position, s.raw_day_position_visible, nameof(show_raw_day_position))) {
|
||||
if (Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) | Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position))) {
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}
|
||||
size_calculator.buttons = s.calculator_buttons;
|
||||
@@ -138,8 +132,8 @@ namespace mbts.spark {
|
||||
//
|
||||
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
|
||||
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : (_show_raw_day_position ? _raw_day_position : position))));
|
||||
send_order_command = new AsyncCommand<(double price, Side? side, MouseButton button)>(send_order_async);
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : position)));
|
||||
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
|
||||
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
|
||||
show_book_command = new DelegateCommand(_show_book);
|
||||
show_ticket_command = new DelegateCommand(_show_ticket);
|
||||
@@ -229,25 +223,17 @@ namespace mbts.spark {
|
||||
.SelectManyInSeries(a => _model.order_gateway.on_security_positions(security.Id)
|
||||
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
|
||||
return Disposable.Create(
|
||||
settings.OnDispatcher().Subscribe(s => {
|
||||
_sticky_algos.SetItems(s.favorite_algos.stickied_algos);
|
||||
order_properties_vm.on_favorite_algos(s.favorite_algos);
|
||||
}),
|
||||
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
|
||||
order_properties_vm,
|
||||
position_src.OnDispatcher().Subscribe(pos => {
|
||||
double total, day, raw_day;
|
||||
double total, day;
|
||||
if (pos.HasValue) {
|
||||
total = pos.Value.total_pos();
|
||||
day = pos.Value.day_pos();
|
||||
raw_day = pos.Value.raw_day_pos();
|
||||
} else {
|
||||
total = day = raw_day = double.NaN;
|
||||
total = day = double.NaN;
|
||||
}
|
||||
if ((
|
||||
Set(ref _position, total, nameof(position)) |
|
||||
Set(ref _day_position, day, nameof(day_position)) |
|
||||
Set(ref _raw_day_position, raw_day, nameof(raw_day_position))
|
||||
) && (show_day_position || show_account_position || show_raw_day_position))
|
||||
if ((Set(ref _position, total, nameof(position)) | Set(ref _day_position, day, nameof(day_position))) && (show_day_position || show_account_position))
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}));
|
||||
}
|
||||
@@ -298,7 +284,6 @@ namespace mbts.spark {
|
||||
public double settlement => _settlement.Value;
|
||||
public double position => _position;
|
||||
public double day_position => _day_position;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public ulong volume => _volume.Value;
|
||||
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
|
||||
public CalculatorLogic size_calculator { get; } = new CalculatorLogic(0);
|
||||
@@ -325,7 +310,6 @@ namespace mbts.spark {
|
||||
public bool show_order_type => _show_order_type.view;
|
||||
public bool show_account_position => _show_account_position;
|
||||
public bool show_day_position => _show_day_position;
|
||||
public bool show_raw_day_position => _show_raw_day_position;
|
||||
public bool show_size_preset => _show_size_preset.view;
|
||||
public bool show_auto_clear => _show_auto_clear.view;
|
||||
public bool show_clear => _show_clear.view;
|
||||
@@ -337,18 +321,12 @@ namespace mbts.spark {
|
||||
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
|
||||
public string position_display_text {
|
||||
get {
|
||||
string rv = null;
|
||||
if (show_day_position)
|
||||
rv = SparkUtils.fmt_position(day_position);
|
||||
if (show_raw_day_position) {
|
||||
var raw_day_text = SparkUtils.fmt_position(raw_day_position);
|
||||
rv = rv == null ? raw_day_text : $"{rv} / {raw_day_text}";
|
||||
}
|
||||
if (show_account_position) {
|
||||
var acc_text = SparkUtils.fmt_position(position);
|
||||
rv = rv == null ? SparkUtils.fmt_position(position) : $"{rv} ({acc_text})";
|
||||
}
|
||||
return rv ?? "";
|
||||
if (show_day_position) {
|
||||
string day_pos_text = SparkUtils.fmt_position(day_position);
|
||||
return show_account_position ? $"{day_pos_text} ({SparkUtils.fmt_position(position)})" : day_pos_text;
|
||||
} else if (show_account_position) {
|
||||
return SparkUtils.fmt_position(position);
|
||||
} else return string.Empty;
|
||||
}
|
||||
}
|
||||
//public double window_height { get => _window_height.Value; set => _window_height.set(value); }
|
||||
@@ -399,7 +377,7 @@ namespace mbts.spark {
|
||||
if (order_properties_vm.selected_account.HasValue)
|
||||
rv.default_account = order_properties_vm.selected_account.Value.id;
|
||||
if (order_properties_vm.selected_algo_definition != null)
|
||||
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key);
|
||||
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key, _model.order_gateway.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -422,19 +400,20 @@ namespace mbts.spark {
|
||||
settings_invalidated?.Invoke();
|
||||
}
|
||||
|
||||
public Task send_order_async((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm != null && args.button != MouseButton.Middle) {
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || current_settings.alternate_order_size > 0)) {
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
} else {
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
return send_order_async(args.side.Value, size, args.price);
|
||||
}
|
||||
}
|
||||
public void send_order((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm == null || args.button == MouseButton.Middle)
|
||||
return;
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
|
||||
return;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task send_order_async(Side side, uint size, double price) {
|
||||
@@ -451,16 +430,9 @@ namespace mbts.spark {
|
||||
price = temp_price;
|
||||
}
|
||||
}
|
||||
await _model.order_gateway.send_order(security, order, price, size);
|
||||
await _model.order_gateway.send_order(order, price, size);
|
||||
if (auto_clear)
|
||||
size_calculator.value = 0;
|
||||
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
|
||||
if (fav_algo != null && !_sticky_algos.Contains(fav_algo.key) && !order_properties_vm.try_pop_selected_algo_definition()) {
|
||||
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
|
||||
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
|
||||
order_properties_vm.selected_algo_definition = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _update_header() {
|
||||
|
||||
@@ -805,7 +805,6 @@
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
|
||||
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
|
||||
@@ -815,7 +814,6 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
@@ -825,7 +823,6 @@
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="1" HorizontalAlignment="Stretch" Command="{Binding DataContext.add_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}">Add</Button>
|
||||
</Grid>
|
||||
|
||||
@@ -39,8 +39,6 @@
|
||||
<converters:ResponsiveTextConverter x:Key="OpenTextConverter" LongText="Open:" ShortText="O:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="SettleTextConverter" LongText="Settle:" ShortText="S:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="VolumeTextConverter" LongText="Volume:" ShortText="V:" Threshold="99" />
|
||||
<converters:DoubleSubtractionConverter x:Key="DoubleSubtractionConverter" Value="4" />
|
||||
<converters:DoubleSubtractionConverter x:Key="HeightDoubleSubtractionConverter" Value="6" />
|
||||
<Style TargetType="GridViewColumnHeader">
|
||||
<Setter Property="Background" Value="{mbts:BTSColor header}"/>
|
||||
<Setter Property="Foreground" Value="{mbts:BTSColor header_text}"/>
|
||||
@@ -476,7 +474,6 @@
|
||||
</converters:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="show_day_position"/>
|
||||
<Binding Path="show_raw_day_position"/>
|
||||
<Binding Path="show_account_position"/>
|
||||
</MultiBinding>
|
||||
</Button.Visibility>
|
||||
@@ -487,7 +484,6 @@
|
||||
<MultiBinding Converter="{x:Static spark:LadderView.PositionToolTipConverter}">
|
||||
<Binding Path="order_properties_vm.selected_account"/>
|
||||
<Binding Path="show_day_position"/>
|
||||
<Binding Path="show_raw_day_position"/>
|
||||
<Binding Path="show_account_position"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
@@ -583,9 +579,7 @@
|
||||
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
||||
<Setter Property="Foreground" Value="{Binding foreground}"/>
|
||||
<Setter Property="TextLines" Value="{Binding text_lines}" />
|
||||
<!--<Setter Property="Width" Value="{x:Static controls:Badge.DefaultWidth}"/>-->
|
||||
<Setter Property="Width" Value="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type spark:LadderOrdersHost}}, Converter={StaticResource DoubleSubtractionConverter}}" />
|
||||
<Setter Property="Height" Value="{Binding grid_row_height, Converter={StaticResource HeightDoubleSubtractionConverter}}" />
|
||||
<Setter Property="Width" Value="{x:Static controls:Badge.DefaultWidth}"/>
|
||||
<Setter Property="HasMultipleOrdersAtLevel" Value="{Binding has_multiple_orders_at_same_level}" />
|
||||
<Setter Property="IsHedgeOrder" Value="{Binding is_hedge_order}" />
|
||||
<Setter Property="AlgoType" Value="{Binding algo_type}" />
|
||||
|
||||
@@ -18,60 +18,32 @@ namespace mbts.spark {
|
||||
MaxZoomFactor = 1.5;
|
||||
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", raw_day_pos_text = "raw day", total_pos_text = "total";
|
||||
ValueConverter.create<AccountTuple?, bool, bool, string>((account, show_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", total_pos_text = "total";
|
||||
if (!account.HasValue)
|
||||
return string.Empty;
|
||||
/// Note: 'day,' and 'raw day,' are interchangeable wrt the string format, 'total' however is treated differently.
|
||||
/// Different Formats:
|
||||
// No parts are visible:
|
||||
// null
|
||||
//
|
||||
// 1 part 'foo' is visible:
|
||||
// "<account> foo position
|
||||
// click to copy to size"
|
||||
//
|
||||
// 2 parts 'day' and 'raw day' are visible:
|
||||
// "<account> 'day / raw day' position
|
||||
// click to copy day to size"
|
||||
//
|
||||
// 2 parts 'day' and 'total' are visible:
|
||||
// "<account> 'day (total)' position
|
||||
// click to copy day to size"
|
||||
//
|
||||
|
||||
string primary_pos = null;
|
||||
// part count is incremented in the 'add_part' method
|
||||
int part_count = 0;
|
||||
var sb = new StringBuilderEx();
|
||||
|
||||
if (show_day_pos) add_part(day_pos_text, " / ");
|
||||
if (show_raw_day_pos) add_part(raw_day_pos_text, " / ");
|
||||
if (show_total_pos) add_part(total_pos_text, " (", ")");
|
||||
//
|
||||
if (part_count > 1) {
|
||||
// This is a complex expression (showing more than 1 value) - wrap it in single quotes.
|
||||
// Explicitly state which value will be copied to the size input.
|
||||
sb.Insert(0, "'");
|
||||
sb.Add("'");
|
||||
sb.Add(" position\n(click to copy ", primary_pos, " to size)");
|
||||
} else {
|
||||
// Only showing 1 value, no need for single quotes,
|
||||
// nor an explicit statement that indicates which value will be copied to the size input.
|
||||
sb.Add(" position\n(click to copy to size)");
|
||||
}
|
||||
return sb.ToString();
|
||||
|
||||
void add_part(string part_name, string separator = null, string separator_suffix = null) {
|
||||
if (part_count++ > 0) {
|
||||
sb.Add(separator);
|
||||
sb.Add(part_name);
|
||||
sb.Add(separator_suffix);
|
||||
var sb = new StringBuilderEx(account.Value.name);
|
||||
string primary_pos;
|
||||
if (show_day_pos) {
|
||||
primary_pos = day_pos_text;
|
||||
if (show_total_pos) {
|
||||
sb.Add(" '", primary_pos, " ");
|
||||
sb.Add("(", total_pos_text, ")' ");
|
||||
} else {
|
||||
primary_pos = part_name;
|
||||
sb.Add($"{account.Value.name} {part_name}");
|
||||
sb.Add(" ", primary_pos, " ");
|
||||
}
|
||||
} else if (show_total_pos) {
|
||||
primary_pos = total_pos_text;
|
||||
sb.Add(" ", primary_pos, " ");
|
||||
} else {
|
||||
// unexpected code path (the button for which this tooltip is destined should be invisible in this case), but no need to throw an exception.
|
||||
return string.Empty;
|
||||
}
|
||||
sb.Add("position\n(click to copy ");
|
||||
if (show_day_pos && show_total_pos)
|
||||
sb.Add(primary_pos, " position ");
|
||||
sb.Add("to size)");
|
||||
return sb.ToString();
|
||||
});
|
||||
|
||||
internal LadderView(LadderVM vm) {
|
||||
|
||||
@@ -79,8 +79,8 @@ namespace bts.shell.manual_fills {
|
||||
|
||||
public ManualFillsModel(OrdersModel model)
|
||||
: this(model, model.on_manual_fill_tuples,
|
||||
model.factory.subscribe_accounts().Select(d => MappedDictionary.create(d, (k, v) => new AccountTuple(k, v))).Table(u => u.id),
|
||||
model.factory.subscribe_users().Select(d => MappedDictionary.create(d, (k, v) => new UserTuple(k, v))).Table(a => a.id)) { }
|
||||
model.factory.subscribe_accounts().Select(d => MappedDictionary.create(d, kv => new AccountTuple(kv.Key, kv.Value))).Table(u => u.id),
|
||||
model.factory.subscribe_users().Select(d => MappedDictionary.create(d, kv => new UserTuple(kv.Key, kv.Value))).Table(a => a.id)) { }
|
||||
|
||||
public ManualFillsModel(OrdersModel model, IObservable<TableUpdate<ManualFillTuple, int>> manual_fills, IObservable<TableUpdate<AccountTuple, uint>> accounts, IObservable<TableUpdate<UserTuple, uint>> users) {
|
||||
_model = model;
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace bts.shell.manual_fills {
|
||||
// The security desc lookup could be better optimized, but the speed at which fills are imported is probably not a paramount concern.
|
||||
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, IReadOnlyDictionary<uint, SecurityImmutable> security_map, IReadOnlyDictionary<uint, string> account_map, IReadOnlyDictionary<uint, string> user_map) =>
|
||||
parse_rows(rows,
|
||||
reverse_lookup(MappedDictionary.create(security_map, s => s.ToString())),
|
||||
reverse_lookup(MappedDictionary.create(security_map, kv => kv.Value.ToString())),
|
||||
reverse_lookup(account_map),
|
||||
reverse_lookup(user_map));
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace bts.shell.manual_fills {
|
||||
var lines = File.ReadAllLines(file_name);
|
||||
var parsed_fills = ManualFillsParser.parse_rows(
|
||||
lines,
|
||||
MappedDictionary.create(security_map, smd => smd.security),
|
||||
MappedDictionary.create(security_map, kv => kv.Value.security),
|
||||
accounts.ToDictionary(a => a.Value.id, a => a.Value.name),
|
||||
users.ToDictionary(u => u.Value.id, u => u.Value.name)
|
||||
).ToList();
|
||||
|
||||
@@ -15,11 +15,10 @@ namespace mbts.spark {
|
||||
CVRowStyleKey = "CVRowStyle",
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
public MiniOrderBookView() =>
|
||||
public MiniOrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
|
||||
public MiniOrderBookView(OrderBookVM vm) : this() =>
|
||||
DataContext = vm;
|
||||
}
|
||||
}
|
||||
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
|
||||
@@ -34,7 +34,7 @@ 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 {
|
||||
ListSortDirection? SortDirection { get; set; }
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> filter { get; }
|
||||
IDataGridColumnFilter<OrderSecurity> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ namespace mbts.spark {
|
||||
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
|
||||
// security
|
||||
Add(security_column = new SecurityColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, SecurityImmutable, SecurityCriteria>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, SecurityImmutable, SecurityCriteria>(
|
||||
order_securities,
|
||||
ort => ort.order.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
ort => ort.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
SecurityCriteria.from_securities,
|
||||
SecurityCriteria.get_to_securities(bus),
|
||||
nameof(OrderBookRowVM.security),
|
||||
@@ -85,9 +85,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Symbol,
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
ort => ort.order.security?.Symbol ?? string.Empty,
|
||||
ort => ort.security?.Symbol ?? string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)
|
||||
),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
@@ -98,8 +98,8 @@ namespace mbts.spark {
|
||||
"Time",
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.created(),
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.created(),
|
||||
nameof(OrderBookRowVM.time_created)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
_wrap_comparer(r => r.time_created)));
|
||||
@@ -109,8 +109,8 @@ namespace mbts.spark {
|
||||
"Last Change",
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_change(),
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_change(),
|
||||
nameof(OrderBookRowVM.last_change)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
_wrap_comparer(r => r.last_change)));
|
||||
@@ -120,9 +120,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.User,
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
ort => ort.order.order.user(),
|
||||
ort => ort.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
_wrap_comparer(r => r.user)));
|
||||
@@ -132,9 +132,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Account,
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string, string[]>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
ort => ort.order.order.account(),
|
||||
ort => ort.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
vals => Observable.Return(vals),
|
||||
nameof(OrderBookRowVM.account)),
|
||||
@@ -146,9 +146,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Exchange,
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, Venue>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Venue>(
|
||||
EnumHelper.get_values_except(Venue.Unset).ToList(),
|
||||
ort => ort.order.security.exchange_or_default(),
|
||||
ort => ort.security.exchange_or_default(),
|
||||
nameof(OrderBookRowVM.exchange)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.exchange)));
|
||||
@@ -158,14 +158,14 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, TimeInForce>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, TimeInForce>(
|
||||
EnumHelper.get_values<TimeInForce>().ToList(),
|
||||
ort => ort.order.order.time_in_force(),
|
||||
ort => ort.order.time_in_force(),
|
||||
nameof(OrderBookRowVM.time_in_force)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.time_in_force)));
|
||||
// (algo items)
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher();
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key, model.dynamic_helper.dynamic_type_index)).OnDispatcher();
|
||||
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
|
||||
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
|
||||
// order type
|
||||
@@ -174,9 +174,9 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.badge_fill),
|
||||
nameof(OrderBookRowVM.display_name),
|
||||
_wrap_comparer(r => r.order_type),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityRootPair, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.order.algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
|
||||
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
|
||||
order_type_items_view.CustomSort = algo_items_comp;
|
||||
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
|
||||
@@ -185,10 +185,10 @@ namespace mbts.spark {
|
||||
Columns.RootAlgoType,
|
||||
nameof(OrderBookRowVM.root_algo_badge_fill),
|
||||
nameof(OrderBookRowVM.root_algo_display_name),
|
||||
_wrap_comparer(r => r.order_tuple.root.algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityRootPair, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root.algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root_algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
DefaultColumnHeaders.RootAlgoType));
|
||||
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
|
||||
root_algo_type_items_view.CustomSort = algo_items_comp;
|
||||
@@ -199,36 +199,36 @@ namespace mbts.spark {
|
||||
"Sub Type",
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, OrderSubType>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
ort => ort.order.order.sub_type(),
|
||||
ort => ort.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
OrderBookGridView.SubTypeFilterTemplateKey,
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, Side>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Side>(
|
||||
new[] { Side.Buy, Side.Sell },
|
||||
ort => ort.order.order.side(),
|
||||
ort => ort.order.side(),
|
||||
nameof(OrderBookRowVM.side))));
|
||||
// quantity
|
||||
Add(qty_column = new SizeColumn(
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
// edit quantity column
|
||||
// not usually part of the main order book, but it's useful to add it
|
||||
// rather than debugging the popup window
|
||||
//Add(edit_qty_column = new EditableQtyColumn());
|
||||
// filled quantity
|
||||
Add(filled_qty_column = new FilledColumn(
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
// price
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.price(),
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.price(),
|
||||
nameof(OrderBookRowVM.price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.price)));
|
||||
@@ -243,8 +243,8 @@ namespace mbts.spark {
|
||||
"Avg Price",
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.avg_price(),
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.avg_price(),
|
||||
nameof(OrderBookRowVM.avg_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.avg_price)));
|
||||
@@ -254,30 +254,30 @@ namespace mbts.spark {
|
||||
"Last Filled",
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_price(),
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.last_price(),
|
||||
nameof(OrderBookRowVM.last_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.last_price)));
|
||||
// piq
|
||||
Add(piq_column = new PiqColumn(
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.piq ?? uint.MaxValue, nameof(OrderBookRowVM.piq))));
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.piq ?? uint.MaxValue, nameof(OrderBookRowVM.piq))));
|
||||
// status
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, OrderStatus>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderStatus>(
|
||||
EnumHelper.get_values<OrderStatus>().ToList(),
|
||||
ort => ort.order.order.status(),
|
||||
ort => ort.order.status(),
|
||||
nameof(OrderBookRowVM.status)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.status)));
|
||||
// last message
|
||||
Add(last_message_column = new OrderBookLastMessageColumn(
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_msg(),
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_msg(),
|
||||
nameof(OrderBookRowVM.last_msg))));
|
||||
// security key
|
||||
Add(security_key_column = new SimpleTextColumn<string>(
|
||||
@@ -285,8 +285,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.security_key().ToString(),
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.security_key().ToString(),
|
||||
nameof(OrderBookRowVM.security_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.security_key)));
|
||||
@@ -296,8 +296,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.Key.ToString(),
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.Key.ToString(),
|
||||
nameof(OrderBookRowVM.order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order_key)));
|
||||
@@ -307,8 +307,8 @@ namespace mbts.spark {
|
||||
"Parent Key",
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.ParentKey.ToString(),
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.ParentKey.ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.parent_order_key)));
|
||||
@@ -318,8 +318,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.AttachedTargetOrderKey,
|
||||
r => r.order.attached_target_order_key().ToString(),
|
||||
DefaultColumnWidths.AttachedTargetOrderKey,
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.attached_target_order_key().ToString(),
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.attached_target_order_key().ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order.attached_target_order_key())));
|
||||
@@ -327,18 +327,6 @@ namespace mbts.spark {
|
||||
|
||||
public event Action filter_changed;
|
||||
|
||||
public static IEnumerable<ColumnBase> editable_columns {
|
||||
get {
|
||||
yield return new AttachDetachColumn(typeof(MiniOrderBookView));
|
||||
yield return new CommitColumn();
|
||||
yield return new EditablePriceColumn();
|
||||
yield return new EditableQtyColumn();
|
||||
yield return new UpdateColumn(typeof(MiniOrderBookView));
|
||||
yield return new CancelColumn(nameof(OrderBookRowVM.cancel_command));
|
||||
yield return new CheckColumn(nameof(OrderBookRowVM.is_selected));
|
||||
}
|
||||
}
|
||||
|
||||
public SecurityColumn security_column { get; }
|
||||
public SimpleTextColumn<string[]> symbol_column { get; }
|
||||
public SimpleTextColumn<TimeFilterTuple> time_created_column { get; }
|
||||
@@ -440,9 +428,9 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public Func<OrderSecurityRootPair, bool> get_filter() {
|
||||
public Func<OrderSecurity, bool> get_filter() {
|
||||
var active_filters = this.OfType<ICVOrderBookColumn>().Where(c => c.filter.active.GetValueOrDefault()).Select(c => c.filter).ToArray();
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurityRootPair, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurity, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
}
|
||||
|
||||
public OrderBookSortingData get_sorting_data() {
|
||||
@@ -543,7 +531,7 @@ namespace mbts.spark {
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityRootPair, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
@@ -560,10 +548,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityRootPair, T> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, T> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => _filter_template;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
@@ -577,7 +565,7 @@ namespace mbts.spark {
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityRootPair, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
: base(key, header, get_text, width, filter, filter_template, comparer) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -587,7 +575,7 @@ namespace mbts.spark {
|
||||
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, Side[]> filter)
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurity, Side[]> filter)
|
||||
: 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) =>
|
||||
@@ -596,7 +584,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
: 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) =>
|
||||
@@ -605,7 +593,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
: 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) =>
|
||||
@@ -617,7 +605,7 @@ namespace mbts.spark {
|
||||
readonly BindingBase _foreground_binding;
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurityRootPair, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
: base(key, new Binding(badge_fill_binding_path), new Binding(display_name_binding_path), filter, header) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -627,11 +615,11 @@ namespace mbts.spark {
|
||||
this.comparer = row_comparer;
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, AlgoID[]> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter { get; }
|
||||
public override string filter_template => DataGridFilterKnownTemplates.AlgoType;
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
@@ -649,7 +637,7 @@ namespace mbts.spark {
|
||||
public class OrderBookLastMessageColumn : TextColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurity, string> filter)
|
||||
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -658,10 +646,10 @@ namespace mbts.spark {
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, string> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.last_msg);
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
@@ -671,7 +659,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class PiqColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
: base(Columns.Piq, "PIQ", r => r.piq_text, DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.piq)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -686,7 +674,7 @@ namespace mbts.spark {
|
||||
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
_foreground_binding = RenderUtils.get_foreground_binding(this);
|
||||
@@ -695,10 +683,10 @@ namespace mbts.spark {
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityRootPair, SecurityCriteria> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.security, SecurityHelper.Comparer.InstanceImmutable.to_nullable());
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => DataGridFilterKnownTemplates.DiscreteValues;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
@@ -743,14 +731,10 @@ namespace mbts.spark {
|
||||
Width = 30;
|
||||
_checked_binding = new Binding(checked_property);
|
||||
|
||||
var header_cb = new CheckBox {
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
ToolTip = "Select / Unselect All"
|
||||
};
|
||||
var header_cb = new CheckBox();
|
||||
header_cb.ToolTip = "Select / Unselect All";
|
||||
header_cb.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(OrderBookVM.all_rows_selected)));
|
||||
this.Header = header_cb;
|
||||
is_visible = true;
|
||||
}
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
@@ -1180,7 +1164,6 @@ namespace mbts.spark {
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var price_up_down = new PriceEntryCommitControl();
|
||||
price_up_down.SetBinding(PriceEntryCommitControl.RealPriceProperty, new Binding(nameof(OrderBookRowVM.price)));
|
||||
price_up_down.SetBinding(PriceEntryCommitControl.SourceValueProperty, nameof(OrderBookRowVM.prospective_price));
|
||||
price_up_down.SetBinding(PriceEntryCommitControl.SourceContractSpecProperty, nameof(OrderBookRowVM.contract_spec));
|
||||
price_up_down.SetBinding(PriceEntryCommitControl.OnCommitProperty, nameof(OrderBookRowVM.modify_order_quantity_andor_price));
|
||||
|
||||
@@ -3,11 +3,9 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -16,9 +14,7 @@ using System.Windows.Media;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurityRootPair>, INotifyPropertyChanged {
|
||||
public static readonly IComparer<OrderBookRowVM> key_comparer = Comparer<uint>.Default.wrap<uint, OrderBookRowVM>(r => r.order_key).to_safe();
|
||||
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurity>, INotifyPropertyChanged {
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -27,13 +23,13 @@ namespace mbts.spark {
|
||||
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd, _cancel_cmd, _detach_command, _attach_command, _update_command, _modify_order_quantity_andor_price, _revert_prospective_order_quantity_andor_price;
|
||||
bool _is_parent, _is_expanded, _is_selected, _is_attachable, _is_detachable;
|
||||
int _depth_level;
|
||||
SecurityImmutable? _security => order_tuple.order.security;
|
||||
SecurityImmutable? _security => order_tuple.security;
|
||||
// for mini-ob
|
||||
uint _prospective_quantity;
|
||||
double _prospective_price;
|
||||
Action<bool> _on_selection_changed;
|
||||
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurityRootPair order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
is_visible = !is_child;
|
||||
@@ -49,22 +45,22 @@ namespace mbts.spark {
|
||||
//// 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<OrderSecurityRootPair> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurityRootPair>(
|
||||
public IObservable<OrderSecurity> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurity>(
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => invalidated -= cb);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
|
||||
public event Action<(OrderSecurity oldval, OrderSecurity newval)> updated;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// Core
|
||||
public OrderSecurityRootPair order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order.order;
|
||||
public OrderSecurity order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order;
|
||||
public OrderMetadata order_metadata => _owner.get_metadata(this);
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.order.meta, order_metadata));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.meta, order_metadata));
|
||||
public uint order_key => order.Key;
|
||||
public uint parent_order_key => order.ParentKey;
|
||||
public uint security_key => order.security_key();
|
||||
@@ -75,7 +71,7 @@ namespace mbts.spark {
|
||||
public string symbol => _security.symbol_or_default();
|
||||
// Def
|
||||
public CustomOrderDefinition.CustomOneofCase order_type => order.order_type();
|
||||
public string display_name => order_tuple.order.display_name();
|
||||
public string display_name => order_tuple.display_name();
|
||||
public OrderSubType order_sub_type => order.sub_type();
|
||||
public string order_sub_type_text => _fmt_order_sub_type(order_sub_type);
|
||||
public Side side => order.side();
|
||||
@@ -97,7 +93,7 @@ namespace mbts.spark {
|
||||
public uint quantity => order.quantity();
|
||||
public uint filled_quantity => order.filled();
|
||||
public string last_msg => order.last_msg();
|
||||
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
|
||||
public uint piq => order_tuple.piq ?? uint.MaxValue;
|
||||
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.ToString();
|
||||
public bool is_non_terminal => order.is_non_terminal();
|
||||
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
|
||||
@@ -105,10 +101,10 @@ namespace mbts.spark {
|
||||
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
|
||||
|
||||
// Root Algo
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root_algo.value.CustomCase;
|
||||
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
|
||||
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
|
||||
public string root_algo_display_name => order_tuple.root.algo.name;
|
||||
public string root_algo_display_name => order_tuple.root_algo.name;
|
||||
|
||||
// Tree
|
||||
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
|
||||
@@ -217,7 +213,7 @@ namespace mbts.spark {
|
||||
|
||||
// private props
|
||||
Color _root_algo_badge_color {
|
||||
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root.meta, root_algo_order_metadata)).Value;
|
||||
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata)).Value;
|
||||
set {
|
||||
if (_root_algo_badge_color_backing != value) {
|
||||
_root_algo_badge_color_backing = value;
|
||||
@@ -230,7 +226,7 @@ namespace mbts.spark {
|
||||
bool IHierarchicalRow.is_parent => is_parent;
|
||||
bool IHierarchicalRow.is_expanded => is_expanded;
|
||||
int IHierarchicalRow.depth_level => depth_level;
|
||||
OrderSecurityRootPair IHierarchicalRow<OrderSecurityRootPair>.value => order_tuple;
|
||||
OrderSecurity IHierarchicalRow<OrderSecurity>.value => order_tuple;
|
||||
|
||||
void IHierarchicalRow.on_is_parent_changed(bool is_parent) => this.is_parent = is_parent;
|
||||
/// Do NOT actually call the <see cref="is_expanded"/> setter (it has side effects) - the row collection calls this method when the row is expanded/collapsed by some **external means, outside of this class.
|
||||
@@ -242,7 +238,7 @@ namespace mbts.spark {
|
||||
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.collapse_row(TKey)"/>
|
||||
void IHierarchicalRow.on_is_expanded_changed(bool is_expanded) => Set(ref _is_expanded, is_expanded, nameof(is_expanded));
|
||||
void IHierarchicalRow.on_depth_level_changed(int depth_level) => this.depth_level = depth_level;
|
||||
void IHierarchicalRow<OrderSecurityRootPair>.on_value_changed(OrderSecurityRootPair value) => update(value);
|
||||
void IHierarchicalRow<OrderSecurity>.on_value_changed(OrderSecurity value) => update(value);
|
||||
#endregion
|
||||
|
||||
private void _unselect_me() {
|
||||
@@ -258,8 +254,8 @@ namespace mbts.spark {
|
||||
internal void expand_internal() => Set(ref _is_expanded, true, nameof(is_expanded));
|
||||
internal void collapse_internal() => Set(ref _is_expanded, false, nameof(is_expanded));
|
||||
|
||||
public void update(OrderSecurityRootPair order_tuple) {
|
||||
Log.assert(order_tuple.order.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
public void update(OrderSecurity order_tuple) {
|
||||
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
// _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);
|
||||
var old_order_tuple = this.order_tuple;
|
||||
@@ -285,25 +281,25 @@ namespace mbts.spark {
|
||||
if ((Columns.Security & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(security), nameof(security_text));
|
||||
// badge fill
|
||||
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
|
||||
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
// invalidate by setting to null (will be re-instantiated in the public getter)
|
||||
_badge_fill = null;
|
||||
RaisePropertyChanged(nameof(badge_fill));
|
||||
}
|
||||
// display name
|
||||
if (old_order_tuple.order.algo.name != order_tuple.order.algo.name)
|
||||
if (old_order_tuple.display_name() != order_tuple.display_name())
|
||||
RaisePropertyChanged(nameof(display_name));
|
||||
// root badge fill
|
||||
if (_root_algo_badge_color_backing.HasValue) {
|
||||
// only need to invalidate it if it had a value (it is lazily instantiated)
|
||||
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root.meta, root_algo_order_metadata);
|
||||
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata);
|
||||
RaisePropertyChanged(nameof(root_algo_badge_fill));
|
||||
}
|
||||
// root display name
|
||||
if (old_order_tuple.root.algo.name != order_tuple.root.algo.name)
|
||||
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
|
||||
RaisePropertyChanged(nameof(root_algo_display_name));
|
||||
// root order type
|
||||
if (old_order_tuple.root.algo.value.CustomCase != order_tuple.root.algo.value.CustomCase) {
|
||||
if (old_order_tuple.root_algo.value.CustomCase != order_tuple.root_algo.value.CustomCase) {
|
||||
RaisePropertyChanged(nameof(root_algo_order_type));
|
||||
}
|
||||
// status
|
||||
@@ -321,7 +317,7 @@ namespace mbts.spark {
|
||||
// always raise updated
|
||||
updated?.Invoke((old_order_tuple, order_tuple));
|
||||
// terminal state
|
||||
if (old_order_tuple.order.order.is_non_terminal() != order_tuple.order.order.is_non_terminal()) {
|
||||
if (old_order_tuple.order.is_non_terminal() != order_tuple.order.is_non_terminal()) {
|
||||
// while we call refresh_is_at/detachable in the OrderBookRowCollection.update
|
||||
// on all the children of a modified row, we used to not call it on the row istelf,
|
||||
// which should of course be done here. This manifested via the very top parent in any
|
||||
@@ -402,35 +398,35 @@ namespace mbts.spark {
|
||||
Columns.AvgFilled |
|
||||
Columns.Status;
|
||||
|
||||
public static Columns get_dirty_cells(OrderSecurityRootPair oldval, OrderSecurityRootPair newval) {
|
||||
public static Columns get_dirty_cells(OrderSecurity oldval, OrderSecurity newval) {
|
||||
var dirty = (Columns)0;
|
||||
if (oldval.order.security != newval.order.security)
|
||||
if (oldval.security != newval.security)
|
||||
dirty |= Columns.Security;
|
||||
if (oldval.order.order.last_change() != newval.order.order.last_change())
|
||||
if (oldval.order.last_change() != newval.order.last_change())
|
||||
dirty |= Columns.LastChange;
|
||||
if (oldval.order.order.filled() != newval.order.order.filled())
|
||||
if (oldval.order.filled() != newval.order.filled())
|
||||
dirty |= Columns.Filled;
|
||||
if (oldval.order.order.last_price() != newval.order.order.last_price())
|
||||
if (oldval.order.last_price() != newval.order.last_price())
|
||||
dirty |= Columns.LastPrice;
|
||||
if (oldval.order.order.avg_price() != newval.order.order.avg_price())
|
||||
if (oldval.order.avg_price() != newval.order.avg_price())
|
||||
dirty |= Columns.AvgFilled;
|
||||
if (oldval.order.order.price() != newval.order.order.price())
|
||||
if (oldval.order.price() != newval.order.price())
|
||||
dirty |= Columns.Price;
|
||||
if (oldval.order.order.quantity() != newval.order.order.quantity())
|
||||
if (oldval.order.quantity() != newval.order.quantity())
|
||||
dirty |= Columns.Size;
|
||||
if (oldval.order.order.ParentKey != newval.order.order.ParentKey)
|
||||
if (oldval.order.ParentKey != newval.order.ParentKey)
|
||||
dirty |= Columns.ParentKey;
|
||||
if (oldval.order.order.attached_target_order_key() != newval.order.order.attached_target_order_key())
|
||||
if (oldval.order.attached_target_order_key() != newval.order.attached_target_order_key())
|
||||
dirty |= Columns.AttachedTargetOrderKey;
|
||||
if (oldval.order.order.status() != newval.order.order.status())
|
||||
if (oldval.order.status() != newval.order.status())
|
||||
dirty |= Columns.Status;
|
||||
if (oldval.order.order.last_msg() != newval.order.order.last_msg())
|
||||
if (oldval.order.last_msg() != newval.order.last_msg())
|
||||
dirty |= Columns.LastMessage;
|
||||
if (oldval.order.piq != newval.order.piq) {
|
||||
if (oldval.piq != newval.piq) {
|
||||
dirty |= Columns.Piq;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,9 +42,8 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
readonly ObservableSortedCollection<OrderBookRowVM> _selected_rows = new ObservableSortedCollection<OrderBookRowVM>(OrderBookRowVM.key_comparer);
|
||||
readonly DelegateProperty<bool> _show_order_params, _show_ladder, _show_fills, _can_attach_selected_orders, _can_detach_selected_orders;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityRootPair, uint> _row_collection;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint> _row_collection;
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
|
||||
@@ -62,30 +61,26 @@ namespace mbts.spark {
|
||||
_is_attaching_orders,
|
||||
_is_row_collection_populated,
|
||||
_all_rows_selected;
|
||||
IReadOnlyDictionary<uint, OrderSecurityRootPair> _cached_orders = DictionaryHelper.empty<uint, OrderSecurityRootPair>();
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cached_orders = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
IReadOnlyDictionary<uint, bool> _cached_attachable = DictionaryHelper.empty<uint, bool>(), _cached_detachable = DictionaryHelper.empty<uint, bool>();
|
||||
HashSet<uint>
|
||||
_attachable_selected_orders = new HashSet<uint>(),
|
||||
_detachable_selected_orders = new HashSet<uint>();
|
||||
FilterData _cached_filter_data;
|
||||
|
||||
public OrderBookVM(
|
||||
IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool rows_are_initially_expanded = false)
|
||||
: this(bus, notification_service, model, OrdersModel.piq_enabled ? model.on_order_tuples_with_root_and_piq : model.on_order_tuples_with_root, settings_manager, rows_are_initially_expanded) { }
|
||||
Dictionary<uint, IDisposable> _on_updated_single_callback_gatekeeper = new Dictionary<uint, IDisposable>();
|
||||
|
||||
public OrderBookVM(
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source,
|
||||
IWindowSettingsManager<OrderBookGridSettings> settings_manager,
|
||||
bool rows_are_initially_expanded = false) {
|
||||
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle, k => _cached_orders.TryGetValue(k, out var o) ? o.order : (OrderSecurityAlgo?)null);
|
||||
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle);
|
||||
ladder_vm = new LadderVMContainer(bus, model);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoFormats.Exchange, AlgoEditingPermissions.AllowEditValues);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, OrderDataHelper.Defaults.AlgoData(), editing_permissions: AlgoEditingPermissions.AllowEditValues));
|
||||
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
|
||||
order_mod_vm.algo_vm.algo_root = null;
|
||||
_show_order_params = backing(nameof(show_order_params), () => (_right_panel.visible_components & OrderBookRightPanelSettings.Components.Params) != 0);
|
||||
@@ -98,12 +93,16 @@ namespace mbts.spark {
|
||||
};
|
||||
_can_attach_selected_orders = backing(nameof(can_attach_selected_orders), () => _attachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
|
||||
_can_detach_selected_orders = backing(nameof(can_detach_selected_orders), () => _detachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
|
||||
attach_vm = new OrderParamsVM(this, AlgoFormats.Attachable, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
|
||||
attach_vm = new OrderParamsVM(this,
|
||||
AlgoDefinitionVM.create(_model,
|
||||
OrderDataHelper.Defaults.AlgoData(CustomOrderDefinition.CustomOneofCase.Iceberg),
|
||||
init_format: AlgoFormats.Attachable,
|
||||
editing_permissions: AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues));
|
||||
_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 OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityRootPair, uint>(
|
||||
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint>(
|
||||
o => new OrderBookRowVM(this, o, rows_are_initially_expanded, (row_is_selected) => {
|
||||
// all are selected and one is checked off
|
||||
if (all_rows_selected && !row_is_selected) {
|
||||
@@ -115,11 +114,11 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(nameof(this.all_rows_selected));
|
||||
}
|
||||
}) {
|
||||
is_attachable = _cached_attachable.TryGetValue(o.order.order.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.order.Key, out bool id)
|
||||
is_attachable = _cached_attachable.TryGetValue(o.order.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
|
||||
},
|
||||
r => r.order.order.Key,
|
||||
r => r.order.order.ParentKey,
|
||||
r => r.order.Key,
|
||||
r => r.order.ParentKey,
|
||||
_columns.get_sorting_data().comparer);
|
||||
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
|
||||
_current_row = backing<OrderBookRowVM>(nameof(current_row));
|
||||
@@ -162,6 +161,7 @@ namespace mbts.spark {
|
||||
begin_attaching += on_begin_attaching;
|
||||
end_attaching += on_end_attaching;
|
||||
// switch between orders and/or orders w/ piq
|
||||
var orders_source = OrdersModel.piq_enabled ? model.on_order_tuples_with_root_and_piq : model.on_order_tuples_with_root;
|
||||
concat_disposable(
|
||||
connect_order_mod_vm(),
|
||||
_can_attach_selected_orders.observe_current().Subscribe(send_attached_orders_async_cmd, begin_attach_cmd),
|
||||
@@ -189,24 +189,8 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(nameof(security_plurality));
|
||||
}),
|
||||
orders_source.OnDispatcher().Subscribe(t => {
|
||||
if (_selected_rows.Count == 0) {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(OrderSecurityHelper.root_pair_key_fn));
|
||||
} else {
|
||||
using (_selected_params_mon.defer_updates())
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(OrderSecurityHelper.root_pair_key_fn));
|
||||
// Remove, then re-add, all selected roots from _selected_params_mon
|
||||
// to capture algo param changes at any level (this keeps the update panel in sync).
|
||||
_selected_params_mon.Clear();
|
||||
_selected_security_mon.clear();
|
||||
foreach (var r in _selected_rows) {
|
||||
_selected_params_mon.Add(r.order_tuple.root);
|
||||
_selected_security_mon.add(r.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(o => o.order.Key));
|
||||
}),
|
||||
model.on_orders_can_attach.OnDispatcher().Subscribe(table => {
|
||||
_cached_attachable = new MappedDictionary<uint, KeyValuePair<uint, bool>, bool>(table.state, kv => kv.Value);
|
||||
@@ -287,18 +271,11 @@ namespace mbts.spark {
|
||||
try {
|
||||
double price = attach_vm.price;
|
||||
uint? size = attach_vm.size;
|
||||
uint algo_template_key = attach_vm.algo_vm.algo_root.id.is_persisted ? attach_vm.algo_vm.algo_root.id.persisted : 0u;
|
||||
// Don't pass any order tuples that don't have a security defined.
|
||||
// The actual security is passed to the OrdersModel - instead of just the security key -
|
||||
// so it can prevent GlbxIceberg orders from being sent to a non-glbx exchange (such as Ice).
|
||||
/// Impl details: <see cref="OrdersModel.send_order(SecurityImmutable, OrderDefinitionImmutable?, double, uint)"/>
|
||||
var order_tups = selected_rows
|
||||
.Select(r => r.order_tuple)
|
||||
.Where(ot => ot.order.security.HasValue)
|
||||
.ToList();
|
||||
uint algo_template_key = attach_vm.algo_vm.algo_root.id.is_persisted ? attach_vm.algo_vm.algo_root.id.persisted.id_uint : 0u;
|
||||
var order_tups = selected_rows.Select(r => r.order_tuple).ToList();
|
||||
var mod_algo_vm = attach_vm.algo_vm.algo_root;
|
||||
await Task.WhenAll(order_tups.Select(t => {
|
||||
var algo = t.order.order.Def.CustomDef.actual().CloneToMutable();
|
||||
var algo = t.order.Def.CustomDef.actual().CloneToMutable();
|
||||
mod_algo_vm.assign_to(algo);
|
||||
if (algo_template_key != 0) {
|
||||
algo = new CustomOrderDefinition {
|
||||
@@ -309,13 +286,12 @@ namespace mbts.spark {
|
||||
};
|
||||
}
|
||||
return _model.send_order(
|
||||
t.order.security.Value,
|
||||
t.order.order.Def.with(
|
||||
attach_target_order_key: t.order.order.Key,
|
||||
t.order.Def.with(
|
||||
attach_target_order_key: t.order.Key,
|
||||
custom_def: algo
|
||||
),
|
||||
double.IsNaN(price) ? t.order.order.price() : price,
|
||||
size ?? t.order.order.quantity());
|
||||
double.IsNaN(price) ? t.order.price() : price,
|
||||
size ?? t.order.quantity());
|
||||
}));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Error sending attached orders: {ex}.");
|
||||
@@ -342,13 +318,14 @@ namespace mbts.spark {
|
||||
}),
|
||||
// custom templates (created by user)
|
||||
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
|
||||
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_algo_def_vm))));
|
||||
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_factory(true)))));
|
||||
// vm factory
|
||||
AlgoDefinitionVM mk_algo_def_vm(AlgoData fd) {
|
||||
var vm = AlgoDefinitionVM.create(_model, fd, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
|
||||
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
|
||||
return vm;
|
||||
}
|
||||
Func<AlgoData, AlgoDefinitionVM> mk_factory(bool connect) =>
|
||||
fd => {
|
||||
var vm = AlgoDefinitionVM.create(_model, fd, connect, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
|
||||
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
|
||||
return vm;
|
||||
};
|
||||
}
|
||||
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
|
||||
|
||||
@@ -367,7 +344,6 @@ namespace mbts.spark {
|
||||
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
|
||||
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
|
||||
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
|
||||
|
||||
}
|
||||
|
||||
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -399,7 +375,7 @@ namespace mbts.spark {
|
||||
public OrderParamsVM order_mod_vm { get; }
|
||||
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
|
||||
public OrderParamsVM attach_vm { get; }
|
||||
public IReadOnlyObservableCollection<OrderBookRowVM> selected_rows => _selected_rows;
|
||||
public ObservableCollection<OrderBookRowVM> selected_rows { get; } = new ObservableCollection<OrderBookRowVM>();
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => selected_rows.Select(r => r.order);
|
||||
public IEnumerable<OrderBookRowVM> selected_hierarchical_rows => selected_rows.SelectMany(r => new[] { r }.Concat(_row_collection.get_descendent_rows(r.order_key)));
|
||||
public OrderBookGridSettingsManager grid_settings_manager { get; }
|
||||
@@ -480,30 +456,65 @@ namespace mbts.spark {
|
||||
using (_selected_params_mon.defer_updates())
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
if (e.RemovedItems != null) {
|
||||
foreach (var row in e.RemovedItems.OfType<OrderBookRowVM>()) {
|
||||
row.is_selected = false;
|
||||
_selected_rows.Remove(row);
|
||||
_selected_params_mon.Remove(row.order_tuple.root);
|
||||
_selected_security_mon.remove(row.security);
|
||||
_attachable_selected_orders.Remove(row.order_key);
|
||||
_detachable_selected_orders.Remove(row.order_key);
|
||||
foreach (var row_vm in e.RemovedItems.OfType<OrderBookRowVM>()) {
|
||||
row_vm.is_selected = false;
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_attachable_selected_orders.Remove(row_vm.order_key);
|
||||
_detachable_selected_orders.Remove(row_vm.order_key);
|
||||
}
|
||||
}
|
||||
if (e.AddedItems != null) {
|
||||
foreach (var row in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
row.is_selected = true;
|
||||
_selected_rows.Add(row);
|
||||
_selected_params_mon.Add(row.order_tuple.root);
|
||||
_selected_security_mon.add(row.security);
|
||||
if (row.is_attachable) _attachable_selected_orders.Add(row.order_key);
|
||||
if (row.is_detachable) _detachable_selected_orders.Add(row.order_key);
|
||||
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
var root = _get_root(row_vm.order_tuple);
|
||||
row_vm.is_selected = true;
|
||||
_selected_security_mon.add(row_vm.security);
|
||||
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
|
||||
row_vm.updated += on_updated;
|
||||
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
|
||||
}
|
||||
_selected_params_mon.Add(root);
|
||||
if (row_vm.is_attachable)
|
||||
_attachable_selected_orders.Add(row_vm.order_key);
|
||||
if (row_vm.is_detachable)
|
||||
_detachable_selected_orders.Add(row_vm.order_key);
|
||||
void on_updated((OrderSecurity oldval, OrderSecurity newval) change) {
|
||||
if (!row_vm.is_selected) {
|
||||
// self destruct
|
||||
if (_on_updated_single_callback_gatekeeper.TryGetValue(row_vm.order_key, out var unsubscribe_updated_disposable)) {
|
||||
unsubscribe_updated_disposable.Dispose();
|
||||
_on_updated_single_callback_gatekeeper.Remove(row_vm.order_key);
|
||||
}
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
return;
|
||||
}
|
||||
var new_root = _get_root(change.newval);
|
||||
if (!new_root.Equals(root)) {
|
||||
using (_selected_params_mon.defer_updates()) {
|
||||
// unselect and select in one smooth motion via on_row_updated
|
||||
// because we thought there may be gremlins afoot when doing
|
||||
// on_row_unselected and then on_row_selected
|
||||
_selected_params_mon.Remove(root);
|
||||
_selected_params_mon.Add(new_root);
|
||||
root = new_root;
|
||||
}
|
||||
}
|
||||
if (change.oldval.security?.Id != change.newval.security?.Id) {
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
_selected_security_mon.remove(change.oldval.security);
|
||||
_selected_security_mon.add(change.newval.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowSelectionHelper.apply_changes(selected_rows, e);
|
||||
_can_attach_selected_orders.refresh();
|
||||
_can_detach_selected_orders.refresh();
|
||||
selected_order_changed?.Invoke(current_order);
|
||||
}
|
||||
_can_attach_selected_orders.refresh();
|
||||
_can_detach_selected_orders.refresh();
|
||||
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}");
|
||||
}
|
||||
@@ -573,11 +584,11 @@ namespace mbts.spark {
|
||||
if (!new_sort.Equals(old_sort)) {
|
||||
/// Both filter and sort have changed, use <see cref="OperableHierarchicalCollection{TRow, T, TKey}.defer_refresh"/> to avoid syncing twice.
|
||||
using (_row_collection.defer_refresh()) {
|
||||
_row_collection.filter = new_filter.build_predicate();
|
||||
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
_row_collection.comparer = new_sort.comparer;
|
||||
}
|
||||
} else {
|
||||
_row_collection.filter = new_filter.build_predicate();
|
||||
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
} else if (!new_sort.Equals(old_sort))
|
||||
_row_collection.comparer = new_sort.comparer;
|
||||
@@ -637,11 +648,17 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
OrderSecurity _get_root(OrderSecurity os) {
|
||||
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
|
||||
os = pos;
|
||||
return os;
|
||||
}
|
||||
|
||||
void _on_filter_changed() {
|
||||
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
|
||||
if (!fd.Equals(_cached_filter_data)) {
|
||||
_cached_filter_data = fd;
|
||||
_row_collection.filter = fd.build_predicate();
|
||||
_row_collection.filter = fd.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,9 +687,9 @@ namespace mbts.spark {
|
||||
// their values will be cleared (in the param grid gui), and the modifications will not be applied.
|
||||
// FYI this behavior is implemented to support 'partial edits.' (e.g., changing the 'price' for multiple orders that do not have the same 'size').
|
||||
// ----
|
||||
var update_params = selected_rows.Select(r => update_params_factory(r.order_tuple.root)).ToList();
|
||||
var update_params = selected_rows.Select(r => update_params_factory(_get_root(r.order_tuple))).ToList();
|
||||
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurityAlgo order_tuple) {
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurity order_tuple) {
|
||||
var mod_algo = order_tuple.order.Def.CustomDef.CloneToMutable();
|
||||
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
|
||||
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
|
||||
@@ -685,7 +702,7 @@ namespace mbts.spark {
|
||||
} else await Task.WhenAll(selected_rows.Select(r => _model.update_order(r.order, price)));
|
||||
}
|
||||
|
||||
IObservable<IReadOnlyList<OrderBookRowVM>> _subscribe_throttled_selected_rows() =>
|
||||
IObservable<ObservableCollection<OrderBookRowVM>> _subscribe_throttled_selected_rows() =>
|
||||
selected_rows.Observe().BufferLatest(TimeSpan.FromSeconds(0.2)).Select(_ => selected_rows);
|
||||
|
||||
static OrderBookFillsGridSettings _get_fills_settings(FillsVM fills_vm) =>
|
||||
@@ -735,15 +752,14 @@ namespace mbts.spark {
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
SecurityMD? _security_md;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions)
|
||||
public OrderParamsVM(OrderBookVM owner)
|
||||
: this(owner,
|
||||
AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions)) { }
|
||||
new AlgoData(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg)),
|
||||
editing_permissions: AlgoEditingPermissions.AllowEditValues)) { }
|
||||
|
||||
OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
_owner = owner;
|
||||
this.algo_vm = algo_vm;
|
||||
_accounts = backing(
|
||||
@@ -773,7 +789,7 @@ namespace mbts.spark {
|
||||
_size_param = new AlgoParamVM<uint?>("size", "Size", owner._selected_params_mon.size) {
|
||||
permissions = _size_permissions & _param_permissions
|
||||
};
|
||||
var price_param = new AlgoParamVM<double>("price", "Price", owner._selected_params_mon.price, spec?.tick_equality_comparer() ?? MathUtils.epsilon_eq_comparer) {
|
||||
var price_param = new AlgoParamVM<double>("price", "Price", owner._selected_params_mon.price, spec?.tick_equality_comparer() ?? MathUtils.epsilon_comparer) {
|
||||
permissions = _price_permissions & _param_permissions
|
||||
};
|
||||
_price_param = price_param;
|
||||
@@ -888,8 +904,8 @@ namespace mbts.spark {
|
||||
this.column_filters = column_filters;
|
||||
}
|
||||
|
||||
public Func<OrderSecurityRootPair, bool> build_predicate() {
|
||||
var pred = column_filters.build_predicate();
|
||||
public Func<OrderSecurity, bool> build_predicate(IMaskIndexInt32<string> dynamic_type_index) {
|
||||
var pred = column_filters.build_predicate(dynamic_type_index);
|
||||
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
|
||||
if (pred == null) {
|
||||
pred = tree_disp_pred;
|
||||
@@ -897,10 +913,10 @@ namespace mbts.spark {
|
||||
var col_pred = pred;
|
||||
pred = o => tree_disp_pred(o) && col_pred(o);
|
||||
}
|
||||
bool tree_disp_pred(OrderSecurityRootPair o) =>
|
||||
!o.order.order.has_parent() ||
|
||||
o.order.order.is_exchange_order() ||
|
||||
o.order.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
bool tree_disp_pred(OrderSecurity o) =>
|
||||
!o.order.has_parent() ||
|
||||
o.order.is_exchange_order() ||
|
||||
o.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
}
|
||||
return pred;
|
||||
}
|
||||
|
||||
@@ -119,36 +119,36 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurityRootPair, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurityRootPair>();
|
||||
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurity>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
|
||||
pb.add_time_filter(fs.time_created, o => o.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo.key));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
||||
pb.add_discrete_values(fs.users, o => o.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key, dynamic_type_index));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
||||
pb.add_price(fs.price, o => o.order.order.price(), o => o.order.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_price(fs.price, o => o.order.price(), o => o.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace bts.shell.orderbook {
|
||||
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
|
||||
return pvm;
|
||||
},
|
||||
(gen, anims) => new OrderBookGridSettings(gen, anims.empty_if_null().Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))) { algo_tree_display_mode = _algo_tree_display_mode },
|
||||
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))) { algo_tree_display_mode = _algo_tree_display_mode },
|
||||
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,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using bts.api;
|
||||
using bts.settings;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -40,14 +40,13 @@ namespace bts.shell {
|
||||
_bus.post(SparkTags.ShowWindow, w);
|
||||
}
|
||||
|
||||
IShellWindow _create(PAndLStartupData state) =>
|
||||
new PAndLWindow(
|
||||
_orders_model,
|
||||
this,
|
||||
_bus,
|
||||
state,
|
||||
SparkCommands.ShowPAndL.icon,
|
||||
_settings_hub.create_pandl_settings_manager(state.grid));
|
||||
IShellWindow _create(PAndLStartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.pnl_grid,
|
||||
set: (us, gs) => us.with(provided_pnl_gridsettings: gs));
|
||||
return new PAndLWindow(_orders_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class PAndLStartupData {
|
||||
@@ -93,12 +92,10 @@ namespace bts.shell {
|
||||
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 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 raw_day_net_position => new GridColumnSettings(col_id(Columns.RawDayPos), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
|
||||
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 pnl_price => new GridColumnSettings(col_id(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings has_day_trades => new GridColumnSettings(col_id(Columns.TradedToday), "Traded Today", width: 100, horizontal_alignment: System.Windows.HorizontalAlignment.Center);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
@@ -120,12 +117,10 @@ namespace bts.shell {
|
||||
yield return start_pnl;
|
||||
yield return day_long_pnl;
|
||||
yield return day_short_pnl;
|
||||
yield return raw_day_net_position;
|
||||
yield return day_pnl;
|
||||
yield return total_net_pos;
|
||||
yield return total_pnl;
|
||||
yield return pnl_price;
|
||||
yield return has_day_trades;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,8 +145,6 @@ namespace bts.shell {
|
||||
public UIntMinMaxState day_long_pos;
|
||||
[ProtoMember(8)]
|
||||
public UIntMinMaxState day_short_pos;
|
||||
[ProtoMember(16)]
|
||||
public DoubleMinMaxState raw_day_net_pos;
|
||||
[ProtoMember(9)]
|
||||
public DoubleMinMaxState day_pnl;
|
||||
[ProtoMember(10)]
|
||||
@@ -169,11 +162,7 @@ namespace bts.shell {
|
||||
public bool[] has_day_trades;
|
||||
}
|
||||
|
||||
// migration rules
|
||||
[Export(typeof(IPAndLGridSettingsMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : IPAndLGridSettingsMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
settings.add_missing_columns(PAndLStartupData.DefaultGridSettings.columns, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.controls;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
public enum PNLCalculationType {
|
||||
None,
|
||||
Bid,
|
||||
Ask,
|
||||
Opposite,
|
||||
Same,
|
||||
[Description("Cascade")]
|
||||
Waterfall,
|
||||
Last,
|
||||
Mid,
|
||||
Settle
|
||||
}
|
||||
|
||||
readonly struct PositionPriceSize : IEquatable<PositionPriceSize> {
|
||||
public static readonly PositionPriceSize empty = new PositionPriceSize(0, 0, 0, 0);
|
||||
public readonly double long_pos, short_pos, long_avg, short_avg;
|
||||
public double net_pos => long_pos - short_pos;
|
||||
public bool has_position => long_pos > 0 || short_pos > 0;
|
||||
|
||||
public PositionPriceSize(double long_pos, double long_avg, double short_pos, double short_avg) {
|
||||
this.long_pos = long_pos;
|
||||
this.long_avg = long_avg;
|
||||
this.short_pos = short_pos;
|
||||
this.short_avg = short_avg;
|
||||
}
|
||||
|
||||
public bool Equals(PositionPriceSize other) =>
|
||||
MathUtils.epsilon_equals(long_pos, other.long_pos) &&
|
||||
MathUtils.epsilon_equals(long_avg, other.long_avg) &&
|
||||
MathUtils.epsilon_equals(short_pos, other.short_pos) &&
|
||||
MathUtils.epsilon_equals(short_avg, other.short_avg);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PositionPriceSize other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
long_pos.GetHashCode(),
|
||||
long_avg.GetHashCode(),
|
||||
short_pos.GetHashCode(),
|
||||
short_avg.GetHashCode());
|
||||
|
||||
public static bool operator ==(PositionPriceSize a, PositionPriceSize b) => a.Equals(b);
|
||||
public static bool operator !=(PositionPriceSize a, PositionPriceSize b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
readonly struct PNLRowKey : IEquatable<PNLRowKey>, IComparable<PNLRowKey>, IComparable {
|
||||
public static readonly PNLRowKey empty = new PNLRowKey();
|
||||
static readonly IComparer<PNLRowKey> _comparer_inst = new ComparerBuilder<PNLRowKey>(
|
||||
new[] {
|
||||
Comparer<uint>.Default.wrap<uint, PNLRowKey>(r => r.account ?? 0),
|
||||
Comparer<uint>.Default.wrap<uint, PNLRowKey>(r => r.security ?? 0)
|
||||
}).to_comparer();
|
||||
|
||||
public readonly Venue? exchange;
|
||||
public readonly Security.ContractOneofCase? security_type;
|
||||
public readonly string symbol;
|
||||
public readonly PNLKey data_key;
|
||||
public uint? security => data_key.security_id;
|
||||
public uint? account => data_key.account_id;
|
||||
|
||||
public PNLRowKey(
|
||||
Venue? exchange = null,
|
||||
Security.ContractOneofCase? security_type = null,
|
||||
string symbol = null,
|
||||
uint? security = null,
|
||||
uint? account = null) {
|
||||
//
|
||||
this.exchange = exchange;
|
||||
this.security_type = security_type;
|
||||
this.symbol = symbol;
|
||||
data_key = new PNLKey(account.Value, security.Value);
|
||||
}
|
||||
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is PNLRowKey other ? CompareTo(other) : 1;
|
||||
|
||||
public int CompareTo(PNLRowKey other) =>
|
||||
_comparer_inst.Compare(this, other);
|
||||
|
||||
public bool Equals(PNLRowKey other) =>
|
||||
exchange == other.exchange &&
|
||||
security_type == other.security_type &&
|
||||
symbol == other.symbol &&
|
||||
security == other.security &&
|
||||
account == other.account;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLRowKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
exchange?.GetHashCode() ?? 0,
|
||||
security_type?.GetHashCode() ?? 0,
|
||||
symbol?.GetHashCode() ?? 0,
|
||||
security?.GetHashCode() ?? 0,
|
||||
account?.GetHashCode() ?? 0);
|
||||
|
||||
public static bool operator ==(PNLRowKey a, PNLRowKey b) => a.Equals(b);
|
||||
public static bool operator !=(PNLRowKey a, PNLRowKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
readonly struct WorkingOrderData {
|
||||
public readonly WorkingOrderSideData long_data, short_data;
|
||||
|
||||
WorkingOrderData(WorkingOrderSideData long_data, WorkingOrderSideData short_data) {
|
||||
this.long_data = long_data;
|
||||
this.short_data = short_data;
|
||||
}
|
||||
|
||||
public static WorkingOrderData Empty =>
|
||||
new WorkingOrderData(WorkingOrderSideData.EmptyLong, WorkingOrderSideData.EmptyShort);
|
||||
|
||||
public double top_bid =>
|
||||
long_data.data.Count > 0 ?
|
||||
long_data.data.First().Key :
|
||||
double.NaN;
|
||||
|
||||
public double top_ask =>
|
||||
short_data.data.Count > 0 ?
|
||||
short_data.data.First().Key :
|
||||
double.NaN;
|
||||
|
||||
public PositionPriceSize summary =>
|
||||
new PositionPriceSize(long_data.working_total, long_data.avg_price, short_data.working_total, short_data.avg_price);
|
||||
|
||||
public static WorkingOrderData Create(IEnumerable<OrderRecordImmutable> orders) =>
|
||||
orders.Aggregate(Empty, (seed, order) => seed.Add(order));
|
||||
|
||||
public WorkingOrderData Add(OrderRecordImmutable order) {
|
||||
if (order.working_quantity() <= 0)
|
||||
return new WorkingOrderData(long_data, short_data);
|
||||
switch (order.side()) {
|
||||
case Side.Buy: return new WorkingOrderData(long_data.Add(order), short_data);
|
||||
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order));
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
|
||||
}
|
||||
}
|
||||
|
||||
public WorkingOrderData Remove(OrderRecordImmutable order) {
|
||||
if (order.working_quantity() <= 0)
|
||||
return new WorkingOrderData(long_data, short_data);
|
||||
switch (order.side()) {
|
||||
case Side.Buy: return new WorkingOrderData(long_data.Remove(order.Key, order.price()), short_data);
|
||||
case Side.Sell: return new WorkingOrderData(long_data, short_data.Remove(order.Key, order.price()));
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct WorkingOrderSideData {
|
||||
public readonly double avg_price;
|
||||
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordImmutable>> data;
|
||||
public readonly int working_total;
|
||||
|
||||
WorkingOrderSideData(double avg_price, int working_total, ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordImmutable>> data) {
|
||||
this.avg_price = avg_price;
|
||||
this.working_total = working_total;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static WorkingOrderSideData EmptyLong => new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(new ReverseComparer<double>(OrderDataHelper.PriceComparer)));
|
||||
public static WorkingOrderSideData EmptyShort => new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(OrderDataHelper.PriceComparer));
|
||||
|
||||
public double top_price =>
|
||||
data.Count > 0 ?
|
||||
data.First().Key :
|
||||
double.NaN;
|
||||
|
||||
public int top_volume =>
|
||||
data.Count > 0 ?
|
||||
data.Values.First().Values.Sum(o => o.working_quantity()) :
|
||||
0;
|
||||
|
||||
public WorkingOrderSideData Add(OrderRecordImmutable order_record) {
|
||||
uint order = order_record.Key;
|
||||
int working_qty = order_record.working_quantity();
|
||||
double price = order_record.price();
|
||||
if (working_qty <= 0)
|
||||
throw new ArgumentException(nameof(working_qty));
|
||||
if (working_total == 0) {
|
||||
return new WorkingOrderSideData(price, working_qty, data.Add(price, ImmutableDictionary<uint, OrderRecordImmutable>.Empty.Add(order, order_record)));
|
||||
} else return new WorkingOrderSideData(
|
||||
avg_price + ((price - avg_price) * ((double)working_qty / (working_total + working_qty))),
|
||||
working_total + working_qty,
|
||||
data.SetItem(price, data.TryGetValue(price, out var curr) ?
|
||||
curr.Add(order, order_record) :
|
||||
ImmutableDictionary<uint, OrderRecordImmutable>.Empty.Add(order, order_record)));
|
||||
}
|
||||
|
||||
public WorkingOrderSideData Remove(uint order, double price) {
|
||||
var tmp_data = data;
|
||||
if (!tmp_data.TryGetValue(price, out var wdata))
|
||||
throw new KeyNotFoundException(nameof(price));
|
||||
if (!wdata.TryGetValue(order, out var worder))
|
||||
throw new KeyNotFoundException(nameof(order));
|
||||
var wqty = worder.working_quantity();
|
||||
wdata = wdata.Remove(order);
|
||||
if (wdata.Count == 0)
|
||||
tmp_data = tmp_data.Remove(price);
|
||||
if (tmp_data.Count == 0)
|
||||
return new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(data.KeyComparer));
|
||||
return new WorkingOrderSideData(
|
||||
avg_price - ((price - avg_price) * (wqty / (working_total - wqty))),
|
||||
working_total - wqty,
|
||||
tmp_data);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
if (data.Count > 0) {
|
||||
var best_order = data.Values.First().Values.First();
|
||||
return OrderUtils.order_collection_summary(best_order.price(), avg_price, best_order.working_quantity(), working_total, SecurityDataHelper.Defaults.ContractSpecificationImmutable);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct PNLRowData : IEquatable<PNLRowData> {
|
||||
public static readonly Func<PNLRowData, PNLKey> key_fn = d => d.key;
|
||||
public static IComparer<PNLRowData> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowData>(d => d.key);
|
||||
|
||||
static readonly WaterfallPriceFlags[] _waterfall_preferences = new[] {
|
||||
WaterfallPriceFlags.LastInsideSpread,
|
||||
WaterfallPriceFlags.Mid,
|
||||
WaterfallPriceFlags.LastGTEBid,
|
||||
WaterfallPriceFlags.Bid,
|
||||
WaterfallPriceFlags.LastLTEAsk,
|
||||
WaterfallPriceFlags.Ask,
|
||||
WaterfallPriceFlags.Last,
|
||||
WaterfallPriceFlags.Settle
|
||||
};
|
||||
|
||||
public readonly PNLKey key;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly string account_name;
|
||||
public readonly PNLCalculationType calc_type;
|
||||
public readonly PositionPriceSize day, raw_day, sod;
|
||||
public readonly WorkingOrderData working;
|
||||
|
||||
public readonly double
|
||||
cpp,
|
||||
bid_price,
|
||||
ask_price,
|
||||
last_price,
|
||||
settlement_price;
|
||||
|
||||
public PNLRowData(
|
||||
PNLKey key,
|
||||
double cpp = 0,
|
||||
double bid_price = 0,
|
||||
double ask_price = 0,
|
||||
double last_price = 0,
|
||||
double settlement_price = 0,
|
||||
PositionPriceSize? sod = null,
|
||||
PositionPriceSize? day = null,
|
||||
PositionPriceSize? raw_day = null,
|
||||
WorkingOrderData? working = null,
|
||||
SecurityImmutable? security = null,
|
||||
string account_name = null,
|
||||
PNLCalculationType calc_type = PNLCalculationType.Mid) {
|
||||
//
|
||||
this.key = key;
|
||||
this.cpp = cpp;
|
||||
this.bid_price = bid_price;
|
||||
this.ask_price = ask_price;
|
||||
this.last_price = last_price;
|
||||
this.settlement_price = settlement_price;
|
||||
this.sod = sod ?? PositionPriceSize.empty;
|
||||
this.day = day ?? PositionPriceSize.empty;
|
||||
this.raw_day = raw_day ?? PositionPriceSize.empty;
|
||||
this.working = working ?? WorkingOrderData.Empty;
|
||||
this.security = security;
|
||||
this.account_name = account_name;
|
||||
this.calc_type = calc_type;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum WaterfallPriceFlags {
|
||||
None = 0,
|
||||
Bid = 1,
|
||||
Ask = 1 << 1,
|
||||
Last = 1 << 2,
|
||||
Settle = 1 << 3,
|
||||
Mid = Bid | Ask,
|
||||
LastGTEBid = Bid | Last | 1 << 4,
|
||||
LastLTEAsk = Ask | Last | 1 << 5,
|
||||
LastInsideSpread = LastGTEBid | LastLTEAsk,
|
||||
BidGTESettle = Bid | Settle | 1 << 6,
|
||||
AskLTESettle = Ask | Settle | 1 << 7
|
||||
}
|
||||
|
||||
/// Market/Calc
|
||||
public double mid_price => (bid_price + ask_price) / 2;
|
||||
public double pnl_price {
|
||||
get {
|
||||
if (!day.has_position && !sod.has_position)
|
||||
return double.NaN;
|
||||
switch (calc_type) {
|
||||
case PNLCalculationType.Bid: return bid_price;
|
||||
case PNLCalculationType.Ask: return ask_price;
|
||||
case PNLCalculationType.Mid: return mid_price;
|
||||
case PNLCalculationType.Last: return last_price;
|
||||
// waited averages must be used when the pnl price varies, depending on the side.
|
||||
case PNLCalculationType.Same: return MathUtils.weighted_average(bid_price, day_long_pos, ask_price, day_short_pos);
|
||||
case PNLCalculationType.Opposite: return MathUtils.weighted_average(ask_price, day_long_pos, bid_price, day_short_pos);
|
||||
case PNLCalculationType.Settle: return settlement_price;
|
||||
case PNLCalculationType.Waterfall:
|
||||
default: return _waterfall_price();
|
||||
}
|
||||
}
|
||||
}
|
||||
public double long_pnl_price {
|
||||
get {
|
||||
switch (calc_type) {
|
||||
case PNLCalculationType.Same: return bid_price;
|
||||
case PNLCalculationType.Opposite: return ask_price;
|
||||
default: return pnl_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
public double short_pnl_price {
|
||||
get {
|
||||
switch (calc_type) {
|
||||
case PNLCalculationType.Same: return ask_price;
|
||||
case PNLCalculationType.Opposite: return bid_price;
|
||||
default: return pnl_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Sod
|
||||
public double sod_pnl => cpp *
|
||||
DoubleUtil.nan_to_zero_sum(
|
||||
sod_long_avg_price_delta_raw * sod_long_pos,
|
||||
sod_short_avg_price_delta_raw * sod_short_pos
|
||||
);
|
||||
// long
|
||||
public double sod_long_pos => sod.long_pos;
|
||||
public double sod_long_avg_price => cpp * sod.long_avg;
|
||||
public double sod_long_avg_price_raw => sod.long_avg;
|
||||
public double sod_long_avg_price_delta => cpp * sod_long_avg_price_delta_raw;
|
||||
public double sod_long_avg_price_delta_raw => sod.long_pos == 0 ? double.NaN : long_pnl_price - sod.long_avg;
|
||||
// short
|
||||
public double sod_short_pos => sod.short_pos;
|
||||
public double sod_short_avg_price => cpp * sod.short_avg;
|
||||
public double sod_short_avg_price_raw => sod.short_avg;
|
||||
public double sod_short_avg_price_delta => cpp * sod_short_avg_price_delta_raw;
|
||||
public double sod_short_avg_price_delta_raw => sod.short_pos == 0 ? double.NaN : sod.short_avg - short_pnl_price;
|
||||
// net
|
||||
public double sod_net_pos => sod.net_pos;
|
||||
public double sod_net_avg_price_delta => cpp * sod_net_avg_price_delta_raw;
|
||||
public double sod_net_avg_price_delta_raw =>
|
||||
MathUtils.weighted_average(
|
||||
sod_long_avg_price_delta_raw, sod.long_pos,
|
||||
sod_short_avg_price_delta_raw, sod.short_pos);
|
||||
|
||||
/// Day
|
||||
public double day_pnl => cpp *
|
||||
DoubleUtil.nan_to_zero_sum(
|
||||
day_long_avg_price_delta_raw * day_long_pos,
|
||||
day_short_avg_price_delta_raw * day_short_pos
|
||||
);
|
||||
// long
|
||||
public double day_long_pos => day.long_pos;
|
||||
public double day_long_avg_price => cpp * day.long_avg;
|
||||
public double day_long_avg_price_raw => day.long_avg;
|
||||
public double day_long_avg_price_delta => cpp * day_long_avg_price_delta_raw;
|
||||
public double day_long_avg_price_delta_raw => day.long_pos == 0 ? double.NaN : long_pnl_price - day.long_avg;
|
||||
// short
|
||||
public double day_short_pos => day.short_pos;
|
||||
public double day_short_avg_price => cpp * day.short_avg;
|
||||
public double day_short_avg_price_raw => day.short_avg;
|
||||
public double day_short_avg_price_delta => cpp * day_short_avg_price_delta_raw;
|
||||
public double day_short_avg_price_delta_raw => day.short_pos == 0 ? double.NaN : day.short_avg - short_pnl_price;
|
||||
public bool has_day_trades => day.long_pos > 0 || day.short_pos > 0;
|
||||
// net
|
||||
public double day_net_pos => day.net_pos;
|
||||
public double day_net_avg_price_delta => cpp * day_net_avg_price_delta_raw;
|
||||
public double day_net_avg_price_delta_raw =>
|
||||
MathUtils.weighted_average(
|
||||
day_long_avg_price_delta_raw, day.long_pos,
|
||||
day_short_avg_price_delta_raw, day.short_pos);
|
||||
|
||||
/// Raw Day
|
||||
public double raw_day_long_pos => raw_day.long_pos;
|
||||
public double raw_day_short_pos => raw_day.short_pos;
|
||||
public double raw_day_net_pos => raw_day.net_pos;
|
||||
|
||||
/// Total
|
||||
public double total_pnl => DoubleUtil.nan_to_zero_sum(sod_pnl, day_pnl);
|
||||
// long
|
||||
public double total_long_pos => DoubleUtil.nan_to_zero_sum(sod.long_pos, day.long_pos);
|
||||
public double total_long_avg_price => cpp * total_long_avg_price_raw;
|
||||
public double total_long_avg_price_raw => DoubleUtil.nan_to_zero_sum(sod.long_avg, day.long_avg);
|
||||
public double total_long_avg_price_delta => cpp * total_long_avg_price_delta_raw;
|
||||
public double total_long_avg_price_delta_raw =>
|
||||
MathUtils.weighted_average(
|
||||
sod_long_avg_price_delta_raw, sod.long_pos,
|
||||
day_long_avg_price_delta_raw, day.long_pos);
|
||||
// short
|
||||
public double total_short_pos => DoubleUtil.nan_to_zero_sum(sod.short_pos, day.short_pos);
|
||||
public double total_short_avg_price => cpp * total_short_avg_price_raw;
|
||||
public double total_short_avg_price_raw => DoubleUtil.nan_to_zero_sum(sod.short_avg, day.short_avg);
|
||||
public double total_short_avg_price_delta => cpp * total_short_avg_price_delta_raw;
|
||||
public double total_short_avg_price_delta_raw =>
|
||||
MathUtils.weighted_average(
|
||||
sod_short_avg_price_delta_raw, sod.short_pos,
|
||||
day_short_avg_price_delta_raw, day.short_pos);
|
||||
// net
|
||||
public double total_net_pos => DoubleUtil.nan_to_zero_sum(sod.long_pos, -sod.short_pos, day.long_pos, -day.short_pos);
|
||||
public double total_net_avg_price_delta => cpp * total_net_avg_price_delta_raw;
|
||||
public double total_net_avg_price_delta_raw =>
|
||||
MathUtils.weighted_average(
|
||||
total_long_avg_price_delta_raw, total_long_pos,
|
||||
total_short_avg_price_delta_raw, total_short_pos);
|
||||
|
||||
#region Working
|
||||
// long
|
||||
public double working_long_pos => working.long_data.working_total;
|
||||
public double working_long_avg_price => cpp * working_long_avg_price_raw;
|
||||
public double working_long_top_price => cpp * working_long_top_price_raw;
|
||||
public double working_long_top_price_raw => working.top_bid;
|
||||
public double working_long_avg_price_raw => working.long_data.avg_price;
|
||||
public double working_long_avg_price_delta => cpp * working_long_avg_price_delta_raw;
|
||||
public double working_long_avg_price_delta_raw => working_long_pos == 0 ? double.NaN : long_pnl_price - working_long_avg_price_raw;
|
||||
// short
|
||||
public double working_short_pos => working.short_data.working_total;
|
||||
public double working_short_avg_price => cpp * working_short_avg_price_raw;
|
||||
public double working_short_top_price => cpp * working_short_top_price_raw;
|
||||
public double working_short_top_price_raw => working.top_ask;
|
||||
public double working_short_avg_price_raw => working.short_data.avg_price;
|
||||
public double working_short_avg_price_delta => cpp * working_short_avg_price_delta_raw;
|
||||
public double working_short_avg_price_delta_raw => working_short_pos == 0 ? double.NaN : working_short_avg_price_raw - short_pnl_price;
|
||||
// net
|
||||
public double working_net_pos => working_long_pos - working_short_pos;
|
||||
public double working_net_avg_price_delta => cpp * working_net_avg_price_delta_raw;
|
||||
public double working_net_avg_price_delta_raw =>
|
||||
MathUtils.weighted_average(
|
||||
working_long_avg_price_delta_raw, working_long_pos,
|
||||
working_short_avg_price_delta_raw, working_short_pos);
|
||||
#endregion
|
||||
|
||||
WaterfallPriceFlags _waterfall_flags() {
|
||||
var flags = WaterfallPriceFlags.None;
|
||||
if (!double.IsNaN(bid_price)) flags |= WaterfallPriceFlags.Bid;
|
||||
if (!double.IsNaN(ask_price)) flags |= WaterfallPriceFlags.Ask;
|
||||
if (!double.IsNaN(last_price)) flags |= WaterfallPriceFlags.Last;
|
||||
if (last_price >= bid_price) flags |= WaterfallPriceFlags.LastGTEBid;
|
||||
if (last_price <= ask_price) flags |= WaterfallPriceFlags.LastLTEAsk;
|
||||
if (!double.IsNaN(settlement_price)) flags |= WaterfallPriceFlags.Settle;
|
||||
if (bid_price >= settlement_price) flags |= WaterfallPriceFlags.BidGTESettle;
|
||||
if (ask_price <= settlement_price) flags |= WaterfallPriceFlags.AskLTESettle;
|
||||
return flags;
|
||||
}
|
||||
|
||||
double _waterfall_price() {
|
||||
var flags = _waterfall_flags();
|
||||
// return price for the best match
|
||||
for (int i = 0; i < _waterfall_preferences.Length; i++) {
|
||||
if ((_waterfall_preferences[i] & flags) == _waterfall_preferences[i])
|
||||
return _waterfall_price(_waterfall_preferences[i]);
|
||||
}
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
double _waterfall_price(WaterfallPriceFlags ltp) {
|
||||
switch (ltp) {
|
||||
case WaterfallPriceFlags.Last:
|
||||
case WaterfallPriceFlags.LastInsideSpread:
|
||||
case WaterfallPriceFlags.LastGTEBid:
|
||||
case WaterfallPriceFlags.LastLTEAsk: return last_price;
|
||||
case WaterfallPriceFlags.Mid: return mid_price;
|
||||
case WaterfallPriceFlags.Bid:
|
||||
case WaterfallPriceFlags.BidGTESettle: return bid_price;
|
||||
case WaterfallPriceFlags.Ask:
|
||||
case WaterfallPriceFlags.AskLTESettle: return ask_price;
|
||||
case WaterfallPriceFlags.Settle: return settlement_price;
|
||||
default: return double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowData other) =>
|
||||
key == other.key &&
|
||||
cpp == other.cpp &&
|
||||
security.HasValue == other.security.HasValue &&
|
||||
account_name == other.account_name &&
|
||||
sod == other.sod &&
|
||||
day == other.day &&
|
||||
raw_day == other.raw_day &&
|
||||
working.summary == other.working.summary;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLRowData other && Equals(other);
|
||||
|
||||
// assuming callers will not combine different values with the same key,
|
||||
// the other components shouldn't be included in the hash calculation.
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public static bool operator ==(PNLRowData a, PNLRowData b) => a.Equals(b);
|
||||
public static bool operator !=(PNLRowData a, PNLRowData b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static class PNLRowDataFactory {
|
||||
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value) t) =>
|
||||
new PNLRowData(new PNLKey(t.value.Pos.AccountId, t.value.Pos.SecurityId),
|
||||
t.value.CashValue,
|
||||
t.value.BidPrice,
|
||||
t.value.AskPrice,
|
||||
t.value.LastPrice,
|
||||
t.value.SettlementPrice,
|
||||
new PositionPriceSize(t.value.Pos.SodLongPos, t.value.Pos.SodLongAvgPrice, t.value.Pos.SodShortPos, t.value.Pos.SodShortAvgPrice),
|
||||
new PositionPriceSize(t.value.Pos.DayLongPos, t.value.Pos.DayLongAvgPrice, t.value.Pos.DayShortPos, t.value.Pos.DayShortAvgPrice),
|
||||
t.value.Pos.raw_day_pps(),
|
||||
WorkingOrderData.Empty,
|
||||
t.sec,
|
||||
t.value.Pos.Account);
|
||||
}
|
||||
|
||||
static class PNLDataExtensions {
|
||||
// Note: PNLRowData is passed by ref because it is fairly large struct, and since it is a readonly struct, we know it cannot be modified (so might as well avoid excessive copying).
|
||||
public static PNLRowData with(this PNLRowData pnl_data, SecurityImmutable? security = null, string account_name = null, WorkingOrderData? working = null, PNLCalculationType? calc_type = null) =>
|
||||
new PNLRowData(
|
||||
pnl_data.key,
|
||||
pnl_data.cpp,
|
||||
pnl_data.bid_price,
|
||||
pnl_data.ask_price,
|
||||
pnl_data.last_price,
|
||||
pnl_data.settlement_price,
|
||||
pnl_data.sod,
|
||||
pnl_data.day,
|
||||
pnl_data.raw_day,
|
||||
working ?? pnl_data.working,
|
||||
security ?? pnl_data.security,
|
||||
account_name ?? pnl_data.account_name,
|
||||
calc_type ?? pnl_data.calc_type);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
readonly struct PNLKey : IEquatable<PNLKey>, IComparable<PNLKey>, IComparable {
|
||||
public readonly uint account_id, security_id;
|
||||
|
||||
public PNLKey(uint account_id, uint security_id) {
|
||||
this.account_id = account_id;
|
||||
this.security_id = security_id;
|
||||
}
|
||||
|
||||
public int CompareTo(PNLKey other) {
|
||||
int c = account_id.CompareTo(other.account_id);
|
||||
if (c == 0)
|
||||
c = security_id.CompareTo(other.security_id);
|
||||
return c;
|
||||
}
|
||||
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is PNLKey other ? CompareTo(other) : 1;
|
||||
|
||||
public bool Equals(PNLKey other) =>
|
||||
security_id == other.security_id &&
|
||||
account_id == other.account_id;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
(security_id.GetHashCode() % MathUtils.Primes.P_65449) |
|
||||
((account_id.GetHashCode() % MathUtils.Primes.P_65449) << 16);
|
||||
|
||||
public override string ToString() =>
|
||||
$"Account: {account_id}, Security: {security_id}";
|
||||
|
||||
public static bool operator ==(PNLKey a, PNLKey b) => a.Equals(b);
|
||||
public static bool operator !=(PNLKey a, PNLKey b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
readonly struct PNLNormalizedData : IEquatable<PNLNormalizedData> {
|
||||
public static readonly PNLNormalizedData zero =
|
||||
new PNLNormalizedData(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
public PNLNormalizedData(
|
||||
decimal day_pnl,
|
||||
decimal day_long_pos,
|
||||
decimal day_short_pos,
|
||||
decimal raw_day_long_pos,
|
||||
decimal raw_day_short_pos,
|
||||
decimal sod_pnl,
|
||||
decimal sod_long_pos,
|
||||
decimal sod_short_pos,
|
||||
decimal working_long_pos = 0,
|
||||
decimal working_short_pos = 0) {
|
||||
//
|
||||
this.day_pnl = day_pnl;
|
||||
this.day_long_pos = day_long_pos;
|
||||
this.day_short_pos = day_short_pos;
|
||||
this.raw_day_long_pos = raw_day_long_pos;
|
||||
this.raw_day_short_pos = raw_day_short_pos;
|
||||
this.sod_pnl = sod_pnl;
|
||||
this.sod_long_pos = sod_long_pos;
|
||||
this.sod_short_pos = sod_short_pos;
|
||||
this.working_long_pos = working_long_pos;
|
||||
this.working_short_pos = working_short_pos;
|
||||
}
|
||||
|
||||
// sod
|
||||
public decimal sod_pnl { get; }
|
||||
public decimal sod_long_pos { get; }
|
||||
public decimal sod_short_pos { get; }
|
||||
public decimal sod_net_pos => sod_long_pos - sod_short_pos;
|
||||
// day
|
||||
public decimal day_pnl { get; }
|
||||
public decimal day_long_pos { get; }
|
||||
public decimal day_short_pos { get; }
|
||||
public decimal day_net_pos => day_long_pos - day_short_pos;
|
||||
// raw day
|
||||
public decimal raw_day_long_pos { get; }
|
||||
public decimal raw_day_short_pos { get; }
|
||||
public decimal raw_day_net_pos => raw_day_long_pos - raw_day_short_pos;
|
||||
// total
|
||||
public decimal total_pnl => sod_pnl + day_pnl;
|
||||
public decimal total_net_pos => total_long_pos - total_short_pos;
|
||||
public decimal total_long_pos => sod_long_pos + day_long_pos;
|
||||
public decimal total_short_pos => sod_short_pos + day_short_pos;
|
||||
// working
|
||||
public decimal working_long_pos { get; }
|
||||
public decimal working_net_pos => working_long_pos - working_short_pos;
|
||||
public decimal working_short_pos { get; }
|
||||
|
||||
public bool Equals(PNLNormalizedData other) =>
|
||||
day_pnl == other.day_pnl &&
|
||||
day_long_pos == other.day_long_pos &&
|
||||
day_short_pos == other.day_short_pos &&
|
||||
raw_day_long_pos == other.raw_day_long_pos &&
|
||||
raw_day_short_pos == other.raw_day_short_pos &&
|
||||
sod_pnl == other.sod_pnl &&
|
||||
sod_long_pos == other.sod_long_pos &&
|
||||
sod_short_pos == other.sod_short_pos &&
|
||||
working_long_pos == other.working_long_pos &&
|
||||
working_short_pos == other.working_short_pos;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLNormalizedData other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
day_pnl.GetHashCode(),
|
||||
sod_pnl.GetHashCode());
|
||||
|
||||
public override string ToString() =>
|
||||
string.Join(",", new[] {
|
||||
$"SOD PNL : {sod_pnl}",
|
||||
$"SOD Long : {sod_long_pos}",
|
||||
$"SOD Short: {sod_short_pos}",
|
||||
$"Day PNL : {day_pnl}",
|
||||
$"Day Long : {day_long_pos}",
|
||||
$"Day Short: {day_short_pos}",
|
||||
$"Raw Day Long : {raw_day_long_pos}",
|
||||
$"Raw Day Short: {raw_day_short_pos}",
|
||||
$"Work Long : {working_long_pos}",
|
||||
$"Work Short: {working_short_pos}",
|
||||
});
|
||||
|
||||
public static bool operator ==(PNLNormalizedData a, PNLNormalizedData b) => a.Equals(b);
|
||||
public static bool operator !=(PNLNormalizedData a, PNLNormalizedData b) => !a.Equals(b);
|
||||
|
||||
public static PNLNormalizedData operator +(PNLNormalizedData a, PNLNormalizedData b) =>
|
||||
new PNLNormalizedData(
|
||||
a.day_pnl + b.day_pnl,
|
||||
a.day_long_pos + b.day_long_pos,
|
||||
a.day_short_pos + b.day_short_pos,
|
||||
a.raw_day_long_pos + b.raw_day_long_pos,
|
||||
a.raw_day_short_pos + b.raw_day_short_pos,
|
||||
a.sod_pnl + b.sod_pnl,
|
||||
a.sod_long_pos + b.sod_long_pos,
|
||||
a.sod_short_pos + b.sod_short_pos,
|
||||
a.working_long_pos + b.working_long_pos,
|
||||
a.working_short_pos + b.working_short_pos);
|
||||
|
||||
public static PNLNormalizedData operator -(PNLNormalizedData a, PNLNormalizedData b) =>
|
||||
new PNLNormalizedData(
|
||||
a.day_pnl - b.day_pnl,
|
||||
a.day_long_pos - b.day_long_pos,
|
||||
a.day_short_pos - b.day_short_pos,
|
||||
a.raw_day_long_pos - b.raw_day_long_pos,
|
||||
a.raw_day_short_pos - b.raw_day_short_pos,
|
||||
a.sod_pnl - b.sod_pnl,
|
||||
a.sod_long_pos - b.sod_long_pos,
|
||||
a.sod_short_pos - b.sod_short_pos,
|
||||
a.working_long_pos - b.working_long_pos,
|
||||
a.working_short_pos - b.working_short_pos);
|
||||
}
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.settings;
|
||||
using bts.shell.pnl;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell {
|
||||
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
|
||||
const string _window_type = "PNL", _window_title = "PNL";
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly PNLConfig _pnl_conf;
|
||||
readonly IShellWindowThreadIDProvider _thread_id_provider;
|
||||
|
||||
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
|
||||
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
|
||||
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(PNLSaveState state) =>
|
||||
Task.FromResult(_create(state.settings));
|
||||
|
||||
void _show(PNLSettings settings) =>
|
||||
_bus.post(SparkTags.ShowWindow, _create(settings));
|
||||
|
||||
IShellWindow _create(PNLSettings settings) {
|
||||
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
|
||||
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
|
||||
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
|
||||
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
|
||||
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
|
||||
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
|
||||
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
|
||||
var content = new PNLView {
|
||||
DataContext = vm
|
||||
};
|
||||
vm.sorting_changed += sh => {
|
||||
try {
|
||||
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
|
||||
} catch { }
|
||||
};
|
||||
vm.SubscribeDisposed(
|
||||
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
|
||||
Func<PNLSaveState> get_save_state = () => {
|
||||
var cols = cached_columns.ToArray(c => c.create());
|
||||
var filters = vm.get_filters_states();
|
||||
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
|
||||
var calc_type = vm.calculation_type;
|
||||
var state = settings_manager.instance_settings;
|
||||
if (state == null) {
|
||||
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
|
||||
var defaults = settings_manager.default_settings;
|
||||
if (calc_type == defaults.calculation_type &&
|
||||
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
|
||||
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
||||
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
|
||||
return new PNLSaveState { use_defaults = true, settings = null };
|
||||
}
|
||||
state = defaults;
|
||||
}
|
||||
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
|
||||
state = state.with(
|
||||
sorting_criterion,
|
||||
vm.anchors,
|
||||
filters,
|
||||
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
|
||||
calc_type,
|
||||
vm.header_row_display);
|
||||
return new PNLSaveState { use_defaults = false, settings = state };
|
||||
};
|
||||
IEnumerable<SparkCommand> commands = new[] {
|
||||
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
|
||||
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
|
||||
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
|
||||
show_in_context_menu = _pnl_conf.show_pause_resume,
|
||||
show_in_toolbar = _pnl_conf.show_pause_resume
|
||||
},
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
settings_manager.instance_settings = get_save_state().settings;
|
||||
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
|
||||
settings_manager,
|
||||
us => us.pnl,
|
||||
UserSettings.FileExtensions.PNL,
|
||||
s => new PNLSettingsVM(s));
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
return dialog;
|
||||
void on_request_close(object sender, EventArgs e) =>
|
||||
WindowUtilities.force_close(dialog);
|
||||
}))
|
||||
};
|
||||
IDisposable dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
|
||||
vm.load_from(s);
|
||||
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
|
||||
var fallback_family = new FontFamily(s.grid.font_family);
|
||||
// group
|
||||
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
|
||||
var group_appearance = s.group_cell_appearance.to_appearance();
|
||||
// header
|
||||
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
|
||||
var header_appearance = s.header_cell_appearance.to_appearance();
|
||||
//
|
||||
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
|
||||
if (col_setting_map.TryGetValue(c.id, out var cs)) {
|
||||
c.load_from(cs);
|
||||
}
|
||||
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
|
||||
c.load_group_params(
|
||||
group_appearance.font_size,
|
||||
gcs.font_weight_override ?? group_appearance.font_weight,
|
||||
gcs.font_color_override ?? group_appearance.foreground,
|
||||
group_appearance.font_family);
|
||||
}
|
||||
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
|
||||
c.load_header_params(
|
||||
header_appearance.font_size,
|
||||
hcs.font_weight_override ?? header_appearance.font_weight,
|
||||
hcs.font_color_override ?? header_appearance.foreground,
|
||||
header_appearance.font_family);
|
||||
}
|
||||
}
|
||||
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
|
||||
throw new InvalidOperationException();
|
||||
}, c => c.id.ToLower(), cs => cs.id.ToLower());
|
||||
}),
|
||||
content._grid.Columns.Subscribe((s, e) => {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: add_new(); break;
|
||||
case NotifyCollectionChangedAction.Remove: remove_old(); break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
remove_old();
|
||||
add_new();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
|
||||
break;
|
||||
}
|
||||
void add_new() =>
|
||||
cached_columns = e.NewStartingIndex >= 0 ?
|
||||
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
|
||||
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
|
||||
|
||||
void remove_old() =>
|
||||
cached_columns = e.OldStartingIndex >= 0 ?
|
||||
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
|
||||
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
|
||||
}),
|
||||
Disposable.wrap_collection(commands.OfType<IDisposable>()),
|
||||
vm);
|
||||
return (content, commands, get_save_state, dispose);
|
||||
},
|
||||
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
|
||||
min_height_offset: 60);
|
||||
}
|
||||
|
||||
readonly struct PNLConfig {
|
||||
public readonly bool
|
||||
enable_animations,
|
||||
show_pause_resume;
|
||||
|
||||
public PNLConfig(bts.config.PNLOptions options) {
|
||||
enable_animations = options.enable_animations;
|
||||
show_pause_resume = options.show_pause_resume;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && (
|
||||
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
|
||||
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
|
||||
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
|
||||
//
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
|
||||
if (_apply(actual_triggers, default_triggers, out var result)) {
|
||||
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
|
||||
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = actual_trigger_map;
|
||||
foreach (var kv in snapshot) {
|
||||
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
|
||||
// assume 1 animation per column
|
||||
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
|
||||
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
|
||||
actual_col_anims.Difference(default_col_anims,
|
||||
out var exclusive_actual,
|
||||
out var exclusive_defaults,
|
||||
out var diff);
|
||||
var new_col_anims = exclusive_defaults.ToList();
|
||||
if (new_col_anims.Count > 0) {
|
||||
// add the original col animations to the new ones (combine them)
|
||||
new_col_anims.AddRange(actual_col_anims);
|
||||
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var kv in default_trigger_map) {
|
||||
if (!actual_trigger_map.ContainsKey(kv.Key))
|
||||
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
|
||||
}
|
||||
if (actual_trigger_map != snapshot) {
|
||||
result = actual_trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
var defaults = PNLSettings.Default;
|
||||
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
|
||||
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
|
||||
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
|
||||
if (_apply(columns, out var new_cols)) {
|
||||
settings = set_columns(settings, new_cols);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
|
||||
var col_list = columns.ToList();
|
||||
int orig_count = col_list.Count;
|
||||
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
|
||||
if (col_list.Count < orig_count) {
|
||||
result = col_list;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null) {
|
||||
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
|
||||
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
|
||||
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
|
||||
new_settings = settings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
|
||||
if (_apply(triggers, out var new_triggers)) {
|
||||
settings = set_triggers(settings, new_triggers);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
|
||||
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
|
||||
var snapshot = trigger_map;
|
||||
foreach (var t in trigger_map.Values) {
|
||||
var anims = t.animations.ToList();
|
||||
int orig_count = anims.Count;
|
||||
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
|
||||
if (anims.Count < orig_count)
|
||||
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
|
||||
}
|
||||
if (trigger_map != snapshot) {
|
||||
result = trigger_map.Values;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(IPNLSettingsMigrationRule))]
|
||||
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
|
||||
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
|
||||
if (settings != null && !settings.grid.enable_background_color) {
|
||||
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
|
||||
return true;
|
||||
}
|
||||
new_settings = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using bts.utils;
|
||||
using mbts.expression;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using static bts.shell.pnl.PNLView;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
interface IPNLAnimationTrigger<T> {
|
||||
IReadOnlyDictionary<Columns, PNLCellAppearance> trigger(T value);
|
||||
}
|
||||
|
||||
readonly struct PNLAnimationTrigger<T> : IPNLAnimationTrigger<T> {
|
||||
public readonly Func<T, bool> predicate;
|
||||
public readonly IReadOnlyDictionary<Columns, PNLCellAppearance> animations;
|
||||
|
||||
public PNLAnimationTrigger(Func<T, bool> predicate, IReadOnlyDictionary<Columns, PNLCellAppearance> animations) {
|
||||
this.predicate = predicate;
|
||||
this.animations = animations;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<Columns, PNLCellAppearance> trigger(T value) =>
|
||||
predicate(value) ? animations : DictionaryHelper.empty<Columns, PNLCellAppearance>();
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class PNLAnimationTriggerData : IEquatable<PNLAnimationTriggerData> {
|
||||
static readonly int _type_hash = typeof(PNLAnimationTriggerData).GetHashCode();
|
||||
|
||||
[ProtoMember(1)]
|
||||
public string expression { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public PNLAnimation[] animations { get; set; }
|
||||
|
||||
public IPNLAnimationTrigger<T> to_trigger<T>() =>
|
||||
new PNLAnimationTrigger<T>(
|
||||
CoreExpressionEvaluator<T>.try_create(expression, out var eval) ?
|
||||
eval.apply : new Func<T, bool>(_ => true),
|
||||
animations.ToDictionary(a => a.column, a => a.to_appearance()));
|
||||
|
||||
public PNLAnimationTriggerData with(string expression = null, PNLAnimation[] animations = null) =>
|
||||
new PNLAnimationTriggerData {
|
||||
expression = expression ?? this.expression,
|
||||
animations = animations ?? this.animations
|
||||
};
|
||||
|
||||
public bool Equals(PNLAnimationTriggerData other) =>
|
||||
other != null &&
|
||||
expression == other.expression &&
|
||||
EqualityComparer.safe_sequence_equals(animations, other.animations);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as PNLAnimationTriggerData);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_type_hash;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class PNLAnimation : IEquatable<PNLAnimation> {
|
||||
static readonly int _type_hash = typeof(PNLAnimation).GetHashCode();
|
||||
|
||||
[ProtoMember(1)]
|
||||
public Columns column { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public int? font_weight { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public uint? foreground { get; set; }
|
||||
[ProtoMember(4)]
|
||||
public uint? background { get; set; }
|
||||
|
||||
public PNLCellAppearance to_appearance() =>
|
||||
new PNLCellAppearance(
|
||||
foreground?.argb_color(),
|
||||
background?.argb_color(),
|
||||
font_weight.HasValue ? FontWeight.FromOpenTypeWeight(font_weight.Value) : (FontWeight?)null);
|
||||
|
||||
public bool Equals(PNLAnimation other) =>
|
||||
other != null &&
|
||||
column == other.column &&
|
||||
font_weight == other.font_weight &&
|
||||
foreground == other.foreground &&
|
||||
background == other.background;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as PNLAnimation);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_type_hash;
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility.text;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
readonly struct PNLCellAppearance : IEquatable<PNLCellAppearance> {
|
||||
public readonly FontWeight? font_weight;
|
||||
public readonly Color? foreground, background;
|
||||
public readonly double font_size;
|
||||
public readonly FontFamily font_family;
|
||||
|
||||
public PNLCellAppearance(Color? foreground = null, Color? background = null, FontWeight? font_weight = null, FontFamily font_family = null, double font_size = double.NaN) {
|
||||
this.foreground = foreground;
|
||||
this.background = background;
|
||||
this.font_weight = font_weight;
|
||||
this.font_family = font_family;
|
||||
this.font_size = font_size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the specified appearance to replace null/absent values.
|
||||
/// </summary>
|
||||
public PNLCellAppearance merge(PNLCellAppearance other) =>
|
||||
new PNLCellAppearance(
|
||||
foreground ?? other.foreground,
|
||||
background ?? other.background,
|
||||
font_weight ?? other.font_weight,
|
||||
font_family ?? other.font_family,
|
||||
double.IsNaN(font_size) || font_size == 0 ? other.font_size : font_size);
|
||||
|
||||
public bool Equals(PNLCellAppearance other) =>
|
||||
font_weight == other.font_weight &&
|
||||
foreground == other.foreground &&
|
||||
background == other.background &&
|
||||
font_size.Equals(other.font_size) &&
|
||||
Equals(font_family, other.font_family);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLCellAppearance other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(
|
||||
font_weight?.GetHashCode() ?? 0,
|
||||
foreground?.GetHashCode() ?? 0,
|
||||
background?.GetHashCode() ?? 0,
|
||||
font_size.GetHashCode(),
|
||||
font_family?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class PNLCellAppearanceProto : IEquatable<PNLCellAppearanceProto> {
|
||||
static readonly int _type_hash = typeof(PNLCellAppearanceProto).GetHashCode();
|
||||
|
||||
[ProtoMember(1)]
|
||||
public FontParamsProto font_params { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint? background { get; set; }
|
||||
|
||||
public static PNLCellAppearanceProto from_appearance(PNLCellAppearance cell_appearance) =>
|
||||
new PNLCellAppearanceProto {
|
||||
font_params = new FontParamsProto {
|
||||
typeface = new TypefaceProto(
|
||||
cell_appearance.font_family?.name(),
|
||||
(cell_appearance.font_weight ?? FontWeights.Normal).ToOpenTypeWeight()),
|
||||
font_color = cell_appearance.foreground?.rgb_color() ?? 0,
|
||||
font_size = cell_appearance.font_size,
|
||||
},
|
||||
background = cell_appearance.background?.rgb_color()
|
||||
};
|
||||
|
||||
public PNLCellAppearance to_appearance() {
|
||||
var font = font_params?.to_wpf_params();
|
||||
return new PNLCellAppearance(
|
||||
font?.color,
|
||||
background?.argb_color(),
|
||||
font?.weight,
|
||||
font?.family,
|
||||
font?.size ?? double.NaN);
|
||||
}
|
||||
|
||||
public bool Equals(PNLCellAppearanceProto other) =>
|
||||
other != null &&
|
||||
background == other.background &&
|
||||
font_params.safe_equals(other.font_params);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as PNLCellAppearanceProto);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_type_hash;
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
using bts.api;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
[ProtoContract]
|
||||
class FilterStates : IEquatable<FilterStates> {
|
||||
static readonly int _type_hash = typeof(FilterStates).GetHashCode();
|
||||
|
||||
// account
|
||||
[ProtoMember(1)]
|
||||
uint[] _account;
|
||||
public uint[] account {
|
||||
get => _account ?? Array.Empty<uint>();
|
||||
set => _account = value;
|
||||
}
|
||||
// exchange
|
||||
[ProtoMember(2)]
|
||||
Venue[] _exchange;
|
||||
public Venue[] exchange {
|
||||
get => _exchange ?? Array.Empty<Venue>();
|
||||
set => _exchange = value;
|
||||
}
|
||||
// symbol
|
||||
[ProtoMember(3)]
|
||||
string[] _symbol;
|
||||
public string[] symbol {
|
||||
get => _symbol ?? Array.Empty<string>();
|
||||
set => _symbol = value;
|
||||
}
|
||||
// security type
|
||||
[ProtoMember(4)]
|
||||
Security.ContractOneofCase[] _security_type;
|
||||
public Security.ContractOneofCase[] security_type {
|
||||
get => _security_type ?? Array.Empty<Security.ContractOneofCase>();
|
||||
set => _security_type = value;
|
||||
}
|
||||
// security
|
||||
[ProtoMember(5)]
|
||||
public string security { get; set; }
|
||||
[ProtoMember(6)]
|
||||
public string security_key { get; set; }
|
||||
// cpp
|
||||
[ProtoMember(7)]
|
||||
public DoubleMinMaxState cpp { get; set; }
|
||||
|
||||
/// sod
|
||||
[ProtoMember(8)]
|
||||
public DoubleMinMaxState sod_pnl { get; set; }
|
||||
// sod long
|
||||
[ProtoMember(9)]
|
||||
public DoubleMinMaxState sod_long_pos { get; set; }
|
||||
[ProtoMember(10)]
|
||||
public DoubleMinMaxState sod_long_avg_price { get; set; }
|
||||
// sod short
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState sod_short_pos { get; set; }
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState sod_short_avg_price { get; set; }
|
||||
// sod net
|
||||
[ProtoMember(13)]
|
||||
public DoubleMinMaxState sod_net_pos { get; set; }
|
||||
|
||||
/// day
|
||||
[ProtoMember(14)]
|
||||
public DoubleMinMaxState day_pnl { get; set; }
|
||||
// day long
|
||||
[ProtoMember(15)]
|
||||
public DoubleMinMaxState day_long_pos { get; set; }
|
||||
[ProtoMember(16)]
|
||||
public DoubleMinMaxState day_long_avg_price { get; set; }
|
||||
// day short
|
||||
[ProtoMember(17)]
|
||||
public DoubleMinMaxState day_short_pos { get; set; }
|
||||
[ProtoMember(18)]
|
||||
public DoubleMinMaxState day_short_avg_price { get; set; }
|
||||
// day net
|
||||
[ProtoMember(19)]
|
||||
public DoubleMinMaxState day_net_pos { get; set; }
|
||||
// has day trades
|
||||
[ProtoMember(20)]
|
||||
bool[] _has_day_trades;
|
||||
public bool[] has_day_trades {
|
||||
get => _has_day_trades ?? Array.Empty<bool>();
|
||||
set => _has_day_trades = value;
|
||||
}
|
||||
|
||||
/// raw day
|
||||
[ProtoMember(21)]
|
||||
public DoubleMinMaxState raw_day_long_pos { get; set; }
|
||||
[ProtoMember(22)]
|
||||
public DoubleMinMaxState raw_day_short_pos { get; set; }
|
||||
[ProtoMember(23)]
|
||||
public DoubleMinMaxState raw_day_net_pos { get; set; }
|
||||
|
||||
/// working (long)
|
||||
[ProtoMember(24)]
|
||||
public DoubleMinMaxState working_long_pos { get; set; }
|
||||
[ProtoMember(25)]
|
||||
public DoubleMinMaxState working_long_avg_price { get; set; }
|
||||
[ProtoMember(26)]
|
||||
// working short
|
||||
public DoubleMinMaxState working_short_pos { get; set; }
|
||||
[ProtoMember(27)]
|
||||
public DoubleMinMaxState working_short_avg_price { get; set; }
|
||||
// working net
|
||||
[ProtoMember(28)]
|
||||
public DoubleMinMaxState working_net_pos { get; set; }
|
||||
|
||||
/// total
|
||||
[ProtoMember(29)]
|
||||
public DoubleMinMaxState total_pnl { get; set; }
|
||||
// total long
|
||||
[ProtoMember(30)]
|
||||
public DoubleMinMaxState total_long_pos { get; set; }
|
||||
[ProtoMember(31)]
|
||||
public DoubleMinMaxState total_long_avg_price { get; set; }
|
||||
// total short
|
||||
[ProtoMember(32)]
|
||||
public DoubleMinMaxState total_short_pos { get; set; }
|
||||
[ProtoMember(33)]
|
||||
public DoubleMinMaxState total_short_avg_price { get; set; }
|
||||
// total net
|
||||
[ProtoMember(34)]
|
||||
public DoubleMinMaxState total_net_pos { get; set; }
|
||||
|
||||
/// market
|
||||
[ProtoMember(35)]
|
||||
public DoubleMinMaxState bid_price { get; set; }
|
||||
[ProtoMember(36)]
|
||||
public DoubleMinMaxState mid_price { get; set; }
|
||||
[ProtoMember(37)]
|
||||
public DoubleMinMaxState ask_price { get; set; }
|
||||
[ProtoMember(38)]
|
||||
public DoubleMinMaxState last_price { get; set; }
|
||||
[ProtoMember(39)]
|
||||
public DoubleMinMaxState settlement_price { get; set; }
|
||||
[ProtoMember(40)]
|
||||
public DoubleMinMaxState pnl_price { get; set; }
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
account.SequenceEqual(other.account) &&
|
||||
exchange.SequenceEqual(other.exchange) &&
|
||||
symbol.SequenceEqual(other.symbol) &&
|
||||
security_type.SequenceEqual(other.security_type) &&
|
||||
security == other.security &&
|
||||
security_key == other.security_key &&
|
||||
cpp.safe_equals(other.cpp) &&
|
||||
/// sod
|
||||
sod_pnl.safe_equals(other.sod_pnl) &&
|
||||
sod_long_pos.safe_equals(other.sod_long_pos) &&
|
||||
sod_long_avg_price.safe_equals(other.sod_long_avg_price) &&
|
||||
sod_short_pos.safe_equals(other.sod_short_pos) &&
|
||||
sod_short_avg_price.safe_equals(other.sod_short_avg_price) &&
|
||||
sod_net_pos.safe_equals(other.sod_net_pos) &&
|
||||
/// day
|
||||
day_pnl.safe_equals(other.day_pnl) &&
|
||||
day_long_pos.safe_equals(other.day_long_pos) &&
|
||||
day_long_avg_price.safe_equals(other.day_long_avg_price) &&
|
||||
day_short_pos.safe_equals(other.day_short_pos) &&
|
||||
day_short_avg_price.safe_equals(other.day_short_avg_price) &&
|
||||
day_net_pos.safe_equals(other.day_net_pos) &&
|
||||
has_day_trades.SequenceEqual(other.has_day_trades) &&
|
||||
/// raw day
|
||||
raw_day_long_pos.safe_equals(other.raw_day_long_pos) &&
|
||||
raw_day_short_pos.safe_equals(other.raw_day_short_pos) &&
|
||||
raw_day_net_pos.safe_equals(other.raw_day_net_pos) &&
|
||||
/// working
|
||||
working_long_pos.safe_equals(other.working_long_pos) &&
|
||||
working_long_avg_price.safe_equals(other.working_long_avg_price) &&
|
||||
working_short_pos.safe_equals(other.working_short_pos) &&
|
||||
working_short_avg_price.safe_equals(other.working_short_avg_price) &&
|
||||
working_net_pos.safe_equals(other.working_net_pos) &&
|
||||
/// total
|
||||
total_pnl.safe_equals(other.total_pnl) &&
|
||||
total_long_pos.safe_equals(other.total_long_pos) &&
|
||||
total_long_avg_price.safe_equals(other.total_long_avg_price) &&
|
||||
total_short_pos.safe_equals(other.total_short_pos) &&
|
||||
total_short_avg_price.safe_equals(other.total_short_avg_price) &&
|
||||
total_net_pos.safe_equals(other.total_net_pos) &&
|
||||
/// market
|
||||
bid_price.safe_equals(other.bid_price) &&
|
||||
ask_price.safe_equals(other.ask_price) &&
|
||||
mid_price.safe_equals(other.mid_price) &&
|
||||
last_price.safe_equals(other.last_price) &&
|
||||
settlement_price.safe_equals(other.settlement_price) &&
|
||||
pnl_price.safe_equals(other.pnl_price);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_type_hash;
|
||||
}
|
||||
|
||||
static class PNLFilterStatesExtensions {
|
||||
public static Func<PNLRowData, bool> build_predicate(this FilterStates fs) {
|
||||
var bd = new ColumnPredicateBuilder<PNLRowData>();
|
||||
// impl (add all)
|
||||
bd.add_discrete_values(fs.account, d => d.key.account_id);
|
||||
bd.add_discrete_values(fs.exchange, d => d.security.exchange_or_default());
|
||||
bd.add_discrete_values(fs.symbol, d => d.security.symbol_or_default());
|
||||
bd.add_discrete_values(fs.security_type, d => d.security.HasValue ? d.security.Value.ContractCase : Security.ContractOneofCase.None);
|
||||
bd.add_regex(fs.security, d => d.security.ToString());
|
||||
bd.add_regex(fs.security_key, d => d.key.security_id.ToString());
|
||||
bd.add_numeric(fs.cpp, d => d.cpp);
|
||||
/// sod
|
||||
bd.add_numeric(fs.sod_pnl, d => d.sod_pnl);
|
||||
bd.add_numeric(fs.sod_long_pos, d => d.sod_long_pos);
|
||||
bd.add_numeric(fs.sod_long_avg_price, d => d.sod_long_avg_price_raw);
|
||||
bd.add_numeric(fs.sod_short_pos, d => d.sod_short_pos);
|
||||
bd.add_numeric(fs.sod_short_avg_price, d => d.sod_short_avg_price_raw);
|
||||
bd.add_numeric(fs.sod_net_pos, d => d.sod_net_pos);
|
||||
/// day
|
||||
bd.add_numeric(fs.day_pnl, d => d.day_pnl);
|
||||
bd.add_numeric(fs.day_long_pos, d => d.day_long_pos);
|
||||
bd.add_numeric(fs.day_long_avg_price, d => d.day_long_avg_price_raw);
|
||||
bd.add_numeric(fs.day_short_pos, d => d.day_short_pos);
|
||||
bd.add_numeric(fs.day_short_avg_price, d => d.day_short_avg_price_raw);
|
||||
bd.add_numeric(fs.day_net_pos, d => d.day_net_pos);
|
||||
bd.add_discrete_values(fs.has_day_trades, d => d.has_day_trades);
|
||||
/// raw day
|
||||
bd.add_numeric(fs.raw_day_long_pos, d => d.raw_day_long_pos);
|
||||
bd.add_numeric(fs.raw_day_short_pos, d => d.raw_day_short_pos);
|
||||
bd.add_numeric(fs.raw_day_net_pos, d => d.raw_day_net_pos);
|
||||
/// working
|
||||
bd.add_numeric(fs.working_long_pos, d => d.working_long_pos);
|
||||
bd.add_numeric(fs.working_long_avg_price, d => d.working_long_avg_price_raw);
|
||||
bd.add_numeric(fs.working_short_pos, d => d.working_short_pos);
|
||||
bd.add_numeric(fs.working_short_avg_price, d => d.working_short_avg_price_raw);
|
||||
bd.add_numeric(fs.working_net_pos, d => d.working_net_pos);
|
||||
/// total
|
||||
bd.add_numeric(fs.total_pnl, d => d.total_pnl);
|
||||
bd.add_numeric(fs.total_long_pos, d => d.total_long_pos);
|
||||
bd.add_numeric(fs.total_long_avg_price, d => d.total_long_avg_price_raw);
|
||||
bd.add_numeric(fs.total_short_pos, d => d.total_short_pos);
|
||||
bd.add_numeric(fs.total_short_avg_price, d => d.total_short_avg_price_raw);
|
||||
bd.add_numeric(fs.total_net_pos, d => d.total_net_pos);
|
||||
/// market
|
||||
bd.add_numeric(fs.bid_price, d => d.bid_price);
|
||||
bd.add_numeric(fs.ask_price, d => d.ask_price);
|
||||
bd.add_numeric(fs.mid_price, d => d.mid_price);
|
||||
bd.add_numeric(fs.last_price, d => d.last_price);
|
||||
bd.add_numeric(fs.settlement_price, d => d.settlement_price);
|
||||
bd.add_numeric(fs.pnl_price, d => d.pnl_price);
|
||||
//
|
||||
return bd.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,489 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.proto;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.pnl.PNLView;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using Headers = mbts.ui.controls.DIYGrid.DefaultColumnHeaders.PNLHeaders;
|
||||
using Widths = mbts.ui.controls.DIYGrid.DefaultColumnWidths.PNL;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
[ProtoContract]
|
||||
class PNLSaveState : IEquatable<PNLSaveState> {
|
||||
[ProtoMember(1)]
|
||||
public bool use_defaults { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public PNLSettings settings { get; set; }
|
||||
|
||||
public bool Equals(PNLSaveState other) =>
|
||||
other != null &&
|
||||
use_defaults == other.use_defaults &&
|
||||
(use_defaults || settings.safe_equals(other.settings));
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class PNLSettings : IEquatable<PNLSettings> {
|
||||
const double _default_group_font_size = double.NaN;
|
||||
// bold
|
||||
const int _default_group_font_weight = 700;
|
||||
|
||||
public static readonly IReadOnlyList<PNLHeaderRowDisplay> header_row_display_options =
|
||||
new[] {
|
||||
PNLHeaderRowDisplay.None,
|
||||
PNLHeaderRowDisplay.Total,
|
||||
PNLHeaderRowDisplay.SelectedTotal,
|
||||
PNLHeaderRowDisplay.Total | PNLHeaderRowDisplay.SelectedTotal
|
||||
};
|
||||
|
||||
public static readonly IReadOnlyList<Columns> anchorable_columns =
|
||||
new[] {
|
||||
Columns.Account,
|
||||
Columns.Exchange,
|
||||
Columns.Symbol,
|
||||
Columns.SecurityType,
|
||||
Columns.Security
|
||||
};
|
||||
|
||||
[ProtoMember(1)]
|
||||
SortingCriteria[] _sorting_criterion;
|
||||
[ProtoMember(5)]
|
||||
Columns[] _anchors;
|
||||
[ProtoMember(7)]
|
||||
PNLCellAppearanceProto _group_cell_appearance;
|
||||
[ProtoMember(8)]
|
||||
PNLCellAppearanceProto _header_cell_appearance;
|
||||
// don't store in proto yet (this type may change)
|
||||
[ProtoMember(9)]
|
||||
PNLAnimationTriggerData[] _animations;
|
||||
[ProtoMember(10)]
|
||||
PNLAnimationTriggerData[] _group_header_animations;
|
||||
[ProtoMember(11)]
|
||||
GridColumnSettings[] _group_columns;
|
||||
[ProtoMember(12)]
|
||||
GridColumnSettings[] _header_columns;
|
||||
[ProtoMember(13)]
|
||||
PNLAnimationTriggerData[] _header_row_animations;
|
||||
[ProtoMember(14)]
|
||||
List<uint> _group_background_colors;
|
||||
|
||||
GridSettings _grid_backing;
|
||||
PNLCellAppearanceProto _grid_cell_appearance_backing;
|
||||
|
||||
public static PNLSettings Default =>
|
||||
new PNLSettings {
|
||||
_sorting_criterion = new[] {
|
||||
new SortingCriteria(nameof(Columns.Account), true),
|
||||
new SortingCriteria(nameof(Columns.Security), true)
|
||||
},
|
||||
filters = new FilterStates {
|
||||
security_type = new Security.ContractOneofCase[] {
|
||||
Security.ContractOneofCase.Future
|
||||
},
|
||||
has_day_trades = new[] { true }
|
||||
},
|
||||
grid = DefaultGridSettings,
|
||||
calculation_type = PNLCalculationType.Mid,
|
||||
header_row_display = PNLHeaderRowDisplay.None,
|
||||
_anchors = new[] { Columns.Account, Columns.Symbol },
|
||||
_group_background_colors = BTSColors.PNL.group_alternating_background_colors.Select(c => c.rgb_color()).ToList(),
|
||||
_group_cell_appearance = new PNLCellAppearanceProto {
|
||||
font_params = new FontParamsProto {
|
||||
typeface = new TypefaceProto(
|
||||
GridSettings.Default.font_family,
|
||||
FontWeights.Bold.ToOpenTypeWeight()),
|
||||
font_color = Colors.Black.rgb_color(),
|
||||
font_size = 11
|
||||
},
|
||||
background = Colors.Transparent.rgb_color()
|
||||
},
|
||||
_header_cell_appearance = new PNLCellAppearanceProto {
|
||||
font_params = new FontParamsProto {
|
||||
typeface = new TypefaceProto(
|
||||
GridSettings.Default.font_family,
|
||||
FontWeights.Bold.ToOpenTypeWeight()),
|
||||
font_color = Colors.Black.rgb_color(),
|
||||
font_size = 12
|
||||
},
|
||||
background = BTSColors.PNL.header_row_background_color.rgb_color()
|
||||
}
|
||||
};
|
||||
|
||||
public static GridSettings DefaultGridSettings =>
|
||||
GridSettings.Default.with(
|
||||
DefaultColumns.all,
|
||||
DefaultColumns.default_sorting_criterion,
|
||||
enable_background_color: true);
|
||||
|
||||
public static fills.FillsSettings DefaultAuxiliaryFills {
|
||||
get {
|
||||
var grid = fills.FillsSettings.MiniGridSettings;
|
||||
if (grid.columns.TryGetFirst(c => c.id == nameof(mbts.ui.spark.FillsView.Columns.Side), out var side_col, out int side_col_pos)) {
|
||||
var cols = grid.columns.ToList();
|
||||
cols[side_col_pos] = side_col.with(is_visible: false);
|
||||
grid = grid.with(cols);
|
||||
}
|
||||
var rv = fills.FillsSettings.Default();
|
||||
rv.grid = grid;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<PNLAnimationTriggerData> default_animations {
|
||||
get {
|
||||
yield return _trigger($"{nameof(PNLRowData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, font_weight: FontWeights.Bold, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, font_weight: FontWeights.Bold, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.SODNetPos));
|
||||
yield return _trigger($"{nameof(PNLRowData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.SODNetPos));
|
||||
// day
|
||||
yield return _trigger($"{nameof(PNLRowData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, font_weight: FontWeights.Bold, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, font_weight: FontWeights.Bold, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.DayNetPos));
|
||||
yield return _trigger($"{nameof(PNLRowData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.DayNetPos));
|
||||
// raw day
|
||||
yield return _trigger($"{nameof(PNLRowData.raw_day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.RawDayNetPos));
|
||||
yield return _trigger($"{nameof(PNLRowData.raw_day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.RawDayNetPos));
|
||||
// total
|
||||
yield return _trigger($"{nameof(PNLRowData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, font_weight: FontWeights.Bold, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, font_weight: FontWeights.Bold, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.TotalNetPos));
|
||||
// working
|
||||
yield return _trigger($"{nameof(PNLRowData.working_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.WorkingNetPos));
|
||||
yield return _trigger($"{nameof(PNLRowData.working_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.WorkingNetPos));
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<PNLAnimationTriggerData> default_group_header_animations {
|
||||
get {
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.SODNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.SODNetPos));
|
||||
// day
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.DayNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.DayNetPos));
|
||||
// raw day
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.raw_day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.RawDayNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.raw_day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.RawDayNetPos));
|
||||
// total
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.TotalNetPos));
|
||||
// working
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.WorkingNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.WorkingNetPos));
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<PNLAnimationTriggerData> default_header_row_animations {
|
||||
get {
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.SODNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.SODNetPos));
|
||||
// day
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.DayNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.DayNetPos));
|
||||
// raw day
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.raw_day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.RawDayNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.raw_day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.RawDayNetPos));
|
||||
// total
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit_color, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss_color, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.TotalNetPos));
|
||||
// working
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid_color, column: Columns.WorkingNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.working_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask_color, column: Columns.WorkingNetPos));
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoMember(2)]
|
||||
public FilterStates filters { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public PNLCalculationType calculation_type { get; set; }
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid {
|
||||
get => _grid_backing;
|
||||
set {
|
||||
_grid_backing = value;
|
||||
_grid_cell_appearance_backing = null;
|
||||
}
|
||||
}
|
||||
[ProtoMember(6)]
|
||||
public PNLHeaderRowDisplay header_row_display { get; set; }
|
||||
|
||||
public IReadOnlyList<Columns> anchors => _anchors ?? (_anchors = Array.Empty<Columns>());
|
||||
public IReadOnlyList<PNLAnimationTriggerData> animations => _animations ?? (_animations = default_animations.ToArray());
|
||||
|
||||
public PNLCellAppearance grid_defaults =>
|
||||
new PNLCellAppearance(
|
||||
font_family: new FontFamily(grid.font_family),
|
||||
font_size: grid.font_size,
|
||||
font_weight: grid.font_weight,
|
||||
background: grid.background,
|
||||
foreground: grid.font_color.rgb_color());
|
||||
|
||||
// Group
|
||||
public List<uint> group_background_colors => _group_background_colors ?? (_group_background_colors = new List<uint>(0));
|
||||
public PNLCellAppearanceProto group_cell_appearance { get => _group_cell_appearance ?? _grid_cell_appearance; set => _group_cell_appearance = value; }
|
||||
public IReadOnlyList<GridColumnSettings> group_columns => _group_columns ?? (_group_columns = DefaultColumns.group_columns.ToArray());
|
||||
public PNLCellAppearance group_defaults => group_cell_appearance.to_appearance().merge(grid_defaults);
|
||||
public IReadOnlyList<PNLAnimationTriggerData> group_header_animations => _group_header_animations ?? (_group_header_animations = default_group_header_animations.ToArray());
|
||||
// Header
|
||||
public Color header_background_color => header_cell_appearance.background?.argb_color() ?? Colors.Transparent;
|
||||
public PNLCellAppearanceProto header_cell_appearance { get => _header_cell_appearance ?? _grid_cell_appearance; set => _header_cell_appearance = value; }
|
||||
public IReadOnlyList<GridColumnSettings> header_columns => _header_columns ?? (_header_columns = DefaultColumns.header_columns.ToArray());
|
||||
public PNLCellAppearance header_defaults => header_cell_appearance.to_appearance().merge(grid_defaults);
|
||||
public IReadOnlyList<PNLAnimationTriggerData> header_row_animations => _header_row_animations ?? (_header_row_animations = default_header_row_animations.ToArray());
|
||||
//
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion =>
|
||||
_sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToArray());
|
||||
|
||||
PNLCellAppearanceProto _grid_cell_appearance => _grid_cell_appearance_backing ?? (
|
||||
_grid_cell_appearance_backing = new PNLCellAppearanceProto {
|
||||
background = grid.background.rgb_color(),
|
||||
font_params = new FontParamsProto {
|
||||
font_color = grid.font_color,
|
||||
typeface = new TypefaceProto(
|
||||
grid.font_family,
|
||||
grid.font_weight.ToOpenTypeWeight()),
|
||||
font_size = grid.font_size
|
||||
}
|
||||
});
|
||||
|
||||
public PNLSettings with(
|
||||
IEnumerable<SortingCriteria> sorting_criterion = null,
|
||||
IEnumerable<Columns> anchors = null,
|
||||
FilterStates filters = null,
|
||||
GridSettings grid = null,
|
||||
PNLCalculationType? calculation_type = null,
|
||||
PNLHeaderRowDisplay? header_row_display = null,
|
||||
IEnumerable<GridColumnSettings> group_columns = null,
|
||||
IEnumerable<PNLAnimationTriggerData> animations = null,
|
||||
IEnumerable<PNLAnimationTriggerData> group_header_animations = null,
|
||||
IEnumerable<PNLAnimationTriggerData> header_row_animations = null,
|
||||
IEnumerable<GridColumnSettings> header_columns = null) =>
|
||||
new PNLSettings {
|
||||
calculation_type = calculation_type ?? this.calculation_type,
|
||||
header_row_display = header_row_display ?? this.header_row_display,
|
||||
// row operators (anchors, sorting, filters)
|
||||
_anchors = (anchors ?? _anchors.empty_if_null()).ToArray(),
|
||||
_sorting_criterion = (sorting_criterion ?? _sorting_criterion.empty_if_null()).ToArray(),
|
||||
filters = filters ?? this.filters,
|
||||
// cell appearance (group/header)
|
||||
_group_background_colors = _group_background_colors?.ToList(),
|
||||
_group_cell_appearance = _group_cell_appearance,
|
||||
_header_cell_appearance = _header_cell_appearance,
|
||||
// columns (group/header)
|
||||
_group_columns = group_columns?.ToArray() ?? _group_columns,
|
||||
_header_columns = header_columns?.ToArray() ?? _header_columns,
|
||||
// animations
|
||||
_animations = animations?.ToArray() ?? _animations,
|
||||
_group_header_animations = group_header_animations?.ToArray() ?? _group_header_animations,
|
||||
_header_row_animations = header_row_animations?.ToArray() ?? _header_row_animations,
|
||||
// grid
|
||||
grid = grid ?? this.grid
|
||||
};
|
||||
|
||||
public bool Equals(PNLSettings other) =>
|
||||
other != null &&
|
||||
calculation_type == other.calculation_type &&
|
||||
header_row_display == other.header_row_display &&
|
||||
// row operators (anchors, sorting, filters)
|
||||
anchors.SequenceEqual(other.anchors) &&
|
||||
sorting_criterion.SequenceEqual(other.sorting_criterion) &&
|
||||
filters.safe_equals(other.filters) &&
|
||||
// cell appearance (group/header)
|
||||
group_background_colors.SequenceEqual(other.group_background_colors) &&
|
||||
group_cell_appearance.safe_equals(other.group_cell_appearance) &&
|
||||
header_cell_appearance.safe_equals(other.header_cell_appearance) &&
|
||||
// columns (group/header)
|
||||
group_columns.SequenceEqual(other.group_columns) &&
|
||||
header_columns.SequenceEqual(other.header_columns) &&
|
||||
// animations
|
||||
animations.SequenceEqual(other.animations) &&
|
||||
group_header_animations.SequenceEqual(other.group_header_animations) &&
|
||||
header_row_animations.SequenceEqual(other.header_row_animations) &&
|
||||
// grid
|
||||
grid.safe_equals(other.grid);
|
||||
|
||||
static PNLAnimationTriggerData _trigger(string expression, params PNLAnimation[] animations) =>
|
||||
new PNLAnimationTriggerData {
|
||||
expression = expression,
|
||||
animations = animations
|
||||
};
|
||||
|
||||
static PNLAnimation _animation(
|
||||
Columns column,
|
||||
Color? foreground = null,
|
||||
Color? background = null,
|
||||
FontWeight? font_weight = null) =>
|
||||
new PNLAnimation {
|
||||
column = column,
|
||||
foreground = foreground?.rgb_color(),
|
||||
background = background?.rgb_color(),
|
||||
font_weight = font_weight?.ToOpenTypeWeight()
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings account => new GridColumnSettings(nameof(Columns.Account), DefaultColumnHeaders.Account, width: Widths.Account);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(nameof(Columns.Exchange), DefaultColumnHeaders.Exchange, false, width: Widths.Exchange);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(nameof(Columns.Symbol), DefaultColumnHeaders.Symbol, width: Widths.Symbol);
|
||||
public static GridColumnSettings security_type => new GridColumnSettings(nameof(Columns.SecurityType), Headers.SecurityType, width: Widths.SecurityType);
|
||||
public static GridColumnSettings security => new GridColumnSettings(nameof(Columns.Security), DefaultColumnHeaders.Security, width: Widths.Security);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(nameof(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings cpp => new GridColumnSettings(nameof(Columns.CPP), "CPP", false, width: 50, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// sod
|
||||
public static GridColumnSettings sod_pnl => new GridColumnSettings(nameof(Columns.SODPNL), Headers.SOD.PNL, false, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_long_pos => new GridColumnSettings(nameof(Columns.SODLongPos), Headers.SOD.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_long_avg_price => new GridColumnSettings(nameof(Columns.SODLongAvgPrice), Headers.SOD.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_short_pos => new GridColumnSettings(nameof(Columns.SODShortPos), Headers.SOD.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_short_avg_price => new GridColumnSettings(nameof(Columns.SODShortAvgPrice), Headers.SOD.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_net_pos => new GridColumnSettings(nameof(Columns.SODNetPos), Headers.SOD.Net, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// day
|
||||
public static GridColumnSettings day_pnl => new GridColumnSettings(nameof(Columns.DayPNL), Headers.Day.PNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_long_pos => new GridColumnSettings(nameof(Columns.DayLongPos), Headers.Day.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_long_avg_price => new GridColumnSettings(nameof(Columns.DayLongAvgPrice), Headers.Day.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_short_pos => new GridColumnSettings(nameof(Columns.DayShortPos), Headers.Day.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_short_avg_price => new GridColumnSettings(nameof(Columns.DayShortAvgPrice), Headers.Day.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_net_pos => new GridColumnSettings(nameof(Columns.DayNetPos), Headers.Day.Net, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings has_day_trades => new GridColumnSettings(nameof(Columns.HasDayTrades), Headers.HasDayTrades, width: Widths.HasDayTrades, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// raw day
|
||||
public static GridColumnSettings raw_day_long_pos => new GridColumnSettings(nameof(Columns.RawDayLongPos), Headers.RawDay.Long, false, width: Widths.RawDayQty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings raw_day_short_pos => new GridColumnSettings(nameof(Columns.RawDayShortPos), Headers.RawDay.Short, false, width: Widths.RawDayQty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings raw_day_net_pos => new GridColumnSettings(nameof(Columns.RawDayNetPos), Headers.RawDay.Net, false, width: Widths.RawDayQty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// working
|
||||
public static GridColumnSettings working_long_pos => new GridColumnSettings(nameof(Columns.WorkingLongPos), Headers.Working.Long, false, width: Widths.WorkingQty, font_color_override: BTSColors.MarketData.bid, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings working_long_avg_price => new GridColumnSettings(nameof(Columns.WorkingLongAvgPrice), Headers.Working.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings working_short_pos => new GridColumnSettings(nameof(Columns.WorkingShortPos), Headers.Working.Short, false, width: Widths.WorkingQty, font_color_override: BTSColors.MarketData.ask, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings working_short_avg_price => new GridColumnSettings(nameof(Columns.WorkingShortAvgPrice), Headers.Working.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings working_net_pos => new GridColumnSettings(nameof(Columns.WorkingNetPos), Headers.Working.Net, width: Widths.WorkingQty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// total
|
||||
public static GridColumnSettings total_pnl => new GridColumnSettings(nameof(Columns.TotalPNL), Headers.Total.PNL, false, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_long_pos => new GridColumnSettings(nameof(Columns.TotalLongPos), Headers.Total.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_long_avg_price => new GridColumnSettings(nameof(Columns.TotalLongAvgPrice), Headers.Total.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_short_pos => new GridColumnSettings(nameof(Columns.TotalShortPos), Headers.Total.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_short_avg_price => new GridColumnSettings(nameof(Columns.TotalShortAvgPrice), Headers.Total.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_net_pos => new GridColumnSettings(nameof(Columns.TotalNetPos), Headers.Total.Net, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// market
|
||||
public static GridColumnSettings bid_price => new GridColumnSettings(nameof(Columns.BidPrice), DefaultColumnHeaders.Bid, false, width: DefaultColumnWidths.Price, font_color_override: BTSColors.MarketData.bid, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings ask_price => new GridColumnSettings(nameof(Columns.AskPrice), DefaultColumnHeaders.Ask, false, width: DefaultColumnWidths.Price, font_color_override: BTSColors.MarketData.ask, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings mid_price => new GridColumnSettings(nameof(Columns.MidPrice), DefaultColumnHeaders.Mid, false, width: DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(nameof(Columns.LastPrice), DefaultColumnHeaders.Last, false, width: DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings settlement_price => new GridColumnSettings(nameof(Columns.SettlementPrice), DefaultColumnHeaders.Settlement, false, width: DefaultColumnWidths.Settlement, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings pnl_price => new GridColumnSettings(nameof(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(account.id, true);
|
||||
yield return new SortingCriteria(security.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return symbol;
|
||||
yield return security_type;
|
||||
yield return security;
|
||||
yield return security_key; // hidden
|
||||
|
||||
yield return total_pnl; // hidden
|
||||
yield return sod_pnl;
|
||||
yield return day_pnl;
|
||||
/// total
|
||||
yield return total_net_pos;
|
||||
// total long
|
||||
yield return total_long_pos; // hidden
|
||||
yield return total_long_avg_price; // hidden
|
||||
// total short
|
||||
yield return total_short_pos; // hidden
|
||||
yield return total_short_avg_price; // hidden
|
||||
/// sod
|
||||
yield return sod_net_pos;
|
||||
// sod long
|
||||
yield return sod_long_pos; // hidden
|
||||
yield return sod_long_avg_price; // hidden
|
||||
// sod short
|
||||
yield return sod_short_pos; // hidden
|
||||
yield return sod_short_avg_price; // hidden
|
||||
/// day
|
||||
yield return day_net_pos;
|
||||
// day long
|
||||
yield return day_long_pos; // hidden
|
||||
yield return day_long_avg_price; // hidden
|
||||
// day short
|
||||
yield return day_short_pos; // hidden
|
||||
yield return day_short_avg_price; // hidden
|
||||
/// raw day
|
||||
yield return raw_day_long_pos; // hidden
|
||||
yield return raw_day_short_pos; // hidden
|
||||
yield return raw_day_net_pos; // hidden
|
||||
|
||||
yield return has_day_trades;
|
||||
/// working
|
||||
// working net
|
||||
yield return working_net_pos;
|
||||
// working long
|
||||
yield return working_long_pos;
|
||||
yield return working_long_avg_price;
|
||||
// working short
|
||||
yield return working_short_pos;
|
||||
yield return working_short_avg_price;
|
||||
/// market
|
||||
yield return bid_price;
|
||||
yield return ask_price;
|
||||
yield return mid_price;
|
||||
yield return last_price;
|
||||
yield return settlement_price;
|
||||
yield return pnl_price;
|
||||
yield return cpp;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> group_columns {
|
||||
get {
|
||||
var rv = all.Select(cs => cs.with(font_weight_override: new NullableFontWeightProto(FontWeights.Bold))).ToList();
|
||||
GridSettingsHelper.try_replace_column(rv, Columns.SODPNL, _override_color);
|
||||
GridSettingsHelper.try_replace_column(rv, Columns.DayPNL, _override_color);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> header_columns {
|
||||
get {
|
||||
var rv = all.Select(cs => cs.with(font_weight_override: new NullableFontWeightProto(FontWeights.Bold))).ToList();
|
||||
GridSettingsHelper.try_replace_column(rv, Columns.SODPNL, _override_color);
|
||||
GridSettingsHelper.try_replace_column(rv, Columns.DayPNL, _override_color);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
static GridColumnSettings _override_color(GridColumnSettings col_settings) =>
|
||||
_override_color(col_settings, 0);
|
||||
|
||||
static GridColumnSettings _override_color(GridColumnSettings col_settings, uint color) =>
|
||||
col_settings.with(font_color_override: new NullableColorProto(color));
|
||||
}
|
||||
}
|
||||
|
||||
static class PNLSettingsExtensions {
|
||||
public static IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers(this PNLSettings settings) => settings.animations.Select(a => a.to_trigger<PNLRowData>()).ToList();
|
||||
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> group_triggers(this PNLSettings settings) => settings.group_header_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
|
||||
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> header_triggers(this PNLSettings settings) => settings.header_row_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
using bts.proto;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility.text;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLCellAppearanceOverrideVM : ViewModelBase {
|
||||
bool
|
||||
_is_background_overridden,
|
||||
_is_font_color_overridden,
|
||||
_is_font_weight_overridden,
|
||||
_is_font_family_overridden,
|
||||
_is_font_size_overridden;
|
||||
|
||||
FontWeight _font_weight_override;
|
||||
FontFamily _font_family_override;
|
||||
double _font_size_override = double.NaN;
|
||||
|
||||
// background
|
||||
public bool is_background_overridden { get => _is_background_overridden; set => Set(ref _is_background_overridden, value); }
|
||||
public ColorPickerPopupVM background_override { get; } = new ColorPickerPopupVM {
|
||||
bind_opacity_to_alpha = true,
|
||||
opacity = 1
|
||||
};
|
||||
// font color
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
|
||||
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
|
||||
// font weight
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
|
||||
// font family
|
||||
public bool is_font_family_overriden { get => _is_font_family_overridden; set => Set(ref _is_font_family_overridden, value); }
|
||||
public FontFamily font_family_override { get => _font_family_override; set => Set(ref _font_family_override, value); }
|
||||
// font size
|
||||
public bool is_font_size_overriden { get => _is_font_size_overridden; set => Set(ref _is_font_size_overridden, value); }
|
||||
public double font_size_override { get => _font_size_override; set => Set(ref _font_size_override, value); }
|
||||
|
||||
public GridColumnSettings apply_to(GridColumnSettings target) =>
|
||||
target.with(
|
||||
font_weight_override: new NullableFontWeightProto(is_font_weight_overridden ? font_weight_override : (FontWeight?)null),
|
||||
font_color_override: new NullableColorProto(is_font_color_overridden ? font_color_override.color : (Color?)null),
|
||||
background_color_override: new NullableColorProto(is_background_overridden ? background_override.color : (Color?)null));
|
||||
|
||||
public void load(GridColumnSettings settings) {
|
||||
if (settings != null)
|
||||
load(settings.font_weight_override, settings.font_color_override, settings.background_color_override);
|
||||
}
|
||||
|
||||
public void load(FontParamsProto font_params) {
|
||||
if (font_params == null)
|
||||
return;
|
||||
// font weight
|
||||
is_font_weight_overridden = font_params.font_weight > 0;
|
||||
if (is_font_weight_overridden)
|
||||
font_weight_override = FontWeight.FromOpenTypeWeight(font_params.font_weight);
|
||||
// font color
|
||||
is_font_color_overridden = font_params.font_color > 0;
|
||||
if (is_font_color_overridden)
|
||||
font_color_override.color = font_params.font_color.rgb_color();
|
||||
// font family
|
||||
if (!string.IsNullOrEmpty(font_params.font_family)) {
|
||||
font_family_override = new FontFamily(font_params.font_family);
|
||||
}
|
||||
if (!double.IsNaN(font_params.font_size) && font_params.font_size > 0) {
|
||||
font_size_override = font_params.font_size;
|
||||
} else {
|
||||
font_size_override = double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(PNLAnimation animation) {
|
||||
if (animation != null)
|
||||
load(
|
||||
animation.font_weight.HasValue ? FontWeight.FromOpenTypeWeight(animation.font_weight.Value) : (FontWeight?)null,
|
||||
animation.foreground?.argb_color(),
|
||||
animation.background?.argb_color());
|
||||
}
|
||||
|
||||
public void load(FontWeight? font_weight, Color? font_color, Color? background) {
|
||||
// font weight
|
||||
is_font_weight_overridden = font_weight.HasValue;
|
||||
if (is_font_weight_overridden)
|
||||
font_weight_override = font_weight.Value;
|
||||
// font color
|
||||
is_font_color_overridden = font_color.HasValue;
|
||||
if (is_font_color_overridden)
|
||||
font_color_override.color = font_color.Value;
|
||||
// background
|
||||
is_background_overridden = background.HasValue;
|
||||
if (background.HasValue)
|
||||
background_override.color = background.Value;
|
||||
}
|
||||
|
||||
public PNLAnimation to_animation(PNLView.Columns key) =>
|
||||
new PNLAnimation {
|
||||
column = key,
|
||||
foreground = is_font_color_overridden ? font_color_override.color.rgb_color() : (uint?)null,
|
||||
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
|
||||
font_weight = is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : (int?)null
|
||||
};
|
||||
|
||||
public PNLCellAppearanceProto to_appearance_proto() =>
|
||||
new PNLCellAppearanceProto {
|
||||
background = is_background_overridden ? background_override.color.rgb_color() : (uint?)null,
|
||||
font_params = to_font_params()
|
||||
};
|
||||
|
||||
public FontParamsProto to_font_params() =>
|
||||
new FontParamsProto {
|
||||
typeface = new TypefaceProto(
|
||||
font_family_override?.name(),
|
||||
is_font_weight_overridden ? font_weight_override.ToOpenTypeWeight() : 0),
|
||||
font_color = is_font_color_overridden ? font_color_override.color.rgb_color() : 0,
|
||||
font_size = font_size_override
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System.Windows;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLCellAppearanceVM : ViewModelBase {
|
||||
FontWeight _font_weight;
|
||||
FontFamily _font_family;
|
||||
double _font_size = double.NaN;
|
||||
|
||||
public ColorPickerPopupVM background { get; } = new ColorPickerPopupVM { bind_opacity_to_alpha = true, opacity = 1 };
|
||||
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
|
||||
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
|
||||
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
|
||||
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
|
||||
|
||||
public PNLCellAppearance to_appearance() =>
|
||||
new PNLCellAppearance(font_color.color, background.color, font_weight, font_family, font_size);
|
||||
|
||||
public void load(PNLCellAppearance appearance) {
|
||||
if (appearance.background.HasValue)
|
||||
background.color = appearance.background.Value;
|
||||
if (appearance.foreground.HasValue)
|
||||
font_color.color = appearance.foreground.Value;
|
||||
if (appearance.font_family != null)
|
||||
font_family = appearance.font_family;
|
||||
if (appearance.font_weight.HasValue)
|
||||
font_weight = appearance.font_weight.Value;
|
||||
if (appearance.font_size > 0)
|
||||
font_size = appearance.font_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLColumnVM : ViewModelBase, ISimpleColumnVM {
|
||||
readonly SimpleGridSettingsVM _owner;
|
||||
readonly PNLView.Columns _pnl_id;
|
||||
object _selected_item;
|
||||
string _display_name;
|
||||
bool _is_visible;
|
||||
double _width;
|
||||
HorizontalAlignment _horizontal_alignment;
|
||||
|
||||
public PNLColumnVM(SimpleGridSettingsVM owner, PNLView.Columns id) {
|
||||
_owner = owner;
|
||||
_pnl_id = id;
|
||||
default_column_vm.PropertyChanged += echo;
|
||||
void echo(object sender, PropertyChangedEventArgs e) {
|
||||
switch (e.PropertyName) {
|
||||
case nameof(is_font_weight_overridden):
|
||||
case nameof(is_font_color_overridden):
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PNLCellAppearanceOverrideVM default_column_vm { get; } = new PNLCellAppearanceOverrideVM();
|
||||
public PNLCellAppearanceOverrideVM group_column_vm { get; } = new PNLCellAppearanceOverrideVM();
|
||||
public PNLCellAppearanceOverrideVM header_column_vm { get; } = new PNLCellAppearanceOverrideVM();
|
||||
//
|
||||
public ObservableCollection<PNLRuleColumnVM> rule_columns { get; } = new ObservableCollection<PNLRuleColumnVM>();
|
||||
|
||||
public object selected_item {
|
||||
get => _selected_item;
|
||||
set {
|
||||
if (value != null)
|
||||
Set(ref _selected_item, value);
|
||||
}
|
||||
}
|
||||
|
||||
public string id { get => _pnl_id.ToString(); set => Log.assert(value == _pnl_id.ToString()); }
|
||||
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
|
||||
public double width { get => _width; set => Set(ref _width, value); }
|
||||
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
|
||||
public bool is_font_color_overridden {
|
||||
get => default_column_vm.is_font_color_overridden;
|
||||
set => default_column_vm.is_font_color_overridden = value;
|
||||
}
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
|
||||
|
||||
public ColorPickerPopupVM font_color_override => default_column_vm.font_color_override;
|
||||
|
||||
public bool is_font_weight_overridden { get => default_column_vm.is_font_weight_overridden; set => default_column_vm.is_font_weight_overridden = value; }
|
||||
public FontWeight font_weight_override { get => default_column_vm.font_weight_override; set => default_column_vm.font_weight_override = value; }
|
||||
|
||||
public void apply_flags(GridColumnSettingsFeatureFlags flags) { }
|
||||
|
||||
public GridColumnSettings create() {
|
||||
var result = new GridColumnSettings(id: id, display_name: display_name, is_visible: is_visible, horizontal_alignment: horizontal_alignment, width: width);
|
||||
result = default_column_vm.apply_to(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void load_from(GridColumnSettings value) {
|
||||
_assert_id(value.id);
|
||||
default_column_vm.load(value);
|
||||
display_name = value.display_name;
|
||||
width = value.width;
|
||||
is_visible = value.is_visible;
|
||||
horizontal_alignment = value.horizontal_alignment;
|
||||
}
|
||||
|
||||
public void load_from(GridColumnSettings value, IReadOnlyDictionary<string, PNLAnimation> triggers, GridColumnSettings group, IReadOnlyDictionary<string, PNLAnimation> group_triggers, IReadOnlyDictionary<string, PNLAnimation> header_triggers, GridColumnSettings header) {
|
||||
_assert_id(value.id);
|
||||
load_from(value);
|
||||
if (group != null)
|
||||
group_column_vm.load(group);
|
||||
if (header != null)
|
||||
header_column_vm.load(header);
|
||||
rule_columns.SetItems(triggers.Select(kv => {
|
||||
var rule_col = new PNLRuleColumnVM(kv.Key);
|
||||
rule_col.load_from(kv.Value, group_triggers.GetValueOrDefault(kv.Key), header_triggers.GetValueOrDefault(kv.Key));
|
||||
return rule_col;
|
||||
}));
|
||||
}
|
||||
|
||||
public IEnumerable<(string expression, PNLAnimation row, PNLAnimation group, PNLAnimation header)> get_triggers() {
|
||||
foreach (var col in rule_columns)
|
||||
yield return (col.rule_text, col.row_params.to_animation(_pnl_id), col.group_params.to_animation(_pnl_id), col.header_params.to_animation(_pnl_id));
|
||||
}
|
||||
|
||||
void _assert_id(string id) =>
|
||||
Log.assert(id == _pnl_id.ToString(), $"PNLColumn id string '{id}' does not match the expected {typeof(PNLView.Columns)} key '{_pnl_id}.'");
|
||||
|
||||
public override string ToString() => "Default";
|
||||
}
|
||||
|
||||
class PNLRuleColumnVM : ViewModelBase, IEquatable<PNLRuleColumnVM> {
|
||||
public PNLRuleColumnVM(string rule_text) =>
|
||||
this.rule_text = rule_text;
|
||||
|
||||
public string rule_text { get; }
|
||||
|
||||
public PNLCellAppearanceOverrideVM row_params { get; } = new PNLCellAppearanceOverrideVM();
|
||||
public PNLCellAppearanceOverrideVM group_params { get; } = new PNLCellAppearanceOverrideVM();
|
||||
public PNLCellAppearanceOverrideVM header_params { get; } = new PNLCellAppearanceOverrideVM();
|
||||
|
||||
public void load_from(PNLAnimation row, PNLAnimation group, PNLAnimation header) {
|
||||
row_params.load(row);
|
||||
group_params.load(group);
|
||||
header_params.load(header);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRuleColumnVM other) =>
|
||||
other != null && rule_text == other.rule_text;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as PNLRuleColumnVM);
|
||||
public override int GetHashCode() => rule_text.GetHashCode();
|
||||
public override string ToString() => rule_text;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLGroupRowAppearanceVM : ViewModelBase, IValueFactory<(PNLCellAppearance appearance, IReadOnlyList<Color> background_colors)> {
|
||||
FontWeight _font_weight;
|
||||
FontFamily _font_family;
|
||||
double _font_size = double.NaN;
|
||||
|
||||
public PNLGroupRowAppearanceVM() {
|
||||
add_background_cmd = new DelegateCommand(_add_background);
|
||||
// 4 elements is enough to alternate through any reasonable number of groups,
|
||||
// and clicking to add new backgrounds seems awkward
|
||||
for (int i = 0; i < 4; i++)
|
||||
_add_background();
|
||||
}
|
||||
|
||||
public ObservableCollection<NamedColorPickerPopupVM> background_colors { get; } = new ObservableCollection<NamedColorPickerPopupVM>();
|
||||
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
|
||||
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
|
||||
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
|
||||
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
|
||||
public ICommand add_background_cmd { get; }
|
||||
|
||||
public (PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) create() =>
|
||||
(new PNLCellAppearance(font_color.color, Colors.Transparent, font_weight, font_family, font_size),
|
||||
background_colors.WrapSelectorRO(cp => cp.color));
|
||||
|
||||
public void load_from((PNLCellAppearance appearance, IReadOnlyList<Color> background_colors) value) {
|
||||
var (app, bg_colors) = value;
|
||||
// background colors
|
||||
while (background_colors.Count < bg_colors.Count)
|
||||
_add_background();
|
||||
for (int i = 0; i < bg_colors.Count; i++)
|
||||
background_colors[i].color = bg_colors[i];
|
||||
//
|
||||
if (app.foreground.HasValue) font_color.color = app.foreground.Value;
|
||||
if (app.font_family != null) font_family = app.font_family;
|
||||
if (app.font_weight.HasValue) font_weight = app.font_weight.Value;
|
||||
if (app.font_size > 0) font_size = app.font_size;
|
||||
}
|
||||
|
||||
void _add_background() =>
|
||||
background_colors.Add(new NamedColorPickerPopupVM {
|
||||
bind_opacity_to_alpha = true,
|
||||
name = $"Background {background_colors.Count + 1}"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.expression;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
|
||||
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
|
||||
readonly DeferrableActionWrapper _sync_anchors;
|
||||
|
||||
PNLCalculationType _calculation_type;
|
||||
PNLHeaderRowDisplay _header_row_display;
|
||||
PNLSettings _last_loaded;
|
||||
|
||||
public PNLSettingsVM(PNLSettings settings) {
|
||||
// init anchor cache, deferrable resync
|
||||
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
|
||||
_sync_anchors = new DeferrableActionWrapper(() => {
|
||||
for (int i = 0; i < anchor_items.Count; i++) {
|
||||
anchor_items[i].anchor_index = i + 1;
|
||||
anchor_items[i].refresh();
|
||||
}
|
||||
});
|
||||
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
|
||||
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
|
||||
col_vm.load_from(col_settings);
|
||||
return col_vm;
|
||||
});
|
||||
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
|
||||
_dispose = Disposable.Create(
|
||||
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
|
||||
load(settings);
|
||||
}
|
||||
|
||||
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
|
||||
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
|
||||
public SimpleGridSettingsVM core_settings_vm { get; }
|
||||
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
|
||||
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
|
||||
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
|
||||
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
|
||||
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
|
||||
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
|
||||
|
||||
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
|
||||
|
||||
public PNLSettings get_result() {
|
||||
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
|
||||
s.calculation_type = _calculation_type;
|
||||
s.grid = core_settings_vm.create();
|
||||
// move anchor cols to the left, order wrt grouping hierarchy
|
||||
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
|
||||
for (int i = 0; i < s.grid.columns.Count; i++) {
|
||||
var key = s.grid.columns[i].strong_id<ColumnKeys>();
|
||||
if (_anchor_cache.TryGetValue(key, out var a) && a.anchor_index.HasValue) {
|
||||
// anchor index is 1 based, subtract 1 to get 0 based
|
||||
int anchor_index = a.anchor_index.Value - 1;
|
||||
if (anchor_index != i && anchor_index >= 0 && anchor_index < s.grid.columns.Count) {
|
||||
new_cols.Value.RemoveAt(i);
|
||||
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// rule-based animations
|
||||
var trigger_anims = core_settings_vm.columns
|
||||
.OfType<PNLColumnVM>()
|
||||
.SelectMany(c => c.get_triggers())
|
||||
.GroupBy(tup => tup.expression)
|
||||
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
|
||||
.Select(g => (
|
||||
expression: g.Key,
|
||||
animations: g.ToArray()))
|
||||
.ToList();
|
||||
|
||||
var normalized_anims = trigger_anims
|
||||
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
|
||||
.ToList();
|
||||
|
||||
s = s.with(anchors: anchor_items.Select(vm => vm.key),
|
||||
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
|
||||
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.group_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.header_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
animations: trigger_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.row)
|
||||
}),
|
||||
group_header_animations: normalized_anims
|
||||
.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.group)
|
||||
}),
|
||||
header_row_animations: normalized_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.header)
|
||||
}));
|
||||
s.header_row_display = header_row_display;
|
||||
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
|
||||
// group
|
||||
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
|
||||
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
|
||||
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
|
||||
//
|
||||
return s;
|
||||
}
|
||||
|
||||
public void load(PNLSettings value) {
|
||||
_last_loaded = value;
|
||||
header_row_defaults.load(value.header_cell_appearance.to_appearance());
|
||||
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
|
||||
var column_map = _map_column_data(value.grid.columns, value.animations);
|
||||
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
|
||||
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
|
||||
|
||||
core_settings_vm.load_from(value.grid);
|
||||
|
||||
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
|
||||
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
|
||||
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
|
||||
var (col_settings, trigger_animations) = column_map[key];
|
||||
var group_data = group_column_map.GetNullable(key);
|
||||
var header_data = header_column_map.GetNullable(key);
|
||||
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
|
||||
}
|
||||
using (_sync_anchors.defer_execution())
|
||||
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
|
||||
header_row_display = value.header_row_display;
|
||||
calculation_type = value.calculation_type;
|
||||
}
|
||||
|
||||
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
|
||||
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
|
||||
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
|
||||
|
||||
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
|
||||
var map = columns.ToDictionary(
|
||||
c => c.strong_id<ColumnKeys>(),
|
||||
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
|
||||
foreach (var trig in triggers) {
|
||||
foreach (var anim in trig.animations) {
|
||||
if (map.TryGetValue(anim.column, out var col_data))
|
||||
col_data.triggers[trig.expression] = anim;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
void _append_or_remove_anchor(ColumnKeys key, bool include) {
|
||||
if (!_anchor_cache.TryGetValue(key, out var vm))
|
||||
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
|
||||
if (vm.include == include)
|
||||
return;
|
||||
if (include) {
|
||||
if (anchor_items.Any(a => a.key == key))
|
||||
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
|
||||
anchor_items.Add(vm);
|
||||
vm.anchor_index = anchor_items.Count;
|
||||
} else {
|
||||
if (!anchor_items.Remove(vm))
|
||||
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
|
||||
vm.anchor_index = null;
|
||||
}
|
||||
_sync_anchors.execute();
|
||||
}
|
||||
|
||||
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
|
||||
readonly PNLSettingsVM _owner;
|
||||
int? _anchor_index;
|
||||
|
||||
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
|
||||
_owner = owner;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public ColumnKeys key { get; }
|
||||
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
|
||||
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
|
||||
|
||||
public void refresh() =>
|
||||
RaisePropertyChanged(nameof(include), nameof(anchor_index));
|
||||
|
||||
public bool Equals(AnchorItemVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as AnchorItemVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
EnumHelper.get_display_name(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLCellEmbeddedAnimationModel<TRow> {
|
||||
IReadOnlyList<IPNLAnimationTrigger<TRow>> _triggers;
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _defaults;
|
||||
TRow _row;
|
||||
|
||||
public PNLCellEmbeddedAnimationModel(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<TRow>> triggers) {
|
||||
_defaults = defaults;
|
||||
_triggers = triggers;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<ColumnKeys, PNLCellAppearance> cell_appearances { get; private set; }
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<TRow>> triggers) {
|
||||
_defaults = defaults;
|
||||
_triggers = triggers;
|
||||
on_row(_row);
|
||||
}
|
||||
|
||||
public void on_row(TRow data) {
|
||||
_row = data;
|
||||
var bd = _defaults.ToBuilder();
|
||||
foreach (var t in _triggers) {
|
||||
foreach (var kv in t.trigger(data)) {
|
||||
if (!bd.TryGetValue(kv.Key, out var app)) {
|
||||
app = _defaults[kv.Key];
|
||||
}
|
||||
app = kv.Value.merge(app);
|
||||
bd[kv.Key] = app;
|
||||
}
|
||||
}
|
||||
cell_appearances = bd.ToImmutable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLCellIndexBuilder<TRow> {
|
||||
public readonly DIYGridAnimationModel<PNLView.Columns, TRow> animation_model = new DIYGridAnimationModel<PNLView.Columns, TRow>(TimeSpan.FromSeconds(0.3));
|
||||
|
||||
static readonly Func<WorkingOrderSideData, string> _fmt_working_order_data =
|
||||
d => OrderUtils.order_collection_summary(d.top_price, d.top_volume, d.working_total);
|
||||
|
||||
static readonly IComparer<WorkingOrderSideData> _working_total_comparer =
|
||||
Comparer<int>.Default.wrap<int, WorkingOrderSideData>(d => d.working_total);
|
||||
|
||||
readonly Dictionary<PNLView.Columns, IPNLCellVM<TRow>> _cell_index = new Dictionary<PNLView.Columns, IPNLCellVM<TRow>>();
|
||||
|
||||
public PNLCellVM<TRow, T> add<T>(PNLView.Columns key, Func<TRow, T> get, Func<T, string> fmt = null) =>
|
||||
add(key, get, fmt, EqualityComparer<T>.Default, Comparer<T>.Default);
|
||||
|
||||
public PNLCellVM<TRow, T> add_pnl<T>(PNLView.Columns key, Func<TRow, T> get) where T : struct, IComparable<T>, IFormattable =>
|
||||
add_delta(key, BTSColors.PNL.profit_color, BTSColors.PNL.loss_color, get, FormattingDelegates.Price<T>.Value);
|
||||
|
||||
public PNLCellVM<TRow, decimal> add_pnl_decimal(PNLView.Columns key, Func<TRow, decimal> get) =>
|
||||
add_delta<decimal>(key, BTSColors.PNL.profit_color, BTSColors.PNL.loss_color, OrderDataHelper.PriceComparerDecimal, get, FormattingDelegates.Price<decimal>.Value);
|
||||
|
||||
public PNLCellVM<TRow, double> add_pnl_double(PNLView.Columns key, Func<TRow, double> get) =>
|
||||
add_delta(key, BTSColors.PNL.profit_color, BTSColors.PNL.loss_color, OrderDataHelper.PriceComparer, get, FormattingDelegates.Price<double>.Value);
|
||||
|
||||
public PNLCellVM<TRow, double> add_net_price(PNLView.Columns key, Func<TRow, double> get) =>
|
||||
add_delta(key, BTSColors.MarketData.bid, BTSColors.MarketData.ask, OrderDataHelper.PriceComparer, get, FormattingDelegates.Price<double>.Value);
|
||||
|
||||
public PNLCellVM<TRow, T> add_net_pos<T>(PNLView.Columns key, Func<TRow, T> get, Func<T, string> fmt = null) where T : struct, IComparable<T> =>
|
||||
add_delta(key, BTSColors.MarketData.bid, BTSColors.MarketData.ask, get, fmt);
|
||||
|
||||
public PNLCellVM<TRow, double> add_long_price(PNLView.Columns key, Func<TRow, double> get) =>
|
||||
add_delta(key, BTSColors.MarketData.bid, BTSColors.MarketData.bid, OrderDataHelper.PriceComparer, get, FormattingDelegates.Price<double>.Value);
|
||||
|
||||
public PNLCellVM<TRow, T> add_long_pos<T>(PNLView.Columns key, Func<TRow, T> get, Func<T, string> fmt = null) where T : struct, IComparable<T> =>
|
||||
add_delta(key, BTSColors.MarketData.bid, BTSColors.MarketData.ask, get, fmt);
|
||||
|
||||
public PNLCellVM<TRow, double> add_short_price(PNLView.Columns key, Func<TRow, double> get) =>
|
||||
add_delta(key, BTSColors.MarketData.ask, BTSColors.MarketData.ask, OrderDataHelper.PriceComparer, get, FormattingDelegates.Price<double>.Value);
|
||||
|
||||
public PNLCellVM<TRow, T> add_short_pos<T>(PNLView.Columns key, Func<TRow, T> get, Func<T, string> fmt = null) where T : struct, IComparable<T> =>
|
||||
add_delta(key, BTSColors.MarketData.ask, BTSColors.MarketData.bid, get, fmt);
|
||||
|
||||
public PNLCellVM<TRow, T> add_price<T>(PNLView.Columns key, Func<TRow, T> get) where T : struct, IComparable<T>, IFormattable =>
|
||||
add_delta(key, BTSColors.PNL.profit_color, BTSColors.PNL.loss_color, get, FormattingDelegates.Price<T>.Value);
|
||||
|
||||
public PNLCellVM<TRow, double> add_price_double(PNLView.Columns key, Func<TRow, double> get) =>
|
||||
add_delta(key, BTSColors.PNL.profit_color, BTSColors.PNL.loss_color, OrderDataHelper.PriceComparer, get, FormattingDelegates.Price<double>.Value);
|
||||
|
||||
public PNLCellVM<TRow, WorkingOrderSideData> add_working_long(PNLView.Columns key, Func<TRow, WorkingOrderSideData> get, Func<TRow, double> get_market) =>
|
||||
_add_working(key, BTSColors.MarketData.bid, BTSColors.MarketData.ask, get, get_market, OrderDataHelper.PriceComparer);
|
||||
|
||||
public PNLCellVM<TRow, WorkingOrderSideData> add_working_short(PNLView.Columns key, Func<TRow, WorkingOrderSideData> get, Func<TRow, double> get_market) =>
|
||||
_add_working(key, BTSColors.MarketData.ask, BTSColors.MarketData.bid, get, get_market, new ReverseComparer<double>(OrderDataHelper.PriceComparer));
|
||||
|
||||
PNLCellVM<TRow, WorkingOrderSideData> _add_working(PNLView.Columns key, Color increase, Color decrease, Func<TRow, WorkingOrderSideData> get, Func<TRow, double> get_market, IComparer<double> price_comparer) {
|
||||
var delta_trig = DIYGridTriggerAnimations.delta_trigger<int, PNLView.Columns>(key, increase, decrease)
|
||||
.wrap<int, TRow, PNLView.Columns>(r => get(r).working_total);
|
||||
var top_of_book_trig = new TopOfBookTrigger<int, double>(key, increase,
|
||||
r => get(r).working_total,
|
||||
r => get(r).top_price,
|
||||
get_market,
|
||||
price_comparer);
|
||||
var zipped_trig = delta_trig.zip_indefinite_frame(r => {
|
||||
var frames = top_of_book_trig.on_data(r);
|
||||
return frames.TryGetFirst(out var f) ? f.animation : null;
|
||||
});
|
||||
var cell = add(key, get, _fmt_working_order_data, EqualityComparer<WorkingOrderSideData>.Default, _working_total_comparer);
|
||||
animation_model.add_trigger(delta_trig);
|
||||
animation_model.add_trigger(top_of_book_trig);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public PNLCellVM<TRow, T> add_delta<T>(PNLView.Columns key, Color increase, Color decrease, Func<TRow, T> get, Func<T, string> fmt = null) where T : struct, IComparable<T> =>
|
||||
add_delta(key, increase, decrease, Comparer<T>.Default, get, fmt);
|
||||
|
||||
public PNLCellVM<TRow, T> add_delta<T>(PNLView.Columns key, Color increase, Color decrease, IComparer<T> comparer, Func<TRow, T> get, Func<T, string> fmt = null) where T : struct {
|
||||
var cell = add(key, get, fmt, EqualityComparer<T>.Default, Comparer<T>.Default);
|
||||
animation_model.add_trigger(
|
||||
DIYGridTriggerAnimations.delta_trigger(key, increase, decrease, comparer)
|
||||
.wrap(get));
|
||||
return cell;
|
||||
}
|
||||
|
||||
public PNLCellVM<TRow, T> add<T>(PNLView.Columns key, Func<TRow, T> get, Func<T, string> fmt, IEqualityComparer<T> eq_comparer, IComparer<T> comparer) {
|
||||
var cell = new PNLCellVM<TRow, T>(key, get, fmt, eq_comparer, comparer);
|
||||
_cell_index[key] = cell;
|
||||
return cell;
|
||||
}
|
||||
|
||||
public void on_row(TRow row) {
|
||||
foreach (var cell in _cell_index.Values)
|
||||
cell.on_row(row);
|
||||
animation_model.on_row(row);
|
||||
}
|
||||
|
||||
public void set_appearances(IReadOnlyDictionary<PNLView.Columns, PNLCellAppearance> appearances) {
|
||||
foreach (var kv in appearances) {
|
||||
if (_cell_index.TryGetValue(kv.Key, out var c))
|
||||
c.apply_appearance(kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
class TopOfBookTrigger<TPosition, TPrice> : ITriggerAnimation<TRow, PNLView.Columns> where TPosition : struct, IComparable<TPosition> where TPrice : struct, IComparable<TPrice> {
|
||||
readonly PNLView.Columns _column;
|
||||
readonly Func<TRow, TPosition> _get_pos;
|
||||
readonly Func<TRow, TPrice> _get_price, _get_market;
|
||||
readonly IComparer<TPrice> _comparer;
|
||||
readonly Color _border_color;
|
||||
|
||||
public TopOfBookTrigger(
|
||||
PNLView.Columns column,
|
||||
Color border_color,
|
||||
Func<TRow, TPosition> get_pos,
|
||||
Func<TRow, TPrice> get_price,
|
||||
Func<TRow, TPrice> get_market,
|
||||
IComparer<TPrice> comparer) {
|
||||
_column = column;
|
||||
_border_color = border_color;
|
||||
_get_pos = get_pos;
|
||||
_get_price = get_price;
|
||||
_get_market = get_market;
|
||||
_comparer = comparer;
|
||||
}
|
||||
|
||||
public IEnumerable<AnimationFrame<PNLView.Columns>> on_data(TRow value) {
|
||||
var pos = _get_pos(value);
|
||||
if (pos.CompareTo(default) > 0) {
|
||||
var price = _get_price(value);
|
||||
var market = _get_market(value);
|
||||
if (_comparer.Compare(price, market) >= 0)
|
||||
yield return new AnimationFrame<PNLView.Columns>(
|
||||
TimeSpan.Zero,
|
||||
ImmutableDictionary<SymbolKey<PNLView.Columns>, Animation>.Empty.Add(
|
||||
new SymbolKey<PNLView.Columns>(_column, mbts.ui.spark.CellAnimation.Type.Border),
|
||||
new Animation(mbts.ui.spark.CellAnimation.Type.Border, _border_color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
interface IPNLCellVM {
|
||||
object value { get; }
|
||||
IPNLCellData cell_data { get; }
|
||||
FontWeight font_weight { get; set; }
|
||||
Color foreground { get; set; }
|
||||
Color background { get; set; }
|
||||
Color border { get; set; }
|
||||
}
|
||||
|
||||
interface IPNLCellVM<TRow> : IPNLCellVM {
|
||||
void on_row(TRow row);
|
||||
}
|
||||
|
||||
/// <see cref="IPNLCellData"/> is needed for the non-generic rows/group IComparer impl.
|
||||
/// The groups are bound to IPNLCellData, so the column property can be used to retrieve the appropriate comparer for that cell.
|
||||
interface IPNLCellData {
|
||||
PNLView.Columns column { get; }
|
||||
object value { get; }
|
||||
}
|
||||
|
||||
readonly struct PNLCellValue<T> : IPNLCellData {
|
||||
public readonly T value;
|
||||
readonly string _text;
|
||||
|
||||
public PNLCellValue(PNLView.Columns column, T value, string text) {
|
||||
this.value = value;
|
||||
this.column = column;
|
||||
_text = text;
|
||||
}
|
||||
|
||||
public PNLView.Columns column { get; }
|
||||
object IPNLCellData.value => value;
|
||||
|
||||
public bool Equals(PNLCellValue<T> other) =>
|
||||
column == other.column &&
|
||||
Equals(value, other.value);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLCellValue<T> other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase((int)column, value?.GetHashCode() ?? 0);
|
||||
|
||||
public override string ToString() => _text;
|
||||
}
|
||||
|
||||
class PNLCellVM<TRow, TValue> : ViewModelBase, IObserver<PNLAnimation>, IPNLCellVM<TRow>, IComparable<PNLCellVM<TRow, TValue>>, IComparable {
|
||||
readonly Func<TValue, string> _fmt;
|
||||
readonly Func<TRow, TValue> _get;
|
||||
readonly IEqualityComparer<TValue> _eq_comparer;
|
||||
readonly IComparer<TValue> _comparer;
|
||||
readonly PNLView.Columns _column;
|
||||
IComparer _value_comparer;
|
||||
Color
|
||||
_foreground = Colors.Black,
|
||||
_background = Colors.Transparent,
|
||||
_border = Colors.Transparent;
|
||||
string _text;
|
||||
TValue _value;
|
||||
IPNLCellData _cell_value;
|
||||
|
||||
public PNLCellVM(PNLView.Columns column, Func<TRow, TValue> get)
|
||||
: this(column, get, null, EqualityComparer<TValue>.Default, Comparer<TValue>.Default) { }
|
||||
|
||||
public PNLCellVM(PNLView.Columns column, Func<TRow, TValue> get, Func<TValue, string> fmt)
|
||||
: this(column, get, fmt, EqualityComparer<TValue>.Default, Comparer<TValue>.Default) { }
|
||||
|
||||
public PNLCellVM(PNLView.Columns column, Func<TRow, TValue> get, Func<TValue, string> fmt, IEqualityComparer<TValue> eq_comparer, IComparer<TValue> comparer) {
|
||||
_column = column;
|
||||
_get = get;
|
||||
_fmt = fmt;
|
||||
_eq_comparer = eq_comparer;
|
||||
_comparer = comparer;
|
||||
}
|
||||
|
||||
public IComparer value_comparer => _value_comparer ?? (_value_comparer = _comparer.to_non_generic());
|
||||
|
||||
object IPNLCellVM.value => value;
|
||||
public IPNLCellData cell_data => _cell_value ?? (_cell_value = new PNLCellValue<TValue>(_column, _value, text));
|
||||
public TValue value {
|
||||
get => _value;
|
||||
set {
|
||||
if (Set(ref _value, value)) {
|
||||
_text = null;
|
||||
_cell_value = null;
|
||||
RaisePropertyChanged(nameof(cell_data), nameof(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
public string text {
|
||||
get => _text ?? (_text = _fmt?.Invoke(value) ?? value?.ToString());
|
||||
set => Set(ref _text, value);
|
||||
}
|
||||
|
||||
public Color foreground {
|
||||
get => _foreground;
|
||||
set => Set(ref _foreground, value);
|
||||
}
|
||||
|
||||
public Color background {
|
||||
get => _background;
|
||||
set => Set(ref _background, value);
|
||||
}
|
||||
|
||||
public Color border {
|
||||
get => _border;
|
||||
set => Set(ref _border, value);
|
||||
}
|
||||
|
||||
FontWeight _font_weight;
|
||||
public FontWeight font_weight {
|
||||
get => _font_weight;
|
||||
set => Set(ref _font_weight, value);
|
||||
}
|
||||
|
||||
public void on_row(TRow row) =>
|
||||
value = _get(row);
|
||||
|
||||
public void OnNext(PNLAnimation animation) {
|
||||
if (animation.foreground.HasValue)
|
||||
foreground = animation.foreground.Value.argb_color();
|
||||
if (animation.background.HasValue)
|
||||
background = animation.background.Value.argb_color();
|
||||
if (animation.font_weight.HasValue)
|
||||
font_weight = FontWeight.FromOpenTypeWeight(animation.font_weight.Value);
|
||||
}
|
||||
public void OnError(Exception error) { }
|
||||
public void OnCompleted() { }
|
||||
|
||||
bool Equals(PNLCellVM<TRow, TValue> other) =>
|
||||
Equals(value, other.value);
|
||||
|
||||
public override string ToString() =>
|
||||
text;
|
||||
|
||||
int IComparable<PNLCellVM<TRow, TValue>>.CompareTo(PNLCellVM<TRow, TValue> other) =>
|
||||
other == null ? throw new ArgumentNullException(nameof(other)) : _comparer.Compare(value, other.value);
|
||||
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is PNLCellVM<TRow, TValue> other ?
|
||||
_comparer.Compare(value, other.value) :
|
||||
throw new ArgumentException(nameof(obj), $"The specified object is not of type {typeof(PNLCellVM<TRow, TValue>)} (actually {obj?.GetType()}).");
|
||||
}
|
||||
|
||||
static class PNLCellVMExtensions {
|
||||
public static void apply_appearance(this IPNLCellVM cell, PNLCellAppearance animation) {
|
||||
if (animation.foreground.HasValue)
|
||||
cell.foreground = animation.foreground.Value;
|
||||
if (animation.background.HasValue)
|
||||
cell.background = animation.background.Value;
|
||||
if (animation.font_weight.HasValue)
|
||||
cell.font_weight = animation.font_weight.Value;
|
||||
// TODO: font family, size
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
using bts.utils;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Media;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLHeaderRowVM : ViewModelBase, IRaiseNotifyPropertyChanged, INotifyPropertyChanged {
|
||||
static readonly IPropertyChangedDetector<PNLHeaderRowVM> _change_detector =
|
||||
ObjectSnapshot.build_detector_except<PNLHeaderRowVM>(nameof(data));
|
||||
|
||||
readonly PNLCellIndexBuilder<PNLNormalizedData> _cell_map = new PNLCellIndexBuilder<PNLNormalizedData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLNormalizedData> _animator;
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _default_appearances;
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> _triggers;
|
||||
PNLNormalizedData _data = PNLNormalizedData.zero;
|
||||
Color _background_color;
|
||||
|
||||
public PNLHeaderRowVM(
|
||||
Color background_color,
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
|
||||
//
|
||||
_background_color = background_color;
|
||||
_default_appearances = default_appearances;
|
||||
_triggers = triggers;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLNormalizedData>(default_appearances, triggers);
|
||||
_cell_map.set_appearances(default_appearances);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_decimal(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
|
||||
// raw day
|
||||
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
|
||||
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
|
||||
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
|
||||
working_long_pos = _cell_map.add_long_pos(ColumnKeys.WorkingLongPos, d => d.working_long_pos);
|
||||
working_short_pos = _cell_map.add_short_pos(ColumnKeys.WorkingShortPos, r => r.working_short_pos);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_decimal(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.total_net_pos);
|
||||
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.total_long_pos);
|
||||
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
|
||||
}
|
||||
|
||||
public event Action data_changed;
|
||||
|
||||
public PNLNormalizedData data {
|
||||
get => _data;
|
||||
set {
|
||||
if (value != _data) {
|
||||
_data = value;
|
||||
_cell_map.on_row(value);
|
||||
_animator.on_row(value);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
data_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
public Color background_color {
|
||||
get => _background_color;
|
||||
set => Set(ref _background_color, value);
|
||||
}
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
|
||||
// raw day
|
||||
public PNLCellVM<PNLNormalizedData, decimal> raw_day_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> raw_day_short_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> raw_day_net_pos { get; }
|
||||
// sod
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_net_pos { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_short_pos { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLNormalizedData, decimal> working_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> working_short_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> working_net_pos { get; }
|
||||
|
||||
public void on_settings(
|
||||
Color background_color,
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
|
||||
//
|
||||
this.background_color = background_color;
|
||||
_default_appearances = default_appearances;
|
||||
_triggers = triggers;
|
||||
_animator.on_settings(_default_appearances, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Data;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
interface IPNLColumnComparer {
|
||||
IComparer value_comparer { get; }
|
||||
IComparer<PNLRowVM> row_comparer { get; }
|
||||
}
|
||||
|
||||
class PNLColumnComparer<TInner> : IPNLColumnComparer, IComparer<PNLRowVM>, IComparer {
|
||||
public readonly IComparer<TInner> inner_comparer;
|
||||
IComparer _value_comparer;
|
||||
readonly Func<PNLRowVM, TInner> _get;
|
||||
|
||||
public PNLColumnComparer(Func<PNLRowVM, TInner> get)
|
||||
: this(get, Comparer<TInner>.Default) { }
|
||||
|
||||
public PNLColumnComparer(Func<PNLRowVM, TInner> get, IComparer<TInner> inner_comparer) {
|
||||
_get = get;
|
||||
this.inner_comparer = inner_comparer;
|
||||
}
|
||||
|
||||
public IComparer value_comparer => _value_comparer ?? (_value_comparer = inner_comparer.to_non_generic());
|
||||
public IComparer<PNLRowVM> row_comparer => this;
|
||||
|
||||
public int Compare(object x, object y) =>
|
||||
Compare(x as PNLRowVM, y as PNLRowVM);
|
||||
|
||||
public int Compare(PNLRowVM x, PNLRowVM y) {
|
||||
if (x == null) return y == null ? 0 : -1;
|
||||
if (y == null) return 1;
|
||||
var xval = _get(x);
|
||||
var yval = _get(y);
|
||||
return inner_comparer.Compare(xval, yval);
|
||||
}
|
||||
}
|
||||
|
||||
class ReversePNLColumnComparer : IPNLColumnComparer, IComparer<PNLRowVM>, IComparer {
|
||||
readonly IPNLColumnComparer _comp_tuple;
|
||||
|
||||
public ReversePNLColumnComparer(IPNLColumnComparer comp_tuple) =>
|
||||
_comp_tuple = comp_tuple;
|
||||
|
||||
public IComparer value_comparer => this;
|
||||
public IComparer<PNLRowVM> row_comparer => this;
|
||||
|
||||
int IComparer.Compare(object x, object y) =>
|
||||
_comp_tuple.value_comparer.Compare(y, x);
|
||||
|
||||
int IComparer<PNLRowVM>.Compare(PNLRowVM x, PNLRowVM y) =>
|
||||
_comp_tuple.row_comparer.Compare(y, x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IComparer"/> that can compare 2 objects of type <see cref="PNLRowVM"/>, and 2 objects of type <see cref="CollectionViewGroup"/>
|
||||
/// (assuming the latter's <see cref="CollectionViewGroup.Name"/> is of type <see cref="IPNLCellData"/>). Cross type comparisons are not supported.
|
||||
/// </summary>
|
||||
class PNLSuperRowComparer : IComparer, IComparer<PNLRowVM>, IComparer<CollectionViewGroup> {
|
||||
readonly Func<ColumnKeys, IPNLColumnComparer> _get_column_comparer;
|
||||
IComparer<PNLRowVM> _row_comp;
|
||||
|
||||
public PNLSuperRowComparer(IComparer<PNLRowVM> row_comp, Func<ColumnKeys, IPNLColumnComparer> get_column_comparer) {
|
||||
_row_comp = row_comp;
|
||||
_get_column_comparer = get_column_comparer;
|
||||
}
|
||||
|
||||
public int Compare(object x, object y) {
|
||||
var rc = _try_compare_rows(x as PNLRowVM, y as PNLRowVM);
|
||||
if (rc.HasValue)
|
||||
return rc.Value;
|
||||
var gc = _try_compare_groups(x as CollectionViewGroup, y as CollectionViewGroup);
|
||||
if (gc.HasValue)
|
||||
return gc.Value;
|
||||
throw new InvalidOperationException($"{nameof(PNLSuperRowComparer)} unexpected comparison '{x}' with '{y}.'");
|
||||
}
|
||||
|
||||
int IComparer<PNLRowVM>.Compare(PNLRowVM x, PNLRowVM y) =>
|
||||
_try_compare_rows(x, y) ?? throw new InvalidOperationException($"Failed to compare '{x}' with '{y}' (expected both to be of type {typeof(PNLRowVM)}).");
|
||||
|
||||
int IComparer<CollectionViewGroup>.Compare(CollectionViewGroup x, CollectionViewGroup y) =>
|
||||
_try_compare_groups(x, y) ?? throw new InvalidOperationException($"Failed to compare '{x}' with '{y}' (expected both to be of type {typeof(CollectionViewGroup)}, with Names of type {typeof(IPNLCellVM)}).");
|
||||
|
||||
int? _try_compare_rows(PNLRowVM x, PNLRowVM y) {
|
||||
if (x == null || y == null) return null;
|
||||
return _row_comp.Compare(x, y);
|
||||
}
|
||||
|
||||
int? _try_compare_groups(CollectionViewGroup x, CollectionViewGroup y) {
|
||||
if (x == null || y == null)
|
||||
return null;
|
||||
var xcell = (IPNLCellData)x.Name;
|
||||
var ycell = (IPNLCellData)y.Name;
|
||||
var comp = _get_column_comparer(xcell.column);
|
||||
return comp.value_comparer.Compare(xcell.value, ycell.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
|
||||
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
|
||||
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
|
||||
//
|
||||
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
|
||||
bool _refresh_normalized_data = true, _is_selected;
|
||||
PNLNormalizedData _normalized_data;
|
||||
|
||||
public PNLRowVM(PNLRowData data, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
|
||||
this.data = data;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
|
||||
_animator.on_row(data);
|
||||
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
|
||||
account_name_cell = _cell_map.add(ColumnKeys.Account, d => d.account_name);
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.security?.Symbol ?? string.Empty);
|
||||
security_cell = _cell_map.add(ColumnKeys.Security, d => d.security, null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.cpp);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
|
||||
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price_raw);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
|
||||
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price_raw);
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
|
||||
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price_raw);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
|
||||
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price_raw);
|
||||
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
|
||||
// raw day
|
||||
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
|
||||
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
|
||||
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
|
||||
working_long_pos = _cell_map.add_working_long(ColumnKeys.WorkingLongPos, d => d.working.long_data, d => d.bid_price);
|
||||
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price_raw);
|
||||
working_short_pos = _cell_map.add_working_short(ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price);
|
||||
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price_raw);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.total_net_pos);
|
||||
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.total_long_pos);
|
||||
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price_raw);
|
||||
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
|
||||
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price_raw);
|
||||
// market
|
||||
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
|
||||
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
|
||||
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
|
||||
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
|
||||
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
|
||||
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
|
||||
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
|
||||
RaisePropertyChanged(nameof(animations));
|
||||
});
|
||||
}
|
||||
|
||||
// events
|
||||
public event Action<PNLNormalizedData> updated;
|
||||
public event Action<bool> is_selected_changed;
|
||||
|
||||
// data
|
||||
public PNLKey key => data.key;
|
||||
public PNLRowData data { get; private set; }
|
||||
public PNLNormalizedData normalized_data {
|
||||
get {
|
||||
if (_refresh_normalized_data) {
|
||||
_refresh_normalized_data = false;
|
||||
// only count futures for [non-raw] positions (they already take spread legs into account)
|
||||
decimal day_long, day_short, sod_long, sod_short;
|
||||
if (security.HasValue && security.Value.ContractCase != Security.ContractOneofCase.Future) {
|
||||
day_long = day_short = sod_long = sod_short = 0;
|
||||
} else {
|
||||
day_long = (decimal)DoubleUtil.nan_to_zero(data.day.long_pos);
|
||||
day_short = (decimal)DoubleUtil.nan_to_zero(data.day.short_pos);
|
||||
sod_long = (decimal)DoubleUtil.nan_to_zero(data.sod.long_pos);
|
||||
sod_short = (decimal)DoubleUtil.nan_to_zero(data.sod.short_pos);
|
||||
}
|
||||
var nd = new PNLNormalizedData(
|
||||
(decimal)DoubleUtil.nan_to_zero(day_pnl.value),
|
||||
day_long,
|
||||
day_short,
|
||||
(decimal)DoubleUtil.nan_to_zero(data.raw_day.long_pos),
|
||||
(decimal)DoubleUtil.nan_to_zero(data.raw_day.short_pos),
|
||||
(decimal)DoubleUtil.nan_to_zero(sod_pnl.value),
|
||||
sod_long,
|
||||
sod_short,
|
||||
(decimal)DoubleUtil.nan_to_zero(data.working_long_pos),
|
||||
(decimal)DoubleUtil.nan_to_zero(data.working_short_pos));
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
}
|
||||
}
|
||||
return _normalized_data;
|
||||
}
|
||||
}
|
||||
// security
|
||||
public string account_name => data.account_name;
|
||||
public Venue exchange => security?.Exchange ?? Venue.Unset;
|
||||
public string symbol => security.symbol_or_default();
|
||||
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
public SecurityImmutable? security => data.security;
|
||||
// cells
|
||||
public PNLCellVM<PNLRowData, string> account_name_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
|
||||
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
|
||||
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
|
||||
public PNLCellVM<PNLRowData, uint> security_key { get; }
|
||||
public PNLCellVM<PNLRowData, double> cpp { get; }
|
||||
// sod
|
||||
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
|
||||
// day
|
||||
public PNLCellVM<PNLRowData, double> day_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
|
||||
// raw day
|
||||
public PNLCellVM<PNLRowData, double> raw_day_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> raw_day_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> raw_day_short_pos { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLRowData, double> total_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
|
||||
// (market)
|
||||
public PNLCellVM<PNLRowData, double> bid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> mid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> ask_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> last_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> settlement_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> pnl_price { get; }
|
||||
|
||||
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (Set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void update(PNLRowData data) {
|
||||
_refresh_normalized_data = true;
|
||||
this.data = data;
|
||||
_cell_map.on_row(data);
|
||||
_animator.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
|
||||
_animator.on_settings(defaults, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
|
||||
}
|
||||
}
|
||||
@@ -1,764 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
using SecurityType = bts.api.Security.ContractOneofCase;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLVM : DisposableViewModelBase, IHandleRowSelection {
|
||||
static readonly IReadOnlyList<Func<PNLVM, IDataGridColumnFilter<PNLRowData>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<PNLVM, IDataGridColumnFilter<PNLRowData>>().ToReadOnlyList();
|
||||
static readonly TimeSpan _auxiliary_grid_update_throttle = TimeSpan.FromSeconds(0.2);
|
||||
// delegates
|
||||
static readonly Func<OrderSecurityRootPair, PNLKey> _order_pnl_key_fn = o => new PNLKey(o.order.order.account_id(), o.order.order.security_key());
|
||||
static readonly Func<FillTuple, PNLKey> _fill_pnl_key_fn = f => new PNLKey(f.account_id, f.fill.SecurityKey);
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IObservableCollectionAddRemoveOnly<PNLRowVM> _selected_rows = new ObservableSortedCollection<PNLRowVM>(PNLRowVM.key_comparer);
|
||||
readonly IList<PNLRowData> _rows_data_wrapper;
|
||||
readonly IList<PNLKey> _rows_key_wrapper;
|
||||
readonly DeferrableActionWrapper _refresh_filter;
|
||||
//
|
||||
readonly Property<PNLCalculationType> _calculation_type;
|
||||
PNLHeaderRowDisplay _header_row_display = PNLHeaderRowDisplay.Total;
|
||||
PNLRowCollectionView _rows_view;
|
||||
IReadOnlyList<(ColumnKeys key, ListSortDirection direction)> _sorting_hierarchy;
|
||||
Brush _selection_highlight;
|
||||
Color _grid_background_color = Colors.Transparent;
|
||||
bool _is_paused;
|
||||
// default appearances
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance>
|
||||
_default_appearances = ImmutableDictionary<ColumnKeys, PNLCellAppearance>.Empty,
|
||||
_default_group_appearances = ImmutableDictionary<ColumnKeys, PNLCellAppearance>.Empty,
|
||||
_default_header_appearances = ImmutableDictionary<ColumnKeys, PNLCellAppearance>.Empty;
|
||||
// triggers
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>>
|
||||
_triggers = Array.Empty<IPNLAnimationTrigger<PNLRowData>>();
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>>
|
||||
_group_triggers = Array.Empty<IPNLAnimationTrigger<PNLNormalizedData>>(),
|
||||
_header_triggers = Array.Empty<IPNLAnimationTrigger<PNLNormalizedData>>();
|
||||
// pause/resume
|
||||
(TableUpdate<PNLRowData, PNLKey> filtered, TableUpdate<PNLRowData, PNLKey> raw)? _resume;
|
||||
TableUpdate<PNLRowData, PNLKey> _last_table = TableUpdate<PNLRowData, PNLKey>.mk_empty(PNLRowData.key_fn);
|
||||
AlternationConverter _group_background_converter = new AlternationConverter();
|
||||
|
||||
public PNLVM(IBus bus, OrdersModel orders_model, PNLSettings settings)
|
||||
: this(bus, orders_model, settings, orders_model.security_account_pnl) { }
|
||||
|
||||
PNLVM(IBus bus, OrdersModel orders_model, PNLSettings settings, IObservable<TableUpdate<PNLRowData, PNLKey>> pnl_source) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_rows_data_wrapper = rows.WrapSelector(r => r.data, d => new PNLRowVM(d, _default_appearances, _triggers));
|
||||
_rows_key_wrapper = rows.WrapSelector(r => r.key, null);
|
||||
_rows_view = new PNLRowCollectionView(rows, _default_group_appearances, _group_triggers);
|
||||
_calculation_type = backing(nameof(calculation_type), PNLCalculationType.Waterfall);
|
||||
anchors.AddRange(settings.anchors);
|
||||
_group_background_converter.Values.AddRange(BTSColors.PNL.group_alternating_background_colors);
|
||||
// commands
|
||||
set_anchor_cmd = new DelegateCommand<ColumnKeys>(key => anchors.SetItems(new[] { key }));
|
||||
append_anchor_cmd = new DelegateCommand<ColumnKeys>(key => {
|
||||
if (!anchors.Remove(key))
|
||||
anchors.Add(key);
|
||||
});
|
||||
// filters
|
||||
account_filter = DiscreteValuesFilterVM.Common.account<PNLRowData>(orders_model, r => new AccountTuple(r.key.account_id, r.account_name), nameof(PNLRowVM.account_name));
|
||||
_refresh_filter = new DeferrableActionWrapper(() => _filter_changed?.Invoke(get_filters_states().build_predicate()));
|
||||
var filter_src = Observable.Create<Func<PNLRowData, bool>>(o => {
|
||||
o.OnNext(get_filters_states().build_predicate());
|
||||
_filter_changed += o.OnNext;
|
||||
return Disposable.Create(() => _filter_changed -= o.OnNext);
|
||||
});
|
||||
foreach (var f in _all_filters)
|
||||
f.filter_changed += _refresh_filter.execute;
|
||||
// totals/selected headers
|
||||
total_aggregate = new PNLHeaderRowVM(settings.header_background_color, _default_header_appearances, _header_triggers);
|
||||
selected_aggregate = new PNLHeaderRowVM(settings.header_background_color, _default_header_appearances, _header_triggers);
|
||||
// load/subscribe to data
|
||||
load_from(settings);
|
||||
_dispose = Disposable.Create(
|
||||
pnl_source.Operate(
|
||||
_calculation_type.observe_current(),
|
||||
(data, calc) => data.with(calc_type: calc)
|
||||
).FilterWithRaw(filter_src, PNLRowData.key_fn, null)
|
||||
.OnDispatcher()
|
||||
.Subscribe(_update),
|
||||
Disposable.Create(() => {
|
||||
foreach (var f in _all_filters)
|
||||
f.filter_changed -= _refresh_filter.execute;
|
||||
rows.DisposeAll();
|
||||
_rows_view.Groups.OfTypeSafe<IDisposable>().DisposeAll();
|
||||
}));
|
||||
}
|
||||
|
||||
public event Action<IReadOnlyList<(ColumnKeys key, ListSortDirection direction)>> sorting_changed;
|
||||
event Action<SelectionChangedEventArgs> _selection_changed;
|
||||
event Action<TableUpdate<PNLRowData, PNLKey>> _updated;
|
||||
event Action<Func<PNLRowData, bool>> _filter_changed;
|
||||
|
||||
// rows/selected rows
|
||||
public ObservableCollection<PNLRowVM> rows { get; } = new ObservableCollection<PNLRowVM>();
|
||||
public ICollectionView rows_view => _rows_view;
|
||||
public int filtered_row_count => rows.Count;
|
||||
public int total_row_count { get; private set; }
|
||||
public IReadOnlyObservableCollection<PNLRowVM> selected_rows => _selected_rows;
|
||||
// sorting
|
||||
public IReadOnlyList<(ColumnKeys key, ListSortDirection direction)> sorting_hierarchy {
|
||||
get => _sorting_hierarchy ?? (_sorting_hierarchy = Array.Empty<(ColumnKeys, ListSortDirection)>());
|
||||
set {
|
||||
_sorting_hierarchy = value;
|
||||
// use 'sorting_hierarchy' instead of 'value' bc the getter ensures not null
|
||||
comparer = DIYGridSortHelper.build_comparer(sorting_hierarchy, get_column_comparer, ColumnKeys.Account, ColumnKeys.Security);
|
||||
var column_comp_map = sorting_hierarchy.ToDictionary(c => c.key, c => _get_column_comparer(c.key, c.direction));
|
||||
var rows_view_comparer = new PNLSuperRowComparer(comparer, col => {
|
||||
if (column_comp_map.TryGetValue(col, out var comp_tup))
|
||||
return comp_tup;
|
||||
return _get_column_comparer(col);
|
||||
});
|
||||
_rows_view.CustomSort = rows_view_comparer;
|
||||
foreach (var g in _rows_view.GroupDescriptions)
|
||||
g.CustomSort = _rows_view.CustomSort;
|
||||
sorting_changed?.Invoke(sorting_hierarchy);
|
||||
}
|
||||
}
|
||||
|
||||
public IComparer<PNLRowVM> comparer { get; private set; }
|
||||
// display/grouping
|
||||
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
|
||||
public ObservableCollection<ColumnKeys> anchors { get; } = new ObservableCollection<ColumnKeys>();
|
||||
// calc
|
||||
public PNLCalculationType calculation_type { get => _calculation_type.Value; set => _calculation_type.set(value); }
|
||||
public PNLHeaderRowVM total_aggregate { get; }
|
||||
public PNLHeaderRowVM selected_aggregate { get; }
|
||||
public bool is_paused {
|
||||
get => _is_paused;
|
||||
set {
|
||||
if (Set(ref _is_paused, value) && !value && _resume.HasValue) {
|
||||
var curr_state = ImmutableDictionary<PNLKey, PNLRowData>.Empty.AddRange(rows.Select(r => new KeyValuePair<PNLKey, PNLRowData>(r.key, r.data)));
|
||||
var reset = TableUpdate<PNLRowData, PNLKey>.update(
|
||||
new TableUpdate<PNLRowData, PNLKey>(
|
||||
curr_state,
|
||||
curr_state.Values,
|
||||
Enumerable.Empty<Change<PNLRowData>>(),
|
||||
Enumerable.Empty<PNLRowData>(),
|
||||
PNLRowData.key_fn),
|
||||
_resume.Value.filtered.state,
|
||||
EqualityComparer<PNLRowData>.Default);
|
||||
_update((reset, _resume.Value.raw));
|
||||
_resume = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Brush selection_highlight { get => _selection_highlight; set => Set(ref _selection_highlight, value); }
|
||||
public Color grid_background_color { get => _grid_background_color; set => Set(ref _grid_background_color, value); }
|
||||
public IValueConverter group_background_converter => _group_background_converter;
|
||||
|
||||
// commands
|
||||
public ICommand set_anchor_cmd { get; }
|
||||
public ICommand append_anchor_cmd { get; }
|
||||
public ICommand show_working_orders_cmd { get; }
|
||||
|
||||
/// security
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, uint[]> account_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, Venue[]> exchange_filter { get; } = DiscreteValuesFilterVM.Common.exchange<PNLRowData>(r => r.security.exchange_or_default(), nameof(PNLRowVM.exchange));
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, string[]> symbol_filter { get; } = DiscreteValuesFilterVM.Common.symbol<PNLRowData>(r => r.security.symbol_or_default(), nameof(PNLRowVM.symbol));
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, string> security_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.security?.ToString(), nameof(PNLRowData.security));
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, string> security_key_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.key.security_id.ToString());
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, SecurityType[]> security_type_filter { get; } = DiscreteValuesFilterVM.Create<PNLRowData, SecurityType>(
|
||||
EnumHelper.get_values_except(SecurityType.Option, SecurityType.Synthetic).ToArray(),
|
||||
r => r.security?.ContractCase ?? SecurityType.None,
|
||||
nameof(PNLRowData.security));
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> cpp_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.cpp);
|
||||
/// sod
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.long_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_long_avg_price_raw);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.short_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_short_avg_price_raw);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.net_pos);
|
||||
/// day
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_long_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_long_avg_price_raw);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_short_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_short_avg_price_raw);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_net_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, bool[]> has_day_trades_filter { get; } = DiscreteValuesFilterVM.Create<PNLRowData, bool>(new[] { true, false }, r => r.has_day_trades);
|
||||
/// raw day
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> raw_day_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.raw_day_long_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> raw_day_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.raw_day_short_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> raw_day_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.raw_day_net_pos);
|
||||
/// working (long)
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_long_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_long_avg_price_raw);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_short_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_short_avg_price_raw);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_net_pos);
|
||||
/// total
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_avg_price_raw);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_avg_price_raw);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_net_pos);
|
||||
/// market
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.bid_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.ask_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> mid_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.mid_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.last_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> settlement_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.settlement_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> pnl_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.pnl_price);
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<PNLRowData>> _all_filters =>
|
||||
_filter_getters.Select(get => get(this));
|
||||
|
||||
public static IComparer<PNLRowVM> get_column_comparer(ColumnKeys column) =>
|
||||
_get_column_comparer(column).row_comparer;
|
||||
|
||||
public FilterStates get_filters_states() => new FilterStates {
|
||||
account = account_filter.get_state(),
|
||||
exchange = exchange_filter.get_state(),
|
||||
symbol = symbol_filter.get_state(),
|
||||
security_type = security_type_filter.get_state(),
|
||||
security = security_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
cpp = cpp_filter.get_state(),
|
||||
/// sod
|
||||
sod_pnl = sod_pnl_filter.get_state(),
|
||||
sod_long_pos = sod_long_pos_filter.get_state(),
|
||||
sod_long_avg_price = sod_long_avg_price_filter.get_state(),
|
||||
sod_short_pos = sod_short_pos_filter.get_state(),
|
||||
sod_short_avg_price = sod_short_avg_price_filter.get_state(),
|
||||
sod_net_pos = sod_net_pos_filter.get_state(),
|
||||
/// day
|
||||
day_pnl = day_pnl_filter.get_state(),
|
||||
day_long_pos = day_long_pos_filter.get_state(),
|
||||
day_long_avg_price = day_long_avg_price_filter.get_state(),
|
||||
day_short_pos = day_short_pos_filter.get_state(),
|
||||
day_short_avg_price = day_short_avg_price_filter.get_state(),
|
||||
day_net_pos = day_net_pos_filter.get_state(),
|
||||
has_day_trades = has_day_trades_filter.get_state(),
|
||||
/// raw day
|
||||
raw_day_long_pos = raw_day_long_pos_filter.get_state(),
|
||||
raw_day_short_pos = raw_day_short_pos_filter.get_state(),
|
||||
raw_day_net_pos = raw_day_net_pos_filter.get_state(),
|
||||
/// total
|
||||
total_pnl = total_pnl_filter.get_state(),
|
||||
total_long_pos = total_long_pos_filter.get_state(),
|
||||
total_long_avg_price = total_long_avg_price_filter.get_state(),
|
||||
total_short_pos = total_short_pos_filter.get_state(),
|
||||
total_short_avg_price = total_short_avg_price_filter.get_state(),
|
||||
total_net_pos = total_net_pos_filter.get_state(),
|
||||
/// working
|
||||
working_long_pos = working_long_pos_filter.get_state(),
|
||||
working_long_avg_price = working_long_avg_price_filter.get_state(),
|
||||
working_short_pos = working_short_pos_filter.get_state(),
|
||||
working_short_avg_price = working_short_avg_price_filter.get_state(),
|
||||
working_net_pos = working_net_pos_filter.get_state(),
|
||||
/// market
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
ask_price = ask_price_filter.get_state(),
|
||||
mid_price = mid_price_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
settlement_price = settlement_price_filter.get_state(),
|
||||
pnl_price = pnl_price_filter.get_state(),
|
||||
};
|
||||
|
||||
public void load_from(PNLSettings settings) {
|
||||
selection_highlight = settings.grid.selection_highlight.to_brush();
|
||||
grid_background_color = settings.grid.background;
|
||||
calculation_type = settings.calculation_type;
|
||||
if (settings.filters != null) {
|
||||
using (_refresh_filter.defer_execution()) {
|
||||
var f = settings.filters;
|
||||
/// security
|
||||
account_filter.load_state(f.account);
|
||||
exchange_filter.load_state(f.exchange);
|
||||
symbol_filter.load_state(f.symbol);
|
||||
security_type_filter.load_state(f.security_type);
|
||||
security_filter.load_state(f.security);
|
||||
security_key_filter.load_state(f.security_key);
|
||||
cpp_filter.load_state(f.cpp);
|
||||
/// sod
|
||||
sod_pnl_filter.load_state(f.sod_pnl);
|
||||
sod_long_pos_filter.load_state(f.sod_long_pos);
|
||||
sod_long_avg_price_filter.load_state(f.sod_long_avg_price);
|
||||
sod_short_pos_filter.load_state(f.sod_short_pos);
|
||||
sod_short_avg_price_filter.load_state(f.sod_short_avg_price);
|
||||
sod_net_pos_filter.load_state(f.sod_net_pos);
|
||||
/// day
|
||||
day_pnl_filter.load_state(f.day_pnl);
|
||||
day_long_pos_filter.load_state(f.day_long_pos);
|
||||
day_long_avg_price_filter.load_state(f.day_long_avg_price);
|
||||
day_short_pos_filter.load_state(f.day_short_pos);
|
||||
day_short_avg_price_filter.load_state(f.day_short_avg_price);
|
||||
day_net_pos_filter.load_state(f.day_net_pos);
|
||||
has_day_trades_filter.load_state(f.has_day_trades);
|
||||
/// raw day
|
||||
raw_day_long_pos_filter.load_state(f.raw_day_long_pos);
|
||||
raw_day_short_pos_filter.load_state(f.raw_day_short_pos);
|
||||
raw_day_net_pos_filter.load_state(f.raw_day_net_pos);
|
||||
/// total
|
||||
total_pnl_filter.load_state(f.total_pnl);
|
||||
total_long_pos_filter.load_state(f.total_long_pos);
|
||||
total_long_avg_price_filter.load_state(f.total_long_avg_price);
|
||||
total_short_pos_filter.load_state(f.total_short_pos);
|
||||
total_short_avg_price_filter.load_state(f.total_short_avg_price);
|
||||
total_net_pos_filter.load_state(f.total_net_pos);
|
||||
/// working
|
||||
working_long_pos_filter.load_state(f.working_long_pos);
|
||||
working_long_avg_price_filter.load_state(f.working_long_avg_price);
|
||||
working_short_pos_filter.load_state(f.working_short_pos);
|
||||
working_short_avg_price_filter.load_state(f.working_short_avg_price);
|
||||
working_net_pos_filter.load_state(f.working_net_pos);
|
||||
/// market
|
||||
bid_price_filter.load_state(f.bid_price);
|
||||
mid_price_filter.load_state(f.mid_price);
|
||||
ask_price_filter.load_state(f.ask_price);
|
||||
last_price_filter.load_state(f.last_price);
|
||||
settlement_price_filter.load_state(f.settlement_price);
|
||||
pnl_price_filter.load_state(f.pnl_price);
|
||||
}
|
||||
}
|
||||
anchors.sync_with(settings.anchors);
|
||||
header_row_display = settings.header_row_display;
|
||||
sorting_hierarchy = DIYGridSortHelper.convert_sorting_criterion<ColumnKeys>(settings.sorting_criterion).ToList();
|
||||
// group background colors
|
||||
_group_background_converter = new AlternationConverter();
|
||||
_group_background_converter.Values.AddRange(settings.group_background_colors.Select(c => c.argb_color()));
|
||||
RaisePropertyChanged(nameof(group_background_converter));
|
||||
//
|
||||
map_appearances(ref _default_appearances, settings.grid.columns, settings.grid_defaults);
|
||||
map_appearances(ref _default_group_appearances, settings.group_columns, settings.group_cell_appearance.to_appearance().merge(settings.group_defaults));
|
||||
map_appearances(ref _default_header_appearances, settings.header_columns, settings.header_cell_appearance.to_appearance().merge(settings.header_defaults));
|
||||
_triggers = settings.triggers();
|
||||
_group_triggers = settings.group_triggers();
|
||||
_header_triggers = settings.header_triggers();
|
||||
foreach (var r in rows)
|
||||
r.on_settings(_default_appearances, _triggers);
|
||||
total_aggregate.on_settings(settings.header_background_color, _default_header_appearances, _header_triggers);
|
||||
selected_aggregate.on_settings(settings.header_background_color,_default_header_appearances, _header_triggers);
|
||||
void map_appearances(ref ImmutableDictionary<ColumnKeys, PNLCellAppearance> from, IEnumerable<GridColumnSettings> col_settings, PNLCellAppearance fallback_appearance) {
|
||||
var dabd = from.ToBuilder();
|
||||
foreach (var k in EnumHelper.get_values<ColumnKeys>()) {
|
||||
if (GridSettingsHelper.try_find_column(col_settings, k, out var match))
|
||||
dabd[k] = new PNLCellAppearance(match.font_color_override, match.background_color_override, match.font_weight_override).merge(fallback_appearance);
|
||||
}
|
||||
from = dabd.ToImmutable();
|
||||
}
|
||||
_rows_view.on_settings(_default_group_appearances, _group_triggers);
|
||||
_rows_view.Refresh();
|
||||
}
|
||||
|
||||
public FillsVM mk_fills_vm(PNLRowVM row, ColumnKeys key) {
|
||||
var state = PNLSettings.DefaultAuxiliaryFills;
|
||||
_append_key_filters(state.filters, key);
|
||||
_append_row_filters(state.filters, row);
|
||||
// hide implicit cols
|
||||
state.grid = _hide_cols(state.grid, anchors);
|
||||
return new FillsVM(
|
||||
_bus, _orders_model, Observable.Return(state), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
}
|
||||
|
||||
public FillsVM mk_fills_vm(PNLRowCollectionViewGroup group, ColumnKeys key) {
|
||||
var state = PNLSettings.DefaultAuxiliaryFills;
|
||||
_append_key_filters(state.filters, key);
|
||||
_append_group_filters(state.filters, group, anchors);
|
||||
// hide implicit cols
|
||||
state.grid = _hide_cols(state.grid, anchors.WrapSubRange(0, group.level));
|
||||
return new FillsVM(
|
||||
_bus, _orders_model, Observable.Return(state), _visible_fills_source(), new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
}
|
||||
|
||||
public FillsVM mk_fills_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
|
||||
var state = fills.FillsSettings.Default();
|
||||
state.grid = orderbook.OrderBookStartupData.DefaultFillsGrid.general;
|
||||
state.filters = new fills.FillsFilterStates();
|
||||
// hide implicit cols
|
||||
state.grid = _hide_cols(orderbook.OrderBookStartupData.DefaultFillsGrid.general, anchors);
|
||||
_append_key_filters(state.filters, key);
|
||||
var src = _orders_model.on_fill_tuples;
|
||||
switch (header) {
|
||||
case PNLHeaderRowDisplay.SelectedTotal:
|
||||
// throttle selection changes (they have a potentially high frequency)
|
||||
var filter_src = System.Reactive.Linq.ObservableExtensions.FromEventThrottle<SelectionChangedEventArgs, Func<FillTuple, bool>>(
|
||||
cb => _selection_changed += cb,
|
||||
cb => _selection_changed -= cb,
|
||||
_ => _mk_selected_items_filter(_fill_pnl_key_fn),
|
||||
_auxiliary_grid_update_throttle);
|
||||
src = src.Filter(filter_src, FillTuple.key_fn, _mk_selected_items_filter(_fill_pnl_key_fn));
|
||||
break;
|
||||
case PNLHeaderRowDisplay.Total:
|
||||
src = src.Filter(_visible_items_filter_source(_fill_pnl_key_fn), FillTuple.key_fn);
|
||||
break;
|
||||
}
|
||||
return new FillsVM(
|
||||
_bus, _orders_model, Observable.Return(state), src, new WindowSettingsManager<GridSettings>.DisconnectedImpl(state.grid));
|
||||
}
|
||||
|
||||
public OrderBookVM mk_orderbook_vm(PNLHeaderRowDisplay header, ColumnKeys key) {
|
||||
var filters = new orderbook.FilterStates();
|
||||
_append_key_filters(filters, key);
|
||||
var src = _orders_model.on_order_tuples_with_root_and_piq;
|
||||
switch (header) {
|
||||
case PNLHeaderRowDisplay.SelectedTotal:
|
||||
// throttle selection changes (they have a potentially high frequency)
|
||||
var filter_src = System.Reactive.Linq.ObservableExtensions.FromEventThrottle<SelectionChangedEventArgs, Func<OrderSecurityRootPair, bool>>(
|
||||
cb => _selection_changed += cb,
|
||||
cb => _selection_changed -= cb,
|
||||
_ => _mk_selected_items_filter(_order_pnl_key_fn),
|
||||
_auxiliary_grid_update_throttle);
|
||||
src = src.Filter(filter_src, OrderSecurityHelper.root_pair_key_fn, _mk_selected_items_filter(_order_pnl_key_fn));
|
||||
break;
|
||||
case PNLHeaderRowDisplay.Total:
|
||||
src = src.Filter(_visible_items_filter_source(_order_pnl_key_fn), OrderSecurityHelper.root_pair_key_fn);
|
||||
break;
|
||||
}
|
||||
return _mk_orderbook_vm_common(
|
||||
filters, Enumerable.Empty<ColumnKeys>(), src);
|
||||
}
|
||||
|
||||
public OrderBookVM mk_orderbook_vm(PNLRowCollectionViewGroup group, ColumnKeys key) {
|
||||
var filters = new orderbook.FilterStates();
|
||||
_append_key_filters(filters, key);
|
||||
_append_group_filters(filters, group, anchors);
|
||||
return _mk_orderbook_vm_common(
|
||||
filters, anchors.WrapSubRange(0, group.level), _visible_orders_source());
|
||||
}
|
||||
|
||||
public OrderBookVM mk_orderbook_vm(PNLRowVM row, ColumnKeys key) {
|
||||
var filters = new orderbook.FilterStates();
|
||||
_append_key_filters(filters, key);
|
||||
_append_row_filters(filters, row);
|
||||
return _mk_orderbook_vm_common(
|
||||
filters, anchors.Concat(new[] { ColumnKeys.Account, ColumnKeys.Symbol, ColumnKeys.Security }).Distinct());
|
||||
}
|
||||
|
||||
public void set_all_expanded(bool is_expanded) =>
|
||||
_rows_view.set_all_expanded(is_expanded);
|
||||
|
||||
void IHandleRowSelection.row_selection_changed(SelectionChangedEventArgs e) {
|
||||
RowSelectionHelper.apply_changes(_selected_rows, e);
|
||||
// adding/subtracting pnl incrementally can lose precision over time, due to rounding errors.
|
||||
// do the full calculation on each selection change, as long as it doesn't hurt perf (which seems unlikely).
|
||||
selected_aggregate.data = _selected_rows.Aggregate(PNLNormalizedData.zero, (d, r) => d + r.normalized_data);
|
||||
_selection_changed?.Invoke(e);
|
||||
}
|
||||
|
||||
static IPNLColumnComparer _get_column_comparer(ColumnKeys column, ListSortDirection direction) {
|
||||
var comp = _get_column_comparer(column);
|
||||
if (direction == ListSortDirection.Descending)
|
||||
comp = new ReversePNLColumnComparer(comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
static IPNLColumnComparer _get_column_comparer(ColumnKeys column) {
|
||||
switch (column) {
|
||||
case ColumnKeys.Account: return wrap(r => r.account_name);
|
||||
case ColumnKeys.Exchange: return wrap(r => r.exchange);
|
||||
case ColumnKeys.SecurityType: return wrap(r => r.security?.ContractCase ?? SecurityType.None);
|
||||
case ColumnKeys.Symbol: return wrap(r => r.symbol);
|
||||
case ColumnKeys.Security: return wrap(r => r.security);
|
||||
case ColumnKeys.SecurityKey: return wrap(r => r.security_key);
|
||||
case ColumnKeys.CPP: return wrap(r => r.cpp);
|
||||
/// SOD
|
||||
case ColumnKeys.SODPNL: return wrap(r => r.sod_pnl);
|
||||
case ColumnKeys.SODLongPos: return wrap(r => r.sod_long_pos);
|
||||
case ColumnKeys.SODLongAvgPrice: return wrap(r => r.sod_long_avg_price);
|
||||
case ColumnKeys.SODShortPos: return wrap(r => r.sod_short_pos);
|
||||
case ColumnKeys.SODShortAvgPrice: return wrap(r => r.sod_short_avg_price);
|
||||
case ColumnKeys.SODNetPos: return wrap(r => r.sod_net_pos);
|
||||
/// Day
|
||||
case ColumnKeys.DayPNL: return wrap(r => r.day_pnl);
|
||||
case ColumnKeys.DayLongPos: return wrap(r => r.day_long_pos);
|
||||
case ColumnKeys.DayLongAvgPrice: return wrap(r => r.day_long_avg_price);
|
||||
case ColumnKeys.DayShortPos: return wrap(r => r.day_short_pos);
|
||||
case ColumnKeys.DayShortAvgPrice: return wrap(r => r.day_short_avg_price);
|
||||
case ColumnKeys.DayNetPos: return wrap(r => r.day_net_pos);
|
||||
case ColumnKeys.HasDayTrades: return wrap(r => r.has_day_trades);
|
||||
/// RawDay
|
||||
case ColumnKeys.RawDayLongPos: return wrap(r => r.raw_day_long_pos);
|
||||
case ColumnKeys.RawDayShortPos: return wrap(r => r.raw_day_short_pos);
|
||||
case ColumnKeys.RawDayNetPos: return wrap(r => r.raw_day_net_pos);
|
||||
/// Total
|
||||
case ColumnKeys.TotalPNL: return wrap(r => r.total_pnl);
|
||||
case ColumnKeys.TotalLongPos: return wrap(r => r.total_long_pos);
|
||||
case ColumnKeys.TotalLongAvgPrice: return wrap(r => r.total_long_avg_price);
|
||||
case ColumnKeys.TotalShortPos: return wrap(r => r.total_short_pos);
|
||||
case ColumnKeys.TotalShortAvgPrice: return wrap(r => r.total_short_avg_price);
|
||||
case ColumnKeys.TotalNetPos: return wrap(r => r.total_net_pos);
|
||||
/// Working
|
||||
case ColumnKeys.WorkingLongPos: return wrap(r => r.working_long_pos);
|
||||
case ColumnKeys.WorkingLongAvgPrice: return wrap(r => r.working_long_avg_price);
|
||||
case ColumnKeys.WorkingShortPos: return wrap(r => r.working_short_pos);
|
||||
case ColumnKeys.WorkingShortAvgPrice: return wrap(r => r.working_short_avg_price);
|
||||
case ColumnKeys.WorkingNetPos: return wrap(r => r.working_net_pos);
|
||||
/// Market
|
||||
case ColumnKeys.BidPrice: return wrap(r => r.bid_price);
|
||||
case ColumnKeys.MidPrice: return wrap(r => r.mid_price);
|
||||
case ColumnKeys.AskPrice: return wrap(r => r.ask_price);
|
||||
case ColumnKeys.LastPrice: return wrap(r => r.last_price);
|
||||
case ColumnKeys.SettlementPrice: return wrap(r => r.settlement_price);
|
||||
case ColumnKeys.PNLPrice: return wrap(r => r.pnl_price);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(column, nameof(column));
|
||||
}
|
||||
PNLColumnComparer<T> wrap<T>(Func<PNLRowVM, T> selector) =>
|
||||
new PNLColumnComparer<T>(selector);
|
||||
}
|
||||
|
||||
static GridSettings _hide_cols(GridSettings grid, IEnumerable<ColumnKeys> columns) =>
|
||||
grid.with(_hide_cols(grid.columns, columns));
|
||||
|
||||
static IEnumerable<GridColumnSettings> _hide_cols(IEnumerable<GridColumnSettings> cols, IEnumerable<ColumnKeys> columns) {
|
||||
// hide columns who's data is implied by the grouping
|
||||
var col_hash = new HashSet<string>(columns.Select(k => k.ToString()));
|
||||
foreach (var c in cols)
|
||||
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
|
||||
}
|
||||
|
||||
#region Fills Popup Utils
|
||||
static void _append_key_filters(fills.FillsFilterStates f, ColumnKeys key) {
|
||||
switch (key) {
|
||||
case ColumnKeys.SODNetPos:
|
||||
case ColumnKeys.SODLongPos:
|
||||
case ColumnKeys.SODShortPos:
|
||||
f.fill_kind = new[] { Fill.Types.FillKind.Sod };
|
||||
break;
|
||||
case ColumnKeys.DayNetPos:
|
||||
case ColumnKeys.DayLongPos:
|
||||
case ColumnKeys.DayShortPos:
|
||||
f.fill_kind = new[] { Fill.Types.FillKind.Standard, Fill.Types.FillKind.Leg };
|
||||
break;
|
||||
case ColumnKeys.RawDayNetPos:
|
||||
case ColumnKeys.RawDayLongPos:
|
||||
case ColumnKeys.RawDayShortPos:
|
||||
f.fill_kind = new[] { Fill.Types.FillKind.Standard };
|
||||
break;
|
||||
}
|
||||
switch (key) {
|
||||
case ColumnKeys.SODLongPos:
|
||||
case ColumnKeys.DayLongPos:
|
||||
case ColumnKeys.RawDayLongPos:
|
||||
case ColumnKeys.TotalLongPos:
|
||||
f.side = new[] { Side.Buy };
|
||||
break;
|
||||
case ColumnKeys.SODShortPos:
|
||||
case ColumnKeys.DayShortPos:
|
||||
case ColumnKeys.RawDayShortPos:
|
||||
case ColumnKeys.TotalShortPos:
|
||||
f.side = new[] { Side.Sell };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void _append_group_filters(fills.FillsFilterStates f, PNLRowCollectionViewGroup group, IReadOnlyList<ColumnKeys> anchors) {
|
||||
if (group != null) {
|
||||
int anchor_pos = group.level - 1;
|
||||
if (anchor_pos < 0)
|
||||
throw new InvalidOperationException("Group level is less than 1.");
|
||||
if (anchor_pos >= anchors.Count)
|
||||
throw new InvalidOperationException("Group level is greater than the number of anchors.");
|
||||
_append_fills_filter(f, group, anchors[anchor_pos]);
|
||||
group = group.parent;
|
||||
while (anchor_pos-- > 0 && group != null) {
|
||||
_append_fills_filter(f, group, anchors[anchor_pos]);
|
||||
group = group.parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _append_fills_filter(fills.FillsFilterStates filter, PNLRowCollectionViewGroup group, ColumnKeys key) {
|
||||
switch (key) {
|
||||
case ColumnKeys.Account: filter.account = new[] { group.Name.ToString() }; break;
|
||||
case ColumnKeys.Symbol: filter.symbol = new[] { group.Name.ToString() }; break;
|
||||
case ColumnKeys.Exchange: filter.exchange = new[] { (Venue)group.Name }; break;
|
||||
case ColumnKeys.Security:
|
||||
if (group.Name is SecurityImmutable sec)
|
||||
filter.security = new SecurityCriteria(sec);
|
||||
break;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
static void _append_row_filters(fills.FillsFilterStates f, PNLRowVM row) {
|
||||
f.account = new[] { row.account_name };
|
||||
f.security = SecurityCriteria.from_keys(new[] { row.key.security_id });
|
||||
}
|
||||
|
||||
static void _append_key_filters(orderbook.FilterStates f, ColumnKeys key) {
|
||||
switch (key) {
|
||||
case ColumnKeys.WorkingLongPos:
|
||||
case ColumnKeys.DayLongPos:
|
||||
case ColumnKeys.RawDayLongPos:
|
||||
case ColumnKeys.TotalLongPos:
|
||||
f.sides = new[] { Side.Buy };
|
||||
break;
|
||||
case ColumnKeys.WorkingShortPos:
|
||||
case ColumnKeys.DayShortPos:
|
||||
case ColumnKeys.RawDayShortPos:
|
||||
case ColumnKeys.TotalShortPos:
|
||||
f.sides = new[] { Side.Sell };
|
||||
break;
|
||||
}
|
||||
switch (key) {
|
||||
case ColumnKeys.WorkingLongPos:
|
||||
case ColumnKeys.WorkingShortPos:
|
||||
case ColumnKeys.WorkingNetPos:
|
||||
f.statuses = new[] { OrderStatus.PendingWorking, OrderStatus.Working };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void _append_group_filters(orderbook.FilterStates f, PNLRowCollectionViewGroup group, IReadOnlyList<ColumnKeys> anchors) {
|
||||
if (group != null) {
|
||||
int anchor_pos = group.level - 1;
|
||||
if (anchor_pos < 0)
|
||||
throw new InvalidOperationException("Group level is less than 1.");
|
||||
if (anchor_pos >= anchors.Count)
|
||||
throw new InvalidOperationException("Group level is greater than the number of anchors.");
|
||||
_append_orderbook_filter(f, group, anchors[anchor_pos]);
|
||||
group = group.parent;
|
||||
while (anchor_pos-- > 0 && group != null) {
|
||||
_append_orderbook_filter(f, group, anchors[anchor_pos]);
|
||||
group = group.parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _append_row_filters(orderbook.FilterStates f, PNLRowVM row) {
|
||||
f.accounts = new[] { row.account_name };
|
||||
f.security_criteria = SecurityCriteria.from_keys(new[] { row.key.security_id });
|
||||
}
|
||||
|
||||
static void _append_orderbook_filter(orderbook.FilterStates filter, PNLRowCollectionViewGroup g, ColumnKeys key) {
|
||||
switch (key) {
|
||||
case ColumnKeys.Account: filter.accounts = new[] { g.Name.ToString() }; break;
|
||||
case ColumnKeys.Symbol: filter.symbols = new[] { g.Name.ToString() }; break;
|
||||
case ColumnKeys.Exchange: filter.exchanges = new[] { (Venue)g.Name }; break;
|
||||
case ColumnKeys.Security:
|
||||
if (g.Name is SecurityImmutable sec)
|
||||
filter.security_criteria = new SecurityCriteria(sec);
|
||||
break;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
static Func<T, bool> _mk_items_filter<T>(IEnumerable<PNLKey> keys, Func<T, PNLKey> key_selector) {
|
||||
var key_set = new HashSet<PNLKey>(keys);
|
||||
return v => key_set.Contains(key_selector(v));
|
||||
}
|
||||
|
||||
Func<T, bool> _mk_selected_items_filter<T>(Func<T, PNLKey> key_selector) =>
|
||||
_mk_items_filter(_selected_rows.Select(r => r.key), key_selector);
|
||||
|
||||
OrderBookVM _mk_orderbook_vm_common(orderbook.FilterStates filters, IEnumerable<ColumnKeys> hide_counterparts, IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source = null) {
|
||||
var state = new orderbook.OrderBookStartupData(filters, orderbook.OrderBookStartupData.default_mini_grid, use_defaults: false);
|
||||
state.orderbook_grid.general = _hide_cols(state.orderbook_grid.general, hide_counterparts);
|
||||
var settings_man = new WindowSettingsManager<orderbook.OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
|
||||
var obvm = orders_source == null ?
|
||||
new OrderBookVM(_bus, null, _orders_model, settings_man, true) :
|
||||
new OrderBookVM(_bus, null, _orders_model, orders_source, settings_man, true);
|
||||
foreach (var c in OrderBookVM.OrderBookColumnCollection.editable_columns)
|
||||
obvm.columns.Insert(0, c);
|
||||
obvm.load_from(state);
|
||||
return obvm;
|
||||
}
|
||||
|
||||
IObservable<TableUpdate<FillTuple, string>> _visible_fills_source() =>
|
||||
_orders_model.on_fill_tuples.Filter(_visible_items_filter_source(_fill_pnl_key_fn), FillTuple.key_fn);
|
||||
|
||||
IObservable<TableUpdate<OrderSecurityRootPair, uint>> _visible_orders_source() =>
|
||||
_orders_model.on_order_tuples_with_root_and_piq.Filter(_visible_items_filter_source(_order_pnl_key_fn), OrderSecurityHelper.root_pair_key_fn);
|
||||
|
||||
IObservable<Func<T, bool>> _visible_items_filter_source<T>(Func<T, PNLKey> key_selector) =>
|
||||
Observable.Create<Func<T, bool>>(o => {
|
||||
o.OnNext(_visible_items_filter_snapshot(key_selector));
|
||||
_updated += on_updated;
|
||||
return Disposable.Create(() => _updated -= on_updated);
|
||||
void on_updated(TableUpdate<PNLRowData, PNLKey> _) =>
|
||||
o.OnNext(_visible_items_filter_snapshot(key_selector));
|
||||
});
|
||||
|
||||
// returns a predicate that takes a snapshot of the current state, and returns true if the item's account/security pair is visible in the snapshot state.
|
||||
// subsequent updates are not reflected in this predicate.
|
||||
// Note: this is a necessary behavior if the predicate is used in a TableUpdate filter stream (it must always return the same value for the same item).
|
||||
Func<T, bool> _visible_items_filter_snapshot<T>(Func<T, PNLKey> key_selector) {
|
||||
var t = _last_table;
|
||||
return o => t.state.ContainsKey(key_selector(o));
|
||||
}
|
||||
|
||||
void _update((TableUpdate<PNLRowData, PNLKey> filtered, TableUpdate<PNLRowData, PNLKey> raw) tup) {
|
||||
if (_is_paused) {
|
||||
_resume = tup;
|
||||
return;
|
||||
}
|
||||
var (filtered, raw) = tup;
|
||||
_last_table = filtered;
|
||||
int old_total_row_count = total_row_count;
|
||||
int old_filtered_row_count = filtered_row_count;
|
||||
var total_pnl = total_aggregate.data;
|
||||
// don't adjust selected_pnl incrementally - it may be possible for the selection to change throughout the update, making incremental calculations tricky
|
||||
foreach (var v in filtered.removed) {
|
||||
int i = CollectionHelper.binary_search(_rows_data_wrapper, v, PNLRowData.key_comparer);
|
||||
Log.assert(i >= 0, $"{typeof(PNLVM)} received an update with a removed item that was not found via binary search.");
|
||||
if (i >= 0) {
|
||||
total_pnl -= rows[i].normalized_data;
|
||||
rows.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
foreach (var v in filtered.added) {
|
||||
var r = new PNLRowVM(v.with(calc_type: calculation_type), _default_appearances, _triggers);
|
||||
rows.InsertSorted(r, PNLRowVM.key_comparer);
|
||||
total_pnl += r.normalized_data;
|
||||
}
|
||||
foreach (var d in filtered.changed) {
|
||||
int i = CollectionHelper.binary_search(_rows_data_wrapper, d, PNLRowData.key_comparer);
|
||||
if (i >= 0) {
|
||||
var r = rows[i];
|
||||
total_pnl -= r.normalized_data;
|
||||
r.update(d);
|
||||
total_pnl += r.normalized_data;
|
||||
}
|
||||
}
|
||||
if (_selected_rows.Count > 0) {
|
||||
selected_aggregate.data = _selected_rows
|
||||
.Aggregate(PNLNormalizedData.zero, (d, r) => d + r.normalized_data);
|
||||
}
|
||||
//
|
||||
total_aggregate.data = total_pnl;
|
||||
total_row_count = tup.raw.state.Count;
|
||||
if (total_row_count != old_total_row_count)
|
||||
RaisePropertyChanged(nameof(total_row_count));
|
||||
if (filtered_row_count != old_filtered_row_count)
|
||||
RaisePropertyChanged(nameof(filtered_row_count));
|
||||
_updated?.Invoke(filtered);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using bts.collections;
|
||||
using mbts.ui.utility;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class AnchorIndex {
|
||||
readonly IDictionary<PNLView.Columns, bool> _wrapped_anchors;
|
||||
|
||||
public AnchorIndex() {
|
||||
IDictionary<string, object> anchors_dict = anchors = new ExpandoObject();
|
||||
_wrapped_anchors = MappedDictionary.create_mutable(
|
||||
anchors_dict,
|
||||
EnumHelper.parse<PNLView.Columns>,
|
||||
key => key.ToString(),
|
||||
obj => (bool)obj,
|
||||
_ => _);
|
||||
// initialize the dictionary with every key (the keys should never change)
|
||||
foreach (var k in _keys)
|
||||
_wrapped_anchors[k] = false;
|
||||
}
|
||||
|
||||
public dynamic anchors { get; }
|
||||
|
||||
IReadOnlyList<PNLView.Columns> _keys => EnumHelper.get_values<PNLView.Columns>();
|
||||
|
||||
public void reset(IEnumerable<PNLView.Columns> anchor_list) {
|
||||
var anchor_set = new HashSet<PNLView.Columns>(anchor_list.empty_if_null());
|
||||
foreach (var key in _keys)
|
||||
_wrapped_anchors[key] = anchor_set.Contains(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<HeaderedContentControl
|
||||
x:Class="bts.shell.pnl.PNLAuxiliaryPopupView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:pnl="clr-namespace:bts.shell.pnl"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="1"
|
||||
MaxHeight="300"
|
||||
>
|
||||
<HeaderedContentControl.Resources>
|
||||
<!--Fills-->
|
||||
<DataTemplate DataType="{x:Type sp:FillsVM}">
|
||||
<usp:FillsView u:Behavior.FocusOnLoaded="True"/>
|
||||
</DataTemplate>
|
||||
<!--OrderBook-->
|
||||
<DataTemplate DataType="{x:Type sp:OrderBookVM}">
|
||||
<sp:MiniOrderBookView u:Behavior.FocusOnLoaded="True"/>
|
||||
</DataTemplate>
|
||||
</HeaderedContentControl.Resources>
|
||||
<!--Header (Title Bar)-->
|
||||
<HeaderedContentControl.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Margin="4,0,0,0" FontWeight="Bold" Text="{Binding context}"/>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="4,0,0,0" FontWeight="Bold" Text="{Binding title}">
|
||||
<TextBlock.Foreground>
|
||||
<SolidColorBrush Color="{Binding title_foreground}"/>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
<CheckBox Grid.Column="2" VerticalAlignment="Center" Margin="4,0,0,0" Focusable="False" IsChecked="{Binding keep_open}">Keep Open</CheckBox>
|
||||
<Button Grid.Column="3" HorizontalAlignment="Right" Margin="4" Command="{x:Static ApplicationCommands.Close}" CommandTarget="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLAuxiliaryPopupView}}" Style="{StaticResource RoundCloseButton}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</HeaderedContentControl.HeaderTemplate>
|
||||
<!--Input Bindings-->
|
||||
<HeaderedContentControl.InputBindings>
|
||||
<KeyBinding Key="Esc" Command="{x:Static ApplicationCommands.Close}"/>
|
||||
</HeaderedContentControl.InputBindings>
|
||||
<!--Template-->
|
||||
<HeaderedContentControl.Template>
|
||||
<ControlTemplate TargetType="HeaderedContentControl">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter ContentSource="Header"/>
|
||||
<ContentPresenter Grid.Row="1" Grid.ColumnSpan="4"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</HeaderedContentControl.Template>
|
||||
</HeaderedContentControl>
|
||||
@@ -1,8 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
partial class PNLAuxiliaryPopupView : HeaderedContentControl {
|
||||
public PNLAuxiliaryPopupView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
namespace bts.shell.pnl {
|
||||
readonly struct PNLCell {
|
||||
public readonly object item;
|
||||
public readonly PNLView.Columns column;
|
||||
|
||||
public PNLCell(object item, PNLView.Columns column) {
|
||||
this.item = item;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"Item: {item}, Column: {column}";
|
||||
}
|
||||
|
||||
readonly struct PNLRowCell {
|
||||
public readonly PNLRowVM row;
|
||||
public readonly PNLView.Columns column;
|
||||
|
||||
public PNLRowCell(PNLRowVM row, PNLView.Columns column) {
|
||||
this.row = row;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct PNLGroupCell {
|
||||
public readonly PNLRowCollectionViewGroup group;
|
||||
public readonly PNLView.Columns column;
|
||||
|
||||
public PNLGroupCell(PNLRowCollectionViewGroup group, PNLView.Columns column) {
|
||||
this.group = group;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct PNLHeaderRowCell {
|
||||
public readonly PNLHeaderRowDisplay header_row;
|
||||
public readonly PNLView.Columns column;
|
||||
|
||||
public PNLHeaderRowCell(PNLHeaderRowDisplay header_row, PNLView.Columns column) {
|
||||
this.header_row = header_row;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
|
||||
static class PNLCellExtensions {
|
||||
public static OneOf<PNLRowCell, PNLGroupCell, PNLHeaderRowCell> oneof(this PNLCell cell) =>
|
||||
OneOf<PNLRowVM, PNLRowCollectionViewGroup, PNLHeaderRowDisplay>.infer(cell.item).Extend(
|
||||
r => new PNLRowCell(r, cell.column),
|
||||
g => new PNLGroupCell(g, cell.column),
|
||||
h => new PNLHeaderRowCell(h, cell.column));
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using bts.utils;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility.text;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLColumn : DIYGrid.ColumnBase<PNLView.Columns>, IRaiseNotifyPropertyChanged {
|
||||
static readonly IPropertyChangedDetector<PNLColumn>
|
||||
_group_settings_change_detector = ObjectSnapshot.build_detector<PNLColumn>(
|
||||
nameof(group_font_weight_override),
|
||||
nameof(group_font_weight),
|
||||
nameof(group_font_color_override),
|
||||
nameof(group_font_color),
|
||||
nameof(group_font_family),
|
||||
nameof(group_font_size)),
|
||||
_header_settings_change_detector = ObjectSnapshot.build_detector<PNLColumn>(
|
||||
nameof(header_font_weight_override),
|
||||
nameof(header_font_weight),
|
||||
nameof(header_font_color_override),
|
||||
nameof(header_font_color),
|
||||
nameof(header_font_family),
|
||||
nameof(header_font_size));
|
||||
|
||||
double _group_font_size = 11, _header_font_size = 12;
|
||||
int? _anchor_index;
|
||||
FontFamily
|
||||
_group_font_family = TextUtils.StandardFontFamily,
|
||||
_header_font_family = TextUtils.StandardFontFamily;
|
||||
|
||||
public PNLColumn() {
|
||||
// default to be hidden, so columns are not rendered until their settings are loaded,
|
||||
// in which case, they will only be rendered if 'settings.is_visible' is true.
|
||||
is_visible = false;
|
||||
}
|
||||
|
||||
public int? anchor_index {
|
||||
get => _anchor_index;
|
||||
set => set(ref _anchor_index, value);
|
||||
}
|
||||
// group
|
||||
public FontWeight? group_font_weight_override { get; private set; }
|
||||
public Color? group_font_color_override { get; private set; }
|
||||
public FontFamily group_font_family => _group_font_family ?? font_family;
|
||||
public double group_font_size => double.IsNaN(_group_font_size) || _group_font_size == 0 ? font_size : _group_font_size;
|
||||
public FontWeight group_font_weight => group_font_weight_override ?? font_weight;
|
||||
public Color group_font_color => group_font_color_override ?? font_color;
|
||||
|
||||
public FontWeight? header_font_weight_override { get; private set; }
|
||||
public Color? header_font_color_override { get; private set; }
|
||||
public FontFamily header_font_family => _header_font_family ?? font_family;
|
||||
public double header_font_size => double.IsNaN(_header_font_size) || _header_font_size == 0 ? font_size : _header_font_size;
|
||||
public FontWeight header_font_weight => header_font_weight_override ?? font_weight;
|
||||
public Color header_font_color => header_font_color_override ?? font_color;
|
||||
|
||||
public void load_group_params(
|
||||
double group_font_size = double.NaN, FontWeight? group_font_weight = null, Color? group_font_color = null, FontFamily group_font_family = null) {
|
||||
using (_group_settings_change_detector.detect_changes(this)) {
|
||||
_group_font_size = group_font_size;
|
||||
_group_font_family = group_font_family;
|
||||
group_font_weight_override = group_font_weight;
|
||||
group_font_color_override = group_font_color;
|
||||
}
|
||||
}
|
||||
|
||||
public void load_header_params(
|
||||
double header_font_size = double.NaN, FontWeight? header_font_weight = null, Color? header_font_color = null, FontFamily header_font_family = null) {
|
||||
using (_header_settings_change_detector.detect_changes(this)) {
|
||||
_header_font_size = header_font_size;
|
||||
_header_font_family = header_font_family;
|
||||
header_font_weight_override = header_font_weight;
|
||||
header_font_color_override = header_font_color;
|
||||
}
|
||||
}
|
||||
|
||||
void IRaiseNotifyPropertyChanged.raise_notify_property_changed(string prop) =>
|
||||
raise_prop_changed(prop);
|
||||
|
||||
protected override void on_key_initialized(PNLView.Columns key) =>
|
||||
id = key.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,467 +0,0 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Data;
|
||||
using CellAnimation = mbts.ui.spark.CellAnimation;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLRowCollectionView : ListCollectionView {
|
||||
ReadOnlyObservableCollection<object> _base_groups, _groups_wrapper;
|
||||
ObservableAutoWrapper<object, object> _groups;
|
||||
// appearance/triggers
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _defaults_appearances;
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> _triggers;
|
||||
|
||||
public PNLRowCollectionView(IList list, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) : base(list) {
|
||||
_defaults_appearances = default_appearances;
|
||||
_triggers = triggers;
|
||||
}
|
||||
|
||||
public override bool CanFilter => true;
|
||||
public override bool CanGroup => true;
|
||||
|
||||
// Must return null if there are no GroupDescriptions (i.e., instead of an empty collection).
|
||||
// If an empty collection is returned, the view assumes grouping is active and there are no items.
|
||||
public override ReadOnlyObservableCollection<object> Groups {
|
||||
get {
|
||||
if ((GroupDescriptions?.Count ?? 0) == 0)
|
||||
return null;
|
||||
if (_groups_wrapper == null) {
|
||||
_base_groups = base.Groups;
|
||||
if (_base_groups == null)
|
||||
return null;
|
||||
_groups = new ObservableAutoWrapper<object, object>(_base_groups, obj => new PNLRowCollectionViewGroup((CollectionViewGroup)obj, this, _defaults_appearances, _triggers));
|
||||
_groups_wrapper = new ReadOnlyObservableCollection<object>(_groups);
|
||||
}
|
||||
return _groups_wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
|
||||
_defaults_appearances = default_appearances;
|
||||
_triggers = triggers;
|
||||
foreach (var g in _groups_wrapper.OfTypeSafe<PNLRowCollectionViewGroup>())
|
||||
g.on_settings(default_appearances, triggers);
|
||||
}
|
||||
|
||||
public void set_all_expanded(bool is_expanded) {
|
||||
foreach (var g in Groups.OfTypeSafe<PNLRowCollectionViewGroup>())
|
||||
g.set_all_expanded(is_expanded);
|
||||
}
|
||||
}
|
||||
|
||||
class PNLRowCollectionViewGroup : CollectionViewGroup, IRaiseNotifyPropertyChanged, IEquatable<PNLRowCollectionViewGroup> {
|
||||
readonly PNLRowCollectionView _root;
|
||||
readonly CollectionViewGroup _from;
|
||||
readonly Dictionary<object, InnerGroupSubscription> _inner_groups = new Dictionary<object, InnerGroupSubscription>();
|
||||
readonly Dictionary<PNLKey, IDisposable> _rows = new Dictionary<PNLKey, IDisposable>();
|
||||
readonly VersionContainer<bool?> _is_all_selected;
|
||||
readonly DeferrableActionWrapper _refresh_is_all_selected;
|
||||
readonly HashSet<PNLRowVM> _selected_rows = new HashSet<PNLRowVM>();
|
||||
readonly PNLCellIndexBuilder<PNLNormalizedData> _cell_map = new PNLCellIndexBuilder<PNLNormalizedData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLNormalizedData> _animator;
|
||||
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> _defaults_appearances;
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> _triggers;
|
||||
IDisposable _dispose;
|
||||
bool _is_expanded = true;
|
||||
int _selected_item_count_backing;
|
||||
PNLNormalizedData _data;
|
||||
|
||||
public PNLRowCollectionViewGroup(CollectionViewGroup from, PNLRowCollectionView root, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) : base(from.Name) {
|
||||
_root = root;
|
||||
_from = from;
|
||||
_defaults_appearances = default_appearances;
|
||||
_triggers = triggers;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLNormalizedData>(default_appearances, triggers);
|
||||
_animator.on_row(data);
|
||||
data_changed += () => {
|
||||
_cell_map.on_row(data);
|
||||
_animator.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
};
|
||||
//sod_net_pos
|
||||
_is_all_selected = new VersionContainer<bool?>(() => {
|
||||
if (_selected_item_count == 0)
|
||||
return false;
|
||||
if (_selected_item_count == ProtectedItemCount)
|
||||
return true;
|
||||
return null;
|
||||
});
|
||||
bool? last_published_all_selected = _is_all_selected.value;
|
||||
_refresh_is_all_selected = new DeferrableActionWrapper(() => {
|
||||
if (_is_all_selected.value != last_published_all_selected) {
|
||||
last_published_all_selected = is_all_selected;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(is_all_selected)));
|
||||
}
|
||||
});
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
|
||||
// raw day
|
||||
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
|
||||
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
|
||||
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
|
||||
// total
|
||||
total_pnl = _cell_map.add(ColumnKeys.TotalPNL, d => d.total_pnl, FormattingDelegates.Price<decimal>.Value);
|
||||
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.total_net_pos);
|
||||
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.total_long_pos);
|
||||
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.total_short_pos);
|
||||
// working
|
||||
working_net_pos = _cell_map.add(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
|
||||
working_long_pos = _cell_map.add(ColumnKeys.WorkingLongPos, d => d.working_long_pos);
|
||||
working_short_pos = _cell_map.add(ColumnKeys.WorkingShortPos, d => d.working_short_pos);
|
||||
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
((INotifyCollectionChanged)_from.Items).CollectionChanged += _from_items_CollectionChanged;
|
||||
_from_items_CollectionChanged(_from.Items, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _from.Items, 0));
|
||||
|
||||
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
|
||||
animations = new_animations.Select(kv => new CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(animations)));
|
||||
});
|
||||
}
|
||||
|
||||
public IList<CellAnimation> animations { get; private set; }
|
||||
|
||||
public event Action
|
||||
data_changed,
|
||||
ProtectedItemCountChanged,
|
||||
selected_item_count_changed;
|
||||
|
||||
public PNLNormalizedData data {
|
||||
get => _data;
|
||||
set {
|
||||
if (_data != value) {
|
||||
_data = value;
|
||||
data_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sod
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_short_pos { get; }
|
||||
// day
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
|
||||
// raw day
|
||||
public PNLCellVM<PNLNormalizedData, decimal> raw_day_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> raw_day_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> raw_day_short_pos { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_short_pos { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLNormalizedData, decimal> working_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> working_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> working_short_pos { get; }
|
||||
//
|
||||
public PNLRowCollectionViewGroup parent { get; private set; }
|
||||
public int level => parent == null ? 1 : parent.level + 1;
|
||||
public override bool IsBottomLevel => _from.IsBottomLevel;
|
||||
|
||||
public IEnumerable<PNLRowCollectionViewGroup> ancestors {
|
||||
get {
|
||||
if (parent != null) {
|
||||
var p = parent;
|
||||
do {
|
||||
yield return p;
|
||||
p = p.parent;
|
||||
} while (p != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<object> GroupNamePath {
|
||||
get {
|
||||
var path = new List<object> { Name };
|
||||
foreach (var a in ancestors)
|
||||
path.Add(a.Name);
|
||||
return path.WrapReverseRO();
|
||||
}
|
||||
}
|
||||
|
||||
public bool is_expanded { get => _is_expanded; set => _set(ref _is_expanded, value); }
|
||||
public bool? is_all_selected {
|
||||
get => _is_all_selected.value;
|
||||
set {
|
||||
// The setter should not be called while is_all_selected deferred.
|
||||
// However, it is good to be defensive, so let's add a conditional check anyways:
|
||||
// if 'is_all_selected' is deferred, the value must be blindly propagated,
|
||||
// bc the equality comparison is erroneous, as the current state is unknown.
|
||||
if (value.HasValue && value != is_all_selected) {
|
||||
using (_refresh_is_all_selected.defer_execution()) {
|
||||
foreach (var i in Items) {
|
||||
if (i is PNLRowCollectionViewGroup g)
|
||||
g.is_all_selected = value;
|
||||
else if (i is PNLRowVM r)
|
||||
r.is_selected = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new int ProtectedItemCount {
|
||||
get => base.ProtectedItemCount;
|
||||
set {
|
||||
if (base.ProtectedItemCount != value) {
|
||||
base.ProtectedItemCount = value;
|
||||
_is_all_selected.increment_version();
|
||||
ProtectedItemCountChanged?.Invoke();
|
||||
_refresh_is_all_selected.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int _selected_item_count {
|
||||
get => _selected_item_count_backing;
|
||||
set {
|
||||
Log.assert(value >= 0, $"{nameof(_selected_item_count)} cannot be negative.");
|
||||
_selected_item_count_backing = value;
|
||||
_is_all_selected.increment_version();
|
||||
_refresh_is_all_selected.execute();
|
||||
selected_item_count_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> triggers) {
|
||||
_defaults_appearances = default_appearances;
|
||||
_triggers = triggers;
|
||||
_animator.on_settings(default_appearances, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
foreach (var g in ProtectedItems.OfTypeSafe<PNLRowCollectionViewGroup>())
|
||||
g.on_settings(default_appearances, triggers);
|
||||
}
|
||||
|
||||
public void set_all_expanded(bool is_expanded) {
|
||||
this.is_expanded = is_expanded;
|
||||
foreach (var g in Items.OfTypeSafe<PNLRowCollectionViewGroup>())
|
||||
g.set_all_expanded(is_expanded);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowCollectionViewGroup other) =>
|
||||
other != null &&
|
||||
level == other.level &&
|
||||
Equals(Name, other.Name);
|
||||
|
||||
void _select_all() {
|
||||
foreach (var cvg in Items.OfType<PNLRowCollectionViewGroup>())
|
||||
cvg._select_all();
|
||||
}
|
||||
|
||||
IDisposable _detect_is_all_selected_changed() {
|
||||
bool? was_all_selected = is_all_selected;
|
||||
return Disposable.Create(() => {
|
||||
if (is_all_selected != was_all_selected)
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(is_all_selected)));
|
||||
});
|
||||
}
|
||||
|
||||
void _add_item(int index, object item) {
|
||||
Log.assert(index >= 0, "Index is less than 0.");
|
||||
if (item is CollectionViewGroup group) {
|
||||
var inner_group = new PNLRowCollectionViewGroup(group, _root, _defaults_appearances, _triggers);
|
||||
_inner_groups.Add(group.Name, new InnerGroupSubscription(this, inner_group));
|
||||
ProtectedItems.Insert(index, inner_group);
|
||||
} else {
|
||||
var row = (PNLRowVM)item;
|
||||
ProtectedItems.Insert(index, item);
|
||||
_rows.Add(row.key, _connect_row(row));
|
||||
}
|
||||
}
|
||||
|
||||
void _remove_item(int index, object item) {
|
||||
if (item is PNLRowCollectionViewGroup inner_group) {
|
||||
if (!_inner_groups.TryPopValue(inner_group.Name, out var ig_sub))
|
||||
throw new InvalidOperationException($"Inner groups cache does not have an entry for the specified name '{inner_group.Name}' (entire tree structure may be compromised).");
|
||||
ig_sub.Dispose();
|
||||
Disposable.dispose(ref inner_group._dispose);
|
||||
} else {
|
||||
var row = (PNLRowVM)item;
|
||||
if (!_rows.TryPopValue(row.key, out var row_sub))
|
||||
throw new InvalidOperationException($"Inner rows cache does not have an entry for the specified irowstem key '{row.key}' (entire tree structure may be compromised).");
|
||||
row_sub.Dispose();
|
||||
}
|
||||
ProtectedItems.RemoveAt(index);
|
||||
}
|
||||
|
||||
void _from_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
using (_refresh_is_all_selected.defer_execution()) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
for (int i = 0; i < e.OldItems.Count; i++)
|
||||
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
for (int i = 0; i < e.OldItems.Count; i++)
|
||||
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
while (ProtectedItems.Count > 0)
|
||||
_remove_item(0, ProtectedItems[0]);
|
||||
for (int i = 0; i < _from.Items.Count; i++)
|
||||
_add_item(i, _from.Items[i]);
|
||||
break;
|
||||
}
|
||||
_is_all_selected.increment_version();
|
||||
_refresh_is_all_selected.execute();
|
||||
}
|
||||
}
|
||||
|
||||
IDisposable _connect_row(PNLRowVM row) {
|
||||
using (_detect_is_all_selected_changed()) {
|
||||
var last_data = PNLNormalizedData.zero;
|
||||
on_updated(row.normalized_data);
|
||||
row.updated += on_updated;
|
||||
row.is_selected_changed += on_is_selected_changed;
|
||||
if (row.is_selected) {
|
||||
Log.assert(_selected_rows.Add(row), $"Selected Rows out of sync ({Name}): hash set already contained selected row that was just added to group ({row}).");
|
||||
_selected_item_count++;
|
||||
}
|
||||
ProtectedItemCount++;
|
||||
return Disposable.Create(() => {
|
||||
row.updated -= on_updated;
|
||||
row.is_selected_changed -= on_is_selected_changed;
|
||||
using (_refresh_is_all_selected.defer_execution()) {
|
||||
if (row.is_selected) {
|
||||
Log.assert(_selected_rows.Remove(row), $"Selected Rows out of sync ({Name}): hash set did not contain selected row that was just removed from group ({row}).");
|
||||
_selected_item_count--;
|
||||
}
|
||||
var newval = data - row.normalized_data;
|
||||
data = newval;
|
||||
ProtectedItemCount--;
|
||||
}
|
||||
});
|
||||
void on_updated(PNLNormalizedData new_data) {
|
||||
var less_last = data - last_data;
|
||||
var plus_new = less_last + new_data;
|
||||
data += new_data - last_data;
|
||||
if (plus_new != data) {
|
||||
Log.Ui.warn($"Detected PNL arithmetic rounding error in the group sum for '{string.Join(" ", GroupNamePath)}' (this bug is under investigation).");
|
||||
}
|
||||
last_data = new_data;
|
||||
}
|
||||
void on_is_selected_changed(bool is_selected) {
|
||||
if (is_selected) {
|
||||
Log.assert(_selected_rows.Add(row), $"Selected Rows out of sync ({Name}): hash set already contained row that was just selected ({row}).");
|
||||
_selected_item_count++;
|
||||
} else {
|
||||
Log.assert(_selected_rows.Remove(row), $"Selected Rows out of sync ({Name}): hash set did not contain row that was just unselected ({row}).");
|
||||
_selected_item_count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _set<T>(ref T backing, T new_value, [CallerMemberName]string prop = "") =>
|
||||
_set(ref backing, new_value, EqualityComparer<T>.Default, prop);
|
||||
|
||||
void _set<T>(ref T backing, T new_value, IEqualityComparer<T> comparer, [CallerMemberName]string prop = "") {
|
||||
if (!comparer.Equals(backing, new_value)) {
|
||||
backing = new_value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(prop));
|
||||
}
|
||||
}
|
||||
|
||||
void IRaiseNotifyPropertyChanged.raise_notify_property_changed(string prop) =>
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(prop));
|
||||
|
||||
class InnerGroupSubscription : IDisposable {
|
||||
public readonly PNLRowCollectionViewGroup _inner_group;
|
||||
readonly PNLRowCollectionViewGroup _parent;
|
||||
PNLNormalizedData _last_data = PNLNormalizedData.zero;
|
||||
int _last_count, _last_selected_item_count;
|
||||
|
||||
public InnerGroupSubscription(PNLRowCollectionViewGroup parent, PNLRowCollectionViewGroup inner_group) {
|
||||
_parent = parent;
|
||||
_inner_group = inner_group;
|
||||
_inner_group.parent = parent;
|
||||
// init item count
|
||||
_on_item_count_changed();
|
||||
// listen to item count changes
|
||||
_inner_group.ProtectedItemCountChanged += _on_item_count_changed;
|
||||
// init selected item count
|
||||
_on_selected_item_count_changed();
|
||||
// listen to selected item count changes
|
||||
_inner_group.selected_item_count_changed += _on_selected_item_count_changed;
|
||||
// init data
|
||||
_on_normalized_data_changed();
|
||||
// listen to data changes
|
||||
_inner_group.data_changed += _on_normalized_data_changed;
|
||||
_parent._selected_item_count += _inner_group._selected_item_count;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_inner_group.ProtectedItemCountChanged -= _on_item_count_changed;
|
||||
_inner_group.selected_item_count_changed -= _on_selected_item_count_changed;
|
||||
_inner_group.data_changed -= _on_normalized_data_changed;
|
||||
_inner_group.parent = null;
|
||||
// make sure the state is valid (if a bad state goes by undetected here, nefarious/difficult-to-track bugs could arise)
|
||||
if (_last_count != _inner_group.ProtectedItemCount) {
|
||||
Log.Ui.warn("Cached inner count is not equal to the inner group's ItemCount (entire tree structure may be compromised).");
|
||||
}
|
||||
if (_last_data != _inner_group.data) {
|
||||
Log.Ui.warn($"Cached normalized data is not equal to the inner group's normalized data (aggregated pnl data may be compromised).");
|
||||
}
|
||||
_parent.ProtectedItemCount -= _last_count;
|
||||
_parent.data -= _last_data;
|
||||
_parent._selected_item_count -= _last_selected_item_count;
|
||||
}
|
||||
|
||||
void _on_item_count_changed() {
|
||||
// add the delta
|
||||
_parent.ProtectedItemCount += (_inner_group.ProtectedItemCount - _last_count);
|
||||
// reset the local cache
|
||||
_last_count = _inner_group.ProtectedItemCount;
|
||||
}
|
||||
|
||||
void _on_selected_item_count_changed() {
|
||||
_parent._selected_item_count += (_inner_group._selected_item_count - _last_selected_item_count);
|
||||
_last_selected_item_count = _inner_group._selected_item_count;
|
||||
}
|
||||
|
||||
void _on_normalized_data_changed() {
|
||||
var new_data = _inner_group.data;
|
||||
_parent.data += new_data - _last_data;
|
||||
_last_data = new_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,413 +0,0 @@
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.adorners;
|
||||
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.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
enum PNLHeaderRowDisplay {
|
||||
None,
|
||||
Total,
|
||||
SelectedTotal
|
||||
}
|
||||
|
||||
partial class PNLView : UserControl {
|
||||
public enum Columns {
|
||||
Account = 1,
|
||||
// Security
|
||||
Exchange,
|
||||
Symbol,
|
||||
SecurityType,
|
||||
Security,
|
||||
SecurityKey,
|
||||
CPP,
|
||||
// SOD
|
||||
SODPNL,
|
||||
SODLongPos,
|
||||
SODLongAvgPrice,
|
||||
SODShortPos,
|
||||
SODShortAvgPrice,
|
||||
SODNetPos,
|
||||
// Day
|
||||
DayPNL,
|
||||
DayLongPos,
|
||||
DayLongAvgPrice,
|
||||
DayShortAvgPrice,
|
||||
DayShortPos,
|
||||
DayCalcType,
|
||||
DayNetPos,
|
||||
HasDayTrades,
|
||||
// Raw Day
|
||||
RawDayLongPos,
|
||||
RawDayShortPos,
|
||||
RawDayNetPos,
|
||||
// Working
|
||||
WorkingLongPos,
|
||||
WorkingLongAvgPrice,
|
||||
WorkingShortPos,
|
||||
WorkingShortAvgPrice,
|
||||
WorkingNetPos,
|
||||
// Total
|
||||
TotalPNL,
|
||||
TotalLongPos,
|
||||
TotalLongAvgPrice,
|
||||
TotalShortPos,
|
||||
TotalShortAvgPrice,
|
||||
TotalNetPos,
|
||||
// Market
|
||||
BidPrice,
|
||||
MidPrice,
|
||||
AskPrice,
|
||||
LastPrice,
|
||||
SettlementPrice,
|
||||
PNLPrice
|
||||
}
|
||||
|
||||
public static readonly IValueConverter
|
||||
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
|
||||
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
|
||||
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
|
||||
|
||||
public static readonly IMultiValueConverter
|
||||
TotalHeaderRowDisplayTextConverter = ValueConverter.create<int, int, string>((filtered, total) => $"Total ({(filtered == total ? total.ToString() : $"{filtered}/{total}")})"),
|
||||
CellParamsConverter = ValueConverter.create<object, Columns, PNLCell>((item, col) => new PNLCell(item, col));
|
||||
|
||||
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
|
||||
|
||||
static readonly string _node_path_separator = $" {SpecialCharacters.Dot} ";
|
||||
|
||||
IDisposable _anchors_hook, _animations_hook;
|
||||
int _anchor_version;
|
||||
|
||||
public PNLView() {
|
||||
SetCurrentValue(AnchorIndexProperty, new AnchorIndex());
|
||||
InitializeComponent();
|
||||
SetBinding(AnchorsProperty, new Binding(nameof(PNLVM.anchors)));
|
||||
_grid.ConnectOnLoaded(() => {
|
||||
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter presenter)) {
|
||||
presenter.GroupItemSelectionPivot += on_group_item_selection_pivot;
|
||||
return Disposable.Create(() => presenter.GroupItemSelectionPivot -= on_group_item_selection_pivot);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
void on_group_item_selection_pivot(object sender, GroupItemSelectionPivotEventArgs e) =>
|
||||
e.Handled = e.group_item is PNLRowCollectionViewGroup pnl_group && pnl_group.is_expanded;
|
||||
}
|
||||
|
||||
public new PNLVM DataContext {
|
||||
get => base.DataContext as PNLVM;
|
||||
set => base.DataContext = value;
|
||||
}
|
||||
|
||||
public bool EnableAnimations {
|
||||
get => _animations_hook != null;
|
||||
set {
|
||||
if (EnableAnimations != value) {
|
||||
if (_animations_hook == null) {
|
||||
_animations_hook = AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
} else {
|
||||
_animations_hook.Dispose();
|
||||
_animations_hook = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region AnchorIndex
|
||||
public static readonly DependencyProperty AnchorIndexProperty =
|
||||
WPFHelper.create_dp<AnchorIndex, PNLView>(
|
||||
nameof(AnchorIndex),
|
||||
(o, v) => v?.reset(o?.Anchors));
|
||||
|
||||
public AnchorIndex AnchorIndex {
|
||||
get => GetValue(AnchorIndexProperty) as AnchorIndex;
|
||||
set => SetValue(AnchorIndexProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GroupLevel
|
||||
public static readonly DependencyProperty GroupLevelProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"GroupLevel",
|
||||
typeof(int),
|
||||
typeof(PNLView),
|
||||
new PropertyMetadata(0, (d, e) => {
|
||||
if (d is GroupItem group && e.NewValue is int level)
|
||||
group.SetBinding(DIYGrid.ColumnKeyProperty, new Binding($"{nameof(Anchors)}[{level}]") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(PNLView), 1) });
|
||||
}));
|
||||
|
||||
public static int GetGroupLevel(DependencyObject d) => (int)d.GetValue(GroupLevelProperty);
|
||||
public static void SetGroupLevel(DependencyObject d, int value) => d.SetValue(GroupLevelProperty, value);
|
||||
|
||||
void _grid_loaded(object sender, RoutedEventArgs e) {
|
||||
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
|
||||
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
|
||||
rp.SetBinding(MarginProperty, new Binding(nameof(PNLVM.header_row_display)) {
|
||||
Converter = ValueConverter.create<PNLHeaderRowDisplay, Thickness>(hrd => {
|
||||
double top = 0;
|
||||
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
|
||||
top += 24;
|
||||
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
|
||||
top += 24;
|
||||
return new Thickness(0, top, 0, 0);
|
||||
}),
|
||||
Source = DataContext
|
||||
});
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Anchors
|
||||
public static readonly DependencyProperty AnchorsProperty =
|
||||
WPFHelper.create_dp<IEnumerable<Columns>, PNLView>(
|
||||
nameof(Anchors),
|
||||
(o, v) => {
|
||||
var cols = o?._grid?.Columns;
|
||||
if (cols == null)
|
||||
return;
|
||||
if (!(o._grid.Rows is ICollectionView rows_view))
|
||||
return;
|
||||
if (!(v is INotifyCollectionChanged anchor_notifier && v is IReadOnlyList<Columns> anchor_keys))
|
||||
return;
|
||||
int version = ++o._anchor_version;
|
||||
bool busy = false;
|
||||
var pnl_cols = cols.CastObservable<DIYGrid.ColumnBase, PNLColumn>();
|
||||
Disposable.replace(ref o._anchors_hook, Disposable.Create(
|
||||
anchor_notifier.Subscribe((_, __) => {
|
||||
if (busy)
|
||||
return;
|
||||
// invoke async to avoid noisy consecutive resets due to batch changes
|
||||
busy = true;
|
||||
o.Dispatcher.InvokeAsync(() => {
|
||||
sync_anchor_cols();
|
||||
busy = false;
|
||||
});
|
||||
})));
|
||||
o.AnchorIndex?.reset(anchor_keys);
|
||||
sync_anchor_cols();
|
||||
void sync_anchor_cols() {
|
||||
// since reset is invoked asynchronously, need to check we are still the latest version.
|
||||
if (o._anchor_version != version)
|
||||
return;
|
||||
o.AnchorIndex?.reset(anchor_keys);
|
||||
// move all the anchor columns to the left, ordered by anchor position (grouping hierarchy).
|
||||
for (int i = 0; i < anchor_keys.Count; i++) {
|
||||
if (pnl_cols.TryGetFirst(c => c.key == anchor_keys[i], out var match, out int curr_pos)) {
|
||||
match.is_visible = true;
|
||||
if (curr_pos != i) {
|
||||
cols.Move(curr_pos, i);
|
||||
}
|
||||
match.anchor_index = i + 1;
|
||||
}
|
||||
}
|
||||
for (int i = anchor_keys.Count; i < pnl_cols.Count; i++) {
|
||||
pnl_cols[i].anchor_index = null;
|
||||
}
|
||||
using (rows_view.DeferRefresh()) {
|
||||
rows_view.GroupDescriptions.Clear();
|
||||
var anchor_cols = from key in anchor_keys
|
||||
join col in pnl_cols on key equals col.key
|
||||
select new PNLGroupDescription(col);
|
||||
rows_view.GroupDescriptions.AddRange(anchor_cols);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public IEnumerable<Columns> Anchors {
|
||||
get => GetValue(AnchorsProperty) as IEnumerable<Columns>;
|
||||
set => SetValue(AnchorsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
void _cell_click(PNLCell cell) {
|
||||
Log.Ui.info($"PNLCell Click: {cell}.");
|
||||
try {
|
||||
switch (cell.column) {
|
||||
case Columns.SODNetPos:
|
||||
case Columns.SODLongPos:
|
||||
case Columns.SODShortPos:
|
||||
case Columns.DayNetPos:
|
||||
case Columns.DayLongPos:
|
||||
case Columns.DayShortPos:
|
||||
case Columns.RawDayNetPos:
|
||||
case Columns.RawDayLongPos:
|
||||
case Columns.RawDayShortPos:
|
||||
case Columns.TotalNetPos:
|
||||
case Columns.TotalLongPos:
|
||||
case Columns.TotalShortPos: {
|
||||
var oneof = cell.oneof();
|
||||
if (oneof == null)
|
||||
return;
|
||||
var (vm, context, title, fg) = cell.oneof().Match(_row_fills, _group_fills, _header_fills);
|
||||
_show_auxiliary_popup(vm, context, title, fg);
|
||||
}
|
||||
break;
|
||||
case Columns.WorkingNetPos:
|
||||
case Columns.WorkingLongPos:
|
||||
case Columns.WorkingShortPos: {
|
||||
var (vm, context, title, fg) = cell.oneof().Match(_orderbook_params, _orderbook_params, _orderbook_params);
|
||||
_show_auxiliary_popup(vm, context, title, fg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.warn($"{nameof(PNLView)} error on cell click: {ex}.");
|
||||
}
|
||||
}
|
||||
|
||||
#region Fills
|
||||
(FillsVM vm, string context, string title, Color fg) _row_fills(PNLRowCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
|
||||
}
|
||||
|
||||
(FillsVM vm, string context, string title, Color fg) _group_fills(PNLGroupCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
|
||||
}
|
||||
|
||||
(FillsVM vm, string context, string title, Color fg) _header_fills(PNLHeaderRowCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _fills_title(Columns key) {
|
||||
const uint net_fg = 0x333333u;
|
||||
switch (key) {
|
||||
case Columns.SODLongPos: return ("Open Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.SODShortPos: return ("Open Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.SODNetPos: return ("Open Fills", net_fg.rgb_color());
|
||||
case Columns.DayLongPos: return ("Day Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.DayShortPos: return ("Day Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.DayNetPos: return ("Day Fills", net_fg.rgb_color());
|
||||
case Columns.RawDayLongPos: return ("Raw Day Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.RawDayShortPos: return ("Raw Day Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.RawDayNetPos: return ("Raw Day Fills", net_fg.rgb_color());
|
||||
case Columns.TotalLongPos: return ("All Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.TotalShortPos: return ("All Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.TotalNetPos: return ("All Fills", net_fg.rgb_color());
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OrderBook
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLRowCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
|
||||
}
|
||||
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLGroupCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
|
||||
}
|
||||
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLHeaderRowCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _orderbook_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.WorkingLongPos: return ("Working Buy Orders", BTSColors.MarketData.bid);
|
||||
case Columns.WorkingShortPos: return ("Working Sell Orders", BTSColors.MarketData.ask);
|
||||
case Columns.WorkingNetPos: return ("Working Orders", 0x333333u.rgb_color());
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
void _cell_click_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
if (e.Parameter is PNLCell cell) {
|
||||
_cell_click(cell);
|
||||
} else Log.Ui.warn($"{nameof(PNLView)} '{nameof(_cell_click_executed)}' parameter should be a {typeof(PNLCell)} (actually '{e.Parameter}').");
|
||||
}
|
||||
|
||||
void _on_group_item_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
if (!e.Handled && sender is FrameworkElement el && el.DataContext is PNLRowCollectionViewGroup group) {
|
||||
if (e.ClickCount == 2) {
|
||||
group.is_expanded = !group.is_expanded;
|
||||
} else if (el.try_find_ancestor_of_type(out DIYGrid grid) && grid.SelectionMode != SelectionMode.Single) {
|
||||
group.is_all_selected = !group.is_all_selected.GetValueOrDefault();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
async void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
|
||||
// vm
|
||||
dynamic header = new ExpandoObject();
|
||||
header.context = context_desc;
|
||||
header.title = title_desc;
|
||||
header.title_foreground = fg;
|
||||
header.keep_open = false;
|
||||
// view
|
||||
var view = new PNLAuxiliaryPopupView {
|
||||
Content = content,
|
||||
Header = header,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
};
|
||||
await Task.Delay(100); // Wait for obvm to load rows collection before showing UI to avoid a flash
|
||||
view.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
// popup adorner
|
||||
// tho unlikely, better not assume target cell is non-null
|
||||
var target_cell = _grid.ChildAtPoint<HyperlinkCell>(Mouse.GetPosition(_grid));
|
||||
var popup_adorner = new PopupAdorner(_grid, target_cell ?? (FrameworkElement)_grid, view, HorizontalPosition.Middle, VerticalPosition.Bottom, 0, 0) {
|
||||
OpenRelativeToMouse = target_cell == null,
|
||||
EnableDragMove = true,
|
||||
ClipToBounds = true
|
||||
};
|
||||
popup_adorner.SetBinding(PopupAdorner.StaysOpenProperty, new Binding("keep_open") { Source = header });
|
||||
popup_adorner.PopupElement.InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
|
||||
popup_adorner.PopupElement.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => {
|
||||
Log.assert(popup_adorner != null);
|
||||
popup_adorner.detach();
|
||||
popup_adorner = null;
|
||||
}));
|
||||
popup_adorner.attach();
|
||||
popup_adorner.IsOpen = true;
|
||||
FocusManager.SetIsFocusScope(popup_adorner.PopupElement, true);
|
||||
popup_adorner.AdornedElement.Focus();
|
||||
}
|
||||
|
||||
class PNLGroupDescription : PropertyGroupDescription, IEquatable<PNLGroupDescription> {
|
||||
public PNLGroupDescription(PNLColumn column) : base(column.data_member_path.Path) =>
|
||||
key = column.key;
|
||||
|
||||
public Columns key { get; }
|
||||
|
||||
public bool Equals(PNLGroupDescription other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as PNLGroupDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
@@ -17,17 +16,12 @@ using static mbts.ui.shell.ProtoBufSerializationHelper;
|
||||
namespace bts.shell {
|
||||
[Export(typeof(IShellModule))]
|
||||
class TradingShellModule : IShellModule {
|
||||
readonly PNLConfig _pnl_conf;
|
||||
// window factories
|
||||
readonly LadderWindowFactory _lwf;
|
||||
readonly OrderBookWindowFactory _obwf;
|
||||
readonly FillsWindowFactory _fwf;
|
||||
readonly PAndLWindowFactory _old_plwf;
|
||||
readonly PNLWindowFactory _plwf;
|
||||
readonly AlgoStudioWindowFactory _aswf;
|
||||
readonly ManualFillsWindowFactory _mfwf;
|
||||
//
|
||||
List<SparkCommand> _actions;
|
||||
LadderWindowFactory _lwf;
|
||||
OrderBookWindowFactory _obwf;
|
||||
FillsWindowFactory _fwf;
|
||||
PAndLWindowFactory _plwf;
|
||||
AlgoStudioWindowFactory _aswf;
|
||||
ManualFillsWindowFactory _mfwf;
|
||||
|
||||
[ImportingConstructor]
|
||||
public TradingShellModule(
|
||||
@@ -39,17 +33,10 @@ namespace bts.shell {
|
||||
OrdersModel orders_model,
|
||||
md.MBOService mbo,
|
||||
ErrorReporter error_reporter) {
|
||||
/// Unlike our <see cref="SparkCommand"/> members, <see cref="IShellWindowFactory"/> members must be instantiated immediately,
|
||||
/// as they are responsible for launching windows in response to a SparkTag being published to the bus.
|
||||
/// The tags may be published by anything (i.e., not just the spark commands in <see cref="actions"/>).
|
||||
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, mbo);
|
||||
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
|
||||
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_pnl_conf = new PNLConfig(mbts.config.Configuration.get<bts.config.PNLOptions>());
|
||||
if (_pnl_conf.show_new_pnl)
|
||||
_plwf = new PNLWindowFactory(this, bus, settings_hub, orders_model);
|
||||
if (_pnl_conf.show_old_pnl)
|
||||
_old_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
|
||||
_mfwf = new ManualFillsWindowFactory(this, bus, orders_model);
|
||||
}
|
||||
@@ -60,62 +47,31 @@ namespace bts.shell {
|
||||
|
||||
public int priority => 20;
|
||||
|
||||
public IEnumerable<SparkCommand> actions {
|
||||
get {
|
||||
if (_actions == null) {
|
||||
_actions = new List<SparkCommand> {
|
||||
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
|
||||
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
|
||||
};
|
||||
if (_pnl_conf.show_new_pnl)
|
||||
_actions.Add(SparkCommands.ShowPNL.complete(() => null));
|
||||
if (_pnl_conf.show_old_pnl)
|
||||
_actions.Add(SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()));
|
||||
_actions.AddRange(new[] {
|
||||
SparkCommands.PickLadder,
|
||||
SparkCommands.PickOrderTicket,
|
||||
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
|
||||
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
|
||||
});
|
||||
}
|
||||
return _actions;
|
||||
}
|
||||
}
|
||||
public IEnumerable<SparkCommand> actions => new [] {
|
||||
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
|
||||
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
|
||||
SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()),
|
||||
SparkCommands.PickLadder,
|
||||
SparkCommands.PickOrderTicket,
|
||||
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
|
||||
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
|
||||
};
|
||||
|
||||
IEnumerable<IShellWindowFactory> _window_factories {
|
||||
get {
|
||||
yield return _lwf;
|
||||
yield return _obwf;
|
||||
yield return _fwf;
|
||||
if (_plwf != null)
|
||||
yield return _plwf;
|
||||
if (_old_plwf != null)
|
||||
yield return _old_plwf;
|
||||
yield return _aswf;
|
||||
yield return _mfwf;
|
||||
}
|
||||
IEnumerable<IShellWindowFactory> window_factories() {
|
||||
yield return _lwf;
|
||||
yield return _obwf;
|
||||
yield return _fwf;
|
||||
yield return _plwf;
|
||||
yield return _aswf;
|
||||
yield return _mfwf;
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) =>
|
||||
dispatch_to_factory(load_stream, _window_factories.ToArray());
|
||||
dispatch_to_factory(load_stream, window_factories().ToArray());
|
||||
|
||||
public void Dispose() {
|
||||
if (_actions != null) {
|
||||
_actions.OfType<IDisposable>().DisposeAll();
|
||||
_actions = null;
|
||||
}
|
||||
_window_factories.DisposeAll();
|
||||
}
|
||||
|
||||
readonly struct PNLConfig {
|
||||
public readonly bool
|
||||
show_new_pnl,
|
||||
show_old_pnl;
|
||||
|
||||
public PNLConfig(bts.config.PNLOptions options) {
|
||||
show_new_pnl = options.show_new_pnl;
|
||||
show_old_pnl = options.show_old_pnl;
|
||||
}
|
||||
foreach (var i in window_factories())
|
||||
if (i != null) i.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
@@ -14,7 +13,6 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace bts.shell {
|
||||
interface INotificationService {
|
||||
@@ -24,48 +22,25 @@ namespace bts.shell {
|
||||
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);
|
||||
// Note: security criteria should not be incorporated in the predicate's 'trigger' impl.
|
||||
// It is easier/cleaner if the INotificationService incorporates the criteria internally.
|
||||
IDisposable link_to_trades(SecurityCriteria security_criteria, IPredicate<TradeInfo> predicate, SoundNotification sound);
|
||||
}
|
||||
|
||||
[Export(typeof(INotificationService))]
|
||||
partial class NotificationService : INotificationService, IDisposable {
|
||||
// activity notifications
|
||||
readonly NotificationProcessor<Activity> _activity_notifications;
|
||||
// order notifications
|
||||
readonly NotificationProcessor<OrderSecurity> _order_notifications;
|
||||
// trade notifications
|
||||
readonly NotificationProcessor<TradeInfo> _trade_notifications;
|
||||
readonly MDConsumer _consumer;
|
||||
readonly Subject<IEnumerable<SecurityCriteria>> _trade_criteria_subject = new Subject<IEnumerable<SecurityCriteria>>();
|
||||
readonly RefCountMap<SecurityCriteria> _trade_criteria_refs = new RefCountMap<SecurityCriteria>();
|
||||
readonly object _trade_lock = new object();
|
||||
//
|
||||
readonly DataCache _data_cache = new DataCache();
|
||||
readonly LoggerContext _log = new LoggerContext(Log.Ui, "Notification Service ", null);
|
||||
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public NotificationService(IBus bus, INotifier notifier, OrdersModel orders_model, MDConsumer consumer) {
|
||||
public NotificationService(IBus bus, INotifier notifier, OrdersModel orders_model) {
|
||||
this.notifier = notifier;
|
||||
_activity_notifications = new NotificationProcessor<Activity>(notifier);
|
||||
_order_notifications = new NotificationProcessor<OrderSecurity>(notifier);
|
||||
_trade_notifications = new NotificationProcessor<TradeInfo>(notifier);
|
||||
_consumer = consumer;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.LogActivity).Subscribe(_on_activity),
|
||||
orders_model.on_orders.Subscribe(_on_orders),
|
||||
TradesSource.create(
|
||||
consumer,
|
||||
bus.subscribe(SparkTags.SecuritiesTag).BufferLatest(TimeSpan.FromSeconds(2))
|
||||
.Table(s => s.security.Id)
|
||||
.Where(t => t.has_changes())
|
||||
.Select(m => MappedDictionary.create(m.state, smd => smd.security)),
|
||||
_trade_criteria_subject,
|
||||
TimeSpan.FromSeconds(0.2))
|
||||
.Subscribe(_trade_notifications.process_items));
|
||||
orders_model.on_orders.Subscribe(_on_orders)
|
||||
);
|
||||
}
|
||||
|
||||
[Export(typeof(INotificationServiceDataCache))]
|
||||
@@ -76,42 +51,8 @@ namespace bts.shell {
|
||||
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_trades(SecurityCriteria security_criteria, IPredicate<TradeInfo> predicate, SoundNotification sound) {
|
||||
if (predicate == null) {
|
||||
_log.warn("cannot create trade alert sound because the specified predicate is null.");
|
||||
return Disposable.empty;
|
||||
}
|
||||
if (!security_criteria.is_defined()) {
|
||||
// there are simply too many trades to support universal-product predicates, therefor a product filter must be specified (aka SecrityCriteria).
|
||||
_log.warn("cannot create trade alert sound because the specified security criteria is not defined.");
|
||||
return Disposable.empty;
|
||||
}
|
||||
// This criteria predicate may be redundant, but it safe guards against a case
|
||||
// where the criteria has not been incorporated into the predicate,
|
||||
// as there doesn't seem to be a great way to require this in the interface.
|
||||
// Note: verifying the criteria twice (instead of once) should not hurt on perf at all.
|
||||
predicate = predicate.and<TradeInfo>(PredicateBuilder.create<TradeInfo>(t => ContractPickerModel.security_in_criteria(security_criteria, t.security)));
|
||||
var link = _trade_notifications.link(predicate, sound);
|
||||
lock (_trade_lock) {
|
||||
if (_trade_criteria_refs.increment_adds_key(security_criteria))
|
||||
_reset_trade_readers();
|
||||
}
|
||||
return Disposable.Create(() => {
|
||||
Disposable.dispose(ref link);
|
||||
lock (_trade_lock) {
|
||||
if (_trade_criteria_refs.decrement_removes_key(security_criteria))
|
||||
_reset_trade_readers();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
/// should only call this within a locked scope (<see cref="_trade_lock"/>)
|
||||
void _reset_trade_readers() =>
|
||||
_trade_criteria_subject.OnNext(_trade_criteria_refs.Keys);
|
||||
|
||||
void _on_activity(Activity activity) =>
|
||||
_activity_notifications.process_item(activity);
|
||||
|
||||
@@ -187,31 +128,6 @@ namespace bts.shell {
|
||||
if (sounds.Count > 0 || toast.Count > 0)
|
||||
_notifier.notify(toast, sounds);
|
||||
}
|
||||
|
||||
public void process_items(IEnumerable<T> source) {
|
||||
var sounds = new HashSet<SoundNotification>();
|
||||
var toast = new HashSet<IToastNotification>();
|
||||
var temp_sound_triggers = _sound_trigger_refs;
|
||||
if (temp_sound_triggers.Count > 0) {
|
||||
foreach (var v in source) {
|
||||
foreach (var trigger_kv in temp_sound_triggers) {
|
||||
if (trigger_kv.Key.trigger(v))
|
||||
sounds.AddRange(trigger_kv.Value.Keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
var temp_toast_triggers = _toast_trigger_refs;
|
||||
if (temp_toast_triggers.Count > 0) {
|
||||
foreach (var v in source) {
|
||||
foreach (var trigger_kv in temp_toast_triggers) {
|
||||
if (trigger_kv.Key.trigger(v) is IToastNotification tn && tn.pass)
|
||||
toast.Add(tn);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sounds.Count > 0 || toast.Count > 0)
|
||||
_notifier.notify(toast, sounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.ui.utility;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
|
||||
namespace bts.shell {
|
||||
enum LicenseType {
|
||||
@@ -31,7 +33,7 @@ namespace bts.shell {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class SoundNotification : IEquatable<SoundNotification> {
|
||||
class SoundNotification {
|
||||
SystemSound? _system_sound;
|
||||
string _uri_sound;
|
||||
|
||||
@@ -79,19 +81,6 @@ namespace bts.shell {
|
||||
[ProtoMember(2)]
|
||||
public string uri_sound { get => _uri_sound; set => _set_ref_type(ref _uri_sound, value); }
|
||||
|
||||
public bool Equals(SoundNotification other) =>
|
||||
other != null &&
|
||||
_system_sound == other._system_sound &&
|
||||
_uri_sound == other._uri_sound;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as SoundNotification);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(
|
||||
_system_sound.GetHashCode(),
|
||||
_uri_sound?.GetHashCode() ?? 0);
|
||||
|
||||
void _set<T>(ref T? field, T? value) where T : struct {
|
||||
if (value != null) {
|
||||
_clear_values();
|
||||
|
||||
@@ -2,40 +2,28 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillTuple : IEquatable<FillTuple> {
|
||||
public static readonly Func<FillTuple, string> key_fn = ft => ft.fill.FillKey;
|
||||
public readonly FillImmutable fill;
|
||||
public readonly OrderRecordImmutable? order;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoData? algo;
|
||||
public readonly AlgoDefinitionMetadata? algo_meta;
|
||||
|
||||
OrderSecurityRootPair? _order_tuple;
|
||||
|
||||
public FillTuple(FillImmutable fill, SecurityImmutable? security, OrderSecurityRootPair? order_tuple) {
|
||||
this.fill = fill;
|
||||
this.security = security;
|
||||
_order_tuple = order_tuple;
|
||||
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurity? order_tuple) {
|
||||
this.fill = fill.val;
|
||||
this.security = fill.sec;
|
||||
if (order_tuple is OrderSecurity osam) {
|
||||
order = osam.order;
|
||||
algo = osam.algo;
|
||||
algo_meta = osam.meta;
|
||||
}
|
||||
}
|
||||
|
||||
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurity? order_tuple)
|
||||
: this(fill.val, fill.sec, order_tuple.HasValue ? OrderSecurityRootPair.from_order_security(order_tuple.Value) : (OrderSecurityRootPair?)null) { }
|
||||
|
||||
public AlgoData? algo =>
|
||||
_order_tuple.HasValue ? _order_tuple.Value.order.algo : (AlgoData?)null;
|
||||
|
||||
public AlgoDefinitionMetadata? algo_meta =>
|
||||
_order_tuple.HasValue ? _order_tuple.Value.order.meta : null;
|
||||
|
||||
public OrderRecordImmutable? order =>
|
||||
_order_tuple.HasValue ? _order_tuple.Value.order.order : (OrderRecordImmutable?)null;
|
||||
|
||||
public AlgoData? root_algo =>
|
||||
_order_tuple.HasValue ? _order_tuple.Value.root.algo : (AlgoData?)null;
|
||||
|
||||
public AlgoDefinitionMetadata? root_algo_meta =>
|
||||
_order_tuple.HasValue ? _order_tuple.Value.root.meta : null;
|
||||
|
||||
public string account {
|
||||
get {
|
||||
var account_string = string.Empty;
|
||||
@@ -45,19 +33,12 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public uint account_id =>
|
||||
fill.FillKind != FillKind.Sod && order.HasValue ?
|
||||
order.Value.account_id() :
|
||||
fill.InternalAccountId;
|
||||
public bool Equals(FillTuple other) => other != null
|
||||
&& other.order.HasValue == order.HasValue
|
||||
&& other.security.HasValue == security.HasValue
|
||||
&& other.fill.Equals(fill);
|
||||
|
||||
public bool Equals(FillTuple other) =>
|
||||
other != null &&
|
||||
_order_tuple.Equals(other._order_tuple) &&
|
||||
security.HasValue == other.security.HasValue &&
|
||||
fill.Equals(other.fill);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as FillTuple);
|
||||
public override bool Equals(object obj) => Equals(obj as FillTuple);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
@@ -85,4 +66,4 @@ namespace mbts.spark {
|
||||
public static string user(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 592 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user