Compare commits

..
Author SHA1 Message Date
Jake Norbut 9d1b03d1e2 fixing merge 2020-03-04 11:51:05 -06:00
Jake Norbut 9a677cf980 merging master into csv_export; taking master 2020-03-04 11:50:56 -06:00
Eric Harding 08bdd9c8c7 Update bad links on api doc site 2020-03-03 17:14:03 -05:00
Jake Norbut 886b606093 moving endif for release mode 2020-03-02 14:02:55 -06:00
Jake Norbut b70510b614 Merged in SP-69-fix-stop-and-stop-limit-orders (pull request #400)
SP-69 fix stop and stop limit orders

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2020-03-02 19:46:11 +00:00
John Colvin 6db9a227b1 Merged in SP-287-algo-params-piq-update (pull request #399)
SP-287 algo params piq update

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2020-03-02 19:36:24 +00:00
Jake Norbut a819eb921d PR edit; removing unused local variable 2020-03-02 13:07:01 -06:00
Jake Norbut af4317a7f9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-69-fix-stop-and-stop-limit-orders 2020-02-27 14:27:21 -06:00
John Colvin 5edd7d5467 OrderBookRowVM only resets prospective_qty when the real qty changes 2020-02-27 14:07:05 -06:00
John Colvin 60d2cb5bc1 SP-287 fix algo params piq update bug 2020-02-27 14:05:04 -06:00
John Colvin f26e3d27c4 refactor IEnumerable to IReadOnlyCollection in common_value_group_changed event 2020-02-27 14:01:22 -06:00
Jake Norbut 31dab6ca09 allow stop/stop limit price to occupy same level as price 2020-02-26 12:10:05 -06:00
Jake Norbut 1498bf17bb when modifying stop-limit stop_price, don't change the price price 2020-02-26 12:09:32 -06:00
Jake Norbut 1cafe23bd0 stop_price and stop_limit_price now reference Pedro's new StopPx field in NativeState 2020-02-25 15:42:33 -06:00
Jake Norbut 25290df798 remove stop* modification logic from badge vm 2020-02-25 15:41:27 -06:00
Jake Norbut 20ca48e365 modification logic of stop* orders is now done centrally in OrdersModel 2020-02-25 15:40:08 -06:00
Pedro Pinto 78dfb03b8e Reported stop px in order state 2020-02-25 14:30:26 -05:00
Jake Norbut a1f71153c8 hook OB col filter up to UI price 2020-02-24 14:34:46 -06:00
Jake Norbut b1d260d98c OB RowVM to use ui_price to stop/limit shows expected value in Price column 2020-02-24 14:33:11 -06:00
Jake Norbut a81aafefe0 get_root_at_price to use ui_price so mini ob opens from Ladder 2020-02-24 14:31:43 -06:00
John Colvin 454330e360 fix typo in DiscretePropertyChangedDetector constructor (don't trim the whole array) 2020-02-21 18:51:37 -06:00
John Colvin 995159b365 pnl hyperlinks display hand cursor on hover 2020-02-21 18:49:25 -06:00
John Colvin fd1a33e9d2 PNLView - set Hyperlink/TextCell 'SnapToDevicePixels' to true 2020-02-21 17:39:48 -06:00
John Colvin e9615bc3aa PNLView - get rid of magic const row height 2020-02-21 17:39:35 -06:00
John Colvin b1b1597686 PNLView.xaml - factor out common TextCell props into base style 2020-02-21 17:24:01 -06:00
John Colvin bc2eb65c0a PNLView.xaml - factor out common HyperlinkCell props into base style 2020-02-21 17:22:46 -06:00
John Colvin 7baa7ca8a1 add OrderTicket.close_on_send config toggle 2020-02-21 16:56:30 -06:00
John Colvin 9bbc73b48c fix silent exception in SparkCommandVM.is_selectable 2020-02-21 16:56:30 -06:00
John Colvin b07a379ee1 orderbook LastMessage background always reverts to black on settings saved 2020-02-21 16:56:30 -06:00
John Colvin af2c6174e8 add 'AffectsRender' flag to Hyperlink/TextCell HorizontalContentAlignment dps 2020-02-21 16:56:30 -06:00
John Colvin 6e5444709d fix Hyperlink/TextCell padding (actually use it) 2020-02-21 16:56:30 -06:00
John Colvin 831aa43f0e tweak HyperlinkCell mouse-capture/mouse-over-click-area logic 2020-02-21 16:56:30 -06:00
John Colvin 50a8592e5e clean up DIYGrid TextColumn (mimic recent changes in HyperlinkColumn) 2020-02-21 15:57:25 -06:00
John Colvin cb7b6ef5cd fix TextCell alignment issues 2020-02-21 15:57:07 -06:00
John Colvin b7c72d1607 HighPerformanceRow does not depend on TColumn 2020-02-21 15:56:27 -06:00
John Colvin cb2747528b fix typo in ColumnGeometry (was assigning 'left' to 'unclicpped_left') 2020-02-21 15:50:58 -06:00
John Colvin 52ad4ad2c9 add 'HyperlinkClickMode' to HyperlinkCell to support cmd on click anywhere 2020-02-21 15:50:32 -06:00
John Colvin 93c81089f6 fix HyperlinkCell HorizontalAlignment binding 2020-02-21 15:42:36 -06:00
John Colvin d7d23a7a93 clip mview animations to scroll content presenter bounds 2020-02-21 15:41:14 -06:00
John Colvin 5525e35d1a OrderTypeItem template uses Rectangle instead of Badge (the latter has some unresolved rounding issues) 2020-02-21 15:39:27 -06:00
John Colvin cdd7856ba0 WPFHelper.measure_content() rounds to layout correctly 2020-02-21 14:14:05 -06:00
John Colvin 7a6de46b17 fix WPFHelper.measure_visual_content - 'left edge' calculation for right align; 'bottom edge' for bottom align 2020-02-21 14:13:08 -06:00
John Colvin 1fcccc674a fix HyperlinkCell Alignment issues 2020-02-21 14:11:09 -06:00
John Colvin c0e6b5556d DIYGrid CellRender.draw_text with layout rounding 2020-02-21 14:07:36 -06:00
John Colvin 8aa2a7e44e add spark menu dock/hide commands to pnl 2020-02-21 13:52:14 -06:00
John Colvin 0303d999ed Merged in algo-studio-style-tweaks (pull request #363)
RTM Algo studio style tweaks

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
2020-02-21 19:44:03 +00:00
Pedro Pinto 914a817ddb Fixed crash in handling of stop limit orders 2020-02-21 14:39:18 -05:00
Pedro Pinto 4873e523a9 Re-merged server side stop/stop limit changes 2020-02-21 14:15:56 -05:00
John Colvin 672ef518e2 Merge branch 'SP-279-time-and-sales-column-width' 2020-02-20 12:29:33 -06:00
Pedro Pinto d22cb150f6 Merged 2020-02-20 12:59:51 -05:00
John Colvin 8605b4f11f Merged in SP-281-hidden-toolbar (pull request #393)
SP-281 hidden toolbar

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2020-02-20 17:18:15 +00:00
John Colvin 20eb661fb0 Merged in SP-282-algo-favorite-params (pull request #396)
SP-282 option for algo favorites button to revert modifications after order entry

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2020-02-20 17:14:31 +00:00
John Colvin fad1ca2b6e Merged in SP-285-pnl-working-orders (pull request #395)
SP-285 pnl working qty should only include exchange orders

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2020-02-20 17:10:58 +00:00
John Colvin 747a687fe5 Merged in SP-278-smaller-ladder-columns (pull request #394)
SP-278 Smaller minimum size for Ladder columns

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2020-02-20 17:10:18 +00:00
Jake Norbut e34c63382c merging master and fixing merge conflicts in 3 files by taking master 2020-02-20 08:41:02 -06:00
Pedro Pinto 4b5c744ba5 Updated externals, tools 2020-02-20 08:45:07 -05:00
Eric Harding cb0dfa50b3 Fix dockerfile 2020-02-11 15:02:41 -05:00
Eric Harding 2429e6019c Merge branch 'master' of bitbucket.org:bluetradesys/spark into prod 2020-02-11 14:16:14 -05:00
Eric Harding e0bb48b0c7 Merged in client_sdk (pull request #397)
Client sdk
2020-02-11 18:31:09 +00:00
Eric Harding a38b0dbc79 Merge branch 'master' of bitbucket.org:bluetradesys/spark into client_sdk 2020-02-11 13:29:43 -05:00
Eric Harding 73065ab48a client sdk sample 2020-02-11 13:29:09 -05:00
Eric Harding bd07c96504 Merged in mdsource_crash (pull request #389)
Fix for assert when algo subs to security w/o MD
2020-02-11 17:11:07 +00:00
Eric Harding dc4033875a Fixes from code review feedback 2020-02-11 11:43:04 -05:00
Eric Harding 130794c538 Merge fixup 2020-02-11 11:22:02 -05:00
Eric Harding b1743f0b1c Uninitialized heap, ffs 2020-02-10 18:58:21 -05:00
Eric Harding 33492c9131 Fixes for _WIN32 2020-02-10 18:27:41 -05:00
Eric Harding d434778a82 MarketDataService 2020-02-10 17:06:13 -06:00
Eric Harding 4c6cc9b3e0 Merge branch 'client_sdk' of bitbucket.org:bluetradesys/spark into client_sdk 2020-02-10 12:32:18 -06:00
Eric Harding 6aaeddb7e9 update vscode for linux on linux development 2020-02-10 12:32:10 -06:00
Eric Harding 1685a13994 Add linux on linux build task (vscode) 2020-02-10 13:24:56 -05:00
Eric Harding 54633dd6db note correct config value for local sparkd 2020-02-10 13:21:03 -05:00
Eric Harding 510f163e5b Add to_string methods for Product for new md api 2020-02-10 12:50:05 -05:00
Eric Harding ee4779da73 MarketDataService 2020-02-09 22:39:01 -05:00
Eric Harding 28e98259f6 Add client sdk example 2020-02-07 22:20:42 -05:00
John Colvin 8e6a17cd0c fix sticky algo behavior in LadderVM 2020-02-07 14:18:10 -06:00
John Colvin cbde36e9e0 Merge branch 'master' into algo-studio-style-tweaks 2020-02-07 13:29:50 -06:00
John Colvin 6fc20924bd SP-282 option for algo favorites button to revert modifications after order entry 2020-02-07 13:01:42 -06:00
John Colvin 381b8064fc SP-285 pnl working qty should only include exchange orders 2020-02-07 12:54:32 -06:00
John Colvin fc1a3ff7dc SP-278 Smaller minimum size for Ladder columns 2020-02-06 13:37:31 -06:00
John Colvin 5fd3d13cfe add spark menu dock/hide commands to synthetic management 2020-02-06 12:27:58 -06:00
John Colvin 1b38822459 add spark menu dock/hide commands to orderbook 2020-02-06 12:27:26 -06:00
John Colvin 8b7ecc430b add spark menu dock/hide commands to fills 2020-02-06 12:26:01 -06:00
John Colvin d9d7a08fd6 add spark menu dock/hide commands to time and sales 2020-02-06 12:25:08 -06:00
John Colvin 2383c00bb9 add spark menu dock/hide commands to mview 2020-02-06 12:23:35 -06:00
John Colvin dcce738d53 SP-281 SparkWindow toolbar can be docked to any side of the window, or hidden 2020-02-06 12:22:37 -06:00
John Colvin 0daa3e0685 add 'is_select_changed' event to ISelectable 2020-02-06 10:17:05 -06:00
Eric Harding 82bc402216 Update client examples 2020-02-05 14:58:46 -05:00
Eric Harding ff70e9694b Set spark version from jenkins environment when available 2020-02-05 10:02:17 -05:00
John Colvin e06daa349c normalize white space in SparkMenuItem.cs 2020-02-04 13:11:06 -06:00
Jake Norbut 525d0eda30 fix typo in notes 2020-01-30 07:39:22 -06:00
Jake Norbut e9e85b44c9 updating release notes 2020-01-30 07:37:51 -06:00
John Colvin 13aa693da1 TimeAndSales status bar 2020-01-29 14:12:33 -06:00
John Colvin f8afa7fc57 SP-280 remove Date portion from Time and Sales time column 2020-01-29 14:00:40 -06:00
John Colvin 2f6a66a2ce DIYGrid DragRect/Selection adorners respond to selection highlight property changes 2020-01-29 13:51:42 -06:00
John Colvin b4674a9120 cache sorting criteria to avoid accessing ColumnBase.SortingDirection on another thread when settings are collected 2020-01-29 13:51:42 -06:00
John Colvin d76eb1153b actually assign TimeAndSalesWindow.thread_id 2020-01-29 13:51:42 -06:00
John Colvin 0934c0014d RowSelectionAdorner listens to ItemContainerGenerator changes (invalidates visual) 2020-01-29 13:51:42 -06:00
John Colvin 19da3e1390 added configurable 'pause updates' menu item to TimeAndSales (hidden by default) 2020-01-29 13:51:42 -06:00
John Colvin 2654b2e446 SP-283 Ladder does not display 6c spreads correctly 2020-01-29 12:43:01 -06:00
John Colvin 61d0ae5257 SP-279 Time and Sales not remembering column width 2020-01-29 10:21:16 -06:00
John Colvin ebea6a8f50 move TimeAndSales classes into their own files/namespace 2020-01-27 16:55:51 -06:00
John Colvin f0b3baaf3f remove unused [TimeAndSales] OrderType column enum 2020-01-27 15:29:48 -06:00
John Colvin 956837d3f3 add ShowOrderTicketAutoLookupTag/SparkCommand 2020-01-27 15:15:25 -06:00
Eric Harding be45938b51 Fix for assert when algo subs to security w/o MD 2020-01-22 14:08:26 -05:00
Eric Harding 663f9ecc52 Merge branch 'master' of bitbucket.org:bluetradesys/spark into client_sdk 2020-01-16 17:23:42 -05:00
Kevin Darby b80f96fa5e Merged in ice_excel (pull request #388)
fix RTD crash for ice

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2020-01-10 22:39:27 +00:00
Kevin Darby b7dea6817c fix RTD crash for ice 2020-01-10 14:43:06 -05:00
Jake Norbut 25445d58d0 refactoring TaS CSV export 2019-12-18 13:48:14 -06:00
Jake Norbut 0b313a8aa8 refactoring Fills CSV export 2019-12-18 13:47:36 -06:00
Jake Norbut 54f954fd4b adding CSV export to order book 2019-12-18 13:47:03 -06:00
Jake Norbut 980f38ba03 using OrderedDictionary to CSVs are output with a predictable column order 2019-12-18 13:46:22 -06:00
Jake Norbut 9eada2724b Having the Command be on the Window rather than the VM; nice thinking John 2019-12-18 10:11:10 -06:00
Jake Norbut b5f53ee918 adding csv_properties to TimeAndSalesVM.Row 2019-12-18 10:10:04 -06:00
Jake Norbut efc175f654 adding export to csv command to Fills window 2019-12-17 10:32:58 -06:00
Jake Norbut 6074afd999 adding csv export command to fills vm 2019-12-17 10:32:15 -06:00
Jake Norbut 219dff4055 adding new export_csv IDialogCommand which just creates a new file 2019-12-17 10:28:57 -06:00
Jake Norbut 9a3b07b622 adding ToCsv IEnumerable ext method in a new util namespace 2019-12-17 10:21:38 -06:00
Eric Harding c6cd5fcf93 services.md 2019-12-15 22:09:20 -05:00
Eric Harding 177b96b453 manual updates 2019-12-13 17:46:22 -05:00
Eric Harding 292f66a774 Add package id for nuget 2019-12-13 17:46:03 -05:00
Eric Harding 8c837f9f64 Expound on service fundamentals / ServiceContext 2019-12-03 13:16:41 -05:00
Eric Harding 85f97ae552 Combine rpc/streams into services.md 2019-12-01 21:50:17 -05:00
Eric Harding 7ca0d10eb0 client/overview.md 2019-11-29 11:49:24 -05:00
Eric Harding ca8aed1f2b client pages outline 2019-11-22 18:49:58 -05:00
John Colvin a5f27d0d35 AlgoStudio - fix typos in name/badge header tooltip 2019-11-22 11:28:59 -06:00
John Colvin ce7ff0d14f MDDaemon - check that synthetic metadata.HasStrategy before adding to map 2019-11-22 11:25:38 -06:00
John Colvin d08571e862 Merge remote-tracking branch 'origin/master' into algo-studio-style-tweaks 2019-11-22 11:25:09 -06:00
Eric Harding b32d63fcda Update api site template 2019-11-20 12:39:59 -05:00
Eric Harding 54d9d5e2f3 Add google analytics 2019-11-19 16:38:30 -05:00
John Colvin a4694462ed flip combobox gradient back (there was a light/medium typo in resource keys) 2019-10-07 16:52:23 -05:00
John Colvin f404b0e936 center align ratio checkbox in QuantityMeasureControl 2019-10-07 16:10:47 -05:00
John Colvin 2df1e94fb3 algo studio pr feedback; clean up visual states 2019-10-07 16:05:18 -05:00
John Colvin acd0d7c343 remove 'Cannot change...while templates present' tooltip 2019-10-07 15:27:51 -05:00
John Colvin 1e8a7f09dd make generic algo template child order padding consistent 2019-10-07 14:21:24 -05:00
John Colvin b65d50a9c7 AlgoStudio uses Adorner to display name/abbrev. text prompts 2019-10-07 11:03:33 -05:00
John Colvin 909b4b6cfe AlgoStudio - put badge/name/abbrev uis on a single row 2019-10-07 11:03:33 -05:00
John Colvin 2fbc238ecc AlgoDefinitionVM - update 'is_dirty' when algo_root changes 2019-10-07 11:03:33 -05:00
John Colvin 7c73024919 ObservantAsyncCommand can_execute was not actually changing in response to parameter 2019-10-07 11:03:33 -05:00
John Colvin 0d85aa1644 AlgoStudio - remove old/erroneous 'algo templates' code 2019-10-07 11:03:33 -05:00
John Colvin df16acc4af AlgoStudio - better 'Clone' icon 2019-10-07 11:03:28 -05:00
John Colvin 7ad0c23704 AlgoStudioView - reduce save/revert button width to 90 2019-10-07 11:02:38 -05:00
John Colvin e85e0322e3 AlgoStudio uses consistent vertical padding between params; get rid of cartoonish circle in expander button 2019-10-07 11:02:38 -05:00
John Colvin 1a6a31c857 AlgoStudio - (Glbx)Iceberg/Splitter help button margin is consistent with other algos 2019-10-07 11:02:38 -05:00
John Colvin 65b09c6802 [non-functional] white space changes in AlgoContentTemplateSelector.xaml 2019-10-07 11:02:38 -05:00
John Colvin 148dae32d5 AlgoStudio - remove algo name from buttons, dock save/revert on bottom 2019-10-07 11:02:38 -05:00
John Colvin 9c116fa3d6 AlgoStudio - hide vertical scrollbar 2019-10-07 11:02:38 -05:00
Pedro Pinto 2eba014203 Implement server side of stop limit orders. 2019-07-26 17:36:07 -04:00
Pedro Pinto 37d022251e Fixed another bug in stop order handling. 2019-07-11 09:48:09 -04:00
Pedro Pinto 04cc1da4d9 Hopefully fixed stop order updates. 2019-07-10 17:10:35 -04:00
Jake Norbut 56bba90fdb ladder now determines badge price level based on new ui_price() 2019-07-10 13:11:57 -05:00
Jake Norbut a95841b0e9 OB price filter to hook up to new ui_price() rather than price() 2019-07-10 13:10:32 -05:00
Jake Norbut 9c6be9e87c adding new ui_price ext method that takes into account the status of and type of order (for stop and stop limits) 2019-07-10 13:09:45 -05:00
Pedro Pinto 7c830786ef Fixed issue in stop limit trigger detection 2019-07-10 11:05:00 -04:00
Jake Norbut 9eb9047249 shh...adding M4D secret key 2019-07-09 15:04:56 -05:00
Jake Norbut f7847b7767 adding new M4D cert ilink 2019-07-09 14:13:09 -05:00
Pedro Pinto ae720e4966 Merge branch 'master' into SP-69-fix-stop-and-stop-limit-orders 2019-07-09 12:52:49 -04:00
Pedro Pinto 3b77bc99b6 Fixed update of stop orders. 2019-06-27 13:31:05 -04:00
Jake Norbut b69a0c245e Revert "change price() to report stop/limit price when stop/limit has not yet been triggered"
This reverts commit 14bcef4c0e.
2019-06-26 14:49:26 -05:00
Jake Norbut 14bcef4c0e change price() to report stop/limit price when stop/limit has not yet been triggered 2019-06-26 14:45:27 -05:00
Pedro Pinto a23fe74cdc Re-enabled stop orders 2019-06-26 15:38:06 -04:00
Jake Norbut 7b48f71dec adding stop/limit order modification 2019-06-26 13:34:03 -05:00
Jake Norbut c334c1f636 making use of new has been triggered definition when determining where to draw badge 2019-06-26 13:34:03 -05:00
Jake Norbut 8572ff273f adding helper for determining if stop/limit has been triggered 2019-06-26 13:34:03 -05:00
Pedro Pinto e6ddc71c75 Merged with master 2019-06-26 10:43:29 -04:00
Pedro Pinto 81a0f9193d Support for server side stop orders is code complete. 2019-06-21 17:41:02 -04:00
317 changed files with 6379 additions and 3339 deletions

No files matched your search

+1 -1
View File
@@ -34,4 +34,4 @@ trace_data/
/doc/user/public/*
.ionide/*
/.ignore
CMakeDeps.cmake
/build
+8 -3
View File
@@ -5,7 +5,9 @@
"includePath": [
"/usr/include",
"/usr/local/include",
"${workspaceRoot}"
"${workspaceRoot}",
"${workspaceFolder}/src",
"${workspaceRoot}/externals/linux/include"
],
"defines": [],
"intelliSenseMode": "clang-x64",
@@ -13,13 +15,16 @@
"path": [
"/usr/include",
"/usr/local/include",
"${workspaceRoot}"
"${workspaceRoot}",
"${workspaceFolder}/src",
"${workspaceRoot}/externals/linux/include"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"cStandard": "c11",
"cppStandard": "c++17"
"cppStandard": "c++17",
"compileCommands": "${workspaceFolder}/build/ninja_debug/compile_commands.json"
},
{
"name": "Windows",
+6 -5
View File
@@ -46,7 +46,7 @@
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
},
{
"name": "(gdb) Launch",
"type": "cppdbg",
@@ -345,11 +345,12 @@
"name": ".NET Core Remote Launch",
"type": "coreclr",
"request": "launch",
"program": "sparkcmd",
"program": "/usr/bin/dotnet",
"args": [
"sparkcmd.dll",
"test",
"test_join_create_and_takeout_market"
],
"env": {"LD_LIBRARY_PATH" : "."},
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server",
"pipeTransport": {
@@ -360,11 +361,11 @@
"debuggerPath": "/home/bts/vsdbg/vsdbg",
"pipeCwd": "${workspaceRoot}",
"quoteArgs": true
},
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
},
"justMyCode": false
},
},
]
}
+19 -10
View File
@@ -14,24 +14,21 @@
"focus": false,
"panel": "shared"
},
"group": {
"kind": "build",
"isDefault": true
}
"group": "build"
},
{
"label": "server_build",
"label": "server_build",
"type": "shell",
"command": ".\\tools\\buildsparkd.cmd",
"problemMatcher": {
"base": "$gcc",
"problemMatcher": {
"base": "$gcc",
"owner": "cpp",
"fileLocation": [
"relative",
"${workspaceFolder}"
"${workspaceFolder}"
],
"pattern": {
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
"file": 8,
"line": 9,
"column": 10,
@@ -49,6 +46,18 @@
"kind": "build",
"isDefault": true
}
}
},
{
"label": "linux build (Debug)",
"type": "shell",
"command": "cd build/ninja_debug; time ninja",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
}
]
}
-11
View File
@@ -48,7 +48,6 @@ else ()
add_subdirectory(doc/user)
add_subdirectory(src/sdk)
add_subdirectory(src/data_files)
add_subdirectory(src/client)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
@@ -60,17 +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
COMMENT "Creating server runtime layout"
DEPENDS sparkd data_files_dir spark_client_dir
DEPENDS sparkd data_files_dir)
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
add_custom_command(TARGET server_layout POST_BUILD
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.1/linux-x64/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
else()
add_custom_command(TARGET server_layout POST_BUILD
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.1/linux-x64/publish/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
endif()
endif()
+1 -1
View File
@@ -1,6 +1,6 @@
+++
title="Client API"
template="client.html"
redirect_to = "client/getting-started/overview/"
sort_by="weight"
+++
@@ -1,6 +1,5 @@
+++
title="Getting Started"
template="client.html"
sort_by="weight"
weight = 1
+++
@@ -1,9 +1,25 @@
+++
title = "Install Prerequisites"
template="client.html"
weight = 1
weight = 10
+++
## Coming Soon
# C#/.NET
We target [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) so you can use .NET Framework 4.6.1+ or .NET Core 2.0+. Simply add a reference to the [Spark Nuget package](https://www.nuget.org/packages/spark_sdk/) to get started.
```
dotnet new console -lang c#
dotnet add package spark_sdk
dotnet build
```
# C++
The easiest way to get started with C++ is to use the Spark Server SDK to develop your application. Follow the instructions to [download and install the Spark Server SDK](/server/getting-started/install) and open the client_sdk sample project. If you want to run your C++ Client SDK application on Windows [contact us](mailto://api@bluetradesys.com) to get the required headers and binaries.
# JavaScript
We use JavaScript internally with the Spark Client API over a websocket connection but the documentation and packaging isn't ready yet. If this is something you're interested in get in touch with us at [api@bluetradesys.com](mailto:api@bluetradesys.com).
* Install .net core
@@ -0,0 +1,31 @@
+++
title = "Overview"
weight = 1
+++
The Spark Client SDK is a high level command and control API which can be used to build scripts or integrate Spark into your existing ecosystem. The API is divided into several independent **"services"** responsible for different aspects of the system. A Service can define both [remote procedure call (RPC)](/client/getting-started/services#rpc) and incremental [streaming](/client/getting-started/services#streams) data.
The [Spark Desktop](https://www.bluetradingsystems.com/bts-spark/) is built on top of this API so you can be assured that it is complete and well tested.
Spark uses [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) as an interface definition language similar to [GRPC](https://grpc.io) but backed by our custom high-performance TCP protocol. We currently support C#/.NET and C++ <!--and Javascript--> but the architecture is designed for easy extension so contact us about your specific business needs.
## Orders
Use the [OrderService](/client/services/orderservice) to send and manage orders including native exchange orders, spreader orders, and custom Algo orders created using the [Spark Server SDK](https://api.bluetradesys.com/server/).
## Spreader
Use the [SyntheticSecurityService](/client/services/syntheticsecurityservice) to create and manage synthetic spreads. Once created these spreads show up in the security and market data feeds just like exchange listed spreads.
## Position
Use the [PositionService](/client/services/positionservice) to get your position and P&L reports or add fills manually to keep the Spark system up to date with an external system.
## Administration
Use the [AdminService](/client/services/adminservice) to manage users and control limits.
## Market Data
For .NET subscribe to market data though the high level MarketDataClient interface. From C++ or JavaScript use the MarketDataRelay service directly.
@@ -0,0 +1,127 @@
+++
title = "Services"
weight = 20
+++
The Spark Client SDK uses [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) to define the interface for each service in a language independent manner. Services have both functions which return a single result and streams of data which return values continuously while subscribed. This page describes the mapping from the Protocol Buffer definition to the concrete definition in each supported language.
# ServiceContext
To connect your client to the Spark Server you need to get or create a `ServiceContext` instance. A `ServiceContext` represents a connection to the Spark Server and a background thread for handling messages on that connection. Generally there should be a single `ServiceContext` per process. All of the methods on the `ServiceContext` are safe thread safe.
Construct a ServiceContext by calling the default constructor. The ServiceContext will find the Spark Server by loading the server configuration over an HTTP connection. To determine the URL it will search the following locations and use the first one that is present.
* A command line argument `-configserver <url>`
* An environment variable `BTSCONFIG`
* `%APPDATA%/bts/conf/spark.conf` (Windows)
* /sparkdata/conf/spark.conf (Linux)
* $HOME/sparkdata/conf/spark.conf (Linux)
If your application is running on a system with the BTS Client installed this will probably work without any further configuration. If you plan to run your client application on a separate machine we recommend setting the `BTSCONFIG` environment variable.
<div class="info">
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
If you are using the Spark Server SDK to run a local copy of the Spark server set <p><strong>BTSCONFIG = http://localhost:9000/config/</strong></p>
</div>
To get a proxy for a given service call ServiceContext.locate&lt;T>("qualifier") where T is the type of the service and "qualifier" is an optional qualifier used to disambiguate between 2 instances of the same service. The locate call cannot fail but it may return a disconnected proxy. When you make a call on the proxy you should check the call result to see if the method call succeeded.
# RPC
Here is the OrderService reduced to a single method definition. A RPC method always takes exactly 1 argument and returns 1 value. In this example the `create_order` call takes a `Create` instance and returns a `Response`.
```C
service OrderService {
rpc create_order(Create) returns (Response);
...
}
```
The `Create` and `Response` types are also defined as protocol buffer messages. Each field can either be a primitive type another message or a list or map.
```C
message Create {
OrderDefinition new_order = 1;
double price = 2;
uint32 quantity = 3;
}
message Response {
uint32 order_key = 1;
OrderError error_code = 2;
string msg = 3;
}
```
Each RPC call generates both synchronous (blocking) and asynchronous variants. In C# the asynchronous calls have the `_async` suffix and return a `Task`. In C++ the call is overloaded to either return a value synchronously or accept a callback function to be called on the ServiceContext thread when the call completes. <!-- In Javascript only async methods returning a Promise are generated because it never makes sense to make a blocking call in the browser. -->
To call the resulting service locate a proxy using the [ServiceContext](#servicecontext) and then call `cancel_order` or `cancel_order_async`. If you are calling the Spark API from a GUI application you should always prefer the `_async` version of the calls.
### C# example
```c#
var ctx = new ServiceContext();
var orders = ctx.locate<OrderService>();
var response =
await orders.create_order_async(
new Create { price = 2.25, quantity = 42 });
if (response.success) {
Console.WriteLine($"success: {response.value}");
}
else {
Console.WriteLine($"Error: {response.error_message}");
}
```
### C++ example
```cpp
auto ctx = ServiceContext{};
auto orders = ctx.locate<OrderService>();
Create c;
c.set_price(2.25);
c.set_quantity(42);
// Instead of providing an _async method C++ uses overloading.
// To make a syncronous call simply omit the callback
orders.create_order(c, [=](const Result<Response>& response) {
if(response.success()) {
std::cout << "success.";
}
else {
std::cout << "error: " << response.error_message();
}
});
```
# Streams
Streams allow you to subscribe to live updating data. Each stream represents a changing collection of protocol buffer objects. Similar to a database row each item has an `id` which uniquely identifies it within the collection. Updates from the Spark server can add, remove or replace entries in the collection. Since only the changed items are sent over the wire the stream can remain responsive even when it represents a large collection.
Streams are implemented using the language bindings for [ReativeX](http://reactivex.io/) which provide utility functions to drill down to the specific data your application needs. In C# we provide both mutable and immutable versions of the generated data types (C++ users can rely on `const`).
When defined in a proto file streams use the `stream` keyword on the result type.
```C
service OrderService {
...
rpc fills (FillsSub) returns (stream Fill);
}
```
### C# example
```c#
var sc = new ServiceContext();
var orders = sc.locate<OrderService>();
var fills_observable = orders.subscribei_fills();
var unsub = fills_observable.Subscribe(update => {
Console.WriteLine(
"Added {0} items, Removed {1} items, Updated {2} items. Total items {3}",
update.added.Count(),
update.removed.Count(),
update.changes.Count(),
update.all.Count());
});
Thread.Sleep(TimeSpan.FromMinutes(5));
unsub.Dispose();
```
### C++ Example
```cpp
```
+6
View File
@@ -0,0 +1,6 @@
+++
title="Samples"
sort_by="weight"
weight = 30
+++
@@ -0,0 +1,3 @@
+++
title = "AdminService"
+++
@@ -0,0 +1,3 @@
+++
title = "OrderService"
+++
@@ -0,0 +1,3 @@
+++
title = "PositionService"
+++
@@ -0,0 +1,3 @@
+++
title = "SyntheticService"
+++
@@ -0,0 +1,6 @@
+++
title="Services"
#sort_by="weight"
weight = 10
+++
@@ -0,0 +1,3 @@
+++
title = "Market Data"
+++
-1
View File
@@ -1,6 +1,5 @@
+++
title="Algo API"
template="documentation.html"
redirect_to = "server/getting-started/overview/"
sort_by="weight"
+++
@@ -1,6 +1,5 @@
+++
title="Getting Started"
template="documentation.html"
sort_by="weight"
weight = 1
+++
@@ -5,7 +5,7 @@ weight = 2
## Request Access 🔑
To start using the Server SDK you will need to request access though our sales department. Please email <a href="mailto:info@bluetradesys.com">info@bluetradesys.com</a> or call <a href="tel:919-913-0850">(919) 913-0850</a> and we will provide download credentials.
To start using the Server SDK you will need to request access though our sales department. Please email <a href="mailto:api@bluetradesys.com">api@bluetradesys.com</a> or call <a href="tel:919-913-0850">(919) 913-0850</a> and we will provide download credentials.
## Download
-1
View File
@@ -1,6 +1,5 @@
+++
title="Reference"
template="documentation.html"
sort_by="weight"
weight = 2
+++
+6
View File
@@ -6,3 +6,9 @@ weight = 40
## I need to make a call that might take a few seconds. What is the best way to do this?
Create a background thread in [do_before_launch](/server/ref/initialization#before-launch) or [do_launch](/server/ref/initialization#launch-launch-attached) to do the work. Use [on_timer](/server/ref/subscriptions#timer) to poll until the result is available.
## I don't see the header files in the .zip file. Where are they?
The SDK headers are baked into the docker image along with the build tools. Once you've loaded the image with `docker load < spark_sdk.img` you can run this command to copy them out to your local file system `docker run --rm -it -v $PWD:/out spark_sdk /bin/tar -cvf /out/headers.tar /sdk/include` This will copy the headers to the current directory in headers.tar. We still recommend doing the actual build inside the docker environment but having a copy of the headers can be useful for setting up your development environment.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

-1
View File
@@ -1,6 +1,5 @@
+++
title="Samples"
template="documentation.html"
sort_by="weight"
weight = 3
+++
-31
View File
@@ -1,31 +0,0 @@
{% extends "index.html" %}
{% block extra_nav_class %}{% endblock extra_nav_class %}
{% block extra_content_class %}content--reversed{% endblock extra_content_class %}
{% block content %}
{% set section = get_section(path="client/_index.md") %}
<div class="documentation">
<aside class="documentation__sidebar">
<ul>
{% for p in section.subsections %}
{% set subsection = get_section(path=p) %}
<li>
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
<ul>
{% for page in subsection.pages %}
<li class="{% if current_path == page.path %}active{% endif %}">
<a href="{{page.permalink}}">{{page.title}}</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</aside>
<div class="documentation__content">
{% block doc_content %}
{% endblock doc_content %}
</div>
</div>
{% endblock content %}
-36
View File
@@ -1,36 +0,0 @@
{% extends "index.html" %}
{% block extra_nav_class %}{% endblock extra_nav_class %}
{% block extra_content_class %}content--reversed{% endblock extra_content_class %}
{% block content %}
{%if page.extra.section %}
{% set section = get_section(path="{{page.extra.section}}/_index.md") %}
{% else %}
{% set section = get_section(path="server/_index.md") %}
{% endif %}
<div class="documentation">
<aside class="documentation__sidebar">
<ul>
{% for p in section.subsections %}
{% set subsection = get_section(path=p) %}
<li>
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
<ul>
{% for page in subsection.pages %}
<li class="{% if current_path == page.path %}active{% endif %}">
<a href="{{page.permalink}}">{{page.title}}</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</aside>
<div class="documentation__content">
{% block doc_content %}
{% endblock doc_content %}
{% block next_link %} {% endblock next_link %}
</div>
</div>
{% endblock content %}
+15 -15
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en-gb">
<html lang="en-us">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -14,6 +14,7 @@
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/>
<link rel="icon" href="{{ get_url(path="favicon.ico") }}">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<script> dataLayer = [{ 'visitorType': 'geek' }]; </script>
</head>
<body>
@@ -36,13 +37,13 @@
</nav>
</header>
<div class="content {% block extra_content_class %}{% endblock extra_content_class %}">
<div class="content content--reversed">
{% block content %}
<div class="hero">
<h1>Spark SDK</h1>
<p class="hero__tagline">
Spark consists of a client UI
(<a class="white" href="https://www.bluetradingsystems.com/bts-spark#ladder-algo-screen">Spark Client</a>),
(<a href="https://www.bluetradingsystems.com/bts-spark#ladder-algo-screen">Spark Client</a>),
a high-performance professional futures front end, connected via our proprietary protocol over TCP to our Spark Server, which in turn connects to execution venues via their native protocols.
Use the Spark SDK to extend Spark Server with your custom Algos, then use the Client SDK to launch and control them.
</p>
@@ -64,7 +65,7 @@
<a href="{{ get_url(path="@/server/_index.md") }}" class="button">Get started</a>
</div>
<div class="selling-point disabled">
<div class="selling-point">
<h2>
Client SDK
<i class="fa fa-gamepad" aria-hidden="true"></i>
@@ -72,19 +73,9 @@
<p>
The Spark client SDK allows you to integrate spark with your existing ecosystem. The Spark Client is built entirely on this simple, powerful API.
</p>
<a href="" class="button" disabled>Get started</a>
<a href="{{ get_url(path="@/client/_index.md") }}" class="button">Get started</a>
</div>
<!-- <div class="selling-point disabled">
<h2>
Microsoft Excel
<i class="fa fa-table" aria-hidden="true"></i>
</h2>
<p>
More of your business is built on excel than probably should be. Continue this worrying trend with the Spark Excel API.
</p>
<a href="" class="button" disabled>Get started</a>
</div> -->
</div>
</div>
@@ -94,6 +85,15 @@
© Blue Trading Systems
</footer>
<script>
if (location.hostname == "api.bluetradesys.com") {
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-56FHG8R');
}
</script>
<script type="text/javascript" src="{{ get_url(path="elasticlunr.min.js") }}"></script>
<script type="text/javascript" src="{{ get_url(path="search_index.en.js") }}"></script>
<script type="text/javascript" src="{{ get_url(path="search.js") }}"></script>
+36 -8
View File
@@ -1,13 +1,39 @@
{% extends "documentation.html" %}
{% extends "index.html" %}
{# this has access to the "page" variable but documentation.html does not #}
{% block title %}{{ page.title }} | {{ super() }} {% endblock title %}
{% block doc_content %}
<h1>{{page.title}}</h1>
{{page.content | safe}}
{% endblock doc_content %}
{% block next_link %}
<p>
{% endblock next_link %}
{% block content %}
{% set section = get_section(path=page.ancestors[1]) %}
{#
{#<div>{{ section.title }} </div>#}
<div class="documentation">
<aside class="documentation__sidebar">
<ul>
{% for p in section.subsections %}
{% set subsection = get_section(path=p) %}
<li>
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
<ul>
{% for page in subsection.pages %}
<li class="{% if current_path == page.path %}active{% endif %}">
<a href="{{page.permalink}}">{{page.title}}</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</aside>
<div class="documentation__content">
<h1>{{page.title}}</h1>
{{page.content | safe}}
{# next/pre links #}
<p>
{% if page.lighter %}
<a href="{{ page.lighter.permalink }}">
<i class="fa fa-chevron-left fa-fw" aria-hidden="true"></i>
@@ -21,5 +47,7 @@
<i class="fa fa-chevron-right fa-fw" aria-hidden="true"></i>
</a>
{% endif %}
</p>
{% endblock next_link %}
</p>
</div>
</div>
{% endblock content %}
+1 -1
View File
@@ -1 +1 @@
set(sources /home/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
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)
+9
View File
@@ -4,6 +4,15 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.15.3
### Bug Fixes
- Fixed a bug where some CME currency spreads exhibited incorrect tick values
## 2.15.2
### User Visible Changes
- Excel RTD plugin can now receive ICE market data
## 2.15.1
### Bug Fixes
- Fixed a FIX log parsing issue that resulted in exchange gateway logon problems
## 2.15.0
### User Visible Changes
- Added ability to hide Ladder status messages via Ladder Settings -> Left Panel tab -> Show/Hide groupbox -> Status Message
-95
View File
@@ -1,95 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29609.76
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sparkcmd", "src\client\sparkcmd\sparkcmd.csproj", "{387566C6-C6F6-412C-A30C-9855BBCB4816}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "build\ZERO_CHECK.vcxproj", "{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
MinSizeRel|x64 = MinSizeRel|x64
Release|x64 = Release|x64
RelWithDebInfo|x64 = RelWithDebInfo|x64
EndGlobalSection
# The dotnet (core) build cli does not support c++ projects.
# Until this changes, c++ projects/solutions must be built with msbuild.
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.ActiveCfg = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.Build.0 = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.Debug|x64.ActiveCfg = Debug|x64
# {8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.Debug|x64.Build.0 = Debug|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
# {8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.Release|x64.ActiveCfg = Release|x64
# {8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.Release|x64.Build.0 = Release|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
# {8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.ActiveCfg = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.Build.0 = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.Build.0 = Release|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.Debug|x64.ActiveCfg = Debug|x64
# {23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.Debug|x64.Build.0 = Debug|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
# {23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.Release|x64.ActiveCfg = Release|x64
# {23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.Release|x64.Build.0 = Release|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
# {23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.ActiveCfg = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.Build.0 = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {422D1620-0166-4C26-9A81-CA8A2EB1AB58}
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal
+171 -39
View File
@@ -1,10 +1,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29609.76
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2024
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{3335AAF0-489D-359D-8305-9B7A44E2C760}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
EndProject
@@ -12,81 +12,213 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sparkcmd", "src\client\spar
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "build\ZERO_CHECK.vcxproj", "{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "build\ZERO_CHECK.vcxproj", "{2256015B-E7D5-3614-8F39-814CA68A4444}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
ProjectSection(ProjectDependencies) = postProject
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4} = {8C03E81A-6981-3441-BF6C-6ECE7F0482E4}
{3335AAF0-489D-359D-8305-9B7A44E2C760} = {3335AAF0-489D-359D-8305-9B7A44E2C760}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_excel", "src\client\spark_excel\spark_excel.csproj", "{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
MinSizeRel|Any CPU = MinSizeRel|Any CPU
MinSizeRel|x64 = MinSizeRel|x64
MinSizeRel|x86 = MinSizeRel|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
RelWithDebInfo|Any CPU = RelWithDebInfo|Any CPU
RelWithDebInfo|x64 = RelWithDebInfo|x64
RelWithDebInfo|x86 = RelWithDebInfo|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|Any CPU.ActiveCfg = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.ActiveCfg = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.Build.0 = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x86.ActiveCfg = Debug|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|Any CPU.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x86.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x86.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|Any CPU.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x86.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.Debug|x64.ActiveCfg = Debug|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.Debug|x64.Build.0 = Debug|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.Release|x64.ActiveCfg = Release|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.Release|x64.Build.0 = Release|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{8C03E81A-6981-3441-BF6C-6ECE7F0482E4}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.Build.0 = Release|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|Any CPU.ActiveCfg = Debug|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x64.ActiveCfg = Debug|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x64.Build.0 = Debug|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x86.ActiveCfg = Debug|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|Any CPU.ActiveCfg = Release|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x64.ActiveCfg = Release|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x64.Build.0 = Release|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x86.ActiveCfg = Release|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|Any CPU.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x86.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|Any CPU.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x86.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x86.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|Any CPU.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x86.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x86.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|Any CPU.ActiveCfg = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.ActiveCfg = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.Build.0 = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x86.ActiveCfg = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|Any CPU.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x86.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x86.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|Any CPU.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x86.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.Build.0 = Release|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.Debug|x64.ActiveCfg = Debug|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.Debug|x64.Build.0 = Debug|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.Release|x64.ActiveCfg = Release|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.Release|x64.Build.0 = Release|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{23CC41FA-C9CE-3B77-BED8-71C058B05C4F}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.ActiveCfg = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.Build.0 = Debug|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x86.Build.0 = Release|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|Any CPU.ActiveCfg = Debug|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x64.ActiveCfg = Debug|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x64.Build.0 = Debug|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x86.ActiveCfg = Debug|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|Any CPU.ActiveCfg = Release|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x64.ActiveCfg = Release|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x64.Build.0 = Release|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x86.ActiveCfg = Release|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|Any CPU.ActiveCfg = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.ActiveCfg = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.Build.0 = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x86.ActiveCfg = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|Any CPU.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x86.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x86.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|Any CPU.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x86.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.Build.0 = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x86.Build.0 = Release|x64
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.ActiveCfg = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.Build.0 = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x86.ActiveCfg = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x86.Build.0 = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x86.Build.0 = Debug|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|Any CPU.Build.0 = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.ActiveCfg = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.Build.0 = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x86.ActiveCfg = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x86.Build.0 = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|Any CPU.ActiveCfg = Debug|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x64.ActiveCfg = Debug|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x64.Build.0 = Debug|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x86.ActiveCfg = Debug|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|Any CPU.Build.0 = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x64.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x64.Build.0 = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x86.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x86.Build.0 = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|Any CPU.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x64.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x64.Build.0 = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x86.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.Build.0 = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
-19
View File
@@ -1,19 +0,0 @@
cmake_minimum_required(VERSION 3.5)
project(spark_client)
include(${CMAKE_SOURCE_DIR}/tools/safe_dep_glob.cmake)
file(GLOB_RECURSE spark_client_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cs)
file(GLOB_RECURSE spark_client_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.csproj)
file(GLOB_RECURSE spark_ui_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.cs)
file(GLOB_RECURSE spark_ui_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.csproj)
list(REMOVE_ITEM spark_ui_sources ${spark_client_sources} )
list(REMOVE_ITEM spark_ui_csproj ${spark_client_csproj} )
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/../
COMMAND /bin/bash ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh
ARGS ${CMAKE_BUILD_TYPE} ${CMAKE_BINARY_DIR}
COMMENT "Building sparkcmd in netcore3"
DEPENDS libspark ${spark_client_sources} ${spark_client_csproj} ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh)
add_custom_target(spark_client_dir ALL DEPENDS ${CMAKE_BINARY_DIR}/../)
@@ -5,7 +5,6 @@
<SparkRoot>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)\..\..))</SparkRoot>
<SourceRoot>$([System.IO.Path]::GetFullPath($(SparkRoot)\src))</SourceRoot>
<Externals>$(SparkRoot)\externals\windows</Externals>
<Externals Condition=" '$(OS)' != 'Windows_NT' ">$(SparkRoot)/externals/linux</Externals>
<DepotName>$([System.IO.Path]::GetFileName($([System.IO.Path]::GetFullPath($(SparkRoot)))))</DepotName>
<ErrorReport>None</ErrorReport>
<Prefer32Bit>False</Prefer32Bit>
@@ -20,25 +19,20 @@
<OutputPath>$(BaseOutputPath)\bin</OutputPath>
<BaseIntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</IntermediateOutputPath>
<BaseIntermediateOutputPath Condition=" '$(OS)' != 'Windows_NT' ">$(BaseOutputPath)\linuxobj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<IntermediateOutputPath Condition=" '$(OS)' != 'Windows_NT' ">$(BaseOutputPath)\linuxobj\$(MSBuildProjectName)</IntermediateOutputPath>
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
</PropertyGroup>
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
<ItemGroup>
<AvailableItemName Include="Proto2">
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto2</Targets>
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto2Linux</Targets>
<Targets>CompileProto3</Targets>
</AvailableItemName>
<AvailableItemName Include="Proto3">
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto3</Targets>
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto3Linux</Targets>
<Targets>CompileProto3</Targets>
</AvailableItemName>
<AvailableItemName Include="Proto3Excel">
<Targets Condition=" '$(OS)' == 'Windows_NT' ">CompileProto3Excel</Targets>
<Targets>CompileProto3Excel</Targets>
</AvailableItemName>
</ItemGroup>
@@ -49,14 +43,6 @@
<protogen>$(Externals)\bin\protogen.exe</protogen>
</PropertyGroup>
<PropertyGroup Label="ProtoBuf" Condition=" '$(OS)' != 'Windows_NT' ">
<ProtocPluginBase>$(SparkRoot)/src/lib/api/service_compiler</ProtocPluginBase>
<ProtoInclude>$(Externals)/include</ProtoInclude>
<protoc>$(Externals)/bin/protoc</protoc>
<protoc_rename>$(Externals)/bin/lowercase_files.sh</protoc_rename>
<protogen>$(Externals)/bin/protogen</protogen>
</PropertyGroup>
<ItemDefinitionGroup>
<Proto2>
</Proto2>
@@ -3,7 +3,7 @@
<Target Name="CompileProto2"
Condition="'@(Proto2)'!='' AND '$(OS)' == 'Windows_NT' "
Condition="'@(Proto2)'!=''"
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
Outputs="$(GenDir)\proto2\%(Proto2.filename).cs"
AfterTargets="PreBuildEvent">
@@ -28,7 +28,7 @@
passes wipes out the additional compile includes that we add.
-->
<Target Name="CompileProto3" AfterTargets="GetAssemblyVersion"
Condition="'@(Proto3)'!='' AND '$(OS)' == 'Windows_NT' "
Condition="'@(Proto3)'!=''"
Inputs="$(protoc);
%(Proto3.Identity);$(ProtocPluginBase)\plugin.py;
$(ProtocPluginBase)\templates\c#\client.pb.cs;
@@ -58,7 +58,7 @@
</Target>
<Target Name="CompileProto3Excel" AfterTargets="GetAssemblyVersion"
Condition="'@(Proto3Excel)'!='' AND '$(OS)' == 'Windows_NT' "
Condition="'@(Proto3Excel)'!=''"
Inputs="$(protoc);
%(Proto3Excel.Identity);$(ProtocPluginBase)\plugin.py;
$(ProtocPluginBase)\templates\excel\excel.pb.cs;"
@@ -82,74 +82,12 @@
</ItemGroup>
</Target>
<Target Name="CompileProto2Linux"
Condition="'@(Proto2)'!='' AND '$(OS)' != 'Windows_NT' "
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
Outputs="$(GenDir)/proto2/%(Proto2.filename).cs"
AfterTargets="PreBuildEvent">
<PropertyGroup>
<SourceRelativeFile>$([System.String]::Copy('%(proto2.fullpath)').Replace($(SourceRoot), ''))</SourceRelativeFile>
<SourceRelativeDir>$([System.IO.Path]::GetDirectoryName($(SourceRelativeFile)))</SourceRelativeDir>
<WorkingDirectory>$(GenDir)/proto2</WorkingDirectory>
</PropertyGroup>
<MakeDir Directories="$(WorkingDirectory)" />
<Copy SourceFiles="%(Proto2.FullPath)" DestinationFolder="$(WorkingDirectory)" SkipUnchangedFiles="True" UseHardLinksIfPossible="True" />
<Error Text="protogen.exe does not exist. If you forgot to clone with externals try: git submodule update --init --recursive" Condition="!Exists($(protogen))" />
<Message Importance="High" Text="Doing Proto2 %(proto2.filename)" />
<Exec Command="$(Protogen) %(Proto2.filename)%(proto2.extension) --csharp_out=. -I. -I$(SourceRoot) -I$(Externals)/include"
WorkingDirectory="$(WorkingDirectory)" />
<ItemGroup>
<Compile Include="$(WorkingDirectory)/%(Proto2.filename).cs" />
</ItemGroup>
</Target>
<Target Name="CompileProto3Linux" AfterTargets="GetAssemblyVersion"
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' "
Inputs="$(protoc);
%(Proto3.Identity);$(ProtocPluginBase)/plugin.py;
$(ProtocPluginBase)/templates/c#/client.pb.cs;
$(ProtocPluginBase)/templates/c#/server.pb.cs;
$(ProtocPluginBase)/templates/c#/immutable.pb.cs;"
Outputs="%(Proto3.OutCS);%(Proto3.OutCSServer);%(Proto3.OutCSClient);%(Proto3.OutCSImmutable)">
<MakeDir Directories="%(Proto3.OutBase)" />
<MakeDir Directories="$(IntermediateOutputPath)/gen" />
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
<Exec Command="
PYTHONPATH=$(Externals)/python $(protoc) \
%(Proto3.FullPath) \
--error_format=gcc \
--proto_path=$(protoinc):$(SourceRoot) \
--plugin=&apos;protoc-gen-service=$(ProtocPluginBase)/plugin.py&apos; \
--service_out=lang=c#,strip_out_path=1:%(Proto3.OutBase) \
--csharp_out=%(CompileOptions)%(Proto3.OutBase)"
WorkingDirectory="%(relativedir)" />
<!-- <Exec Command="$(protoc_rename) %(Proto3.OutBase)" /> -->
<ItemGroup>
<Compile Include="%(Proto3.OutCS)" />
<Compile Include="%(Proto3.OutCSClient)" />
<Compile Include="%(Proto3.OutCSServer)" />
<Compile Include="%(Proto3.OutCSImmutable)" />
</ItemGroup>
</Target>
<!-- <Target Name="RenameProto3Linux" AfterTargets="CompileProto3Linux"
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' ">
<Move SourceFiles="$(OutBase)\Apicore.cs" DestinationFiles="$(OutBase)\apicore.cs" />
<Move SourceFiles="$(OutBase)\Apioptions.cs" DestinationFiles="$(OutBase)\apioptions.cs" />
<Move SourceFiles="$(OutBase)\Co2.cs" DestinationFiles="$(OutBase)\co2.cs" />
</Target> -->
<Target Name="AfterClean" AfterTargets="Clean">
<Delete Files="%(Proto3.OutCS)" />
<Delete Files="%(Proto3.OutCSClient)" />
<Delete Files="%(Proto3.OutCSServer)" />
<Delete Files="%(Proto3.OutCSImmutable)" />
<Delete Files="$(GenDir)/proto2/*" />
<Delete Files="$(GenDir)\proto2\*" />
<Delete Files="%(Proto3Excel.OutCSExcel)" />
</Target>
-14
View File
@@ -1,14 +0,0 @@
#!/bin/bash
#$1 = Debug / Release (CMAKE_BUILD_TYPE), $2 = CMAKE_BINARY_DIR
cd "$(dirname "$0")"
echo $PWD dotnet build -c $1 ./sparkcmd/
dotnet build -c $1 -o $2/../$1/bin/netcoreapp3.1/linux-x64 ./sparkcmd/
if [ "$1" == "Release" ]
then
dotnet publish -c $1 -o $2/../$1/bin/netcoreapp3.1/linux-x64 ./sparkcmd/
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.1/linux-x64/publish
fi
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.1/linux-x64
+1 -1
View File
@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:mbts.ui.shell"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:converters="clr-namespace:mbts.ui.converters">
<Application.Resources>
<ResourceDictionary>
+23 -4
View File
@@ -3,11 +3,13 @@ using bts.api.core;
using bts.api.orderman;
using bts.spark;
using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Windows;
@@ -19,20 +21,26 @@ namespace bts.shell {
readonly IBus _bus;
readonly OrdersModel _model;
readonly ErrorReporter _error_reporter;
readonly bool _close_on_send;
IReadOnlyDictionary<uint, SecurityMD> _md_map;
TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> _orders;
IDisposable _dispose;
[ImportingConstructor]
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter) {
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter)
: this(bus, model, error_reporter, Configuration.get<bts.config.OrderTicketOptions>().close_on_send) { }
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter, bool close_on_send) {
_bus = bus;
_model = model;
_error_reporter = error_reporter;
_close_on_send = close_on_send;
_dispose = Disposable.Create(
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketAutoLookupTag).Subscribe(_show_order_tickets),
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
);
}
@@ -54,17 +62,28 @@ namespace bts.shell {
void _show_order_ticket(SecurityMD smd) =>
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd,
Observable.Return(LadderSettings.Default))
Observable.Return(LadderSettings.Default)) {
close_on_send = _close_on_send
}
});
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
Observable.Return(LadderSettings.Default.calculator_buttons))
Observable.Return(LadderSettings.Default.calculator_buttons)) {
close_on_send = _close_on_send
}
});
}
void _show_order_tickets(IImmutableSet<uint> securities) {
foreach (var id in securities.empty_if_null()) {
if (_md_map.TryGetValue(id, out var smd))
_show_order_ticket(smd);
}
}
void _show_modify_order(uint order_key) {
if (_orders.state.TryGetValue(order_key, out var ori) && ori.sec.HasValue
&& (ori.order.HasDef && ori.order.Def.HasCustomDef && !(ori.order.Def.CustomDef.HasStop || ori.order.Def.CustomDef.HasStopLimit))) {
@@ -78,4 +97,4 @@ namespace bts.shell {
}
}
}
}
}
+6
View File
@@ -26,6 +26,7 @@ namespace mbts.ui {
AttachAlgosKey = nameof(AttachAlgosKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
CopyBoxesGeometryKey = nameof(CopyBoxesGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
@@ -45,6 +46,7 @@ namespace mbts.ui {
KeyGeometryKey = nameof(KeyGeometryKey),
LetterAKey = nameof(LetterAKey),
LetterDKey = nameof(LetterDKey),
MenuOrientationGeometryKey = nameof(MenuOrientationGeometryKey),
OrdersGeometryKey = nameof(OrdersGeometryKey),
PauseGeometryKey = nameof(PauseGeometryKey),
PaymentGeometryKey = nameof(PaymentGeometryKey),
@@ -62,6 +64,8 @@ namespace mbts.ui {
public static Geometry PlayGeometry => Geometry.Parse(get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
static readonly Pen _copy_boxes_pen = new Pen(Brushes.Black, HighDpiExtensions.RoundLayoutValue(5)).frozen();
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
@@ -156,6 +160,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 CopyBoxes => create_icon(CopyBoxesGeometryKey, null, _copy_boxes_pen);
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
@@ -179,6 +184,7 @@ namespace mbts.ui {
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 MenuOrientation => create_icon(MenuOrientationGeometryKey);
public static ImageSource Pause => create_icon(PauseGeometryKey);
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
+17 -1
View File
@@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<sys:String x:Key="{x:Static mbts:Icons.AttachAlgosKey}">
@@ -46,6 +46,18 @@
</sys:String>
</x:Array>
<x:Array x:Key="{x:Static mbts:Icons.CopyBoxesGeometryKey}" Type="{x:Type sys:String}">
<sys:String>
M-55,5 L-55,55 L-5,55
</sys:String>
<sys:String>
M-40,-10 L-40,40 L10,40 L10,-10 L-40,-10
</sys:String>
<sys:String>
M-10,10 L40,10 L40,-40 L-10,-40 L-10,10
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
</sys:String>
@@ -280,6 +292,10 @@
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.MenuOrientationGeometryKey}">
M14.997001,19.999038L15.000001,23.499012C15.000001,24.327005,14.327008,25,13.500016,25L9.9970522,25 9.9970522,23.000015 13.000022,23.000015 12.997022,20.001038z M9.000001,19.000006L9.000001,30.000006 30.000001,30.000006 30.000001,19.000006z M2,2L2,23 7.000001,23 7.000001,18.500006C7.000001,17.673006,7.6730013,17.000006,8.500001,17.000006L13.000001,17.000006 13.000001,2z M1.5,0L13.500001,0C14.327001,0,15.000001,0.67300034,15.000001,1.5L15.000001,17.000006 26.956518,17.000006 23.162404,13.207182C22.772376,12.816143 22.772376,12.184205 23.162404,11.793225 23.357907,11.597736 23.613928,11.499992 23.869827,11.499992 24.125725,11.499992 24.381502,11.597736 24.576517,11.793225L25.889371,13.105454 25.866285,12.914201C25.554945,10.547287 24.699391,8.7684691 23.308905,7.6089654 21.974919,6.4954615 20.356436,6.1229606 19.066825,6.0297103L19.059648,6.0292783 20.545799,7.5160289C20.936786,7.9070184 20.936786,8.5390015 20.545799,8.929991 20.350807,9.1249857 20.094815,9.2229831 19.838825,9.2229831 19.582834,9.2229831 19.326843,9.1249857 19.131849,8.929991L15.185988,4.9830971 18.981854,1.1871986C19.371841,0.79620934 20.004819,0.79620934 20.395805,1.1871986 20.785791,1.5771885 20.785791,2.2101717 20.395805,2.601161L18.976535,4.020443 18.999199,4.0214248C20.641604,4.1119318 22.785724,4.567862 24.590892,6.0739603 26.493747,7.660778 27.606404,10.054969 27.91277,13.209869L27.913735,13.221022 29.341699,11.793225C29.536714,11.597736 29.792459,11.499992 30.048328,11.499992 30.304196,11.499992 30.560187,11.597736 30.75569,11.793225 31.146694,12.184205 31.146694,12.816143 30.75569,13.207182L26.962552,17.000006 30.500001,17.000006C31.327001,17.000006,32.000001,17.673006,32.000001,18.500006L32.000001,30.500006C32.000001,31.327005,31.327001,32.000006,30.500001,32.000006L8.500001,32.000006C7.6730013,32.000006,7.000001,31.327005,7.000001,30.500006L7.000001,25 1.5,25C0.67300034,25,0,24.327001,0,23.5L0,1.5C0,0.67300034,0.67300034,0,1.5,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
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>
@@ -48,5 +48,5 @@ namespace mbts.ui.spark {
}
}
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row, MarketViewView.Columns> { }
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row> { }
}
@@ -4,10 +4,10 @@ using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -32,6 +32,7 @@ namespace mbts.spark {
SecurityCriteria
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
SparkMenuDisplayVM _menu_display_vm;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
@@ -48,7 +49,9 @@ namespace mbts.spark {
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override MarketViewWindowFactory.StartupData SaveState =>
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns)) {
menu_display = _menu_display_vm.safe_create()
};
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -101,6 +104,8 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
_view = new MarketViewView { DataContext = _vm };
@@ -160,7 +165,8 @@ namespace mbts.spark {
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
})),
_menu_display_vm.spark_cmd
};
//
_vm.selection_changed += on_selection_changed;
@@ -190,5 +196,8 @@ namespace mbts.spark {
_vm.Dispose();
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
}
}
@@ -1,6 +1,7 @@
using bts.api;
using bts.proto;
using bts.settings;
using bts.view;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -53,6 +54,8 @@ namespace bts.shell {
public readonly FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
public StartupData() { }
@@ -0,0 +1,36 @@
using mbts.spark.filters;
using mbts.ui;
using ProtoBuf;
using System;
namespace bts.shell.time_and_sales {
[ProtoContract]
class FilterStates {
[ProtoMember(1)]
public TimeFilterTuple time;
[ProtoMember(2)]
public string security;
[ProtoMember(3)]
public DoubleMinMaxState price;
[ProtoMember(4)]
public UIntMinMaxState size;
// [ProtoMember(5)]
// ProtoMember 5 cannot be used further
[ProtoMember(6)]
public string security_key;
[ProtoMember(7)]
public co2.AggressorSide[] aggressor_sides;
}
static class TimeAndSalesFilterStatesExtensions {
public static Func<TimeAndSalesRowVM, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<TimeAndSalesRowVM>();
pb.add_regex(fs.security, r => r.security.ToString());
pb.add_time_filter(fs.time, r => r.time);
pb.add_price(fs.price, r => r.price, r => r.security);
pb.add_numeric(fs.size, r => r.size);
pb.add_discrete_values(fs.aggressor_sides, r => r.aggressor_side);
return pb.to_predicate();
}
}
}
@@ -0,0 +1,74 @@
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System.Collections.Generic;
using System.Linq;
using static mbts.ui.controls.DIYGrid;
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
namespace bts.shell.time_and_sales {
[ProtoContract]
class TimeAndSalesSettings {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criterion;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
[ProtoMember(6)]
public bool use_defaults;
public TimeAndSalesSettings() { }
public TimeAndSalesSettings(SecurityCriteria security_criteria) :
this(security_criteria, new FilterStates(), null, null) { }
public TimeAndSalesSettings(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
this.security_criteria = security_criteria;
this.filters = filters;
this.grid = grid;
_sorting_criterion = sorting_criterion;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
public static TimeAndSalesSettings Default(SecurityCriteria security_criteria) =>
new TimeAndSalesSettings(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
public TimeAndSalesSettings with(SecurityCriteria security_criteria) =>
new TimeAndSalesSettings(security_criteria, filters, grid);
public static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.col_id(), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings time => new GridColumnSettings(Columns.Time.col_id(), DefaultColumnHeaders.Time, width: DefaultColumnWidths.TimeOfDay);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.col_id(), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings size => new GridColumnSettings(Columns.Size.col_id(), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings aggressor => new GridColumnSettings(Columns.Aggressor.col_id(), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.col_id(), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return time;
yield return price;
yield return size;
yield return aggressor;
yield return security_key;
}
}
}
}
}
@@ -1,27 +1,26 @@
using bts.api;
using bts.co2;
using mbts;
using mbts.co2;
using mbts.spark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
namespace bts.shell.time_and_sales {
class TimeAndSalesModel : IDisposable {
internal readonly SecurityCriteria security_criteria;
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
IDisposable _dispose;
public TimeAndSalesModel(IBus bus, MDConsumer consumer, SecurityCriteria security_criteria) {
this.security_criteria = security_criteria;
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
public TimeAndSalesModel(IBus bus, MDConsumer consumer, IObservable<SecurityCriteria> security_criteria) {
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
var copy = curr_channels;
return copy.Values.SelectMany(c => c.read_trades()).ToList();
}));
_dispose = security_criteria.subscribe_md_map(bus).Subscribe(
_dispose = security_criteria.SelectManyInSeries(sc =>sc.subscribe_md_map(bus)).Subscribe(
md_map => {
var copy = new Dictionary<ChannelKey, ChannelConsumer>(curr_channels);
copy.sync_with_demux(md_map, get_channel_key, get_channel_consumer, update_channel_consumer);
@@ -95,4 +94,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -0,0 +1,70 @@
using bts.api;
using bts.co2;
using bts.utils;
using bts.utils.csv;
using mbts;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
namespace bts.shell.time_and_sales {
class TimeAndSalesRowVM : ViewModelBase, DIYGrid.IRowVM, ISecurityRow, IEquatable<TimeAndSalesRowVM> {
// key comparer guarentees deterministic sorting
// if the rows in the collection have unique row_keys.
public static readonly IComparer<TimeAndSalesRowVM> key_comparer =
new ComparerBuilder<TimeAndSalesRowVM>(new[] {
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
})
.to_comparer()
.to_safe();
readonly TradeMessageImmutable _trade;
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade) {
this.row_key = row_key;
this.security = security;
_trade = trade;
trade_key = trade.Key;
}
public event Action invalidated;
public int row_key { get; }
public uint trade_key { get; }
public uint security_key => security.Id;
public SecurityImmutable security { get; }
public DateTime time => ((ulong)_trade.Time).lldatetime().ToLocalTime();
public double price => _trade.price();
public uint size => _trade.Size;
public AggressorSide aggressor_side => _trade.AggressorSide;
public void raise_invalidated() =>
invalidated?.Invoke();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(TimeAndSalesRowVM),
new string[] {
nameof(security),
nameof(time),
nameof(price),
nameof(size),
nameof(aggressor_side),
nameof(security_key),
});
public bool Equals(TimeAndSalesRowVM other) =>
other != null &&
row_key == other.row_key;
public override bool Equals(object obj) =>
Equals(obj as TimeAndSalesRowVM);
public override int GetHashCode() =>
row_key;
}
}
@@ -1,86 +1,363 @@
using bts.api;
using bts.co2;
using bts.collections;
using bts.utils;
using mbts;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
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.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace mbts.spark {
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
readonly IBus _bus;
readonly TimeAndSalesModel _model;
IWindowSettingsManager<GridSettings> _settings_manager;
namespace bts.shell.time_and_sales {
class TimeAndSalesVM : DisposableViewModelBase, IHandleGridSorting, IHandleRowSelection {
const int _max_row_count = 1000;
static int _id_counter;
static IReadOnlyList<Func<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>> _filter_getters =
ObjectSnapshot.property_delegates_of_type<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>();
readonly ColumnSortManager _sort_manager;
readonly RowFactory _row_factory = new RowFactory();
readonly LoggerContext _log;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly DeferrableActionWrapper
_refresh_filter,
_refresh_sort,
_refresh_selected_rows;
IComparer<TimeAndSalesRowVM> _comparer_backing;
Func<TimeAndSalesRowVM, bool> _filter_backing;
ImmutableHashSet<TimeAndSalesRowVM> _selected_rows = ImmutableHashSet<TimeAndSalesRowVM>.Empty;
ImmutableList<TimeAndSalesRowVM> _all_rows = ImmutableList<TimeAndSalesRowVM>.Empty;
ImmutableList<TradeInfo> _buffered_trades;
/// Although the sorting criteria can be obtained from the <see cref="_sort_manager"/> (<see cref="ColumnSortManager.SortingCriteria"/>),
/// a local copy is kept because the settings collector may call <see cref="create"/> from a thread
/// other than the dispatcher thread associated with this instance as a viewmodel,
/// and the <see cref="ColumnSortManager.SortingCriteria"/> property is not thread-safe.
/// Therefore, <see cref="create"/> returns this local copy, and thus avoids accessing the non-thread-safe property.
ImmutableList<SortingCriteria> _sorting_criteria = ImmutableList<SortingCriteria>.Empty;
TimeAndSalesRowVM _current_row;
bool _is_paused;
int
_load_settings_count,
_filter_change_count,
_sort_change_count;
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
_bus = bus;
_model = model;
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager) {
_sort_manager = new ColumnSortManager(columns.WrapSelector(c => c as ISortableDIYGridColumn, null), TimeAndSalesView.Columns.RowKey.ToString());
_comparer_backing = _sort_manager.BuildComparer(_get_column_comparer);
_log = new LoggerContext(Log.Ui, $"Time And Sales VM ({Interlocked.Increment(ref _id_counter)})", null);
_settings_manager = settings_manager;
show_ladder = DelegateCommandParamObserver.Create(current_row_observable, r => _show_ladder_async(r).ignore(), r => r != null);
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
if (startup_data.filters is FilterStates filters) {
// deferrable filter/sort refresh
_refresh_filter = new DeferrableActionWrapper(_refresh_filter_impl);
_refresh_sort = new DeferrableActionWrapper(_refresh_sort_impl);
_refresh_selected_rows = new DeferrableActionWrapper(() => selected_rows_changed?.Invoke(_selected_rows));
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<TimeAndSalesRowVM>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
grid_settings_manager.load_from(settings_manager.current_settings());
// Columns
add_column(
TimeAndSalesView.Columns.Security,
r => r.security.ToString(),
DefaultColumnHeaders.Security,
DefaultColumnWidths.Security,
security_filter,
DataGridFilterKnownTemplates.Regex);
add_column(
TimeAndSalesView.Columns.Time,
r => r.time.ToString("h:mm:ss tt"),
DefaultColumnHeaders.Time,
DefaultColumnWidths.Time,
time_filter,
DataGridFilterKnownTemplates.TimeFilter);
add_column(
TimeAndSalesView.Columns.Price,
r => r.security.spec_or_default().format(r.price),
DefaultColumnHeaders.Price,
DefaultColumnWidths.Price,
price_filter,
DataGridFilterKnownTemplates.DoubleMinMax,
System.Windows.HorizontalAlignment.Right);
add_column(
TimeAndSalesView.Columns.Size,
r => r.size.ToString(),
DefaultColumnHeaders.Size,
DefaultColumnWidths.Size,
size_filter,
DataGridFilterKnownTemplates.UIntMinMax,
System.Windows.HorizontalAlignment.Right);
add_column(
TimeAndSalesView.Columns.Aggressor,
r => r.aggressor_side.ToString(),
DefaultColumnHeaders.Aggressor,
DefaultColumnWidths.Aggressor,
aggressor_filter,
DataGridFilterKnownTemplates.EnumValues);
add_column(
TimeAndSalesView.Columns.SecurityKey,
r => r.security_key.ToString(),
DefaultColumnHeaders.SecurityKey,
DefaultColumnWidths.SecurityKey,
security_key_filter,
DataGridFilterKnownTemplates.Regex);
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager);
// commands
show_ladder_cmd = new DelegateCommand(() => {
if (current_row != null)
request_show_ladder?.Invoke(current_row);
});
// init IDisposable resources
var resources = new List<IDisposable>();
resources.AddRange(_filters.Select(f => {
f.filter_changed += _refresh_filter.execute;
return Disposable.Create(() => f.filter_changed -= _refresh_filter.execute);
}));
_sort_manager.active_sorting_changed += _refresh_sort.execute;
resources.Add(Disposable.Create(() => _sort_manager.active_sorting_changed -= _refresh_sort.execute));
_dispose = Disposable.Create(resources.ToArray());
// local methods
void add_column(
TimeAndSalesView.Columns col_key,
Func<TimeAndSalesRowVM, string> get_cell_text,
string header,
double width,
IDataGridColumnFilter filter,
string filter_template,
System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<TimeAndSalesRowVM, TimeAndSalesView.Columns>(
col_key, get_cell_text, filter, filter_template, _get_aggressor_brush) {
id = col_key.col_id(), Header = header, Width = width, horizontal_alignment = h_alignment
}
);
}
public event Action<ImmutableHashSet<TimeAndSalesRowVM>> selected_rows_changed;
public event Action<TimeAndSalesRowVM> current_row_changed, request_show_ladder;
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public TimeAndSalesRowVM current_row {
get => _current_row;
set {
if (Set(ref _current_row, value))
current_row_changed?.Invoke(value);
}
}
public ObservableCollection<TimeAndSalesRowVM> rows { get; } = new ObservableCollection<TimeAndSalesRowVM>();
public ImmutableHashSet<TimeAndSalesRowVM> selected_rows => _selected_rows;
public int total_row_count => _all_rows.Count;
public SimpleGridSettingsManager<TimeAndSalesRowVM> grid_settings_manager { get; }
public bool is_paused {
get => _is_paused;
set {
if (_is_paused != value) {
_is_paused = value;
if (value) {
_buffered_trades = ImmutableList<TradeInfo>.Empty;
} else {
var resume = _buffered_trades;
_buffered_trades = null;
on_new_trades(resume);
}
}
}
}
// filters
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<TimeAndSalesRowVM>(r => r.time);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security.ToString(), nameof(TimeAndSalesRowVM.security));
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<TimeAndSalesRowVM>(r => r.price);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, UIntMinMaxState> size_filter { get; } = new UIntMinMax<TimeAndSalesRowVM>(r => r.size, nameof(TimeAndSalesRowVM.size));
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<TimeAndSalesRowVM, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_key_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security_key.ToString());
// commands
public ICommand show_ladder_cmd { get; }
IComparer<TimeAndSalesRowVM> _comparer {
get => _comparer_backing;
set {
if (value == null) {
_log.warn("Comparer should not be set to null, will use the default comparer instead (based on trade time, then row key).");
_comparer_backing = new ComparerBuilder<TimeAndSalesRowVM>(new[] {
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
})
.to_comparer()
.to_safe();
} else {
_comparer_backing = value;
}
var sorted_rows = rows.ToArray();
var snapshot = _selected_rows;
Array.Sort(sorted_rows, _comparer_backing);
using (_refresh_selected_rows.defer_execution()) {
for (int i = 0; i < sorted_rows.Length; i++) {
if (rows[i].row_key != sorted_rows[i].row_key)
rows[i] = sorted_rows[i];
}
}
}
}
Func<TimeAndSalesRowVM, bool> _filter {
get => _filter_backing;
set {
var old_filter = _filter_backing;
var new_filter = value;
_filter_backing = new_filter;
if (new_filter != null) {
for (int i = 0; i < rows.Count; i++) {
if (!new_filter(rows[i]))
rows.RemoveAt(i);
}
rows.RemoveWhere(r => !new_filter(r));
if (old_filter != null) {
foreach (var r in _all_rows) {
if (!old_filter(r) && new_filter(r))
rows.InsertSorted(r, _comparer);
}
}
} else if (old_filter != null) {
foreach (var r in _all_rows) {
if (!old_filter(r))
rows.InsertSorted(r, _comparer);
}
}
}
}
IEnumerable<IDataGridColumnFilter<TimeAndSalesRowVM>> _filters =>
_filter_getters.Select(fn => fn(this));
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting_criteria, GridSettings grid_settings) create() =>
(_get_filter_states(), _sorting_criteria, grid_settings_manager.create());
public void load(FilterStates filters, IEnumerable<SortingCriteria> sorting_criteria, GridSettings grid_settings) {
using (_refresh_filter.defer_execution())
using (_refresh_sort.defer_execution()) {
_log.info($"begin load settings ({++_load_settings_count})...");
var timer = new Stopwatch();
timer.Start();
grid_settings_manager.load_from(grid_settings);
_sort_manager.SortingCriteria = sorting_criteria;
// load filters
time_filter.load_state(filters.time);
security_filter.load_state(filters.security);
price_filter.load_state(filters.price);
size_filter.load_state(filters.size);
aggressor_filter.load_state(filters.aggressor_sides);
security_key_filter.load_state(filters.security_key);
//
timer.Stop();
_log.info($"load settings ({_load_settings_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
}
// Columns
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, nameof(Row.security), security_filter, DataGridFilterKnownTemplates.Regex);
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, nameof(Row.time), time_filter, DataGridFilterKnownTemplates.TimeFilter);
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
// if the collection is not populated, the column settings will not be applied
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
show_ladder,
DataGridFilterHelper.hook_filters(
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
}
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public DelegateCommandParamObserver<Row> show_ladder { get; }
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
public void on_new_trades(IReadOnlyList<TradeInfo> trades) {
/// The total row count is limited by <see cref="_max_row_count"/>;
/// trades are kept in a circular buffer (FIFO).
/// If paused, just need to add trades to <see cref="_buffered_trades"/>.
if (is_paused) {
if (_buffered_trades == null)
_buffered_trades = ImmutableList<TradeInfo>.Empty;
if (trades.Count >= _max_row_count) {
_buffered_trades = _buffered_trades
.Clear()
.AddRange(ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count));
} else {
int overflow_count = _buffered_trades.Count + trades.Count - _max_row_count;
if (overflow_count > 0) {
var rows_to_remove = _buffered_trades.WrapSubRange(0, overflow_count);
_buffered_trades = _buffered_trades.RemoveRange(0, overflow_count);
}
_buffered_trades = _buffered_trades.AddRange(trades);
}
return;
}
/// If not paused, update in the normal fasion:
/// add to <see cref="_all_rows"/> and <see cref="rows"/>
if (trades.Count >= _max_row_count) {
trades = ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count);
_all_rows = _all_rows.Clear();
rows.Clear();
} else {
int overflow_count = _all_rows.Count + trades.Count - _max_row_count;
if (overflow_count > 0) {
var rows_to_remove = _all_rows.WrapSubRange(0, overflow_count);
_all_rows = _all_rows.RemoveRange(0, overflow_count);
rows.RemoveSorted(rows_to_remove, _comparer);
}
}
var all_rows_bd = _all_rows.ToBuilder();
foreach (var t in trades) {
var r = _row_factory.create(t.security, t.trade);
all_rows_bd.Add(r);
if (_filter_backing == null || _filter_backing(r))
rows.InsertSorted(r, _comparer);
}
_all_rows = all_rows_bd.ToImmutable();
RaisePropertyChanged(nameof(total_row_count));
}
public StartupData create() =>
new StartupData(_model.security_criteria, _get_filter_states(), get_grid_save_state(), rows_view.get_sorting_criteria(columns));
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
try {
if (e.Column is ISortableDIYGridColumn sc) {
_log.info($"handling sort event for column {sc.id}.");
_sort_manager.OnColumnClick(sc, KeyboardExtensions.IsShiftDown());
var sorted_rows = rows.ToArray();
var snapshot = _selected_rows;
_refresh_sort_impl();
} else {
_log.warn($"ignoring sort event because column '{e.Column?.id}' does not implement {nameof(ISortableDIYGridColumn)}.");
}
} catch (Exception ex) {
_log.error($"error handling sort event: {ex}.");
}
}
private Brush _get_aggressor_brush(Row r) {
public void row_selection_changed(SelectionChangedEventArgs e) {
var bd = _selected_rows.ToBuilder();
if (e.AddedItems != null)
bd.AddRange(e.AddedItems.OfType<TimeAndSalesRowVM>());
if (e.RemovedItems != null)
bd.removeRange(e.RemovedItems.OfType<TimeAndSalesRowVM>());
_selected_rows = bd.ToImmutable();
_refresh_selected_rows.execute();
}
static IComparer<TimeAndSalesRowVM> _get_column_comparer(string column_name) =>
_get_column_comparer(EnumHelper.parse<TimeAndSalesView.Columns>(column_name));
static IComparer<TimeAndSalesRowVM> _get_column_comparer(TimeAndSalesView.Columns column) {
switch (column) {
case TimeAndSalesView.Columns.Aggressor: return wrap(r => r.aggressor_side);
case TimeAndSalesView.Columns.Time: return wrap(r => r.time);
case TimeAndSalesView.Columns.Security: return wrap(r => r.security);
case TimeAndSalesView.Columns.Price: return wrap(r => r.price);
case TimeAndSalesView.Columns.Size: return wrap(r => r.size);
case TimeAndSalesView.Columns.SecurityKey: return wrap(r => r.security_key);
case TimeAndSalesView.Columns.RowKey: return wrap(r => r.row_key);
default: throw ExceptionBuilder.Argument.unexpected_case(column, nameof(column));
}
IComparer<TimeAndSalesRowVM> wrap<T>(Func<TimeAndSalesRowVM, T> selector) =>
Comparer<T>.Default.wrap(selector);
}
Brush _get_aggressor_brush(TimeAndSalesRowVM r) {
BTSColor c;
Brush b;
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
@@ -90,14 +367,6 @@ namespace mbts.spark {
return b;
}
#region Filters
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<Row>(r => r.time, nameof(Row.time));
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> size_filter { get; } = new UIntMinMax<Row>(r => r.size, nameof(Row.size));
public IDataGridColumnPersistentFilter<Row, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<Row, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side, nameof(Row.aggressor_side));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
FilterStates _get_filter_states() =>
new FilterStates {
time = time_filter.get_state(),
@@ -107,73 +376,35 @@ namespace mbts.spark {
security_key = security_key_filter.get_state(),
aggressor_sides = aggressor_filter.get_state()
};
#endregion
protected override IEnumerable<SortingCriteria> get_default_sorting() {
yield return new SortingCriteria(nameof(Row.time), false);
void _refresh_sort_impl() {
_log.info($"begin change sort ({++_sort_change_count})...");
var timer = new Stopwatch();
timer.Start();
_comparer = _sort_manager.BuildComparer(_get_column_comparer, TimeAndSalesRowVM.key_comparer);
_sorting_criteria = ImmutableList.CreateRange(_sort_manager.SortingCriteria);
timer.Stop();
_log.info($"sort change ({_sort_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
}
protected override Row row_factory(TradeInfo row_model) =>
new Row(this, row_model.security, row_model.trade);
async Task _show_ladder_async(Row row) {
var smd_lookup = await SparkUtils.get_security_md_async(_bus, row.security.Id);
if (smd_lookup.success) _bus.post(SparkTags.ShowLadderTag, smd_lookup.result);
void _refresh_filter_impl() {
_log.info($"begin change filter ({++_filter_change_count})...");
var timer = new Stopwatch();
timer.Start();
_filter = _get_filter_states().build_predicate();
timer.Stop();
_log.info($"filter change ({_filter_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
}
public class Row : SecurityRow, DIYGrid.IRowVM {
readonly uint _trade_key;
class RowFactory {
// the row key must be unique to each row in the collection
int _row_id_counter;
readonly string
_security_text,
_time_text,
_price_text,
_size_text,
_aggressor_text,
_trade_key_text,
_security_key_text;
public TimeAndSalesRowVM create(TradeInfo trade_info) =>
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade);
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
_trade_key = trade.Key;
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
price = trade.price();
size = trade.Size;
aggressor_side = trade.AggressorSide;
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
_security_text = security.ToString();
_time_text = time.ToString("M/d h:mm:ss tt");
_price_text = security.ContractSpec.format(price);
_size_text = size.ToString();
_aggressor_text = aggressor_side.ToString();
_trade_key_text = _trade_key.ToString();
_security_key_text = security_key.ToString();
}
public event Action invalidated;
public uint trade_key => _trade_key;
public DateTime time { get; }
public double price { get; }
public uint size { get; }
public AggressorSide aggressor_side { get; }
public ICommand show_ladder { get; }
public string this[TimeAndSalesView.Columns col] {
get {
switch (col) {
case TimeAndSalesView.Columns.Security: return _security_text;
case TimeAndSalesView.Columns.Time: return _time_text;
case TimeAndSalesView.Columns.Price: return _price_text;
case TimeAndSalesView.Columns.Size: return _size_text;
case TimeAndSalesView.Columns.Aggressor: return _aggressor_text;
case TimeAndSalesView.Columns.SecurityKey: return _security_key_text;
default: return null;
}
}
}
public void raise_invalidated() => invalidated?.Invoke();
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade) =>
new TimeAndSalesRowVM(_row_id_counter++, security, trade);
}
}
}
}
@@ -1,10 +1,10 @@
<UserControl
x:Class="mbts.ui.spark.TimeAndSalesView"
x:Class="bts.shell.time_and_sales.TimeAndSalesView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:tas="clr-namespace:bts.shell.time_and_sales"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
@@ -26,13 +26,14 @@
x:Name="_grid"
Grid.Row="0"
Columns="{Binding columns}"
Rows="{Binding rows_view}"
Rows="{Binding rows}"
CurrentRow="{Binding current_row}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
>
<c:DIYGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding show_ladder_cmd}"/>
<KeyBinding Key="Return" Command="{Binding show_ladder_cmd}"/>
<KeyBinding Key="A" Modifiers="Ctrl" Command="{Binding select_all_cmd}"/>
</c:DIYGrid.InputBindings>
<c:DIYGrid.CommandBindings>
<CommandBinding
@@ -43,13 +44,18 @@
</c:DIYGrid.CommandBindings>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:TimeAndSalesRowView/>
<tas:TimeAndSalesRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock Text=" "/>
<TextBlock>
<Run Text="Trades:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding total_row_count, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
@@ -1,17 +1,21 @@
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.controls;
using System.Windows.Controls;
namespace mbts.ui.spark {
namespace bts.shell.time_and_sales {
/// <summary>
/// Interaction logic for TimeAndSalesView.xaml
/// </summary>
partial class TimeAndSalesView : UserControl {
public enum Columns { Time, Security, Price, Size, OrderType, SecurityKey, Aggressor }
public enum Columns { Time, Security, Price, Size, SecurityKey, Aggressor, RowKey }
public TimeAndSalesView() =>
InitializeComponent();
}
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row, TimeAndSalesView.Columns> { }
}
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesRowVM> { }
static class TimeAndSalesViewColumnsExtensions {
public static string col_id(this TimeAndSalesView.Columns column) =>
column.ToString();
}
}
@@ -1,125 +1,218 @@
using bts;
using bts.api;
using bts.shell;
using bts.api;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts;
using mbts.co2;
using mbts.persist;
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.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
namespace bts.shell.time_and_sales {
class TimeAndSalesWindow : SparkWindow<TimeAndSalesSettings>, ISharedWindow {
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
readonly MDConsumer _consumer;
readonly TimeAndSalesWindowFactory _factory;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
SingleValuePump<TimeAndSalesVM> _vm;
readonly bool _can_pause_updates;
TimeAndSalesVM _vm;
TimeAndSalesModel _model;
StartupData _state;
SecurityCriteria _security_criteria_backing;
TimeAndSalesSettings _state;
TimeAndSalesView _view;
SparkCommand[] _commands;
SparkMenuDisplayVM _menu_display_vm;
IDisposable _subscriptions;
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager)
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, TimeAndSalesSettings state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager, bool can_pause_updates)
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
Log.assert(state != null);
_consumer = consumer;
_factory = factory;
_state = state;
_state = state ?? TimeAndSalesSettings.Default(new SecurityCriteria());
if (_state.security_criteria == null)
_state = _state.with(new SecurityCriteria());
_security_criteria_backing = _state.security_criteria;
_settings_manager = settings_manager;
_can_pause_updates = can_pause_updates;
thread_id = mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().time_and_sales ? _window_type : null;
Icon.Freeze();
}
event Action<SecurityCriteria> _security_criteria_changed;
public override string window_type => _window_type;
public override IEnumerable<SparkCommand> Commands => _commands;
public override UIElement Content => _view;
public override double MinWidth => StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
public override double MinWidth => TimeAndSalesSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public override StartupData SaveState => _state = _vm.value.create();
public override TimeAndSalesSettings SaveState {
get {
_ensure_state(true);
return _state;
}
}
SecurityCriteria _security_criteria {
get => _security_criteria_backing;
set {
_security_criteria_backing = value;
_state = null;
_security_criteria_changed?.Invoke(value);
}
}
public override void Dispose() =>
_dispose_resources();
public void thread_init() {
_dispose_resources();
_vm = new SingleValuePump<TimeAndSalesVM>();
var connectable_builder = new ConnectableSeriesBuilder<TimeAndSalesVM>(_vm.observable);
_ensure_state();
if (!_state.use_defaults)
_settings_manager.instance_settings = _state.grid;
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
_vm = new TimeAndSalesVM(_settings_manager);
var selected_rows_src = Observable.Create<ImmutableHashSet<TimeAndSalesRowVM>>(o => {
_vm.selected_rows_changed += o.OnNext;
return Disposable.Create(() => _vm.selected_rows_changed -= o.OnNext);
});
var current_row_src = Observable.Create<TimeAndSalesRowVM>(o => {
if (_vm.current_row != null)
o.OnNext(_vm.current_row);
_vm.current_row_changed += o.OnNext;
return Disposable.Create(() => _vm.current_row_changed -= o.OnNext);
});
var current_sec_key_src = current_row_src.Select(r => {
var result = ImmutableHashSet<uint>.Empty;
if (r != null)
result = result.Add(r.security_key);
return result;
});
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
var security_criteria_changed = new Action<SecurityCriteria>(sc => { });
SparkCommand toggle_is_paused_cmd = null;
toggle_is_paused_cmd = new SparkCommandCommand(get_is_paused_cmd_title(), Icons.Pause, new DelegateCommand(() => {
_vm.is_paused = !_vm.is_paused;
toggle_is_paused_cmd.title = get_is_paused_cmd_title();
})) {
show_in_toolbar = _can_pause_updates,
show_in_context_menu = _can_pause_updates
};
_commands = new SparkCommand[] {
SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// csv export
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
// ladder
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
// ticket
SparkCommands.ShowOrderTicketAutoLookup(SparkCommands.KeyGestures.ShowOrderTicket)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
// fills
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills)
.complete(selected_rows_src, always_true, get_fills_startup_data),
// mview
SparkCommands.ShowMarketView
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria),
// pause
toggle_is_paused_cmd,
// change securities
SparkCommands.change_securities_command(() => (update_securities_cmd, _security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
s => _vm.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select(c => c.create()))
s.with(_vm.columns.Select(c => c.create()))
)
);
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
})),
_menu_display_vm.spark_cmd
};
_subscriptions = Disposable.Create(
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(update_criteria),
connectable_builder.connect_all());
_initialize();
_vm.load(_state.filters, _state.sorting_criterion, _state.grid);
_vm.request_show_ladder += show_ladder;
var security_criteria_src = _bus.subscribe(update_securities_cmd.tag);
var subs = new List<IDisposable> {
security_criteria_src.Subscribe(sc => _security_criteria = sc),
Disposable.Create(() => _vm.request_show_ladder -= show_ladder)
};
_model = new TimeAndSalesModel(_bus, _consumer, Observable.Create<SecurityCriteria>(o => {
o.OnNext(_security_criteria);
return security_criteria_src.Subscribe(o);
}));
subs.Add(_model.trades.OnDispatcher().Subscribe(_vm.on_new_trades));
_subscriptions = Disposable.Create(subs.ToArray());
if (_view == null)
_view = new TimeAndSalesView();
_view.DataContext = _vm;
// local methods
bool always_true<T>(T _) => true;
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
FillsSettings get_fills_startup_data(ImmutableHashSet<TimeAndSalesRowVM> rows) =>
new FillsSettings(new FillsFilterStates {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
security = rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria
});
void update_criteria(SecurityCriteria c) {
_state = _vm.value.create().with(c);
_initialize();
string get_is_paused_cmd_title() =>
$"{(_vm.is_paused ? "Resume" : "Pause")} Updates";
void show_ladder(TimeAndSalesRowVM row) {
if (row != null)
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet.Create(new[] { row.security_key }));
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
void _dispose_resources() {
Disposable.dispose(ref _subscriptions);
Disposable.dispose(ref _vm);
Disposable.dispose(ref _model);
if (_commands != null) {
_commands.OfType<IDisposable>().DisposeAll();
_commands = null;
}
Disposable.dispose(ref _model);
Disposable.dispose(ref _vm);
}
void _initialize() {
Disposable.dispose(ref _model);
_model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
// the previous _vm.value is automatically disposed when _vm.value is set - no need to dispose it here
_vm.value = new TimeAndSalesVM(_bus, _settings_manager, _model, _state);
if (_view == null)
_view = new TimeAndSalesView();
_view.DataContext = _vm.value;
void _ensure_state(bool force_recreate = false) {
if (_state == null || force_recreate) {
if (_vm == null) {
_state = TimeAndSalesSettings.Default(_security_criteria);
} else {
var (filters, sorting, grid) = _vm.create();
_state = new TimeAndSalesSettings(_security_criteria, filters, grid, sorting.ToList()) {
use_defaults = _settings_manager.instance_settings == null
};
}
// should be safe to assume _menu_display_vm and _vm will either both be null, or both be non null,
// but redundant null checks don't hurt, and just may prevent a crash.
if (_menu_display_vm != null)
_state.menu_display = _menu_display_vm.safe_create();
}
}
}
}
}
@@ -1,33 +1,32 @@
using bts.api;
using bts.spark;
using bts.shell.time_and_sales;
using mbts;
using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.TimeAndSalesView;
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
using mbts.config;
namespace bts.shell {
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesWindowFactory.StartupData> {
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesSettings> {
readonly IBus _bus;
readonly MDConsumer _consumer;
readonly UserSettingsHub _settings_hub;
readonly TimeAndSalesConfig _time_and_sales_conf;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
_bus = bus;
_consumer = consumer;
_settings_hub = settings_hub;
_time_and_sales_conf = new TimeAndSalesConfig(Configuration.get<bts.config.TimeAndSalesOptions>());
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
@@ -36,91 +35,21 @@ namespace bts.shell {
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(StartupData state) =>
public override Task<IShellWindow> load(TimeAndSalesSettings state) =>
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g))));
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g)),
_time_and_sales_conf.can_pause_updates));
public async void show(SecurityCriteria criteria) {
var w = await load(new StartupData(criteria));
var w = await load(new TimeAndSalesSettings(criteria));
_bus.post(SparkTags.ShowWindow, w);
}
[ProtoContract]
public class StartupData {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criterion;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
readonly struct TimeAndSalesConfig {
public readonly bool can_pause_updates;
public StartupData() { }
public StartupData(SecurityCriteria security_criteria) :
this(security_criteria, new FilterStates(), null, null) { }
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
this.security_criteria = security_criteria;
this.filters = filters;
this.grid = grid;
_sorting_criterion = sorting_criterion;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
public StartupData with(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, filters, grid);
public static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings time => new GridColumnSettings(col_id(Columns.Time), DefaultColumnHeaders.Time, width: DefaultColumnWidths.Time);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings size => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings aggressor => new GridColumnSettings(col_id(Columns.Aggressor), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible:false, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return time;
yield return price;
yield return size;
yield return aggressor;
yield return security_key;
}
}
}
}
[ProtoContract]
public class FilterStates {
[ProtoMember(1)]
public TimeFilterTuple time;
[ProtoMember(2)]
public string security;
[ProtoMember(3)]
public DoubleMinMaxState price;
[ProtoMember(4)]
public UIntMinMaxState size;
// [ProtoMember(5)]
// ProtoMember 5 cannot be used further
[ProtoMember(6)]
public string security_key;
[ProtoMember(7)]
public co2.AggressorSide[] aggressor_sides;
public TimeAndSalesConfig(bts.config.TimeAndSalesOptions options) =>
can_pause_updates = options.can_pause_updates;
}
}
}
}
@@ -6,7 +6,7 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=System.Runtime">
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
@@ -4,7 +4,7 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=System.Runtime">
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<cv:ValueMap x:Key="EmptyStringVisibilityConverter" Default="{u:Visibility Visible}">
<cv:KeyValue Key="{x:Static sys:String.Empty}" Value="{u:Visibility Collapsed}"/>
<cv:KeyValue Key="{x:Null}" Value="{u:Visibility Collapsed}"/>
@@ -20,5 +20,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row> { }
}
@@ -3,6 +3,7 @@ using bts.api;
using bts.shell;
using bts.shell.spreader;
using bts.spark;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -27,6 +28,7 @@ namespace mbts.spark {
StartupData _state;
OrdersModel _orders_model;
SyntheticManagementView _view;
SparkMenuDisplayVM _menu_display_vm;
IDisposable _subscriptions;
IReadOnlyList<SparkCommand> _commands;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -43,7 +45,7 @@ namespace mbts.spark {
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override StartupData SaveState => _state = _vm.value.create().with(menu_display: _menu_display_vm.safe_create());
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -53,6 +55,8 @@ namespace mbts.spark {
public void thread_init() {
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
@@ -97,7 +101,8 @@ namespace mbts.spark {
new SparkCommandCommand(
"Import Synthetics...", Icons.Import, FileDialogCommand.open_file(import, extensions: SyntheticSecurityParser.file_extension)) {
show_in_toolbar = false
}
},
_menu_display_vm.spark_cmd
};
_subscriptions = Disposable.Create(
@@ -129,6 +134,9 @@ namespace mbts.spark {
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
struct RowModificationParams {
public readonly SecurityImmutable security;
public readonly bool has_working_orders;
@@ -48,14 +48,28 @@ namespace bts.shell {
public readonly FilterStates filters;
[ProtoMember(3)]
public readonly GridSettings grid;
[ProtoMember(4)]
public readonly SparkMenuDisplaySettings menu_display;
public StartupData() : this(new List<SortingCriteria>(), null, null) { }
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default)
: this(sorting_criteria, filters, grid, SparkMenuDisplaySettings.Default) { }
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid, SparkMenuDisplaySettings menu_display) {
this.sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
this.menu_display = menu_display;
}
public StartupData with(List<SortingCriteria> sorting_criteria = null, FilterStates filters = null, GridSettings grid = null, SparkMenuDisplaySettings? menu_display = null) =>
new StartupData(
sorting_criteria ?? this.sorting_criteria,
filters ?? this.filters,
grid ?? this.grid,
menu_display ?? this.menu_display);
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
static class DefaultColumns {
@@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteInsideMarketDistanceTowardModel : RowModelBase<uint> {
readonly Dictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
public override string display_name { get; } = "Toward Distance";
public override string help_text { get; } = string.Empty;
@@ -21,7 +21,7 @@ namespace mbts.spark {
_value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.SecurityKey] = value;
_value_store[leg_tup.id] = value;
if (_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance)
inside_market_distance.OnTowardDistance = value;
}
@@ -33,7 +33,7 @@ namespace mbts.spark {
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey);
_value_store.GetValueOrDefault(leg_tup.id);
}
}
}
@@ -11,7 +11,7 @@
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:StateManager.VisualState="{Binding selected_definition.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
Background="{StaticResource CommonBackground}"
>
@@ -25,6 +25,7 @@
<sys:Double x:Key="VerticalPush2">8</sys:Double>
<sys:Double x:Key="VerticalPush3">12</sys:Double>
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP1_RP1" Left="{StaticResource HorizontalPush1}" Right="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
@@ -130,22 +131,6 @@
</ResourceDictionary>
</UserControl.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.EditPane}}">
<VisualState Name="{x:Static u:VisualStates.Active}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static u:VisualStates.Inactive}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<AdornerDecorator>
<Grid>
@@ -153,13 +138,19 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox
x:Name="_tree_view"
Grid.RowSpan="2"
ItemsSource="{Binding definitions_view}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinWidth="180"
Background="Transparent"
BorderThickness="0,0,1,0"
SelectedItem="{Binding selected_definition}"
>
<ListBox.ItemTemplate>
@@ -173,275 +164,176 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
<Grid x:Name="edit_pane">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid u:StateManager.VisualStateCollection="{Binding content_visual_states}" Margin="{StaticResource Margin.TP1}" >
<VisualStateManager.VisualStateGroups>
<!--Name-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameDefinition, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definitions."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameDefinitionWhileTemplatesPresent, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definition while templates present."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameTemplate, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename templates."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!--Abbreviation-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<VisualState Name="{x:Static Populated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Unpopulated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static NotSupportedForTemplates, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(ToolTipService.ShowOnDisabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_not_supported_for_templates_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!--Badge Selector-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="default_badge" Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="0.5"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="default_badge_radio_button" Storyboard.TargetProperty="(ToggleButton.IsChecked)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Name:</TextBlock>
<TextBox
x:Name="name_text_box"
Grid.Column="1"
ToolTipService.ShowOnDisabled="True"
Margin="{StaticResource Margin.LP1}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
/>
<Grid x:Name="abbreviation_root" Grid.Column="2">
<Grid.ToolTip>
<Grid>
<TextBlock x:Name="abbreviation_active_tooltip" Visibility="Collapsed">
<TextBlock.Inlines>
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
</TextBlock.Inlines>
<Grid x:Name="edit_pane" Grid.Column="1">
<u:StateManager.VisualState>
<Binding Path="selected_definition">
<Binding.Converter>
<cv:ValueMap Default="{x:Static u:VisualStates.Active}">
<cv:KeyValue Key="{x:Null}" Value="{x:Static u:VisualStates.Inactive}"/>
</cv:ValueMap>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState Name="{x:Static u:VisualStates.Active}"/>
<VisualState Name="{x:Static u:VisualStates.Inactive}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="algo_editor_scroll_viewer" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(FrameworkElement.ToolTip).(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ToolTip>
<ToolTip Visibility="Collapsed">
<StackPanel Orientation="Vertical">
<TextBlock FontStyle="Italic">Select an existing algo to edit from the list to the left.</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock FontStyle="Italic">Or click the</TextBlock>
<Image Source="{x:Static u:Icons.AddBoxes}" Height="20" Margin="2,0"/>
<TextBlock FontStyle="Italic"> button to create a new one.</TextBlock>
</StackPanel>
</StackPanel>
</ToolTip>
</Grid.ToolTip>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Fill="{x:Static u:BTSColors.dialog_skirt_brush}" UseLayoutRounding="True"/>
<Button
x:Name="custom_badge_color_picker_button"
Background="{Binding selected_definition.badge_fill}"
Margin="{StaticResource Margin.LP1}"
ToolTip="Change the icon color used for this algo."
IsEnabled="True"
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
/>
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CheckBox
x:Name="default_badge_color_checkbox"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="6,4,0,4"
IsChecked="{Binding selected_definition.custom_badge_color.enabled, Converter={StaticResource NotConverter}}"
>
<CheckBox.ToolTip>
<TextBlock>
<Run>Use the default icon color for this algo.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: the default color is that of the algo-root.</Run>
</TextBlock>
<TextBlock x:Name="abbreviation_not_supported_for_templates_tooltip" Visibility="Collapsed">
Not supported for templates.
</TextBlock>
</Grid>
</Grid.ToolTip>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
</Grid>
</Grid>
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Icon:</TextBlock>
<RadioButton
x:Name="default_badge_radio_button"
Grid.Column="1"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="{StaticResource Margin.LP1}"
GroupName="Badge"
>
<RadioButton.ToolTip>
<ToolTip DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<StackPanel Orientation="Vertical">
<TextBlock>Use the default icon color for this algo.</TextBlock>
<StackPanel TextElement.FontStyle="Italic" Orientation="Horizontal">
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
<usp:OrderTypeItem
Margin="2,0"
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
DisplayName="{Binding selected_definition.algo_root.display_name}"
/>
<TextBlock>).</TextBlock>
</StackPanel>
</StackPanel>
</ToolTip>
</RadioButton.ToolTip>
<Grid x:Name="default_badge_content_root">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Default</TextBlock>
<Rectangle
x:Name="default_badge"
Grid.Column="1"
Margin="{StaticResource Margin.LP1}"
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
Style="{StaticResource RectangleBadgeStyle}"
/>
</Grid>
</RadioButton>
<RadioButton
Grid.Column="2"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="{StaticResource Margin.LP2}"
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
GroupName="Badge"
>
<RadioButton.ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</CheckBox.ToolTip>
<Grid x:Name="default_badge_content_root">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.ColumnSpan="3">Specify a custom icon color for this algo.</TextBlock>
<TextBlock Grid.Row="1" FontStyle="Italic">Hint: Click</TextBlock>
<TextBlock>Use Default Icon</TextBlock>
<Rectangle
Grid.Row="1"
x:Name="default_badge"
Grid.Column="1"
Margin="3,0"
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Margin="{StaticResource Margin.LP1}"
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
Style="{StaticResource RectangleBadgeStyle}"
/>
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
</Grid>
</RadioButton.ToolTip>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Custom</TextBlock>
<Button
x:Name="custom_badge_color_picker_button"
Grid.Column="1"
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Margin="{StaticResource Margin.LP1}"
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
/>
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
<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}"
/>
</Border>
</Popup>
</Grid>
</RadioButton>
</Grid>
</CheckBox>
<cp:ColorPickerControl
Grid.Row="1"
ToolTipService.ShowOnDisabled="True"
Margin="{StaticResource Margin.TP1}"
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}"
PreviewMouseDown="_on_badge_color_picker_activated"
PreviewGotKeyboardFocus="_on_badge_color_picker_activated"
/>
</Grid>
</Border>
</Popup>
<TextBox
x:Name="name_text_box"
Grid.Column="1"
u:TextBoxBehavior.TextPrompt="Name"
Margin="{StaticResource Margin.LP1}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
MinWidth="50"
IsEnabled="True"
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
ToolTip="This algo's full name."
/>
<TextBox
x:Name="abbreviation_text_box"
Grid.Column="2"
u:TextBoxBehavior.TextPrompt="Abbrev."
VerticalAlignment="Center"
Margin="{StaticResource Margin.LP1_RP1}"
MinWidth="50"
IsEnabled="True"
Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"
>
<TextBox.ToolTip>
<TextBlock>
<TextBlock.Inlines>
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
</TextBlock.Inlines>
</TextBlock>
</TextBox.ToolTip>
</TextBox>
<ScrollViewer x:Name="algo_editor_scroll_viewer" Grid.Row="1" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto">
<usp:AlgoEditorView
Grid.Row="2"
DataContext="{Binding selected_definition}"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
/>
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
<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">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</ScrollViewer>
</Grid>
<c:DialogSkirt
Grid.Row="2"
Grid.Column="1"
BorderThickness="0"
>
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
Save Algo
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding revert_cmd_tooltip}">
Revert Algo
</Button>
</StackPanel>
</Grid>
</ScrollViewer>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -19,6 +19,17 @@ namespace mbts.ui.spark {
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
}
void _on_badge_color_picker_activated(object sender, MouseButtonEventArgs e) =>
_on_badge_color_picker_activated();
void _on_badge_color_picker_activated(object sender, KeyboardFocusChangedEventArgs e) =>
_on_badge_color_picker_activated();
void _on_badge_color_picker_activated() {
if (DataContext is AlgoStudioVM vm && vm.selected_definition != null)
vm.selected_definition.custom_badge_color.enabled = true;
}
public static class VisualStateNames {
public static class EditPane {
public const string GroupName = nameof(EditPane);
@@ -44,8 +55,7 @@ namespace mbts.ui.spark {
public static readonly string
Populated = $"{GroupName}{nameof(Populated)}",
Unpopulated = $"{GroupName}{nameof(Unpopulated)}",
NotSupportedForTemplates = $"{GroupName}{nameof(NotSupportedForTemplates)}";
Unpopulated = $"{GroupName}{nameof(Unpopulated)}";
}
public static class BadgeSelector {
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -68,23 +69,12 @@ namespace mbts.spark {
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
// 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}'"));
// save
if (_ui_configuration.can_save_specific) {
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_definition is AlgoDefinitionVM d)
_vm.save_definition(d).ignore();
}, content_is_dirty_src));
}
// save all
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.CopyBoxes, _vm.clone_selected_definition_cmd));
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
show_in_toolbar = _ui_configuration.can_save_specific
});
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
// undo/redo
if (_ui_configuration.can_undo_redo) {
// undo
@@ -117,30 +107,11 @@ namespace mbts.spark {
_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) {
string prompt; MessageBoxImage icon;
if (d.key.mode == AlgoKey.Mode.Persisted) {
prompt = $"Are you sure you want to delete '{d.name}?'";
icon = MessageBoxImage.Question;
} else {
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
prompt = null;
icon = default;
}
if (prompt != null) {
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
switch (resp) {
case BTSMessageBoxButtons.Yes: break;
case BTSMessageBoxButtons.No:
default: return;
}
}
_vm.remove_definition(d).ignore();
}
});
_commands.Add(remove_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
vm => vm != null,
out var remove_cmd_observer)));
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
}
void _dispose_core() {
@@ -149,4 +120,4 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
}
}
}
}
@@ -6,7 +6,6 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
@@ -16,7 +15,6 @@ namespace mbts.spark {
readonly AdminModel _admin_model;
readonly ErrorReporter _error_reporter;
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
IDisposable _dispose;
[ImportingConstructor]
@@ -34,17 +32,13 @@ 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 IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving definition");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
return null;
@@ -53,23 +47,22 @@ namespace mbts.spark {
public async Task<bool> remove_definition(AlgoKey definition_key) {
var req = new AlgoDefinitionRemoveRequest();
_set_key(ref req, definition_key);
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(() => _service.remove_algo_definition_async(req), r => r.Success, r => r.ErrorMessage, "remove template");
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(
() => _service.remove_algo_definition_async(req),
r => r.Success,
r => r.ErrorMessage,
"remove definition");
return success;
}
public Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) => Task.FromResult((AlgoTemplateData?)null);
public Task<bool> remove_template(AlgoKey key) =>
Task.FromResult(false);
public void Dispose() =>
Disposable.dispose(ref _dispose);
static AlgoDefinition _template(AlgoData definition_data) =>
static AlgoDefinition _api_definition(AlgoData definition_data) =>
new AlgoDefinition { Name = definition_data.name, Definition = definition_data.value.CloneToMutable() };
static AlgoDefinitionImmutable _templatei(AlgoData definition_data) =>
new AlgoDefinitionImmutable(_template(definition_data));
static AlgoDefinitionImmutable _api_definitioni(AlgoData definition_data) =>
new AlgoDefinitionImmutable(_api_definition(definition_data));
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
AlgoData.from_record(recordi);
@@ -99,11 +92,5 @@ namespace mbts.spark {
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
}
}
void _report_error(string present_progressive_desc, string msg) =>
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
}
}
@@ -7,13 +7,9 @@ using System.Threading.Tasks;
namespace mbts.spark {
interface IAlgoStudioModel {
IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key);
IAlgoDefinitionMetadataService metadata_service { get; }
Task<AlgoData?> save_definition(AlgoData definition_data);
Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data);
Task<bool> remove_definition(AlgoKey definition_key);
Task<bool> remove_template(AlgoKey template_key);
bts.config.AlgoStudioOptions ui_configuration { get; }
}
@@ -68,7 +68,13 @@ namespace mbts.spark {
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
key = def_data.key;
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
// badge color picker palettes
custom_badge_color_palettes.AddRange(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
var root_algo_color_item = new NamedColorVM(new NamedColor { name = "(Root Algo Type)" }, false);
var algo_type_color_palette = new ColorPaletteVM(ColorPalette.factory_order_colors_palette(model.factory));
algo_type_color_palette.named_colors.Insert(0, root_algo_color_item);
custom_badge_color_palettes.Insert(0, algo_type_color_palette);
//
_version = new VersionContainer<Snapshot>(_snapshot);
var conf = _model.algo_model.ui_configuration;
// init backing props
@@ -91,6 +97,7 @@ namespace mbts.spark {
_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;
custom_badge_color.enabled_changed += on_use_custom_badge_color_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
@@ -103,16 +110,13 @@ namespace mbts.spark {
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
_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()))),
_algo_root.observe_current(true).SubscribeEach(vm => {
if (vm != null) {
root_algo_color_item.assign_color(vm.meta.Color.argb_color());
return vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute());
}
return null;
}),
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
Disposable.Create(() => {
_can_redo.Changed -= on_can_redo_changed;
@@ -153,6 +157,10 @@ namespace mbts.spark {
// local methods
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
void on_use_custom_badge_color_changed() {
if (!custom_badge_color.enabled && algo_root != null)
custom_badge_color.value = algo_root.meta.Color.argb_color();
}
void on_local_changed() {
_version.increment_version();
if (algo_root != null)
@@ -190,6 +198,7 @@ namespace mbts.spark {
public AlgoDisplayGroup group => key.display_group();
public bool can_redo => _can_redo.Value;
public bool can_undo => _can_undo.Value;
//public bool use_custom_badge_color
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
public Brush badge_fill => _badge_fill;
@@ -13,7 +13,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;
@@ -82,18 +81,19 @@ namespace mbts.spark {
_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()
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
} }),
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) {
val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
}
}),
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") :
(def.is_dirty ? $"Revert changes to '{def.name}'" : "No changes to revert") :
null;
}
async Task save_async(IAlgoStudioContentVM content) {
@@ -105,7 +105,7 @@ namespace mbts.spark {
if (def != null) {
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 ? $"Save changes to '{def.name}'" : "No changes to save";
}
}
return null;
@@ -11,7 +11,7 @@
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<UserControl.Resources>
<ResourceDictionary>
@@ -9,7 +9,7 @@
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<UserControl.Resources>
<ResourceDictionary>
@@ -5,6 +5,7 @@ using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.collections;
using mbts.persist;
using mbts.spark.filters;
@@ -330,6 +331,27 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(typeof(Row),
new string[] {
nameof(security),
nameof(order_type),
nameof(root_algo_display_name),
nameof(side),
nameof(fill_time),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(size),
nameof(price),
nameof(fill_kind),
nameof(fill_status),
nameof(fill_key),
nameof(order_key),
nameof(security_key)
});
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
}
@@ -31,5 +31,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row, FillsView.Columns> { }
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row> { }
}
@@ -3,6 +3,8 @@ using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -24,6 +26,7 @@ namespace mbts.spark {
FillsVM _vm;
FillsView _view;
FillsSettings _state;
SparkMenuDisplayVM _menu_display_vm;
IReadOnlyList<SparkCommand> _commands;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
@@ -38,7 +41,10 @@ namespace mbts.spark {
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state()) {
menu_display = _menu_display_vm.safe_create()
};
public override double MinWidth =>
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -50,10 +56,13 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
@@ -70,7 +79,8 @@ namespace mbts.spark {
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
})),
_menu_display_vm.spark_cmd
};
}
@@ -78,5 +88,8 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
}
}
@@ -21,6 +21,8 @@ namespace bts.shell.fills {
public FillsFilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
public FillsSettings() { }
public FillsSettings(uint[] orders) : this(orders, null, null) { }
@@ -1,15 +1,18 @@
using bts.api.orderman;
using mbts.ui;
using bts.collections;
using bts.persist.algo;
using bts.proto;
using bts.utils;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using bts.proto;
using mbts.ui.spark;
using System.Collections.Generic;
using System.ComponentModel;
using mbts.ui.settings;
namespace mbts.spark {
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
@@ -182,33 +185,97 @@ namespace mbts.spark {
_type_hash;
}
[Flags]
enum LadderFavoriteAlgoFlags {
Sticky = 1,
AutoRevert = 2,
}
/// <summary>
/// This struct contains properties for a single algo favorite item.
/// The various <see cref="AlgoPersistedKey"/> arrays in <see cref="LadderFavoriteAlgosSettings"/>,
/// can be used to create a collection of <see cref="LadderFavoriteAlgoItem"/>s.
/// In fact, the method <see cref="LadderFavoriteAlgosSettings.to_algo_items"/> does just that.
/// </summary>
readonly struct LadderFavoriteAlgoItem : IEquatable<LadderFavoriteAlgoItem> {
public readonly AlgoKey key;
public readonly int sort_index;
public readonly LadderFavoriteAlgoFlags flags;
public LadderFavoriteAlgoItem(AlgoKey key, int sort_index, LadderFavoriteAlgoFlags flags) {
this.key = key;
this.sort_index = sort_index;
this.flags = flags;
}
public bool Equals(LadderFavoriteAlgoItem other) =>
key == other.key &&
flags == other.flags;
public override bool Equals(object obj) =>
obj is LadderFavoriteAlgoItem other && Equals(other);
public override int GetHashCode() =>
key.GetHashCode();
}
[ProtoContract]
struct LadderFavoriteAlgosSettings : IEquatable<LadderFavoriteAlgosSettings> {
[ProtoMember(1)]
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
readonly AlgoPersistedKey[] _favorite_algos;
[ProtoMember(2)]
public readonly int column_count;
[ProtoMember(3)]
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
readonly AlgoPersistedKey[] _stickied_algos;
[ProtoMember(4)]
readonly AlgoPersistedKey[] _auto_revert_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, AlgoKey[] stickied_algos, AlgoKey[] auto_revert_algos)
: this(ArrayHelper.safe_convert(favorite_algos, k => new AlgoPersistedKey(k.persisted)),
ArrayHelper.safe_convert(stickied_algos, k => new AlgoPersistedKey(k.persisted)),
ArrayHelper.safe_convert(auto_revert_algos, k => new AlgoPersistedKey(k.persisted)),
column_count) { }
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
LadderFavoriteAlgosSettings(AlgoPersistedKey[] favorite_algos, AlgoPersistedKey[] stickied_algos, AlgoPersistedKey[] auto_revert_algos, int column_count) {
_favorite_algos = favorite_algos;
this.column_count = column_count;
_stickied_algos = stickied_algos;
_auto_revert_algos = auto_revert_algos;
this.column_count = column_count;
}
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 IReadOnlyList<AlgoKey> favorite_algos => _favorite_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
public IReadOnlyList<AlgoKey> stickied_algos => _stickied_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
public IReadOnlyList<AlgoKey> auto_revert_algos => _auto_revert_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
/// <summary>
/// A more sane representation of the data contained in this proto,
/// so callers don't need to join the various key lists together to determine which algos are sticky, auto revert, etc.
/// </summary>
/// <returns>An immutable dictionary of favorite algo data.</returns>
public ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> build_map() {
var map = ImmutableDictionary.CreateBuilder<AlgoKey, LadderFavoriteAlgoItem>();
if (_favorite_algos != null) {
var stickied_algo_set = new HashSet<AlgoKey>(stickied_algos);
var auto_revert_algo_set = new HashSet<AlgoKey>(auto_revert_algos);
for (int i = 0; i < _favorite_algos.Length; i++) {
var pk = _favorite_algos[i];
var k = pk.to_algo_key();
var flags = (LadderFavoriteAlgoFlags)0;
if (stickied_algo_set.Contains(k))
flags |= LadderFavoriteAlgoFlags.Sticky;
if (auto_revert_algo_set.Contains(k))
flags |= LadderFavoriteAlgoFlags.AutoRevert;
map[k] = new LadderFavoriteAlgoItem(k, i, flags);
}
}
return map.ToImmutable();
}
public bool Equals(LadderFavoriteAlgosSettings other) =>
column_count == other.column_count &&
favorite_algos.SequenceEqual(other.favorite_algos) &&
stickied_algos.SequenceEqual(other.stickied_algos);
stickied_algos.SequenceEqual(other.stickied_algos) &&
auto_revert_algos.SequenceEqual(other.auto_revert_algos);
public override bool Equals(object obj) =>
EqualityComparer.equals_obj(this, obj);
@@ -217,7 +284,8 @@ namespace mbts.spark {
EqualityComparer.aggregate_hash(
column_count,
EqualityComparer.aggregate_hash_limited(favorite_algos),
EqualityComparer.aggregate_hash_limited(stickied_algos));
EqualityComparer.aggregate_hash_limited(stickied_algos),
EqualityComparer.aggregate_hash_limited(auto_revert_algos));
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
@@ -281,7 +349,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, null, null);
show_warn_on_cross = _default_show_warn_on_cross;
enforce_max_clip = _default_enforce_max_clip;
status_message_visible = true;
@@ -70,7 +70,11 @@ namespace bts.shell {
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);
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(
favorite_algos_array,
state.settings.favorite_algos.column_count,
favorite_algos_array,
state.settings.favorite_algos.auto_revert_algos.ToArray());
}
return load(state);
}
@@ -268,14 +268,15 @@ namespace mbts.spark {
}
}
// on Stop/Limit order updates
// When not triggered, allow for modification to StopPrice
// else treat modification as normal (Price)
public async void try_set_price(OrderRecordImmutable order, double new_price) {
// don't change stop or stop_limits => server crash
if (order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.StopLimit) return;
if (order.working_quantity() <= 0) return;
_phantom_price = new_price;
RaisePropertyChanged(nameof(price));
++_n_active_price_change_ops;
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity)) {
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity, null)) {
// wait 3 secs, then give up if price change still hasn't taken effect
await Task.Delay(3000);
}
@@ -15,6 +15,7 @@ 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;
@@ -746,15 +747,17 @@ 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;
ImmutableHashSet<AlgoPersistedKey>
_sticky_algo_keys,
_auto_revert_algo_keys;
IDisposable _algo_data_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));
_dispose = Disposable.Create(() => Disposable.dispose(ref _algo_data_hook));
}
public ObservableCollection<FavoriteAlgoItem> items { get; }
@@ -771,32 +774,47 @@ 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);
})));
_algo_data_hook?.Dispose();
_sticky_algo_keys = ImmutableHashSet.CreateRange(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
_auto_revert_algo_keys = ImmutableHashSet.CreateRange(value.auto_revert_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
var sticky_algos = _sticky_algo_keys;
var auto_revert_algos = _auto_revert_algo_keys;
_algo_data_hook = ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
x.selector.subscribe_value().Subscribe(algo => {
if (algo != null && algo.key.is_persisted) {
var pk = new AlgoPersistedKey(algo.key.persisted);
// sticky
x.is_sticky = _sticky_algo_keys.Contains(pk);
sticky_algos = sticky_algos.Remove(pk);
// auto revert
x.auto_revert = _auto_revert_algo_keys.Contains(pk);
auto_revert_algos = auto_revert_algos.Remove(pk);
if (sticky_algos.Count == 0 && auto_revert_algos.Count == 0)
Disposable.dispose(ref _algo_data_hook);
}
}));
}
public LadderFavoriteAlgosSettings create() =>
new LadderFavoriteAlgosSettings(_raw_items
public LadderFavoriteAlgosSettings create() {
return new LadderFavoriteAlgosSettings(_raw_items
.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,
// sticky
build_filtered_algo_array(fai => fai.is_sticky),
// auto revert
build_filtered_algo_array(fai => fai.auto_revert));
AlgoKey[] build_filtered_algo_array(Func<FavoriteAlgoItem, bool> filter) =>
_raw_items
.Where(fai => fai.is_sticky)
.Where(filter)
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray());
.ToArray();
}
FavoriteAlgoItem _item_factory() =>
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
@@ -809,7 +827,9 @@ namespace mbts.spark {
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
bool _is_sticky;
bool
_auto_revert,
_is_sticky;
protected FavoriteAlgoItem() { }
@@ -820,6 +840,7 @@ namespace mbts.spark {
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
public bool auto_revert { get => _auto_revert; set => Set(ref _auto_revert, value); }
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
@@ -11,6 +11,7 @@ using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@@ -34,7 +35,6 @@ 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, _show_status_message;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double
_position,
_day_position,
@@ -49,6 +49,7 @@ namespace mbts.spark {
IEqualityComparer<double> _equality;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
@@ -168,17 +169,9 @@ namespace mbts.spark {
foreach (var os in _cache.Values) {
var wo = os.order;
var slot_price_target = wo.price();
var slot_price_target = wo.ui_price();
var order_type = wo.order_type();
var filled = wo.filled();
if (order_type == CustomOrderDefinition.CustomOneofCase.Stop) {
// if the stop order has been submitted but hasn't been triggered
// we draw the badge at the stop/trigger price, else the price price
if (filled == 0) { slot_price_target = wo.stop_price(); }
} else if (order_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
// consistent behavior as Stop order above.
if (filled == 0) { slot_price_target = wo.stop_limit_price(); }
}
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
@@ -239,8 +232,8 @@ namespace mbts.spark {
.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);
_favorite_algo_map = s.favorite_algos.build_map();
order_properties_vm.on_favorite_algos(_favorite_algo_map, s.favorite_algos.column_count);
}),
order_properties_vm,
position_src.OnDispatcher().Subscribe(pos => {
@@ -409,9 +402,9 @@ namespace mbts.spark {
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.price();
double p = order.ui_price();
foreach (var o in lineage(order.ParentKey)) {
if (_equality.Equals(p, o.price()))
if (_equality.Equals(p, o.ui_price()))
order = o;
}
return order;
@@ -510,10 +503,16 @@ namespace mbts.spark {
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;
if (fav_algo != null) {
if (_favorite_algo_map.TryGetValue(fav_algo.key, out var d)) {
if (!d.flags.HasFlag(LadderFavoriteAlgoFlags.Sticky) && !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;
}
}
if (d.flags.HasFlag(LadderFavoriteAlgoFlags.AutoRevert))
fav_algo.algo_root.revert_all_params();
}
}
}
@@ -28,7 +28,8 @@ using System.Windows.Media.Animation;
namespace mbts.spark {
partial class LadderGridHost : Control {
const int _min_row_count = 501, _max_row_count = 2001, _column_min_width = 8;
const int _min_row_count = 501, _max_row_count = 2001;
int _row_count = _min_row_count;
readonly VisualHost _visualHost = new VisualHost();
@@ -108,6 +109,8 @@ namespace mbts.spark {
public event Action visible_columns_changed;
public static double ColumnMinWidth => 6;
public IEnumerable<ColumnType> visible_column_types =>
columns.Where(c => c.settings.show)
.Select(c => c.column_type);
@@ -572,7 +575,7 @@ namespace mbts.spark {
case ColumnType.Price: new_col_width = (int)price_column_width; break;
case ColumnType.Ask: new_col_width = (int)ask_column_width; break;
case ColumnType.Volume: new_col_width = (int)VolumeColumnWidth; break;
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, _column_min_width); break;
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, ColumnMinWidth); break;
}
int true_adj = new_col_width - (int)column.settings.width;
column.settings.width = (uint)new_col_width;
@@ -9,7 +9,7 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
TextElement.FontSize="{x:Static u:FontSizes.Small}"
>
@@ -828,6 +828,7 @@
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
</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}">
@@ -837,7 +838,12 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
<CheckBox Grid.Column="2" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}" ToolTip="Stay Enabled" Margin="4,0">
Stay Enabled
</CheckBox>
<CheckBox Grid.Column="3" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding auto_revert}" ToolTip="Auto Revert" Margin="0,0,4,0">
Auto Revert
</CheckBox>
</Grid>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
@@ -9,7 +9,7 @@
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:converters="clr-namespace:mbts.ui.converters"
xmlns:txt="clr-namespace:mbts.ui.utility.text"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Height="600"
Width="420"
Background="{x:Static mbts:BTSColors.common_background_brush}"
@@ -48,7 +48,7 @@
<Setter Property="BorderThickness" Value="0,0,1,0"/>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Height" Value="23" />
<Setter Property="MinWidth" Value="30" />
<Setter Property="MinWidth" Value="{x:Static spark:LadderGridHost.ColumnMinWidth}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
@@ -6,7 +6,7 @@
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
FocusVisualStyle="{x:Null}"
>
<UserControl.Resources>
@@ -10,7 +10,7 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
mc:Ignorable="d"
MaxHeight="240"
@@ -22,7 +22,7 @@ namespace mbts.spark {
DataContext = vm;
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
public MiniOrderBookRowView() : base() {
FocusManager.SetIsFocusScope(this, true);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
@@ -650,12 +650,13 @@ namespace mbts.spark {
ListSortDirection? _cached_sort_direction;
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
: base(Columns.LastMessage, filter, DataGridFilterKnownTemplates.Regex) {
id = key.ToString();
this.filter = filter;
Header = "Last Message";
Width = DefaultColumnWidths.LastMessage;
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
TextBinding = new Binding(nameof(OrderBookRowVM.last_msg));
ToolTipBinding = new Binding(nameof(OrderBookRowVM.last_msg));
}
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
@@ -4,6 +4,7 @@ using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.ui;
using mbts.ui.controls;
using System;
@@ -11,6 +12,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
@@ -60,6 +62,35 @@ namespace mbts.spark {
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
public event PropertyChangedEventHandler PropertyChanged;
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(OrderBookRowVM),
new string[] {
nameof(security_text),
nameof(symbol),
nameof(time_created),
nameof(last_change),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(order_type),
nameof(root_algo_display_name),
nameof(order_sub_type),
nameof(side),
nameof(quantity),
nameof(filled_quantity),
nameof(price),
nameof(avg_price),
nameof(last_price),
nameof(piq),
nameof(status),
// last message (could contains commas, so skipping)
nameof(security_key),
nameof(order_key),
nameof(parent_order_key),
});
// Core
public OrderSecurityRootPair order_tuple { get; private set; }
public OrderRecordImmutable order => order_tuple.order.order;
@@ -88,7 +119,7 @@ namespace mbts.spark {
public DateTime last_change => order.last_change();
public string last_change_text => _fmt_date(last_change);
public OrderStatus status => order.status();
public double price => order.price();
public double price => order.ui_price();
public double last_price => order.last_price();
public double avg_price => order.avg_price();
public string price_text => _fmt_price(price);
@@ -269,13 +300,14 @@ namespace mbts.spark {
if ((Columns.OrderType & dirty) != 0)
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
// quantity
if ((Columns.Size & dirty) != 0)
if ((Columns.Size & dirty) != 0) {
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
// via the Mini OB update column mechanism (vs the EditableQty column)
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
// row will no longer match the filter
prospective_quantity = quantity;
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
// via the Mini OB update column mechanism (vs the EditableQty column)
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
// row will no longer match the filter
prospective_quantity = quantity;
}
// price
if ((Columns.Price & dirty) != 0) {
RaisePropertyChanged(nameof(price));
@@ -414,7 +446,7 @@ namespace mbts.spark {
dirty |= Columns.LastPrice;
if (oldval.order.order.avg_price() != newval.order.order.avg_price())
dirty |= Columns.AvgFilled;
if (oldval.order.order.price() != newval.order.order.price())
if (oldval.order.order.ui_price() != newval.order.order.ui_price())
dirty |= Columns.Price;
if (oldval.order.order.quantity() != newval.order.order.quantity())
dirty |= Columns.Size;
@@ -4,11 +4,11 @@ using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.utils.csv;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.extensions;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -31,6 +31,7 @@ namespace mbts.spark {
OrderBookVM _vm;
OrderBookView _view;
OrderBookWindowFactory _factory;
SparkMenuDisplayVM _menu_display_vm;
SparkCommand[] _commands;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
@@ -44,7 +45,14 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
public override OrderBookStartupData SaveState => _state = _vm?.create();
public override OrderBookStartupData SaveState {
get {
_state = _vm?.create();
if (_state != null)
_state.menu_display = _menu_display_vm.safe_create();
return _state;
}
}
public override string window_type => _window_type;
public override UIElement Content => _view;
@@ -63,6 +71,9 @@ namespace mbts.spark {
}
public void thread_init() {
_menu_display_vm = new SparkMenuDisplayVM();
if (_state != null)
_menu_display_vm.load_from(_state.menu_display);
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
_vm.load_from(_state);
_view = new OrderBookView(_vm);
@@ -184,6 +195,7 @@ namespace mbts.spark {
show_grid_settings_command.group = CommandGroups.Auxiliary;
_commands = new SparkCommand[] {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
toggle_show_right_panel_command,
attach_selected_orders_command,
detach_selected_orders_command,
@@ -197,7 +209,8 @@ namespace mbts.spark {
show_grid_settings_command,
// hidden cmds (i.e., keyboard shortcuts at window level)
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders))
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
_menu_display_vm.spark_cmd
};
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
@@ -225,11 +238,13 @@ namespace mbts.spark {
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
protected override void OnParentViewCreated(SparkWindowView parent_view) {
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
_vm.is_attaching_orders = false;
}));
_menu_display_vm.safe_bind_to_view(parent_view);
}
static class CommandGroups {
public const string
@@ -144,7 +144,7 @@ namespace bts.shell.orderbook {
pb.add_discrete_values(fs.symbols, o => o.order.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_price(fs.price, o => o.order.order.ui_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());
@@ -65,6 +65,9 @@ namespace bts.shell.orderbook {
// use defaults
[ProtoMember(25)]
public bool use_defaults;
// menu display
[ProtoMember(27)]
public SparkMenuDisplaySettings menu_display { get; set; }
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
@@ -8,7 +8,7 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
InactiveSelectionOpacity="0.3"
>
<c:DIYGrid.Resources>
@@ -99,7 +99,7 @@ namespace mbts.spark {
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
public OrderBookRowView() : base() { }
}
}
@@ -1,5 +1,7 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:ie="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ob="clr-namespace:bts.shell.orderbook"
xmlns:sp="clr-namespace:mbts.spark"
@@ -430,11 +432,11 @@
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{StaticResource ControlBackground}"
>
<!--<i:Interaction.Triggers>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeave">
<ie:ChangePropertyAction TargetName="OverflowPopup" PropertyName="IsOpen" Value="False"/>
</i:EventTrigger>
</i:Interaction.Triggers>-->
</i:Interaction.Triggers>
<StackPanel Orientation="Vertical">
<MenuItem
c:RadioButtonGroupManager.Manager="{Binding Path='(c:RadioButtonGroupManager.Manager)', RelativeSource={RelativeSource AncestorType=ToolBar}}"
@@ -11,7 +11,7 @@
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:ut="clr-namespace:mbts.ui.utilities"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ob="clr-namespace:bts.shell.orderbook"
Grid.Row="0"
FocusManager.IsFocusScope="True"
@@ -5,7 +5,7 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<cv:MultiValueConverterPipe x:Key="CalculatorButtonsVisualStateConverter">
<cv:BooleanNoneConverter/>
@@ -1,375 +1,388 @@
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;
}
}
}
}
using bts.api;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
using bts.spark;
using bts.utils;
using bts.view;
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;
Action<SparkWindowView> on_parent_view_created_impl = null;
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
var init_settings = settings ?? PNLSettings.Default;
var menu_display_vm = new SparkMenuDisplayVM();
menu_display_vm.load_from(init_settings.menu_display);
on_parent_view_created_impl = menu_display_vm.safe_bind_to_view;
var vm = new PNLVM(_bus, _orders_model, init_settings);
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,
menu_display: menu_display_vm.create());
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);
})),
menu_display_vm.spark_cmd
};
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>()),
menu_display_vm,
vm);
return (content, commands, get_save_state, dispose);
},
// the on_parent_view_created_impl delegate is initialized in the main create delegate,
// so it can't be passed directly as an argument because it would just be null,
// hence the proxy/wrapper delegate below.
on_parent_view_created: pview => on_parent_view_created_impl?.Invoke(pview),
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;
}
}
}
}
@@ -224,6 +224,8 @@ namespace bts.shell.pnl {
}
[ProtoMember(6)]
public PNLHeaderRowDisplay header_row_display { get; set; }
[ProtoMember(15)]
public SparkMenuDisplaySettings menu_display { get; set; }
public IReadOnlyList<Columns> anchors => _anchors ?? (_anchors = Array.Empty<Columns>());
public IReadOnlyList<PNLAnimationTriggerData> animations => _animations ?? (_animations = default_animations.ToArray());
@@ -275,7 +277,8 @@ namespace bts.shell.pnl {
IEnumerable<PNLAnimationTriggerData> animations = null,
IEnumerable<PNLAnimationTriggerData> group_header_animations = null,
IEnumerable<PNLAnimationTriggerData> header_row_animations = null,
IEnumerable<GridColumnSettings> header_columns = null) =>
IEnumerable<GridColumnSettings> header_columns = null,
SparkMenuDisplaySettings? menu_display = null) =>
new PNLSettings {
calculation_type = calculation_type ?? this.calculation_type,
header_row_display = header_row_display ?? this.header_row_display,
@@ -295,7 +298,8 @@ namespace bts.shell.pnl {
_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
grid = grid ?? this.grid,
menu_display = menu_display ?? this.menu_display
};
public bool Equals(PNLSettings other) =>
@@ -486,4 +490,4 @@ namespace bts.shell.pnl {
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> group_triggers(this PNLSettings settings) => settings.group_header_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> header_triggers(this PNLSettings settings) => settings.header_row_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
}
}
}
@@ -9,7 +9,7 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
@@ -29,6 +29,10 @@
<Color x:Key="GroupRowForegroundColor">#FF000000</Color>
<!--Converters-->
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
<cv:ValueConverterPipe x:Key="GroupRowHeightConverter">
<cv:RangeBlockConverter Minimum="{x:Static pnl:PNLView.MinGroupRowHeight}"/>
<cv:GridLengthConverter UnitType="Pixel"/>
</cv:ValueConverterPipe>
<cv:MultiValueConverterPipe x:Key="GroupRowBackgroundConverter">
<cv:PartialConverter/>
<cv:ColorToBrushConverter/>
@@ -258,7 +262,7 @@
</Setter>
</Style>
<Style x:Key="RowTextCell" TargetType="c:TextCell">
<Style x:Key="TextCellBase" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
@@ -269,10 +273,14 @@
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
</Style>
<Style x:Key="RowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -284,20 +292,7 @@
</Setter>
</Style>
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -309,21 +304,8 @@
</Setter>
</Style>
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -335,7 +317,22 @@
</Setter>
</Style>
<Style x:Key="HyperlinkCell" TargetType="c:HyperlinkCell">
<Style x:Key="HyperlinkCellBase" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{Binding border}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
@@ -349,41 +346,23 @@
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter Property="Cursor">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
<c:CachedColumnParam SourcePropertyName="font_family" TargetProperty="c:HyperlinkCell.FontFamily"/>
<c:CachedColumnParam SourcePropertyName="font_size" TargetProperty="c:HyperlinkCell.FontSize"/>
<c:CachedColumnParam SourcePropertyName="font_brush" TargetProperty="c:HyperlinkCell.Foreground"/>
<c:CachedColumnParam SourcePropertyName="horizontal_alignment" TargetProperty="c:HyperlinkCell.HorizontalContentAlignment"/>
</x:Array>
<Binding Path="IsMouseOverClickArea" RelativeSource="{RelativeSource Self}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Cursors.Arrow}" ValueForTrue="{x:Static Cursors.Hand}"/>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{Binding border}"/>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="{x:Static c:DIYGrid.AnimatableCellPadding}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
</Style>
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Setter Property="Padding" Value="{x:Static c:DIYGrid.AnimatableCellPadding}"/>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
@@ -403,24 +382,9 @@
</Setter>
</Style>
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -454,23 +418,7 @@
</Setter>
</Style>
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
@@ -506,7 +454,7 @@
<Grid
c:DIYGrid.SyncWithColumnGeometrySource="{Binding ElementName=_grid}"
c:DIYGrid.SyncWithColumnGeometry="True"
Height="{Binding RowHeight, ElementName=_grid}"
Height="{Binding RowHeight, ElementName=_grid, Converter={x:Static pnl:PNLView.HeaderRowHeightConverter}}"
>
<Grid.Visibility>
<Binding Path="header_row_display">
@@ -609,7 +557,6 @@
c:DIYGrid.CachedColumnParamsContainer="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"
VirtualizingPanel.ScrollUnit="Pixel"
ScrollViewer.CanContentScroll="True"
RowHeight="24"
Rows="{Binding rows_view}"
SelectionHighlight="{Binding selection_highlight}"
SupportGroupItems="True"
@@ -1053,7 +1000,7 @@
<!--Row Template-->
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}">
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}" Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}">
<Grid.Background>
<SolidColorBrush Color="{Binding DataContext.grid_background_color, RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
</Grid.Background>
@@ -1147,7 +1094,7 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GridLengthPixelConverter}}"/>
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GroupRowHeightConverter}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Gray" StrokeThickness="1"/>
@@ -1217,4 +1164,4 @@
<Rectangle Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="Gray"/>
</Grid>
</AdornerDecorator>
</UserControl>
</UserControl>
@@ -6,6 +6,7 @@ using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utility;
@@ -31,6 +32,10 @@ namespace bts.shell.pnl {
}
partial class PNLView : UserControl {
public static readonly double
MinGroupRowHeight = 24.0.RoundLayoutValue(),
MinHeaderRowHeight = 24.0.RoundLayoutValue();
public enum Columns {
Account = 1,
// Security
@@ -85,6 +90,7 @@ namespace bts.shell.pnl {
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)),
HeaderRowHeightConverter = new RangeBlockConverter<double> { Minimum = MinHeaderRowHeight },
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
public static readonly IMultiValueConverter
@@ -163,17 +169,22 @@ namespace bts.shell.pnl {
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
});
rp.SetBinding(MarginProperty,
BindingHelper.create_multi(
ValueConverter.create<PNLHeaderRowDisplay, double, Thickness>((hrd, row_height) => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += row_height;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += row_height;
return new Thickness(0, top, 0, 0);
}),
new Binding(nameof(PNLVM.header_row_display)),
new Binding {
Path = new PropertyPath(DIYGrid.RowHeightProperty),
Converter = HeaderRowHeightConverter,
Source = _grid
}));
}
}
#endregion
+3 -1
View File
@@ -54,7 +54,8 @@ namespace mbts.spark {
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
model.send_order(security, properties_vm.create(side), price, size_calculator.value.Value).ignore();
RequestClose?.Invoke(this, new EventArgs());
if (close_on_send)
RequestClose?.Invoke(this, new EventArgs());
}
});
set_price_command = new DelegateCommand<double>(set_price);
@@ -84,6 +85,7 @@ namespace mbts.spark {
public CalculatorLogic size_calculator { get; }
public OrderPropertiesEditorVM properties_vm { get; }
public BBOVM bbo { get; }
public bool close_on_send { get; set; }
public ICommand send_order { get; }
public ICommand set_price_command { get; }
@@ -6,7 +6,7 @@
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:sys="clr-namespace:System;assembly=System.Runtime">
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
Loaded 100 of 317 files, more files were not shown because too many files have changed in this diff. Show more