Compare commits

...
Author SHA1 Message Date
Michael Harding 189157b1c7 initialize_for_test changes (thanks eric) 2019-06-13 14:50:24 -04:00
Michael Harding 64fd8addb2 Update Lean qty factor shell script 2019-06-13 09:38:11 -04:00
Michael Harding 4490ac303e SPARK-2438: Lean Qty test 2019-06-12 15:41:16 -04:00
Michael Harding 666bd07411 Merge branch 'master' into spreader_lean_qty 2019-06-10 12:56:44 -04:00
Pedro Pinto 0ff4624778 Updated release notes. 2019-06-10 11:05:59 -04:00
Taha Afzal ab04b4afab Merged in SPARK-2505_qho_ib_1 (pull request #254)
RTM
2019-06-10 15:02:57 +00:00
Taha Afzal ac226ddd3b Merged in SPARK-2488-LeanAlgoBug (pull request #255)
RTM
2019-06-10 14:36:18 +00:00
Pedro Pinto 0223e43aa2 Updated release notes. 2019-06-10 10:20:51 -04:00
John Colvin 73e5cfdada Merged in SPARK-2481-fills-filter-fix (pull request #249)
SPARK-2481 fills filter fix

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-10 14:19:31 +00:00
Eric Harding 33b4b88af1 For some strange reason the gregorian calender does not start counting days at 0. Maybe 0 hadn't been invented yet. 2019-06-10 10:12:44 -04:00
Pedro Pinto 12f4b4b25b Merged with master 2019-06-10 08:49:56 -04:00
Taha Afzal 96cacb8bb9 qho's were reporting their cancelled state twice, which was causing the spreader to cancel it 2019-06-09 21:16:41 -04:00
John Colvin 300a1ed17f SPARK-2481 filtering in fills window is badly broken (2nd take) 2019-06-07 23:06:14 -05:00
John Colvin 1a0836ca6d RXCollection.connect - always assign local table in callback 2019-06-07 20:41:04 -05:00
Jake Norbut 88908543e1 Merged in SPARK-2165 (pull request #244)
RTM - Ladder Settings - hide net change group setting for synthetic ladders

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-07 21:26:37 +00:00
Pedro Pinto 80c9b6a534 Merged with master 2019-06-07 17:26:05 -04:00
Pedro Pinto f6282c0676 Updated release notes 2019-06-07 17:20:17 -04:00
Pedro Pinto 02fe83f20b Merge branch 'master' into SPARK-2165 2019-06-07 17:15:18 -04:00
John Colvin 84d666c29c Merged in SPARK-2476-OB-KeyNotFoundException (pull request #248)
RTM: SPARK-2476 OB _get_legs KeyNotFoundException

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-06-07 21:14:14 +00:00
Pedro Pinto 147e43b464 Merged with master 2019-06-07 16:58:35 -04:00
Pedro Pinto b909a6062e Updated release notes 2019-06-07 15:58:32 -04:00
Pedro Pinto 421b3af6a7 Merge branch 'master' into SPARK-2481-fills-filter-fix 2019-06-07 15:54:05 -04:00
Pedro Pinto 46a340d493 Merged in fix-fill-accounting (pull request #251)
RTM. Fix fill accounting
2019-06-07 19:52:02 +00:00
Pedro Pinto 3cc7d76a1b Merged with master 2019-06-07 15:49:10 -04:00
Jake Norbut 5123b928f2 Merged in SPARK-2480 (pull request #247)
RTM - SPARK-2480 - fixing Composite order type showing in root algo

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-07 19:40:43 +00:00
Pedro Pinto c2631e3257 Merged with master 2019-06-07 15:16:34 -04:00
Pedro Pinto 685181ae28 Merge branch 'fix-fill-accounting' of bitbucket.org:bluetradesys/spark into fix-fill-accounting 2019-06-07 13:50:58 -04:00
Pedro Pinto 929492c2f0 Made Limiter more robust for orders with undiscovered securities. 2019-06-07 13:50:44 -04:00
Pedro Pinto be023ff6cb Made Limiter more robust for orders with undiscovered securities. 2019-06-07 13:21:06 -04:00
Taha Afzal a4d63a1421 this should fix the confusion 2019-06-06 19:13:46 -04:00
Michael Harding 2fd8c39888 Merge branch 'spreader_lean_qty' of bitbucket.org:bluetradesys/spark into spreader_lean_qty 2019-06-06 16:01:56 -04:00
Michael Harding eb97fd045a SPARK-2438: spreader_lean_qty_factor test setup 2019-06-06 16:01:16 -04:00
Michael Harding a07bc152d1 SPARK-2438: init spreader_lean_qty_factor test 2019-06-06 16:01:16 -04:00
Michael Harding aef8e2dbb1 spell check manual tests 2019-06-06 16:01:16 -04:00
Pedro Pinto baac6a893d Re-directed SecurityDB's expiration to securities::expiration 2019-06-06 11:14:56 -04:00
Pedro Pinto df681ca209 Removed duplicate declaration. 2019-06-06 11:03:36 -04:00
Jake Norbut 21b34434ef Merged in SPARK-2464 (pull request #233)
RTM - SPARK-2464 - ladder cursor provides visual feedback when middle clicking over bid ask cols

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-05 21:57:43 +00:00
Pedro Pinto 0c17dbb55b Updated release notes. 2019-06-05 17:57:11 -04:00
Pedro Pinto fd6c6a3055 Merge branch 'master' into SPARK-2464 2019-06-05 17:52:29 -04:00
Pedro Pinto 259355d629 Updated release notes. 2019-06-05 17:51:22 -04:00
Michael Harding c6c2d3448d Merged in net_contracts_fix (pull request #250)
RTM. Fix lazyview updating net cotracts
2019-06-05 21:49:54 +00:00
Eric Harding f860649996 Merged in SPARK-2472-rerun (pull request #245)
RTM. SPARK-2472 rerun

* SPARK-2482 - End Jake's left handed discrimination

* SPARK-2472 add * to our order size

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-05 21:43:03 +00:00
Pedro Pinto 05c3c97713 Hid fills for expired securities. 2019-06-05 17:11:14 -04:00
Pedro Pinto f3be277484 Fixed limits double counting of spread leg fills 2019-06-05 17:10:16 -04:00
John Colvin 0ba311788c DIYGridRowsManager - expose fills/sort manager methods, so callers don't need to hang on to fills/sort manager references 2019-06-05 11:35:32 -05:00
John Colvin 341eda5962 FillsVM - rows does not need to be an OperableCollection (just a regular ObservableCollection) 2019-06-05 11:35:32 -05:00
John Colvin 3398bb5b86 FillsVM - remove unusued field '_active_comparers' 2019-06-05 11:35:28 -05:00
John Colvin dc7cacc126 get rid of hashset-vs-array optimization (doesn't actually help) 2019-06-05 10:59:43 -05:00
John Colvin 6d3d4e1f56 SPARK-2481 Filtering in fills window is badly broken 2019-06-04 18:57:36 -05:00
John Colvin 433b1abecd abstract out OrderBookFilterStates.build_predicate method for generic use 2019-06-04 18:55:41 -05:00
John Colvin 4544180c5f move 'FillsStartupData' into its own file, change typename to 'FillsSettings' 2019-06-04 18:54:43 -05:00
John Colvin 5722fcffc3 SPARK-2476 OB _get_legs KeyNotFoundException 2019-06-04 16:08:47 -05:00
Jake Norbut f1880615b1 SPARK-2480 - fixing a particular overload of the OrderSecurity constructor (root_* specific) 2019-06-04 14:08:46 -05:00
Jake Norbut e60f5678ec PR nits from chat 2019-06-04 10:22:10 -05:00
Pedro Pinto e9a70e51e4 Merged with master 2019-06-04 10:40:56 -04:00
Jake Norbut e98d18a5aa Merged in SPARK-2482 (pull request #243)
RTM - SPARK-2482 - Ladder middle click change - middle click recentering is back; alt + middle click now brings up mbo popup

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-04 14:35:16 +00:00
Pedro Pinto 513cc248b8 Updated release notes and added shift+middle click to trigger MBO
(Alt+Middle Click doesn't work very well on the linux rdesktop client)
2019-06-04 10:33:54 -04:00
Pedro Pinto c024095f75 Merged master 2019-06-04 10:02:19 -04:00
Pedro Pinto 3e5eb80a7c Updated release notes 2019-06-04 09:57:59 -04:00
Eric Harding 942dc1640e Merged in SPARK-2472 (pull request #242)
RTM: 🏭 SPARK-2472 - our orders are hard to identify

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-06-04 13:17:29 +00:00
Pedro Pinto b5c3abb782 Merged from prod 2019-06-04 09:15:36 -04:00
Pedro Pinto 3478d1ad52 Updated release notes. 2019-06-04 09:14:10 -04:00
Pedro Pinto d54e9233d1 Improved comment. 2019-06-04 09:12:11 -04:00
Jake Norbut e94759cd89 Ladder Settings - hide net change group setting for synthetic ladders 2019-06-03 15:47:54 -05:00
Jake Norbut b58da4a20e SPARK-2482 - Ladder middle click change - middle click recentering is back; alt + middle click now brings up mbo popup 2019-06-03 12:43:44 -05:00
Jake Norbut 8426399a23 SPARK-2479 - bug introduced in 8dd0709; Contract Pickers launched via the Security Picker Daemon now close on cancel button being pressed 2019-06-03 12:11:17 -05:00
Eric Harding 9b2f41c805 SPARK-2472 our orders are hard to identify in mbo 2019-05-30 17:00:09 -04:00
Eric Harding 15f96dda3e Fix mbo command line diagnostic utility 2019-05-30 16:02:07 -04:00
Michael Harding a9d5c17d63 Merged master into spreader_lean_qty 2019-05-30 14:43:53 -04:00
Michael Harding 715b629778 Set use_onixs to false; for system tests 2019-05-30 12:04:21 -04:00
Eric Harding dd82d127ea Merged in shell_crash_empty_size (pull request #241)
RTM: Fix for shell able to send 0 size orders

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-05-29 21:37:27 +00:00
Pedro Pinto f97e22d6eb Updated release notes. 2019-05-29 17:31:34 -04:00
Eric Harding f9e04ee28c Fix for shell able to send 0 size orders 2019-05-29 17:31:24 -04:00
Pedro Pinto 1ce00ca13d Fixed simulator's handling of 0 sized quantities. 2019-05-29 17:27:08 -04:00
Jake Norbut 3b9c2f5d70 adding a try/catch around the leg lookup 2019-05-28 16:58:27 -05:00
Pedro Pinto 5cd2dfec18 Fixed PIQ in GLBX Icerberg, fixed handling of stop limit. 2019-05-28 16:26:16 -04:00
Jake Norbut 31b2ba9165 PR suggestion - if statement readability 2019-05-28 14:38:27 -05:00
Jake Norbut 2b1b1f1681 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SPARK-2464 2019-05-28 13:23:17 -05:00
Pedro Pinto 4427b02d8d _index.md edited online with Bitbucket 2019-05-28 16:11:11 +00:00
Jake Norbut dcd3921379 Merged in SPARK-2460 (pull request #238)
RTM - SPARK-2460

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-05-28 15:59:45 +00:00
Jake Norbut bd156ecbed fix merge 2019-05-28 10:58:02 -05:00
Eric Harding 02f5731e1d Merged in SPARK-2462 (pull request #235)
RTM: SPARK-2462 QueryServer crash
2019-05-28 15:54:15 +00:00
Jake Norbut 7e75a71898 check root algo's can_expand to see if we should draw Cursor.No 2019-05-28 10:47:11 -05:00
Eric Harding 798c4dcd49 Merged in SPARK-2194 (pull request #236)
RTM: SPARK-2194 synthetic book display needs more digits
2019-05-28 15:29:29 +00:00
Jake Norbut 7e47d7f8a5 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SPARK-2464 2019-05-28 10:08:14 -05:00
Pedro Pinto 260ca06cde Merged in pnt_nits (pull request #240)
RTM. PnT work.
2019-05-28 14:57:57 +00:00
Jake Norbut 8615adb564 piq column to be instantiated with Right HorizontalAlignment 2019-05-28 08:18:03 -05:00
Pedro Pinto ab15d9d348 Merge branch 'master' into pnt_nits 2019-05-28 08:52:02 -04:00
Pedro Pinto 9540a15585 Updated release notes and fixed bug that brought Haris down. 2019-05-28 07:06:39 -04:00
Taha Afzal e8a467d5f1 1.Position limit check for QHO and 2. QHO crash for rejected orders 2019-05-27 16:03:44 -04:00
John Colvin 567c18f325 ManualFills - hardcode min width to 466 (hides horizontal scrollbar when window first opens) 2019-05-24 17:35:05 -05:00
John Colvin 270ab9c3f1 change 'row' to 'fill' in ImportConflicts view 2019-05-24 16:59:26 -05:00
John Colvin 9a9024c2c0 OrderBookView - order params/ladder grid splitter should be horizontally stretched (was only 5 pixels wide) 2019-05-24 16:58:52 -05:00
John Colvin 45a5049a2d fix typeo ('or' conditions should be in parenthesis) 2019-05-24 16:58:52 -05:00
Jake Norbut 8b17911da0 refining pnl test 2019-05-24 15:45:28 -05:00
Taha Afzal 61556eb534 'Algo->spreader is not sending the price over' is fixed 2019-05-24 16:43:56 -04:00
Pedro Pinto b691dbae80 Merged from master 2019-05-24 14:05:46 -04:00
Pedro Pinto f6be20ce8d Updated release notes. 2019-05-24 14:02:06 -04:00
Pedro Pinto 68428ab89d Ran clang format. 2019-05-24 13:59:31 -04:00
Pedro Pinto 12d54fdb31 Added manual test to validate spreader performance. 2019-05-24 13:58:59 -04:00
Pedro Pinto 366e13ef05 Fixes requoted performance regression introduced in commit e998edc 2019-05-24 13:56:37 -04:00
Jake Norbut e94ef6a602 Adding PIQ column to OB (and mini); more OrderSecurityAlgoMeta to OrderSecurity param changes 2019-05-24 11:53:02 -05:00
Jake Norbut 3e7f145290 switching on_order_tuples over to OrderSecurity as well so that we can toggle back and forth if necessary via config between orders w/ no piq and with those with 2019-05-24 09:43:50 -05:00
John Colvin 748a99d4ef SPARK-2425 Add day position to ladder 2019-05-23 20:22:17 -05:00
John Colvin 35b8c1e53f Ladder - settings driven properties should share 1 subscription to the settings 2019-05-23 18:38:43 -05:00
Jake Norbut 123b690c6c switch OB over to the new Observable, which means change use of OrderSecurtyAlgoMeta to OrderSecurity (which includes a piq) 2019-05-23 15:15:14 -05:00
Jake Norbut 38a65bd0e3 OrdersModel now includes an on_order_tuples_with_root_and_piq 2019-05-23 15:14:03 -05:00
John Colvin 1cb8c54225 SPARK-2454 Root algos show the incorrect color in the OB (and perhaps miniOB) 2019-05-23 14:04:18 -05:00
John Colvin 414833c0c5 normalize line endings in OrderBookSettings.cs 2019-05-23 14:03:02 -05:00
John Colvin 6cc4064cd3 normalize spacing in OrderBookRowVM.cs 2019-05-23 13:32:35 -05:00
Eric Harding 0fcdc9c493 Merged in SPARK-2474 (pull request #237)
RTM: SPARK-2474 Improve mdp3 error message for missing feed_handler

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-05-23 15:32:56 +00:00
Eric Harding 0f967ae4cd Bump up feed_engine default buffer to 256mb (support is using this as a default already) 2019-05-23 09:41:48 -04:00
Eric Harding a232d738a0 Add instructions to error message 2019-05-23 09:40:40 -04:00
Eric Harding 3d3ac4e49f SPARK-2474 Improve mdp3 error message for missing feed_handler 2019-05-23 09:24:29 -04:00
John Colvin 15a5abf1ad SPARK-2465 Use ladder middle click on bid/ask to launch parameterized order 2019-05-22 20:56:32 -05:00
Eric Harding 76218fd35a SPARK-2194 synthetic book display needs more digits 2019-05-22 17:35:10 -04:00
Pedro Pinto d0370c5dc7 Merge branch 'pnt_nits' of bitbucket.org:bluetradesys/spark into pnt_nits 2019-05-22 16:11:34 -04:00
Pedro Pinto b07700cdc7 Fixed some issues with position service and manual fill integration. 2019-05-22 16:11:13 -04:00
John Colvin 15de3bbfe9 SPARK-2456 GLBX Iceberg is a native order 2019-05-22 13:53:59 -05:00
John Colvin 8d2fa40dcc ManualFills - change placeholder row id from Guid to long, to ensure sort order (so new rows always appear at the bottom) 2019-05-22 13:18:57 -05:00
Eric Harding bced75ea50 Ubuntu 18.04 broke dev dns for mdreplay. Back to IP address. 2019-05-22 14:03:15 -04:00
Jake Norbut b48accf1f9 only iterate over workspaces that contains windows 2019-05-22 12:59:54 -05:00
Pedro Pinto 3758ac6101 Merge branch 'pnt_nits' of bitbucket.org:bluetradesys/spark into pnt_nits 2019-05-22 13:52:21 -04:00
Pedro Pinto ac45e9bdb2 WIP: manual fills in pos server. 2019-05-22 13:52:04 -04:00
John Colvin 769bad89a7 ManualFillsVM - placeholder rows weren't getting removed after they were persisted 2019-05-22 12:39:51 -05:00
John Colvin 7dc272506a ManualFills - focus 'add row' popup when it opens (hooks up the 'OK' button to the enter key) 2019-05-22 12:19:06 -05:00
John Colvin 86c787efaf ManualFills - make account field immutable for persisted rows 2019-05-22 11:42:19 -05:00
John Colvin f917f581c3 SPARK-2469 add 'account_name' field to ManualFill protobuf 2019-05-22 11:03:25 -05:00
Pedro Pinto ca1217cd64 _index.md edited online with Bitbucket 2019-05-22 14:59:05 +00:00
John Colvin 4ed4e2e5e4 Merged in backwards-compatibility (pull request #234)
Backwards compatibility

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-05-22 14:57:09 +00:00
Jake Norbut bb0171a2e4 preemptivly updating pnl test 2019-05-22 09:33:51 -05:00
John Colvin 88bf448e68 ICollectionView.subscribe_sorting_criterion extension was not notifying observers on subscribe 2019-05-21 22:25:36 -05:00
John Colvin 05a615038d ManualFills - added 'Save Selected' spark command 2019-05-21 22:24:43 -05:00
John Colvin 51cfd0e5bb ManualFillsVM initializes side to Buy' for new rows 2019-05-21 22:23:02 -05:00
John Colvin d2ee575f19 set 'size' dropdown width in ManualFillsRowEditor 2019-05-21 22:09:58 -05:00
John Colvin 0b76ffb4f3 DragDropHelper.subscribe_drag_coordinates uses 'regular' mouse events (not 'preview') 2019-05-21 21:22:09 -05:00
John Colvin 0f9b93115a ManualFills - don't access row_view.SortDescriptions in get_save_state, as it is invoked on main the thread (not the window's thread) 2019-05-21 21:17:56 -05:00
John Colvin 8cfc6a7591 normalize spacing in WPFExtensions.cs 2019-05-21 21:14:43 -05:00
John Colvin 9de2dd30bc change ObservableAutoWrapper.connect 'target' arg to IList 2019-05-21 20:37:50 -05:00
John Colvin 38005e3266 change ManualFills launcher icon to match the one in the window title/tab 2019-05-21 20:25:24 -05:00
John Colvin 26ae70f432 fix ContractPickerPopup 'CloseOnCancel' behavior 2019-05-21 20:23:17 -05:00
John Colvin f6ca8286ea SPARK-2467 fix backwards compatibility bug in orderbook settings 2019-05-21 19:05:13 -05:00
John Colvin ae471b07fa SPARK-2466 fix backwards compatibility bug in fills settings 2019-05-21 18:56:59 -05:00
Eric Harding e7cea797ba Simplify append logi 2019-05-21 16:40:07 -04:00
Eric Harding f16545dfaa SPARK-2462 Expunge tbb::concurrent_vector 2019-05-21 16:29:23 -04:00
Jake Norbut dcd62409f1 fixing merge 2019-05-21 15:22:54 -05:00
Eric Harding 11252b4300 Add concurrent test for swcv 2019-05-21 15:45:08 -04:00
Jake Norbut 90526a8bfd SPARK-2464 - ladder cursor provides visual feedback when middle clicking over bid ask cols 2019-05-21 14:18:58 -05:00
Eric Harding e815dade3a Improve SingleWriterConcurrentVector perf (push_back 30-21ns, iterate(2us to 1.3us). Now iterates faster than tbb_concurrent_vector 2019-05-21 14:21:51 -04:00
John Colvin 576be7e961 ManualFillsView - enable mouse drag behavior in new row popup 2019-05-21 12:23:03 -05:00
John Colvin 407de320f6 PopupAdorner - implement MouseDrag behavior to move popup element around 2019-05-21 12:23:03 -05:00
Jake Norbut bc0234a5e9 pnl refactoring - PnLCalculator class does nothing now, so remove it; note file still exists and has a differently named class therein 2019-05-21 10:58:23 -05:00
Jake Norbut e1229186de pnl refactoring - PnLModel didn't really do anything, so I remomed it 2019-05-21 10:45:59 -05:00
Jake Norbut a3e00217a1 pnl refactoring - moving cpp lookup, fill immutable pnl and standard pnl caclulations into helper class 2019-05-21 10:45:59 -05:00
Eric Harding 07fa55d317 Fix settingsmanager crash 2019-05-21 10:32:34 -04:00
John Colvin 16f0beb3d8 ManualFills - added conf to switch between inline/modal transactions (default to modal) 2019-05-20 19:54:10 -05:00
John Colvin d244c62c26 ManualFillsView - add 'size' column 2019-05-20 19:32:41 -05:00
John Colvin 44730204a5 fix PropertyChangedDemuxer.PropertyChannel bug on dispose 2019-05-20 19:03:20 -05:00
John Colvin bd47816d16 ManualFillsParser - don't pass null string key to dictionary 2019-05-20 15:57:58 -05:00
John Colvin 2f12f2609a added RowParser.parse_rows_checked, which supports partial rows (with missing cells) 2019-05-20 15:54:47 -05:00
John Colvin 9c489c03c6 accidentally gave the generic RowParser.AssignColumnValue<T> delegate a name specific to manual fills 2019-05-20 15:33:20 -05:00
Eric Harding 5ae8a66558 Add SingleWriterConcurrentVector 2019-05-20 15:32:48 -04:00
John Colvin 03882718a0 ManualFills - spark commands menu item groups (i.e., separators in ContextMenu) 2019-05-20 13:52:09 -05:00
John Colvin 0f21d9783b file extension filter for ManualFills import file dialog 2019-05-20 13:41:28 -05:00
John Colvin 2c547f37fb ManualFillsVM - use ObservableAggregateCollection to merge persisted/placeholder rows together 2019-05-20 13:41:28 -05:00
John Colvin a7f4944bd6 FillsVM - more functional implementation of row filters/sorting 2019-05-20 13:41:28 -05:00
Jake Norbut a3a33ffb6a vm now consumes data via position service 2019-05-20 13:13:48 -05:00
Jake Norbut b3db0fc4ca also extended to supported calculating pnl via PositionsUnit data instead of Fill data 2019-05-20 13:00:01 -05:00
Jake Norbut 0f066fdeb9 extended to supported calculating pnl via PositionsUnits instead of Fills 2019-05-20 12:58:59 -05:00
Jake Norbut 22f89490a3 adding SecurityMD lookup facility to model; this is ultimately for compatability with the PnLSummaryRow, of which a SecurityMD instance is a component of the row's key tuple 2019-05-20 12:55:09 -05:00
Jake Norbut 2137d37cbd adding SecurityImmutable? to on_positions and updating security_positions_demuxer for additional argument type 2019-05-20 12:51:24 -05:00
Jake Norbut 082a241bb9 passing the OrdersModel, from where the PositionUnit will be published from, down the line 2019-05-20 12:48:14 -05:00
Jake Norbut 0057f8f080 when determining average price, position service should use the fill's last_price() and not avg_price; using avg_price from partials leads to wildly inaccurate pnl 2019-05-20 12:29:34 -05:00
Michael Harding 83650f1e1b System tests: fix timeout, minor improvements 2019-05-20 11:13:36 -04:00
John Colvin 4768c1cd87 SPARK-2447 / SPARK-2458 import dialog 2019-05-19 17:52:03 -05:00
John Colvin 57b70684ea EnumHelper.get_values returns a cached strongly typed IReadOnlyList<T> (simple optimization) 2019-05-19 17:44:34 -05:00
John Colvin af64592f52 ReactiveThresholdMonitor use RefCountMap to support duplicate adds 2019-05-19 17:44:34 -05:00
John Colvin a1b89ccb34 change RefCountMap.keys from IEnumerable to IReadOnlyCollection 2019-05-19 17:44:34 -05:00
John Colvin 44f2a9daf7 RefCountMap implements IReadOnlyCollection 2019-05-19 17:44:34 -05:00
John Colvin 0e5833b275 SetStack - 'SoftCount' logic was incorrect 2019-05-19 17:44:34 -05:00
John Colvin bded6d334c ContractPickerPopup 'CloseOnOK' should just close the popup (not the owning window) 2019-05-19 17:44:34 -05:00
John Colvin 893c6702a2 make DictionaryExtensions.Join more efficient 2019-05-19 17:44:34 -05:00
John Colvin 627b8742c4 SPARK-2447 / SPARK-2457 main window view (for singular transactions) 2019-05-19 17:44:34 -05:00
John Colvin 1f7e0a672e fix DataGridBehavior.SingleClickEdit 2019-05-19 17:44:34 -05:00
John Colvin 29e8fd1050 PopupControl - set default background to ComboBox.Static.Background 2019-05-19 17:44:33 -05:00
John Colvin 6ce741bcaf ContractPickerPopup - set default padding=2,0 2019-05-19 17:44:33 -05:00
John Colvin 8dd070961a ContractPickerPopup - SelectedSecurity(MD) dependency props 2019-05-19 17:44:33 -05:00
John Colvin f6a3a276d9 ContractPickerPopup - load securities lazily 2019-05-19 17:44:33 -05:00
John Colvin 68c8828d4f Account/UserTuple implement IComparable 2019-05-19 17:44:33 -05:00
John Colvin 0d68ebbbea ReactiveThresholdMonitor should notify subscribers (if necessary), when subs are disposed 2019-05-19 17:44:33 -05:00
John Colvin cc04e79f8b made anonymous SparkWindow factory 2019-05-19 17:44:33 -05:00
John Colvin ac76462f7e DiscreteValuesFilterVM.CreateTableUpdate - removed unused 'row_key' param 2019-05-19 17:44:33 -05:00
John Colvin d20b30557f added ObservantAsyncCommand factory methods 2019-05-19 17:44:33 -05:00
John Colvin a6fa5f9852 default Button Template BorderThickness should use TemplateBinding (was hardcoded to 1) 2019-05-19 17:44:33 -05:00
John Colvin 0db165bf76 set wpf DataGridColumnHeader font size to 12 (matches diygrid) 2019-05-19 17:44:33 -05:00
John Colvin 705fd293ba 'find_ancestor_of_type()' does not check the same ancestor obj twice 2019-05-19 17:44:33 -05:00
Michael Harding 523002ac56 Fix System tests 2019-05-17 16:57:24 -04:00
Pedro Pinto 9c25a8751e Updated release notes. 2019-05-17 15:38:42 -04:00
Pedro Pinto 89fc99b59a Updated docker subrepo ptr 2019-05-17 15:37:52 -04:00
Eric Harding cd413d10ab Merged in SPARK-2363 (pull request #231)
RTM: SPARK-2363 - PIQ popup

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-05-17 19:35:39 +00:00
Eric Harding 1c5cc9c1bb Merged in SPARK-2455-crash-on-md-startup-server-si (pull request #232)
SPARK-2455 crash on sim startup

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-05-17 19:33:53 +00:00
Eric Harding 7965743a80 SPARK-2455 crash on sim startup 2019-05-17 15:20:53 -04:00
Pedro Pinto 2172f007a8 Fixed a few bugs in GLBX Iceberg order. 2019-05-17 15:19:11 -04:00
Michael Harding c9db5d13a9 System tests wait for system ready 2019-05-17 13:36:21 -04:00
Eric Harding 13e911760d Middle click off popup shoudl close popup 2019-05-17 11:46:06 -04:00
Eric Harding a12c3128a3 Don't allow mbo popup index number to flicker width 2019-05-17 11:45:22 -04:00
Eric Harding 6bb84b65a8 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SPARK-2363 2019-05-17 10:34:11 -04:00
Eric Harding 6fb1140a97 test review feedback 2019-05-17 10:29:12 -04:00
Eric Harding 0e449844ca Clarify this point 2019-05-17 10:25:28 -04:00
Eric Harding 476206edb0 add piq/mbo manual test 2019-05-17 10:21:12 -04:00
Eric Harding 3483ac0712 Code review feedback 2019-05-17 09:03:49 -04:00
Eric Harding d6e586c4ce SPARK2452 - Update release notes for MBO 2019-05-16 16:44:18 -04:00
Eric Harding 0f1735fba2 SPARK-2453 Do not end order on bid/ask middle click 2019-05-16 16:39:50 -04:00
Eric Harding e5254dcf7f btree_set benchmarks are veerry interesting 2019-05-16 15:40:17 -04:00
Eric Harding 7bd183c8a4 btree_set benchmarks are very interesting 2019-05-16 15:29:59 -04:00
Michael Harding aaf09b197b Fix sdk docs 2019-05-16 15:16:49 -04:00
Eric Harding 6081128b92 move sparkvm launch to the top of the debug menu 2019-05-16 15:11:26 -04:00
Eric Harding 6d6ecb33ba SPARK-2450 Simulator MBO realism ™ 2019-05-16 15:08:51 -04:00
Michael Harding 6e85612adc deleting the right files this time 2019-05-16 14:01:35 -04:00
Eric Harding f33a13bfc8 Wire mbo popup to ladder in order book 2019-05-16 13:36:19 -04:00
Jake Norbut c4eeac8d0a fixing ServiceTail time formatting option 2019-05-16 12:07:49 -05:00
Michael Harding 432f39a1fa Don't delete everything 2019-05-16 12:54:39 -04:00
Eric Harding 6084da64a2 merge master 2019-05-16 12:18:45 -04:00
Jake Norbut 5ae484e6c1 adding comment to method 2019-05-16 10:07:59 -05:00
Eric Harding a2d4eb7d10 SPARK-2444 MBO Polish 2 - the Scrollening 2019-05-16 10:43:23 -04:00
Michael Harding 8082ab12dd Delete everything 2019-05-16 10:37:00 -04:00
Jake Norbut b1def915d6 Merged in SPARK-2449 (pull request #229)
SPARK-2449 - if the badge only represents 1 order, that order's piq has to be the min piq
2019-05-15 23:04:56 +00:00
Jake Norbut 70bb22c966 SPARK-2449 - if the badge only represents 1 order, that order's piq has to be the min piq 2019-05-15 17:39:47 -05:00
Eric Harding 06520791fa SPARK-2444 MBO UI polish 2019-05-15 17:19:46 -04:00
Pedro Pinto 9d762e0777 Merge branch 'pnt_nits' of bitbucket.org:bluetradesys/spark into pnt_nits 2019-05-15 17:02:41 -04:00
Pedro Pinto ef53c70c7e Fixed some bugs in GLBX Iceberg order. 2019-05-15 17:02:25 -04:00
Jake Norbut 8cf4e37310 adding preliminary create, update, delete of manual fills into position service 2019-05-15 14:58:46 -05:00
Pedro Pinto 04e7180b69 Merge branch 'master' into pnt_nits 2019-05-15 15:40:45 -04:00
Jake Norbut d40d3615b2 mutable props that RaisePropertyChanged 2019-05-15 13:41:05 -05:00
Jake Norbut 4efec939aa Security to Nullable, props now have public setters; inherit from VMB 2019-05-15 13:28:55 -05:00
Jake Norbut a0f24d8227 making use of syntactic sugar for less typing 2019-05-15 13:19:37 -05:00
Jake Norbut a2278423f6 users to non nullable for ItemSource 2019-05-15 13:14:15 -05:00
Jake Norbut 7321d5d8f7 filling out account and user, creating ObservableCollections for them to be used as ItemSources 2019-05-15 13:13:29 -05:00
Jake Norbut 464426537e making use of new ManualFillTuple 2019-05-15 12:35:59 -05:00
Michael Harding ebb2d5618c Merge branch 'master' into spreader_lean_qty 2019-05-15 13:35:33 -04:00
John Colvin 7b9c10f47c ManualFillTuple - benign code style tweak 2019-05-15 12:26:50 -05:00
John Colvin 478640e808 ManualFillTuple join with account and user 2019-05-15 12:24:34 -05:00
Jake Norbut 1fa5e3c9af fixing test_join_qty_threshold() of JoinAlgoTests 2019-05-15 11:29:23 -05:00
Michael Harding 7fede58044 Make system tests fail 2019-05-15 11:35:17 -04:00
Pedro Pinto 090a7985c3 Fixed spelling mistakes in release notes. 2019-05-15 10:38:35 -04:00
Michael Harding ab993423a6 Merge branch 'master' into spreader_lean_qty 2019-05-15 10:30:16 -04:00
Pedro Pinto ab8802c3f1 More master merging 2019-05-15 09:51:18 -04:00
Michael Harding f5fa2ff23a Adding Pete's fixes to system test scripts 2019-05-15 09:42:23 -04:00
Taha Afzal 6a1b154061 Fixing the bug causing the broken unit test 2019-05-14 22:43:42 -04:00
Michael Harding 73a4e474f1 System Test scripts: jenkins improvements 2019-05-14 17:26:59 -04:00
Eric Harding 6f630a0e62 MBOService should indicate bid/ask 2019-05-14 17:03:17 -04:00
Pedro Pinto 42906f5a37 Merged 2019-05-14 16:58:54 -04:00
Jake Norbut 931ed76673 implementing IEditableObject via reflection 2019-05-14 14:58:54 -05:00
Jake Norbut ebc4e202fe mf_vm: rows should be populated now 2019-05-14 14:23:43 -05:00
Jake Norbut a7dda587ba orders model - combinging manual fills with securities 2019-05-14 14:19:45 -05:00
Pedro Pinto 1ace2b7a3f Merge remote-tracking branch 'origin' into pnt_nits 2019-05-14 14:45:04 -04:00
Michael Harding 4e30591837 Hopefully fixing line endings this time 2019-05-14 14:38:36 -04:00
Eric Harding 3144032ed6 Fix dictionary key with ShellLatency (add user) 2019-05-14 13:46:03 -04:00
Jake Norbut af2875e0ef vm skeleton 2019-05-14 12:00:25 -05:00
Michael Harding d9221e1053 fix system test scripts 2019-05-14 12:54:01 -04:00
Eric Harding bee1df45ad SPARK-2430 prioritize draining incremental queue over publishing shapshots 2019-05-14 11:52:51 -04:00
Pedro Pinto 316be12b71 Return order key in API create. This broke in 1944. 2019-05-14 11:46:27 -04:00
Pedro Pinto 6c51ff83d5 Release notes again. 2019-05-14 10:01:39 -04:00
Pedro Pinto 1cad5b6b39 Updated release notes. 2019-05-14 09:56:47 -04:00
Michael Harding 4138ab0730 Merged master into spreader_lean_qty 2019-05-14 09:55:20 -04:00
Michael Harding 171396ca57 Merged in system_tests (pull request #228)
System Tests
2019-05-14 13:51:33 +00:00
Pedro Pinto 193eb4e445 Merged in SPARK-1944-attach/detach/update (pull request #141)
SPARK-1944 attach/detach/update
2019-05-14 13:48:17 +00:00
Pedro Pinto 69b3831271 Merged with master 2019-05-14 08:58:30 -04:00
Pedro Pinto 39efacace4 Updated release notes 2019-05-14 08:57:32 -04:00
Eric Harding e70dfc8832 Merged in SPARK-2151 (pull request #225)
RTM: SPARK-2151 Dump shared memory on bad handle for forensic analysis
2019-05-14 12:55:17 +00:00
Jake Norbut 60bf851567 Merged in SPARK-2364 (pull request #222)
RTM - settingsmanager - print descriptive workspace info after selecting a user's workspace to copy

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-05-14 12:54:03 +00:00
Jake Norbut 053038df8d Merged in piq_ladder_dirty (pull request #226)
RTM - don't draw piq on every tick; might save some cycles

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-05-14 12:53:22 +00:00
Eric Harding f0afc74e5f Merged in slightly_fast_webui (pull request #220)
RTM: Apply lazyView to the limits tables

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-05-14 12:51:38 +00:00
Eric Harding 9d3c8aaf5c Merged in SPARK-2303-assert-failure-trade.key-inva (pull request #224)
RTM: Safe workaround for SPARK-2303.
2019-05-14 12:49:34 +00:00
John Colvin 86467b2be1 SPARK-2411 / SPARK-2415 Add param change popup to favorites buttons 2019-05-13 21:26:20 -05:00
Taha Afzal 8575d92373 once the lean's child has crossed, changin lean's price has no effect on the child 2019-05-13 17:53:19 -04:00
Pedro Pinto 21ee7c6af5 2.0.0 Release notes. 2019-05-13 17:30:49 -04:00
Pedro Pinto 7b61b4bd5b Fixed error message 2019-05-13 17:18:32 -04:00
John Colvin 1bcb7ac87f SPARK-2411 / SPARK-2414 Add UI for glbx icerberg order 2019-05-13 14:01:25 -05:00
John Colvin 52305d69f2 normalize spacing in OrderDataHelper.cs 2019-05-13 14:01:25 -05:00
John Colvin bd70e7c09e OrderDataHelper - fix typeo (Offset order metadata was assigning the tick order color) 2019-05-13 14:00:08 -05:00
Michael Harding f6cf0f28ea Move test scripts in spark_test.csproj 2019-05-13 13:31:02 -04:00
John Colvin d04f9f4e96 fix server build 2019-05-13 13:27:35 -04:00
Michael Harding c0d097cb40 SPARK-2438: spreader_lean_qty_factor test setup 2019-05-13 13:26:53 -04:00
Michael Harding 533e688d1a SPARK-2438: init spreader_lean_qty_factor test 2019-05-13 13:09:15 -04:00
Michael Harding 36d40dec70 spell check manual tests 2019-05-13 12:59:49 -04:00
Jake Norbut a3f5de099f swapping out bar brushes and glyph values for using last 2019-05-13 11:18:56 -05:00
Jake Norbut 9b497e323f adding last on volume column to ladder grid host, tooltips 2019-05-13 11:17:15 -05:00
Jake Norbut 9a5b5595c3 adding show last setting to Volumn tab 2019-05-13 11:09:22 -05:00
Jake Norbut 24f23adf24 extending show last trade to VolumnColumnVM 2019-05-13 11:08:36 -05:00
Jake Norbut cc26d0bd8b Adding show_ltq to VolumeColumnSettings 2019-05-13 11:03:57 -05:00
Michael Harding 2b51bda3e7 Update system test scripts 2019-05-13 11:27:12 -04:00
Pedro Pinto 1be2e81920 Merge branch 'SPARK-1944-attach/detach/update' of bitbucket.org:bluetradesys/spark into SPARK-1944-attach/detach/update 2019-05-12 14:33:40 -04:00
John Colvin ea45c5c694 OrderBook - added config for status color opacity when row is selected 2019-05-12 12:24:32 -05:00
John Colvin 768567dfc7 HighPerformanceRow - added BackgroundProperty 2019-05-12 12:14:38 -05:00
John Colvin 870e7a9ef9 DIYGrid - added mutable InactiveSelectionOpacity prop (was just hardcoded at 0.2) 2019-05-12 12:11:39 -05:00
Pedro Pinto a9e572a1bf Merge branch 'master' into SPARK-1944-attach/detach/update 2019-05-11 19:41:18 -04:00
John Colvin c7899a1dae SPARK-2434 OrderBook - security column filter popup is empty if shell is started before server has published all securities 2019-05-11 12:47:07 -05:00
John Colvin 7bb077a74d remove ToLArray extension (it is not more efficient than Enumerable.ToArray()) 2019-05-11 12:27:42 -05:00
John Colvin a29eaa2618 EnumCaseStringConverter - cleanup code style/syntax 2019-05-11 12:25:38 -05:00
John Colvin 762369ad2b SPARK-2433 child order font weight in OB should be dropped or adjusted 2019-05-10 21:22:42 -05:00
John Colvin a8610beafd SPARK-2432 Crash when detaching order window 2019-05-10 20:38:33 -05:00
John Colvin 5e1a91f484 add period to the end of 'Make current parameters the local defaults' tooltip 2019-05-10 16:01:07 -05:00
John Colvin 04fdb9626d SPARK-2409 Changing a composite algo name does not reflect in the attach window dropdown 2019-05-10 15:57:31 -05:00
Michael Harding 6c5429b353 Minor updates to position limits test 2019-05-10 16:36:13 -04:00
Michael Harding 2bedf1f355 Merge branch 'master' into system_tests 2019-05-10 16:22:18 -04:00
Michael Harding 02752d9151 SPARK-2064: simple sanity simplified 2019-05-10 16:14:06 -04:00
John Colvin 689bfa1c76 OrderBook 'attach popup' price/qty vis depends on the selected attach-algo (not the selected orders) 2019-05-10 13:51:39 -05:00
Pedro Pinto 70af046131 Updated docker subrepo ptr 2019-05-10 13:52:44 -04:00
Pedro Pinto 7a45013782 Changed cover to stick around until all hedges are completed. 2019-05-10 13:52:44 -04:00
Michael Harding 6f7851b7ba SPARK-2064: Simple sanity test 2019-05-10 13:41:05 -04:00
Taha Afzal 85f42d1e23 Join won't modify its child's price once it has joined 2019-05-10 11:12:52 -04:00
John Colvin f8ddaab884 OrderBook - (Root)OrderType col filters show green beaker when filter active on startup/load workspace 2019-05-09 18:00:12 -05:00
John Colvin 78373c61ce miniob - hide price/qty param rows in attach popup 2019-05-09 16:10:18 -05:00
John Colvin 13e5c017e2 miniob - set orderbookvm.is_attaching_orders to false when attach popup closes 2019-05-09 15:53:12 -05:00
Eric Harding 3789d73593 Do not allow minTick to be 0 (infinite loop) 2019-05-09 16:25:25 -04:00
John Colvin 38f42fe747 SPARK-2417 Attach window price, qty 2019-05-09 14:28:52 -05:00
John Colvin 3a6f9a608d OrderBookVM.send_attached_orders_async uses CustomDef.actual() to unwrap composite algos 2019-05-09 14:28:52 -05:00
Michael Harding e14a5b2575 update SDK readme 2019-05-09 18:47:09 +00:00
John Colvin 04a68a4e96 OrderBookRowVM - fix typeo in badge_color prop (was using 'root') 2019-05-09 13:43:47 -05:00
Eric Harding dd049374d7 Fix equality comparison between mbo price levels 2019-05-09 14:26:14 -04:00
John Colvin 526ec306ae (Root)OrderType col filters ItemTemplate uses 'OrderTypeItem' (displays badge and name) 2019-05-09 12:41:35 -05:00
John Colvin f16ed40c82 OrderUtils.badge_fill does not need the OrderRecordImmutable arg 2019-05-09 12:35:17 -05:00
Eric Harding 96572539a7 SPARK-2381 View MBO in ladder 2019-05-09 13:05:47 -04:00
John Colvin 793eb14ed7 AlgoDefinitionMetadata - null check _favorite_templates in GetHashCode 2019-05-09 11:39:55 -05:00
John Colvin 1f94454d63 SPARK-2421 Default ladder to new fancy param grid 2019-05-09 11:20:10 -05:00
John Colvin c7e3bdcd6c OrderBookRowVM - normalize spacing 2019-05-09 11:18:27 -05:00
John Colvin 004a7b1395 OrderBookRowVM - don't hook up to invalidated event until '_modify_order_quantity_andor_price' cmd is created 2019-05-09 11:17:03 -05:00
Pedro Pinto 96bb63ba6d Merged 2019-05-09 12:13:37 -04:00
Pedro Pinto 0ea72b017e Storage component of manual fills code complete. 2019-05-09 12:12:02 -04:00
John Colvin b1498f3df1 OrderBookRowVM - forgot to raise property changed when root badge color changes 2019-05-09 11:03:08 -05:00
John Colvin b0759eba62 OrderBookRowVM - fix root badge color 2019-05-09 10:58:40 -05:00
Jake Norbut ff69cafe7f ob alignments - left aligning nearly everything, price and qty cols stay right aligned 2019-05-09 09:59:40 -05:00
Pedro Pinto 698612bb3d Storage component of manual fills code complete. 2019-05-09 10:29:54 -04:00
Jake Norbut f1e90394a0 restoring Root Algo column header after merge 2019-05-09 08:24:44 -05:00
Jake Norbut b24f855321 misc refactoring after root algo branch merge; OrderBookVM parameter passing no longer necessary 2019-05-09 08:20:36 -05:00
Pedro Pinto 2f23292836 Pos server 2019-05-09 09:12:37 -04:00
Pedro Pinto 085e6ae95a Merged 1944 in 2019-05-09 09:01:32 -04:00
Pedro Pinto 3be2aedfe3 Work in progress for manual fills 2019-05-09 08:59:59 -04:00
John Colvin 6e56b6cbaf sort algo type items in (Root)OrderType col filters 2019-05-08 23:12:21 -05:00
John Colvin 16ff473fd6 ViewModelBase - added 'DelegateProperty' backing method that doesn't invoke get_value during creation 2019-05-08 23:07:55 -05:00
John Colvin 4565fada08 change KeyedMultiSelector.selected_items to ObservableCollectionEx (supported by ListBoxEx, which is used by DiscreteValuesFilters) 2019-05-08 23:05:21 -05:00
John Colvin 648f808583 SPARK-2409 / SPARK-2422 fix duplicate algo items in (Root)OrderType col filters 2019-05-08 20:53:54 -05:00
John Colvin 7a6212932e Merge branch 'root_algo_col' into SPARK-1944-attach/detach/update 2019-05-08 19:20:38 -05:00
John Colvin aa933696ec OrderBook - include root_algo member in order_tuples join (for new 'Root Algo' col) 2019-05-08 19:16:10 -05:00
John Colvin 39d59dbbb0 IObservable<IReadOnlyDictionary>.Table() extension applies value-changes to state 2019-05-08 18:43:03 -05:00
Taha Afzal a511ea3a4b Lean cancels itself when detach on OnToward 2019-05-08 17:23:10 -04:00
John Colvin e489e809ab Merge remote-tracking branch 'origin/SPARK-1944-attach/detach/update' into root_algo_col 2019-05-08 16:22:30 -05:00
Jake Norbut e56d41d26b SPARK-2419 - algo studio childs display correct name and badge color now when they are not roots; verified w/ john his great comment no longer applied 2019-05-08 16:05:21 -05:00
John Colvin b1d45ad593 fix benign typeos in UpdateExtensions.ParentDependent 2019-05-08 15:45:17 -05:00
Pedro Pinto 1f01d974e3 Merged from 1944 2019-05-08 16:40:40 -04:00
Pedro Pinto 32038f8948 Fixed algo API bug that could lead to lost order updates. 2019-05-08 16:36:11 -04:00
Pedro Pinto 910c04544b Code complete on update of stop limit and iceberg qty. 2019-05-08 16:27:23 -04:00
Pedro Pinto 10674b8930 Merged 2019-05-08 15:25:58 -04:00
Pedro Pinto 8c9522e22b Work in progress to add support for update to stop limit and glbx iceberg. 2019-05-08 15:22:29 -04:00
Jake Norbut 2a90b521da SPARK-2418 - miniob to make sure of new algo_tree_display_mode setting for RootAndLeafNodes 2019-05-08 14:19:35 -05:00
Jake Norbut 2098a60af1 eliminating root_algo_badge_fill property so that the fill color is reevaluated on all updates, for now ... 2019-05-08 12:01:18 -05:00
Jake Norbut 67c0178b9a adding GridColumnSettings for root algo column 2019-05-08 12:00:28 -05:00
Jake Norbut 5da07c42bb Adding RootAlgoType to ColumnBase 2019-05-08 11:59:31 -05:00
Jake Norbut f82035be98 refactoring OrderBookOrderTypeColumn to eliminate the newly created RootAlgo column type 2019-05-08 11:58:33 -05:00
Jake Norbut a652268eb8 get filtering working correctly 2019-05-08 11:02:33 -05:00
Jake Norbut 13d054a54c just keep checking the OB cache rather than against our local one 2019-05-08 10:04:01 -05:00
Jake Norbut 84cf2a3ad1 refactoring; kind of get filtering working, but not really 2019-05-08 09:42:50 -05:00
Jake Norbut 32e0d60dcd get filering working by passing in an 'owner' reference to the OrderBookColumnCollection 2019-05-08 08:53:04 -05:00
Jake Norbut 0a6755992a removing new non-working rootalgocvorderbookcolumn interface; using OB cache instead of tree; filtering still a mystery 2019-05-08 08:26:47 -05:00
Pedro Pinto f60afe1196 Woark in progress to add support for update to stop limit and glbx iceberg. 2019-05-08 06:46:40 -04:00
Pedro Pinto 0cc30a7934 Added server side support for GLBX Iceberg. 2019-05-08 06:46:40 -04:00
Pedro Pinto 3cc0f32617 Protected iceberg against negative show quantities. 2019-05-08 06:46:40 -04:00
John Colvin 29ae9c6927 SPARK-2404 Make ladder param grid behave more like a popup 2019-05-07 21:44:47 -05:00
John Colvin 892abe098c SPARK-2404 / SPARK-2410 replace dialog skirt buttons with toolbar menu items 2019-05-07 21:27:56 -05:00
John Colvin 9cf683f64f AlgoTemplateToolBar - RevertDefinition's visibility is not contingent upon a studio definition being selected 2019-05-07 21:27:56 -05:00
John Colvin f2bb8de4e1 AlgoTemplateToolBar - execute RevertDefinition visual state transitions through InvokeAsync 2019-05-07 21:27:51 -05:00
John Colvin b6a28af99e SPARK-2406 OrderBook - update param rows misbehave with non-uniform values 2019-05-07 18:35:06 -05:00
John Colvin 13a9dd3696 SPARK-2406 / SPARK-2408 QuantityMeasureControl should assume 'Absolute' mode when QuantityMeasure is null, or QuantityCase is 'None' 2019-05-07 18:33:38 -05:00
John Colvin 60bbd12386 code style change in ReflectionHelper.GetGenericTypeArgs method 2019-05-07 17:57:56 -05:00
John Colvin 3e09274cb5 SPARK-2403 Top is still showing this unknown parameter 2019-05-07 17:17:59 -05:00
John Colvin e9d1da4b51 SPARK-2401 mview - mass selection degrades performance 2019-05-07 17:13:06 -05:00
John Colvin 68e3fcb1cd made extension methods for Ice-dependent security props 2019-05-07 17:13:06 -05:00
John Colvin 745be0909a remove unused implementations of SecurityGrid 2019-05-07 17:13:06 -05:00
Eric Harding 74dd36bde1 SPARK-2380 add MBO popup 2019-05-07 18:02:59 -04:00
Taha Afzal 0bee4168b4 Join test has attach/detach/updates now 2019-05-07 16:19:24 -04:00
Jake Norbut d87c6027c9 display of column works, filtering not so much 2019-05-07 14:27:42 -05:00
John Colvin 859faadecd SPARK-2376 When collapsed the OB scrollbar overlaps with the collapsing widget 2019-05-07 12:22:40 -05:00
Taha Afzal e8741f9151 Top order manual test 2019-05-07 13:19:38 -04:00
Jake Norbut ef8b0fc258 enforce 2 digit algo abbreviation on badge 2019-05-07 11:43:59 -05:00
John Colvin 3b27fedcd3 SPARK-2402 piq merge broke the orderbook mini-ladder order filter 2019-05-07 11:18:27 -05:00
Michael Harding 0991fbfffd Update SDK README 2019-05-07 11:31:56 -04:00
Jake Norbut 499072c94f PR edit - we don't need an array here 2019-05-07 09:33:25 -05:00
Jake Norbut fa8598adbd PR suggestion - explicitly set default value 2019-05-07 09:22:44 -05:00
Jake Norbut 089c4ac35e part of SPARK-2376 - OB footer now spans the entire window width, regardless of the new right panel state 2019-05-07 08:44:48 -05:00
Jake Norbut af8b559fa6 Merge branch 'master' of bitbucket.org:bluetradesys/spark into piq_ladder_dirty 2019-05-07 08:26:15 -05:00
Taha Afzal 62ba85d383 Lean deals with gapped markets better 2019-05-06 19:22:06 -04:00
Taha Afzal f4b2f84e68 Lean's corner cases 2019-05-06 18:14:33 -04:00
Taha Afzal 474e7880d6 LeanAlgo's spurious price/qty controlled 2019-05-06 18:14:33 -04:00
John Colvin 89f74b7c81 SPARK-2399 add commit/revert buttons to ladder algo params grid popup 2019-05-06 17:05:52 -05:00
Jake Norbut 0abcb8bf23 SPARK-2391 - attach/detach icons 2019-05-06 16:33:25 -05:00
Pedro Pinto 47887b67d2 Fixed SIM handling of updates to size of partially filled orders. 2019-05-06 15:57:28 -04:00
John Colvin 41a96b990d SPARK-2382 Windows that take a long time to render do not give any user feedback until they are visible 2019-05-06 14:18:21 -05:00
Jake Norbut ce8bc5c631 SPARK-2394 - rename Fonts and Colors to Column Settings 2019-05-06 12:23:21 -05:00
John Colvin eebb214574 make FixNestedScrolling diygrid popup conditional check more specific 2019-05-06 11:28:53 -05:00
Jake Norbut 47c827caf6 badge - refactor (forgot a ToUpper for 1 of the return paths 2019-05-06 11:22:42 -05:00
Michael Harding a82a22b133 Update test text 2019-05-06 12:17:07 -04:00
Brian Muhr 1fd444c91c adding new tests 2019-05-06 11:12:58 -05:00
Jake Norbut f28ae29990 changing A and D geometry to Segoe UI font 2019-05-06 11:07:56 -05:00
Jake Norbut aedabcdf00 SPARK-2392 - ob attach/detach icons to match mini ob 2019-05-06 11:07:56 -05:00
Jake Norbut 3b37f8e48b adding A and D icons; created from Inkscape 2019-05-06 11:07:56 -05:00
John Colvin beb328ec09 SPARK-2397 grid column popups don't scroll on mouse wheel 2019-05-06 10:58:41 -05:00
Jake Norbut f9b9bdab47 SPARK-2391 - generate badge abbreviation for algo studio orders lacking one 2019-05-06 09:22:44 -05:00
Eric Harding 3beb82c959 Add comment about why dump_to_shm does'nt need to hold mutex 2019-05-06 09:01:45 -04:00
Jake Norbut 9e6776c6fb updating test text 2019-05-06 07:49:31 -05:00
Pedro Pinto e807f6a702 Fixed composite handling of detach. 2019-05-05 11:56:35 -04:00
John Colvin 07212aa5d6 IAlgoVM.load impls unwrap CompositeOrderDefinitions to get the underlying algo 2019-05-04 15:57:50 -05:00
Pedro Pinto a0f31d6443 Fixed composite order some more. 2019-05-04 15:14:20 -04:00
Pedro Pinto df6a5096c2 Composite order was not reflecting child price/size 2019-05-04 14:33:16 -04:00
John Colvin 43336d04d4 SPARK-2375 Composite algo order types do not show as options in the OB filter 2019-05-04 13:26:23 -05:00
John Colvin ca73b20fe5 made 'MergeTable' extension method for TableUpdate 2019-05-04 13:16:45 -05:00
John Colvin 09f6ad4031 OrderBook - fix crash when selecting composite-order row 2019-05-04 13:04:56 -05:00
John Colvin 97b52f2e64 OrdersModel.update_order should ignore size change if size == quantity (not working_quantity) 2019-05-04 13:04:34 -05:00
Pedro Pinto 0adc77a5cf Fixed composite order. 2019-05-04 11:53:12 -04:00
Pedro Pinto e469e8fc70 Tweaked cover test. 2019-05-04 11:53:12 -04:00
Pedro Pinto 10da3c517c Changed hedge ticks to be a uint, from int 2019-05-04 11:53:12 -04:00
Pedro Pinto 5f7c4b5c37 Improved test. 2019-05-04 11:53:12 -04:00
Pedro Pinto c34d05218a Fixed clang build. 2019-05-04 11:53:12 -04:00
Pedro Pinto 3abf99da91 Cleaned up tests. 2019-05-04 11:53:12 -04:00
Pedro Pinto 8d94238e91 Updated cover test 2019-05-04 11:53:12 -04:00
John Colvin dc169fc4fd SPARK-2388 studio-template orders should use new CompositeOrderDefinition (not CustomOrderDefinition) 2019-05-04 11:53:12 -04:00
John Colvin abe8361b18 when updating orders without a 'new size' arg, use the order's actual quantity, not working_quantity 2019-05-03 20:16:18 -05:00
Eric Harding 9f406e83aa SPARK-2151 Dump shared memory on bad handle for forensic analysis 2019-05-03 17:39:47 -04:00
John Colvin 2ee163bf8a SPARK-2387 crash when opening orderbook from other windows 2019-05-03 15:44:22 -05:00
Jake Norbut 775a720e0a SPARK-2027 - it's depressing how many hours of your life 1 line of code can steal 2019-05-03 15:01:27 -05:00
Eric Harding 84aefabfea Safe workaround for SPARK-2303. 2019-05-03 13:19:35 -04:00
Eric Harding 102fefbd92 1.6.2 release notes 2019-05-03 13:13:59 -04:00
Eric Harding fc7a4b0800 Merged in PIQachu (pull request #223)
RTm: spark-2316 mbo support for simulator

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-05-03 15:30:13 +00:00
Michael Harding 6d9c083c0f Verify quoting order 2019-05-02 10:58:45 -04:00
Michael Harding 3e822bcca1 Merge branch 'master' into spreader_test 2019-05-01 10:58:30 -04:00
Jake Norbut 8dde9bf3c5 print descriptive workspace info after selecting a user's workspace to copy 2019-05-01 08:39:37 -05:00
Jake Norbut 0a683dae15 Merge branch 'master' of bitbucket.org:bluetradesys/spark into piq_ladder_dirty 2019-04-30 16:58:37 -05:00
Eric Harding 77f5723936 Apply lazyView to the limits tables 2019-04-30 12:23:18 -04:00
Jake Norbut bd643aa525 don't draw piq on every tick; might save some cycles 2019-04-26 14:50:16 -05:00
Michael Harding 8a53863218 Merge branch 'master' into spreader_test 2019-04-23 16:16:19 -04:00
Michael Harding 6ad12e922c Submit and confirm working spreader order 2019-04-16 13:03:36 -04:00
Michael Harding 3171ae0afb Create a Synthetic Security 2019-04-11 16:06:59 -04:00
Michael Harding c1fe0b3191 SPARK-2066: Create book and common setup 2019-04-04 11:56:05 -04:00
Michael Harding a071b974c8 Merge branch 'master' into system_tests 2019-04-04 10:23:50 -04:00
Michael Harding dd7db5661b Utility for tests to verify the system is ready 2019-03-25 12:21:10 -04:00
Michael Harding 4b8d725528 Test scripts start sparkd. spreader sets config 2019-03-25 12:21:10 -04:00
Jake Norbut db1b2a63af the start of a lean test 2019-03-21 08:05:33 -05:00
Jake Norbut 6006a328fe Merge branch 'master' of bitbucket.org:bluetradesys/spark into system_tests 2019-03-20 08:45:18 -05:00
Jake Norbut 5cb48fb517 Adding qty threshold test 2019-03-15 13:28:30 -05:00
Jake Norbut f2f8c258f9 run all tests within a test class 2019-03-15 13:22:37 -05:00
Jake Norbut cb8dac05d5 if linux no Dispose for now b/c seg fault 2019-03-14 16:10:58 -05:00
Jake Norbut f6eb1e4745 QuickBusClientChanel diag logging for sub / unsub 2019-03-14 15:53:12 -05:00
Jake Norbut 16e8a6e26d adding .NET Core Remote Lauch and Attach debugging options 2019-03-14 15:51:19 -05:00
Jake Norbut 9744f53d65 shell script that will perform setup work; will probably need to chmod this 2019-03-14 11:07:57 -05:00
Jake Norbut b231a66fc7 need to make this test public 2019-03-14 11:06:59 -05:00
Jake Norbut 65b3d4fffa refactoring OrderDefinition utils 2019-03-13 16:58:00 -05:00
Jake Norbut ba1b667ae8 basic join test 2019-03-13 16:47:23 -05:00
Jake Norbut e31b05042f refactoring common services into a base class; other misc refactoring 2019-03-13 16:46:58 -05:00
Jake Norbut 84e443ee4b copying *.sh scripts for testing on linux 2019-03-13 11:40:16 -05:00
Jake Norbut 3821dfb8ff Merge branch 'master' of bitbucket.org:bluetradesys/spark into system_tests 2019-03-11 14:45:10 -05:00
Jake Norbut 01a8502b20 refactoring to resuse user and account between different tests 2019-03-11 14:04:40 -05:00
Jake Norbut edfa079326 ib avg filled test 2019-03-11 13:46:00 -05:00
Jake Norbut 316a617c65 more refactoring 2019-03-11 13:08:16 -05:00
Jake Norbut 0e92a12e65 refactoring/reorg 2019-03-11 10:08:13 -05:00
Jake Norbut 5f4eb58db9 refactoring to promote eventual code reuse 2019-03-08 16:26:00 -06:00
Jake Norbut f7cdb1f00b refactor/cleanup of some common functions 2019-03-08 10:36:51 -06:00
Jake Norbut 0caa6b6ee0 IB test 1 2019-03-08 08:53:00 -06:00
Jake Norbut 6fb8fdc9f6 updating test timeout 2019-02-27 14:28:45 -06:00
Jake Norbut b7fcd17ef0 first test; create retrieve update delete of algo definitions 2019-02-27 11:05:23 -06:00
Michael Harding 482ca6b484 SPARK-2065: Move order functions to OrderUtilities 2019-02-26 14:02:09 -05:00
Michael Harding 7ed3a977ab SPARK-2065: Move AdminUtilities 2019-02-26 11:18:26 -05:00
Michael Harding 59cfe3dc8d SPARK-2067: Setup bash scripts for running system tests 2019-02-26 11:07:47 -05:00
313 changed files with 11657 additions and 3054 deletions

No files matched your search

+177 -91
View File
@@ -1,83 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
"args": ["run"],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "Client Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "SDK Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
"args": ["md","GLBX","Future","CL", "CLZ9", "--user=bts", "--pass="],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Excel Launch",
"type": "clr",
"request": "launch",
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
"args": ["${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Tests",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": ["test","test_filter"],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Attach",
"type": "clr",
"request": "attach",
"processId":"${command:pickProcess}"
},
{
"name": "SparkVM Launch",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": ["run"],
"args": [
"run"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
@@ -102,24 +33,113 @@
"description": "LD LIBRARY",
"text": "set env LD_LIBRARY_PATH=.",
"ignoreFailures": false
}
}
],
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
"args": [
"run"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "Client Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "SDK Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
"args": [
"md",
"GLBX",
"Future",
"CL",
"CLZ9",
"--user=bts",
"--pass="
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Excel Launch",
"type": "clr",
"request": "launch",
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
"args": [
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Tests",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": [
"test",
"test_filter"
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Attach",
"type": "clr",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": "Spreader Tests",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": ["test", "[spreader]"],
"args": [
"test",
"[spreader]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
@@ -139,7 +159,9 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -151,7 +173,10 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": ["test", "[admin_service]"],
"args": [
"test",
"[admin_service]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
@@ -171,7 +196,9 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -183,7 +210,10 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": ["test", "[limiter]"],
"args": [
"test",
"[limiter]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
@@ -203,7 +233,9 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -215,7 +247,10 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": ["test", "[mbo]"],
"args": [
"test",
"SingleWriterConcurrentVector"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
@@ -240,14 +275,16 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
{
"name": "SparkVM attach",
"type": "cppdbg",
"request": "attach",
@@ -255,16 +292,18 @@
"processId": "${command:pickRemoteProcess}",
"MIMode": "gdb",
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"debuggerPath": "/usr/bin/gdb"
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
},
{
"name": "Python",
"type": "python",
"request": "launch",
@@ -274,12 +313,59 @@
"cwd": "${workspaceRoot}",
"env": {},
"envFile": "${workspaceRoot}/.env",
"args": [ "DEBUG" ],
"args": [
"DEBUG"
],
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
},
{
"name": ".NET Core Remote Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickRemoteProcess}",
"pipeTransport": {
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/home/bts/vsdbg/vsdbg",
"pipeCwd": "${workspaceRoot}",
"quoteArgs": true
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
},
"justMyCode": true
},
{
"name": ".NET Core Remote Launch",
"type": "coreclr",
"request": "launch",
"program": "/usr/bin/dotnet",
"args": [
"sparkcmd.dll",
"test",
"test_join_create_and_takeout_market"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server",
"pipeTransport": {
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/home/bts/vsdbg/vsdbg",
"pipeCwd": "${workspaceRoot}",
"quoteArgs": true
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
},
"justMyCode": false
},
]
}
+98
View File
@@ -4,6 +4,104 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.2.0
### User Visible Changes
- Made our orders easier to spot in MBO display
- Changed middle-click on center column to recenter ladder and introduced Alt middle-click on center to bring up the MBO popup
- Fixed Net/Absolute radio button in risk admin UI
- Fixed double counting of spread legs in limits
- Removed expired fills from the system
- Improved handling of orders with undiscovered securities
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
- Fixed display of algo studio algo types in order book
- Hid net change setting in synthetic ladders
- Fixed a number of problems with filtering on the Fills form
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
## 2.1.4
### User Visible Changes
- Fixed handling of cancel button in security picker
## 2.1.3
### User Visible Changes
- Fixed a simulator bug in the handling of 0 sized orders
## 2.2.1
### User Visible Changes
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
## 2.1.1
### User Visible Changes
- Added PIQ to GLBX Iceberg orders
- Corrected handling of stop limit orders
## 2.1.0
### User Visible Changes
- Added support for the GLBX Iceberg order type
- Allowed quick algo param edits by middle-clicking on either the bid/ask columns or a "favorite" button
- Added support for manual fills, either entered manually or imported from file. Manual fills are used only for P&L purposes, they have no impact on limits
- Allowed the LTQ and VAP values to be overlayed on the same ladder column
- Added day position information to the ladder
- Added PIQ to the mini order book
- Fixed a rare bug that could lead to server failure when requesting market data
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
## 2.0.5
### User Visible Changes
- Fixed a bug in the handling of rejected QHO orders
- Implemented stricter position limit enforcement
- Fixed a bug that could cause server failure when trying to attach to already attached orders
## 2.0.4
### User Visible Changes
- Fixed performance regression in order manager
### Internal changes
- Improved market data diagnostics for bad configurations
- Changed market data default buffer size
## 2.0.3
### Internal changes
- Improved backwards compatibility in UI settings
## 2.0.2
### User Visible Changes
- Middle click the ladder price column to show the orders for that price level
- Middle click the bid or ask column in the ladder no longer sends an order
### Internal changes
- Improved market by order performance under heavy load
- Fixed a concurrency bug in SIM
## 2.0.0
### User Visible Changes
- Support for attaching and detaching algos to existing orders
- Support for display and update of all parameters in running algos
- Streamlining of order badge decorations in ladder
- New order search/filtering algorithm for order book
- Simpler representation of complex algo chains in order book and mini order book
- Visual and usability improvements to mini order book
- Streamlining of algo parameter dialog in ladder
- New spreader queue holder behavior
- Visual improvements to the order book
- New server side API
- Improved admin UI performance
- Optimized PIQ rendering
### Internal changes
- Exchange simulator no longer requires daily sequence number reset
- Improved MD resilience to corrupted shared memory
- Native orders can now buffer updates/cancels when disconnected or pending first acknowledgment
- Improved settings manager tool output
- Added diagnostics in the event MD memory corruption is detected
- Added systems tests for join,lean, iceberg and spreader orders
- Made exchange order recovery more resilient in the presence of database loss
## 1.6.2
### User Visible Changes
### Exchange simulator
- Exchange simulator now now produce position in queue data
## 1.6.0
### User Visible Changes
#### New Features
@@ -73,6 +73,7 @@ namespace bts {
window.Close();
message.complete(select_result(vm)).execute(_bus);
};
vm.cancel_action = () => window.Close();
window.move_to_mouse();
window.Show();
}
+25 -4
View File
@@ -22,12 +22,14 @@ namespace mbts.ui {
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
AttachAlgosKey = nameof(AttachAlgosKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
DocumentGeometryKey = nameof(DocumentGeometryKey),
DocumentAddGeometryKey = nameof(DocumentAddGeometryKey),
DocumentBrokenGeometryKey = nameof(DocumentBrokenGeometryKey),
DocumentCopyGeometryKey = nameof(DocumentCopyGeometryKey),
DocumentEmptyGeometryKey = nameof(DocumentEmptyGeometryKey),
@@ -37,11 +39,14 @@ namespace mbts.ui {
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
GearGeometryKey = nameof(GearGeometryKey),
LetterAKey = nameof(LetterAKey),
LetterDKey = nameof(LetterDKey),
OrdersGeometryKey = nameof(OrdersGeometryKey),
PaymentGeometryKey = nameof(PaymentGeometryKey),
PencilGeometryKey = nameof(PencilGeometryKey),
PlayGeometryKey = nameof(PlayGeometryKey),
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
RubberStampGeometryKey = nameof(RubberStampGeometryKey),
StarGeometryKey = nameof(StarGeometryKey),
ToolsGeometryKey = nameof(ToolsGeometryKey),
TrashGeometryKey = nameof(TrashGeometryKey),
@@ -118,7 +123,7 @@ namespace mbts.ui {
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
public static ImageSource AttachAlgos => create_icon(AttachAlgosGeometryKey);
public static ImageSource AttachAlgos => create_icon(AttachAlgosKey);
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
@@ -142,11 +147,13 @@ namespace mbts.ui {
public static ImageSource ModifyOrder { get { return load_resource_and_freeze("ModifyOrder"); } }
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource DataEdit => create_icon(DataEditGeometryKey, Brushes.White, new Pen(Brushes.Black, 1).frozen());
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon(DetachAlgosGeometryKey);
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
public static ImageSource Disk => create_icon(DiskGeometryKey);
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
public static ImageSource DocumentAdd => create_icon(DocumentAddGeometryKey);
public static ImageSource DocumentBroken => create_icon(DocumentBrokenGeometryKey);
public static ImageSource DocumentCopy => create_icon(DocumentCopyGeometryKey);
public static ImageSource DocumentEmpty => create_icon(DocumentEmptyGeometryKey);
@@ -164,6 +171,7 @@ namespace mbts.ui {
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
public static ImageSource RubberStamp => create_icon(RubberStampGeometryKey);
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
public static ImageSource Star => create_icon(StarGeometryKey);
@@ -192,6 +200,19 @@ namespace mbts.ui {
return rv;
}
public static ImageSource create_icon_and_rotate(string geometric_path_key, Brush brush = null, Pen pen = null, Transform transform = null, double degrees = 90) {
if (brush == null) {
brush = Brushes.Black;
}
var drawing = create_drawing(geometric_path_key, brush, pen, transform);
var dg = new DrawingGroup();
dg.Children.Add(drawing);
dg.Transform = new RotateTransform(degrees);
var rv = new DrawingImage(dg);
rv.Freeze();
return rv;
}
public static Drawing create_drawing(string geometric_path_key) =>
create_drawing(geometric_path_key, Brushes.Black);
+18 -51
View File
@@ -5,6 +5,14 @@
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<sys:String x:Key="{x:Static mbts:Icons.AttachAlgosKey}">
M10.519881,14.945064C11.300896,14.955074,12.079896,15.102078,12.819896,15.386075L10.878893,17.326085C9.6458764,17.201085,8.3688531,17.591071,7.4258485,18.533091L3.5977991,22.360102C1.9327704,24.026093 1.9317786,26.736124 3.5977991,28.402117 5.2638197,30.068108 7.9748359,30.068108 9.6398644,28.402117L13.467914,24.575106C14.410919,23.632109,14.800938,22.355097,14.675937,21.121083L16.615963,19.180096C17.523962,21.546103,17.034943,24.325104,15.126931,26.234108L11.298882,30.061119C8.7138591,32.64612 4.5238051,32.64612 1.9387672,30.062127 -0.64625573,27.477126 -0.64625573,23.287106 1.9387672,20.701098L5.7668166,16.875065C7.077837,15.564055,8.8018727,14.923061,10.519881,14.945064z M19.825993,10.418039L21.580012,12.17305 12.089907,21.665091 10.333873,19.911087z M25.383069,0C27.077088,3.0538786E-08 28.771109,0.64599859 30.063117,1.9390028 32.647133,4.5230274 32.647133,8.7120385 30.062109,11.298047L26.235068,15.125058C24.327055,17.033085,21.547022,17.52207,19.181973,16.614077L21.121998,14.674067C22.356022,14.799068,23.633046,14.409051,24.57605,13.466054L28.403094,9.6400513C30.068121,7.9740295 30.069129,5.2630205 28.403094,3.5980362 26.738064,1.9320143 24.027033,1.9320143 22.362034,3.5980362L18.533986,7.4240398C17.591958,8.3670363,17.201968,9.643042,17.32697,10.877056L15.385937,12.817066C14.478913,10.452035,14.968939,7.6730337,16.875945,5.7660436L20.703019,1.9390028C21.995026,0.64599859,23.689047,3.0538786E-08,25.383069,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DetachAlgosKey}">
M19.522852,25.255011L20.920847,25.256994 20.920847,29.729012 19.522852,29.729012z M22.826874,21.952002L25.989905,25.113989 25.001887,26.101996 21.838856,22.941015z M25.269894,19.506992L29.742937,19.509007 29.742937,20.906012 25.269894,20.907995z M10.519729,14.945011C11.300744,14.954991,12.079745,15.103001,12.818766,15.386997L10.878742,17.327C9.6457264,17.200016,8.368704,17.592014,7.4257004,18.532993L3.597655,22.359015C1.9326275,24.025 1.9316508,26.734991 3.597655,28.40299 5.2636887,30.068001 7.9747178,30.066017 9.6397144,28.40299L13.468767,24.575018C14.410794,23.633001,14.800783,22.355993,14.674774,21.122015L16.615807,19.181004C17.523806,21.547003,17.034817,24.325995,15.126776,26.233008L11.298761,30.061012C8.713709,32.644997 4.5236599,32.644997 1.9396465,30.061012 -0.64638097,27.475012 -0.64638097,23.286017 1.9386395,20.702002L5.7666852,16.875004C7.0777046,15.562992,8.8017227,14.922001,10.519729,14.945011z M2.1536381,10.959994L6.6257035,10.959994 6.6266801,12.359988 2.1536381,12.359988z M6.7686803,5.894014L9.9307338,9.0549947 8.9427158,10.044008 5.7806932,6.8819901z M10.976735,2.1390082L12.374761,2.1390082 12.375768,6.6130081 10.975728,6.6130081z M25.382902,0C27.076922,-1.8170738E-08 28.770941,0.6470033 30.063955,1.9389956 32.646992,4.5220043 32.646992,8.7130147 30.061941,11.297L26.234902,15.126011C24.326891,17.032993,21.546862,17.521,19.181845,16.614994L21.121868,14.674015C22.35586,14.798008,23.63389,14.407992,24.575886,13.467013L28.403933,9.6390096C30.067953,7.972994 30.069967,5.2619945 28.403933,3.5960094 26.738905,1.9310002 24.0269,1.9320073 22.361872,3.5960094L18.533827,7.4249893C17.591829,8.3659993,17.201811,9.6440145,17.32782,10.87601L15.385781,12.817996C14.477781,10.453005,14.968784,7.6730062,16.875818,5.7659928L20.702858,1.9379885C21.994864,0.64599622,23.688883,-1.8170738E-08,25.382902,0z
</sys:String>
<!--AddBoxes (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/gregor-cresnar
Ttile: Add free icon
@@ -40,57 +48,9 @@
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/freepik
Ttile: Unlink free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<x:Array x:Key="{x:Static mbts:Icons.AttachAlgosGeometryKey}" Type="sys:String">
<sys:String>
M51.173,3.801c-5.068-5.068-13.315-5.066-18.384,0l-9.192,9.192c-0.781,0.781-0.781,2.047,0,2.828
c0.781,0.781,2.047,0.781,2.828,0l9.192-9.192c1.691-1.69,3.951-2.622,6.363-2.622c2.413,0,4.673,0.932,6.364,2.623
s2.623,3.951,2.623,6.364c0,2.412-0.932,4.672-2.623,6.363L36.325,31.379c-3.51,3.508-9.219,3.508-12.729,0
c-0.781-0.781-2.047-0.781-2.828,0s-0.781,2.048,0,2.828c2.534,2.534,5.863,3.801,9.192,3.801s6.658-1.267,9.192-3.801
l12.021-12.021c2.447-2.446,3.795-5.711,3.795-9.192C54.968,9.512,53.62,6.248,51.173,3.801z
</sys:String>
<sys:String>
M27.132,40.57l-7.778,7.778c-1.691,1.691-3.951,2.623-6.364,2.623c-2.412,0-4.673-0.932-6.364-2.623
c-3.509-3.509-3.509-9.219,0-12.728L17.94,24.306c1.691-1.69,3.951-2.622,6.364-2.622c2.412,0,4.672,0.932,6.363,2.622
c0.781,0.781,2.047,0.781,2.828,0s0.781-2.047,0-2.828c-5.067-5.067-13.314-5.068-18.384,0L3.797,32.793
c-2.446,2.446-3.794,5.711-3.794,9.192c0,3.48,1.348,6.745,3.795,9.191c2.446,2.447,5.711,3.795,9.191,3.795
c3.481,0,6.746-1.348,9.192-3.795l7.778-7.778c0.781-0.781,0.781-2.047,0-2.828S27.913,39.789,27.132,40.57z
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/freepik
Ttile: Disconnected chains free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<x:Array x:Key="{x:Static mbts:Icons.DetachAlgosGeometryKey}" Type="sys:String">
<sys:String>
M462.557,77.627c-14.323-25.556-41.488-41.423-70.889-41.423c-13.835,0-27.54,3.585-39.602,10.364
l-152.557,85.635l17.55,31.279l152.565-85.635c6.747-3.78,14.371-5.779,22.069-5.779c16.428,0,31.596,8.852,39.594,23.11
c5.909,10.535,7.356,22.752,4.072,34.408c-3.268,11.656-10.876,21.346-21.411,27.255l-138.291,77.604
c-2.634-16.851-10.494-32.392-22.793-44.699c-21.687-21.663-56.063-29.328-84.935-18.964l-12.485,4.495l12.128,33.734
l12.534-4.471c16.127-5.869,35.319-1.496,47.406,10.567c6.974,6.982,11.388,15.826,12.786,25.41
c-0.585-0.138-1.171-0.285-1.731-0.447c-11.681-3.284-21.354-10.9-27.271-21.476l-6.519-11.518l-31.303,17.59l6.56,11.518
c10.6,18.915,27.93,32.571,48.836,38.44c0.723,0.187,1.455,0.39,2.187,0.561c-1.105,1.398-2.284,2.731-3.536,3.983
l-114.32,114.328c-8.551,8.543-19.931,13.241-32.035,13.241c-12.112,0-23.492-4.698-32.027-13.241
c-8.535-8.543-13.233-19.907-13.233-32.027c0-12.112,4.698-23.483,13.233-32.018l99.291-99.282l-25.369-25.369l-99.282,99.282
C8.446,319.427,0,339.806,0,361.468c0,21.679,8.446,42.049,23.776,57.396c15.339,15.339,35.709,23.768,57.388,23.768
s42.049-8.429,57.388-23.768l114.345-114.32c8.372-8.405,14.81-18.566,18.842-29.71c6.625-1.764,12.924-4.3,18.777-7.6
l140.99-79.115c18.875-10.608,32.514-27.946,38.375-48.828C475.749,118.4,473.148,96.493,462.557,77.627z
</sys:String>
<sys:String>
M173.171,141.769c4.503-0.821,7.511-5.145,6.69-9.657l-17.103-94.942
c-0.821-4.519-5.162-7.527-9.665-6.722c-4.511,0.821-7.527,5.145-6.706,9.673l17.111,94.925
C164.311,139.575,168.644,142.574,173.171,141.769z
</sys:String>
<sys:String>
M43.561,191.776l94.934-17.119c4.519-0.821,7.527-5.145,6.706-9.657
c-0.821-4.528-5.145-7.543-9.649-6.722L40.61,175.405c-4.503,0.797-7.519,5.145-6.698,9.657
C34.717,189.582,39.058,192.589,43.561,191.776z
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/chanut
@@ -151,6 +111,9 @@
s1-0.448,1-1v-5h5c0.552,0,1-0.448,1-1S52.052,46,51.5,46z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DataEditGeometryKey}">
M17.997977,17.396268C18.197928,17.396268,18.297904,17.496276,18.497975,17.596284L19.497972,18.295283C19.99797,18.595292,20.697919,19.195308,21.197917,19.495299L22.196937,20.195323C22.696935,20.495315,22.596959,20.995322,22.096961,21.195338L20.997966,21.695346C20.497968,21.89434,19.597947,22.294355,19.097949,22.494355L17.997977,22.994363C17.497979,23.194362,17.097957,22.894355,17.197932,22.394347L17.297908,21.195338C17.398005,20.595323,17.497979,19.695315,17.497979,19.195308L17.597955,17.995277C17.497979,17.695283,17.69793,17.396268,17.997977,17.396268z M1.799981,13.597213L19.697923,13.597213 17.997977,15.896244C17.797906,16.096259 17.69793,16.396253 17.69793,16.69626 17.197932,16.796268 16.79791,17.296275 16.697934,17.895284L16.597959,19.195308 1.799981,19.195308C0.79998477,19.195308,0,18.395291,0,17.396268L0,15.396237C0,14.396221,0.79998477,13.597213,1.799981,13.597213z M1.799981,6.7981106L21.796914,6.7981106C22.696935,6.7981106,23.49698,7.498118,23.596955,8.3981257L20.697919,12.397197 1.799981,12.397197C0.79998477,12.397197,0,11.597181,0,10.597165L0,8.5981418C0,7.598126,0.79998477,6.7981106,1.799981,6.7981106z M27.796891,4.3990708C27.996963,4.3990708,28.096938,4.3990708,28.296889,4.5990711L31.695924,7.0981179C31.995851,7.3981105,32.095948,7.7981258,31.895875,8.0981341L23.296908,19.795308C23.096957,20.095315,22.596959,20.195323,22.196937,19.895314L18.797902,17.396268C18.497975,17.096274,18.398002,16.69626,18.597951,16.396253L27.196918,4.6990786C27.396869,4.4990784,27.59694,4.3990708,27.796891,4.3990708z M1.799981,0L21.796914,0C22.79691,4.9549271E-08,23.596955,0.8000156,23.596955,1.800031L23.596955,3.7990554C23.596955,4.7990709,22.79691,5.5990864,21.796914,5.5990864L1.799981,5.5990864C0.79998477,5.4990941,0,4.6990786,0,3.6990629L0,1.7000236C0,0.8000156,0.79998477,4.9549271E-08,1.799981,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DiskGeometryKey}">
M403.213,20H0v472h512V128.787L403.213,20z M131,50h180v100h10h20h10V50h30v130H131V50z M381,462H131V315
c0-8.271,6.729-15,15-15h220c8.271,0,15,6.729,15,15V462z M482,462h-71V315c0-24.813-20.187-45-45-45H146
@@ -334,6 +297,10 @@
M0,0L25.194,16 0,32z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.RubberStampGeometryKey}">
M1.8950181,29.607025L24.423994,29.607025 24.423994,31.103027C24.423994,31.601013,24.025009,32,23.526993,32L2.7920208,32C2.2929976,32,1.8950181,31.601013,1.8950181,31.103027z M0,19.838013L26.218001,19.838013 26.218001,27.813019 0,27.813019z M12.960014,0C16.549,0 19.538988,2.9909973 19.538988,6.57901 19.538988,8.0750122 19.041002,9.3710022 18.243,10.46701L18.043995,10.766022C17.345997,11.863007 16.647999,12.76001 16.647999,12.76001 15.052999,15.651001 15.253011,17.745026 15.253011,17.745026L10.667015,17.745026C10.667015,17.745026 10.866997,15.651001 9.2719967,12.76001 9.2719967,12.76001 8.5739996,11.863007 7.8760016,10.766022L7.6759898,10.46701 7.5769908,10.368011C6.7790176,9.3710022 6.3810074,7.9750061 6.3810074,6.57901 6.3810074,2.9909973 9.2719967,0 12.960014,0z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/smashicons
Ttile: File free icon
@@ -204,13 +204,15 @@ namespace mbts.spark {
}
}
void _show_order_book(Side side) =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData(
new bts.shell.orderbook.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
}));
void _show_order_book(Side side) {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
void _show_order_ticket(MarketViewView.Columns col) {
var order = _orders_model.factory.order(security);
@@ -1,5 +1,6 @@
using bts.api;
using bts.collections;
using bts.utils;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
@@ -22,7 +23,6 @@ namespace mbts.spark {
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
@@ -58,12 +58,12 @@ namespace mbts.spark {
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MarketDescription, "Market Description", r => r.md.security.HasIce ? r.md.security.Ice.MarketDescription : string.Empty, nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.md.security.HasIce ? r.md.security.Ice.ClearedAlias : string.Empty, nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ProductDescription, "Product Description", r => r.md.security.HasIce ? r.md.security.Ice.ProductName : string.Empty, nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.HubAlias, "Hub Alias", r => r.md.security.HasIce ? r.md.security.Ice.HubAlias : string.Empty, nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.StripName, "Strip Name", r => r.md.security.HasIce ? r.md.security.Ice.StripName : string.Empty, nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MicCode, "Mic Code", r => r.md.security.HasIce ? r.md.security.Ice.MicCode : string.Empty, nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
@@ -180,6 +180,40 @@ namespace mbts.spark {
return grid_settings_manager.create();
}
public static IComparer<Row> get_column_comparer(Columns column) {
switch (column) {
case Columns.Security: return wrap(r => r.security);
case Columns.SecurityKey: return wrap(r => r.security.Id);
case Columns.Volume: return wrap(r => r.volume);
case Columns.Last: return wrap(r => r.last_price);
case Columns.LastSize: return wrap(r => r.last_size);
case Columns.Position: return wrap(r => r.position);
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
case Columns.Bid: return wrap(r => r.bid_price);
case Columns.Ask: return wrap(r => r.ask_price);
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
case Columns.Settlement: return wrap(r => r.settlement);
case Columns.DayChange: return wrap(r => r.day_change);
case Columns.SessionLow: return wrap(r => r.session_low);
case Columns.SessionHigh: return wrap(r => r.session_high);
case Columns.MarketDescription: return wrap(r => r.market_description);
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
case Columns.ProductDescription: return wrap(r => r.security.product_name());
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
case Columns.StripName: return wrap(r => r.security.strip_name());
case Columns.MicCode: return wrap(r => r.security.mic_code());
default: return null;
}
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
Comparer<T>.Default.wrap(selector);
}
#region Filters
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
@@ -1,7 +1,9 @@
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -11,11 +13,12 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
namespace mbts.spark {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
const string _window_type = "MarketView", _window_type_display_name = "Market View";
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
@@ -25,6 +28,9 @@ namespace mbts.spark {
MarketViewWindowFactory.StartupData _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
SecurityCriteria
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, WindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
@@ -50,23 +56,91 @@ namespace mbts.spark {
public string thread_id { get; }
SecurityCriteria _selected_security_criteria {
get {
if (_selected_security_criteria_backing == null) {
if (_sorted_selected_security_criteria_backing != null) {
_selected_security_criteria_backing = _sorted_selected_security_criteria_backing;
} else if (_vm != null)
_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Select(r => r.security));
}
return _selected_security_criteria_backing;
}
}
SecurityCriteria _sorted_selected_security_criteria {
get {
if (_sorted_selected_security_criteria_backing == null) {
if (_vm != null) {
var sorting_criteria = _vm.rows_view.get_sorting_criteria(_vm.columns);
var comp_builder = new ComparerBuilder<MarketViewVM.Row>();
foreach (var sc in sorting_criteria) {
if (_vm.columns.TryGetFirst(col => string.Equals(col.id, sc.col_name, StringComparison.CurrentCultureIgnoreCase), out var match)) {
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
if (col_comp != null) {
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
} else {
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
}
} else {
Log.Ui.warn($"{_window_type} is unable find a column with id '{sc.col_name}.'");
}
}
}
var comp = comp_builder.to_comparer();
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
}
}
return _sorted_selected_security_criteria_backing;
}
}
bool _has_selected_rows() => _vm != null && _vm.selected_rows.Count > 0;
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
_view = new MarketViewView { DataContext = _vm };
_view._grid.Sorting += on_grid_sorting;
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
/// For these commands, the objective is to evaluate 'CanExecute' without instantiating the SecurityCriteria (or more importantly, without creating a new List).
// show time and sales
var show_tas_cmd = new DelegateCommand(() => {
var sc = _selected_security_criteria;
if (sc.is_defined())
_bus.post(SparkTags.ShowTimeAndSalesTag, sc);
}, _has_selected_rows);
var show_tas_actual_cmd = SparkCommands.ShowTimeAndSales;
var show_tas_cmd_wrapper = new SparkCommandCommand(show_tas_actual_cmd.title, show_tas_actual_cmd.icon, show_tas_cmd);
// show fills
var show_fills_cmd = new DelegateCommand(() => {
var sc = _selected_security_criteria;
_bus.post(SparkTags.ShowFillsTag, new FillsSettings(
new FillsFilterStates {
security = sc.is_defined() ? sc : _state.security_criteria
}));
});
var show_fills_actual_cmd = SparkCommands.ShowFills();
var show_fills_cmd_wrapper = new SparkCommandCommand(show_fills_actual_cmd.title, show_fills_actual_cmd.icon, show_fills_cmd, SparkCommands.KeyGestures.ShowFills);
// show synthetic create
var show_synth_create_cmd = new DelegateCommand(() => {
var sc = _sorted_selected_security_criteria;
if (sc.is_defined())
_bus.post(SparkTags.ShowSyntheticCreateTag, sc);
}, () => _vm.selected_rows.Count > 0 && _vm.selected_rows.Count <= 24);
var show_synth_create_actual_cmd = SparkCommands.ShowSyntheticCreate();
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
_commands = new List<SparkCommand> {
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(_vm.selected_securities_observable(), _ => true,
secs => new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
})
),
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.ShowSyntheticCreate(SparkCommands.KeyGestures.ShowSyntheticCreate).complete(_vm.sorted_selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
show_fills_cmd_wrapper,
show_tas_cmd_wrapper,
show_synth_create_cmd_wrapper,
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
@@ -87,8 +161,27 @@ namespace mbts.spark {
return dialog;
}))
};
_subscriptions = _bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())));
//
_vm.selection_changed += on_selection_changed;
_subscriptions = new CompositeDisposable(_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())))) {
() => {
_vm.selection_changed -= on_selection_changed;
_view._grid.Sorting -= on_grid_sorting;
}
};
void on_selection_changed((IEnumerable<MarketViewVM.Row> removed, IEnumerable<MarketViewVM.Row> added) change) {
// invalidate selected security criteria
_selected_security_criteria_backing = null;
_sorted_selected_security_criteria_backing = null;
show_tas_cmd.RaiseCanExecuteChanged();
show_fills_cmd.RaiseCanExecuteChanged();
show_synth_create_cmd.RaiseCanExecuteChanged();
}
void on_grid_sorting(object sender, ui.controls.DIYGridSortingEventArgs e) =>
_sorted_selected_security_criteria_backing = null;
}
public override void Dispose() {
@@ -1,6 +1,7 @@
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using mbts.co2;
using mbts.persist;
@@ -92,8 +93,8 @@ namespace mbts.spark {
_initialize();
bool always_true<T>(T _) => true;
FillsWindowFactory.StartupData get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
new FillsSettings(new FillsFilterStates {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
});
void update_criteria(SecurityCriteria c) {
@@ -12,6 +12,9 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="ScrollViewer">
<Setter Property="u:ScrollViewerCorrector.FixNestedScrolling" Value="False"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
@@ -68,12 +68,13 @@ namespace mbts.spark {
rs => rs.HasValue, select_row_strategy_tuple
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
), SparkCommands.ShowOrders.complete(() => new bts.shell.orderbook.OrderBookStartupData {
filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
}
}
), SparkCommands.window_settings_command(new DialogCommand(() => {
), SparkCommands.ShowOrders.complete(() => {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
};
return ob_settings;
}), SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.synthetic_management_grid,
@@ -73,7 +73,7 @@ namespace mbts.spark {
_has_working_orders = backing(nameof(has_working_orders), false);
contract_picker_model = new ContractPickerModel(bus, false, null, new SecurityCriteria(new[] {
new SecurityCriteriaClause() {
exchanges = EnumHelper.get_values_except(Venue.Bts).ToLArray()
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
}
}));
contract_picker_model.cancel_action = _close_new_leg_popup;
@@ -321,9 +321,12 @@ namespace mbts.spark {
return string.Empty;
}
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) =>
synthetic.Synthetic.Legs
.Select(l => new LegSpreaderParamsTuple(l, strategy.LegParamsMap[l.Security.Id].Clone()));
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) {
foreach (var l in synthetic.Synthetic.Legs) {
if (strategy.LegParamsMap.TryGetValue(l.Security.Id, out var p))
yield return new LegSpreaderParamsTuple(l, p);
}
}
void _close_new_leg_popup() {
is_new_leg_popup_open = false;
@@ -168,12 +168,13 @@ namespace mbts.spark {
void _show_ladder() =>
_bus.post(SparkTags.ShowLadderTag, md);
void _show_order_book() =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData {
filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
}
});
void _show_order_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
}
}
}
@@ -2,6 +2,7 @@
using bts.utils;
using mbts.spark.utilities;
using System;
using System.Windows.Media;
namespace mbts.spark {
struct AlgoData : IEquatable<AlgoData> {
@@ -36,4 +37,31 @@ namespace mbts.spark {
public static bool operator ==(AlgoData a, AlgoData b) => a.Equals(b);
public static bool operator !=(AlgoData a, AlgoData b) => !a.Equals(b);
}
struct AlgoDataVM : IEquatable<AlgoDataVM> {
public readonly AlgoData data;
public readonly AlgoDefinitionMetadata? meta;
public AlgoDataVM(AlgoData data, AlgoDefinitionMetadata? meta = null) {
this.data = data;
this.meta = meta;
}
public string name => data.name;
public Brush badge_fill => OrderUtils.badge_fill(meta, OrderDataHelper.get_metadata(data.value.CustomCase));
public AlgoDisplayGroup group => data.key.display_group();
public override string ToString() =>
data.name;
public static AlgoDataVM from_data(AlgoData data, AlgoDefinitionMetadata? meta) =>
new AlgoDataVM(data, meta);
public static AlgoDataVM from_data((AlgoData algo, AlgoDefinitionMetadata? meta) tup) =>
new AlgoDataVM(tup.algo, tup.meta);
public bool Equals(AlgoDataVM other) => data == other.data && meta == other.meta;
public override bool Equals(object obj) => obj is AlgoDataVM other && Equals(other);
public override int GetHashCode() => EqualityComparer.aggregate_hash(data.GetHashCode(), meta.GetHashCode());
}
}
@@ -1,9 +1,76 @@
using bts.api.orderman;
using bts.persist.algo;
using bts.utils;
using ProtoBuf;
using System;
namespace mbts.spark {
[ProtoContract]
struct AlgoID : IEquatable<AlgoID> {
[ProtoMember(1)]
readonly CustomOrderDefinition.CustomOneofCase? _factory_key;
[ProtoMember(2)]
readonly string _dynamic_key;
[ProtoMember(3)]
readonly uint? _persisted_key;
public AlgoID(CustomOrderDefinition.CustomOneofCase factory_key) {
_factory_key = factory_key;
_dynamic_key = null;
_persisted_key = null;
}
public AlgoID(string dynamic_key) {
_factory_key = null;
_dynamic_key = dynamic_key;
_persisted_key = null;
}
public AlgoID(uint persisted_key) {
_factory_key = null;
_dynamic_key = null;
_persisted_key = persisted_key;
}
public static AlgoID from_order_def(OrderDefinitionImmutable order_def) {
switch (order_def.CustomDef.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(order_def.CustomDef.Composite.AlgoTemplateKey);
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(order_def.CustomDef.Dynamic.DynamicOrderType);
default: return new AlgoID(order_def.CustomDef.CustomCase);
}
}
public static AlgoID from_algo_key(AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Factory:
if (key.factory.structure.structure.Count > 0)
return new AlgoID((CustomOrderDefinition.CustomOneofCase)key.factory.structure.structure[0]);
break;
case AlgoKey.Mode.Persisted:
return new AlgoID(key.persisted.id_uint);
}
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
}
public bool Equals(AlgoID other) =>
_factory_key == other._factory_key &&
_dynamic_key == other._dynamic_key &&
_persisted_key == other._persisted_key;
public override bool Equals(object obj) =>
obj is AlgoID && Equals(obj);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
_factory_key?.GetHashCode() ?? 0,
_dynamic_key?.GetHashCode() ?? 0,
_persisted_key?.GetHashCode() ?? 0);
public static bool operator ==(AlgoID a, AlgoID b) => a.Equals(b);
public static bool operator !=(AlgoID a, AlgoID b) => !a.Equals(b);
}
/// TODO: Remove <see cref="AlgoKey"/>, and redirect references to <see cref="AlgoID"/>.
struct AlgoKey : IEquatable<AlgoKey> {
readonly AlgoPersistedKey? _persisted_key;
readonly AlgoNameStructureKey? _factory_key;
@@ -83,4 +150,4 @@ namespace mbts.spark {
InvalidOperationException _unexpected_mode_ex() => new InvalidOperationException($"Unexpected Mode '{mode}.'");
}
}
}
@@ -70,6 +70,9 @@ namespace mbts.spark {
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
parsei(structure_key.structure, dynamic_helper);
public static CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
@@ -70,7 +70,7 @@ namespace mbts.spark {
is_hidden.GetHashCode(),
abbreviation == null ? 0 : abbreviation.GetHashCode(),
badge_color?.GetHashCode() ?? 0,
EqualityComparer.aggregate_hash_limited(_favorite_templates));
_favorite_templates == null ? 0 : EqualityComparer.aggregate_hash_limited(_favorite_templates));
public static bool operator ==(AlgoDefinitionMetadata a, AlgoDefinitionMetadata b) => a.Equals(b);
public static bool operator !=(AlgoDefinitionMetadata a, AlgoDefinitionMetadata b) => !a.Equals(b);
@@ -1,23 +1,36 @@
using bts.api.core;
using bts.api.orderman;
using bts.utils;
using mbts.collections;
using mbts.ui.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
static class AlgoCollectionHelper {
public static readonly IComparer<AlgoDefinitionVM> algo_definition_vm_comparer =
new ComparerBuilder<AlgoDefinitionVM>(
new [] {
new[] {
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_groups),
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
public static readonly IComparer<AlgoData> algo_data_comparer =
new ComparerBuilder<AlgoData>(
new[] {
Comparer<AlgoData>.Create(_CompareUtils.compare_groups),
Comparer<AlgoData>.Create(_CompareUtils.compare_names)
}
).to_comparer();
public static readonly IComparer<AlgoDataVM> algo_data_vm_comparer =
algo_data_comparer.wrap<AlgoData, AlgoDataVM>(vm => vm.data);
public static readonly IComparer<IAlgoVM> algo_vm_comparer =
new ComparerBuilder<IAlgoVM>(
new [] {
new[] {
Comparer<IAlgoVM>.Create(_CompareUtils.compare_groups),
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
@@ -34,18 +47,50 @@ namespace mbts.spark {
}
}
public static void update_persisted_definition_wrappers(ObservableKeyedCollection<IAlgoVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
public static void update_persisted_definition_wrappers(IDictionary<AlgoKey, IAlgoVM> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed)
target[d.key].load_from(d.value);
foreach (var d in update.changed) {
var vm = target[d.key];
if (vm is AlgoVMWrapper wrapper) {
wrapper.value.load(d);
} else vm.load_from(d.value);
}
foreach (var d in update.added) {
if (target.TryGetValue(d.key, out IAlgoVM def)) {
def.load_from(d.value);
} else target.Add(new AlgoVMWrapper(factory(d)));
} else target.Add(d.key, new AlgoVMWrapper(factory(d)));
}
}
public static void commit_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).commit_all();
public static void commit_all(this IEnumerable<IAlgoParamVM> algo_params) {
foreach (var p in algo_params)
p.mark_committed();
}
public static void revert_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).revert_all();
public static void revert_all(this IEnumerable<IAlgoParamVM> algo_params) {
foreach (var p in algo_params)
p.revert();
}
public static AlgoDisplayGroup display_group(CustomOrderDefinition.CustomOneofCase order_type) => order_type.is_bts_algo_type() ? AlgoDisplayGroup.BTS : AlgoDisplayGroup.Exchange;
public static AlgoDisplayGroup display_group(this AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Factory: return display_group(key.factory.structure.order_type());
case AlgoKey.Mode.Unpersisted:
case AlgoKey.Mode.Persisted:
return AlgoDisplayGroup.Studio;
}
return AlgoDisplayGroup.BTS;
}
static class _CompareUtils {
static AlgoDisplayGroup[] _sorted_groups = new[] {
AlgoDisplayGroup.Exchange,
@@ -57,14 +102,20 @@ namespace mbts.spark {
public static int compare_groups(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
public static int compare_groups(AlgoData a, AlgoData b) =>
Array.IndexOf(_sorted_groups, a.key.display_group()).CompareTo(Array.IndexOf(_sorted_groups, b.key.display_group()));
public static int compare_groups(IAlgoVM a, IAlgoVM b) =>
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
public static int compare_names(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
public static int compare_names(AlgoData a, AlgoData b) =>
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
public static int compare_names(IAlgoVM a, IAlgoVM b) =>
DisplayNameHelper.compare_alpha_numeric(a.display_name, b.display_name);
}
}
}
}
@@ -202,6 +202,7 @@ namespace mbts.spark {
public string name { get => _name.Value; set => _name.set(value); }
public string abbreviation { get => _abbreviation.Value; set => _abbreviation.set(value); }
public ObservableCollection<IAlgoVM> algo_roots => _algo_roots;
public IDictionary<AlgoKey, IAlgoVM> algo_roots_index => _algo_roots;
public IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
public IAlgoVM algo_root { get => _algo_root.Value; set => _algo_root.set(value); }
public IAlgoVM visible_algo_root => _visible_algo_root.Value;
@@ -372,7 +373,12 @@ namespace mbts.spark {
}
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> defs) {
var def_list = defs as IList<CustomOrderDefinitionImmutable> ?? defs.ToList();
/// don't load composite algos; we don't support recursive composites (<see cref="OrderUtils.actual(CustomOrderDefinitionImmutable)"/>)
if (defs is IList<CustomOrderDefinitionImmutable> def_list) {
def_list = def_list.WrapSelector(d => d.actual(), null);
} else {
def_list = defs.Select(d => d.actual()).ToList();
}
switch (def_list.Count) {
case 0: algo_root = null; break;
case 1: load_def(def_list[0]); break;
@@ -18,12 +18,16 @@ namespace mbts.spark {
this.algo = algo;
this.parent = parent;
_template_key = template_key;
param.permissions_changed += on_permissions_changed;
_dispose = Disposable.Create(() => param.permissions_changed -= on_permissions_changed);
void on_permissions_changed(AlgoParamPermissions oldval, AlgoParamPermissions newval) => RaisePropertyChanged(nameof(permissions));
}
public event Action invalidated;
public IAlgoVM algo { get; set; }
public IAlgoParamVM param { get; }
public AlgoParamPermissions permissions => param.permissions;
public AlgoParamRowVM parent { get; }
public int level => parent == null ? 0 : parent.level + 1;
public string path_disambiguation {
@@ -29,13 +29,12 @@ namespace mbts.spark {
nameof(editing_permissions),
nameof(definition),
nameof(display_name)
},
_value_echo_props = new HashSet<string> {
nameof(AlgoDefinitionVM.name),
nameof(AlgoDefinitionVM.abbreviation),
nameof(AlgoDefinitionVM.badge_fill),
nameof(AlgoDefinitionVM.can_undo),
nameof(AlgoDefinitionVM.can_redo)
};
static readonly IReadOnlyDictionary<string, string>
_value_echo_props = new Dictionary<string, string> {
{ nameof(AlgoDefinitionVM.name), nameof(display_name) },
{ nameof(AlgoDefinitionVM.abbreviation), nameof(abbreviation) },
{ nameof(AlgoDefinitionVM.badge_fill), nameof(meta) }
};
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
@@ -143,8 +142,8 @@ namespace mbts.spark {
RaisePropertyChanged(e.PropertyName);
}
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_value_echo_props.Contains(e.PropertyName))
RaisePropertyChanged(e.PropertyName);
if (_value_echo_props.TryGetValue(e.PropertyName, out string wrapper_prop))
RaisePropertyChanged(wrapper_prop);
}
}
@@ -377,7 +377,7 @@
<DataTemplate>
<ComboBox
HorizontalAlignment="Stretch"
ItemsSource="{Binding param.value, Converter={StaticResource EnumItemsSourceConverter}, Mode=OneTime}"
ItemsSource="{Binding Converter={x:Static usp:AlgoEditorGridView.NullableEnumParamRowItemsSourceConverter}}"
ItemTemplate="{StaticResource NullableEnumDescriptionTemplate}"
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
/>
@@ -481,10 +481,14 @@
<usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding param.is_dirty}" Value="{u:True}">
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding param.is_dirty}" Value="{u:True}"/>
<Condition Binding="{Binding AnimateDirty, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{u:True}"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#8098FB98"/>
<Setter Property="ToolTip" Value="Value has been modified."/>
</DataTrigger>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
@@ -5,8 +5,8 @@ using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -19,7 +19,19 @@ namespace mbts.ui.spark {
public static readonly IValueConverter
GeneralVisualStateConverter = ValueConverter.create<bool, string>(is_read_only => is_read_only ? VisualStateNames.General.ReadOnly : VisualStateNames.General.Normal),
AlgoRootVisualStateConverter = ValueConverter.create<bool, string>(show_bbo => show_bbo ? VisualStateNames.AlgoRoot.Active : VisualStateNames.AlgoRoot.Inactive),
AlgoRootComboBoxMarginConverter = ValueConverter.create<double, Thickness>(name_col_width => new Thickness(name_col_width + 8, 0, 0, 0));
AlgoRootComboBoxMarginConverter = ValueConverter.create<double, Thickness>(name_col_width => new Thickness(name_col_width + 8, 0, 0, 0)),
NullableEnumParamRowItemsSourceConverter = ValueConverter.create<AlgoParamRowVM, IEnumerable>(param_row => {
if (param_row?.param != null) {
var tenum = param_row.param.GetType().GetInterfaceGenericArgument(AlgoParamTemplateSelector.generic_algo_param_itype);
if (tenum.IsNullable()) {
// .First() should be safe, since we know this is a Nullable<T>.
tenum = tenum.GetGenericArguments().First();
}
if (tenum.IsEnum)
return Enum.GetValues(tenum);
}
return null;
});
public static readonly MouseButtonEventHandler PayupTicksMouseButtonHandler = (s, e) => {
if ((s as Hyperlink)?.Command is ICommand cmd) {
@@ -40,6 +52,8 @@ namespace mbts.ui.spark {
// AlgoParamRows
AlgoParamRowsViewSource = new CollectionViewSource<ListCollectionView<AlgoParamRowVM>>();
BindingOperations.SetBinding(AlgoParamRowsViewSource, CollectionViewSource.SourceProperty, new Binding(nameof(AlgoParamRows)) { Source = this });
AlgoParamRowsViewSource.IsLiveFilteringRequested = true;
AlgoParamRowsViewSource.LiveFilteringProperties.Add(nameof(AlgoParamRowVM.permissions));
AlgoParamRowsViewSource.Filter += param_rows_filter;
DataGridEditHelper.set_hooks(this);
void param_rows_filter(object sender, FilterEventArgs e) =>
@@ -64,6 +78,16 @@ namespace mbts.ui.spark {
}
}
#region AnimateDirtyParams
public static readonly DependencyProperty AnimateDirtyProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(AnimateDirty), true);
public bool AnimateDirty {
get => (bool)GetValue(AnimateDirtyProperty);
set => SetValue(AnimateDirtyProperty, value);
}
#endregion
#region IsReadOnly
public static readonly DependencyProperty IsReadOnlyProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(IsReadOnly));
@@ -19,6 +19,8 @@ namespace mbts.ui.spark {
/// TODO: remove this class and just use a <see cref="KeyedTemplateSelector"/>.
class AlgoParamTemplateSelector : DataTemplateSelector {
public static readonly Type generic_algo_param_itype = typeof(IAlgoParamVM<>);
KeyedCollection<TemplateSelectorKeyValue, string> _keyed_templates = new KeyedCollection<TemplateSelectorKeyValue, string>(tsk => tsk.Key);
public DataTemplate BoolTemplate { get; set; }
@@ -58,7 +60,7 @@ namespace mbts.ui.spark {
if (pvm is IAlgoParamVM<QuantityMeasureImmutable?>) return QuantityMeasureTemplate;
if (pvm is IAlgoParamVM<ExecutionStrategy>) return ExecutionStrategyTemplate;
if (pvm is IAlgoParamVM<Google.Protobuf.WellKnownTypes.Timestamp>) return TimestampTemplate;
var targ = ReflectionHelper.GetGenericTypeArgs(pvm.GetType(), typeof(IAlgoParamVM<>)).FirstOrDefault();
var targ = ReflectionHelper.GetGenericTypeArgs(pvm.GetType(), generic_algo_param_itype).FirstOrDefault();
if (targ != null) {
if (targ.IsEnum)
return EnumTemplate;
@@ -2,6 +2,7 @@
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using mbts.collections;
@@ -18,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@@ -34,22 +34,19 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class FillsVM : DisposableViewModelBase, IHandleGridSorting {
readonly OrdersModel _model;
readonly OperableCollection<Row, FillTuple, string> _rows;
readonly DIYGridSortManager<Columns> _sort_manager;
readonly IDIYGridFiltersManager<FillTuple, Columns, FilterStates> _filters_manager;
readonly DIYGridRowsManager<FillTuple, Columns, FillsFilterStates> _rows_manager;
readonly ObservableKeyedCollection<ColumnBase, Columns> _columns = new ObservableKeyedCollection<ColumnBase, Columns>(_col_key);
List<(ColumnBase col, Columns key, ListSortDirection direction)> _active_comparers = new List<(ColumnBase col, Columns key, ListSortDirection direction)>();
IWindowSettingsManager<GridSettings> _settings_manager;
int _total_row_count;
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FilterStates>, Columns>(c => c.key);
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FillsFilterStates>, Columns>(c => c.key);
// security
add_col(Columns.Security, DefaultColumnHeaders.Security, r => r.security.ToString(),
DiscreteValuesFilterVM.Create<FillTuple, SecurityImmutable, SecurityCriteria>(
@@ -86,29 +83,29 @@ namespace mbts.spark {
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
/// Rows
_rows = new OperableCollection<Row, FillTuple, string>(
(ft, fk) => new Row(ft, model),
ft => ft.fill.FillKey,
new ComparerBuilder<FillTuple>(new[] {
_get_column_comparer(Columns.FillTime),
_get_column_comparer(Columns.FillKey)
}).to_comparer());
// row sort/filter managers
_sort_manager = new DIYGridSortManager<Columns>(col_tups.WrapSelector(t => (t.col, t.key)), Columns.FillKey);
_filters_manager = DIYGridFiltersManager.create(col_tups);
var rows_manager = new DIYGridRowsManager<FillTuple, Columns, FilterStates>(_filters_manager, _sort_manager, _get_column_comparer, f => _rows.filter = f, c => _rows.comparer = c, _rows.reset, FillsWindow.window_type_name);
// rows
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
//
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => _rows_manager.filter_changed += cb, cb => _rows_manager.filter_changed -= cb);
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => _rows_manager.comparer_changed += cb, cb => _rows_manager.comparer_changed -= cb);
TableUpdate<FillTuple, string> last_unfiltered_fills = TableUpdate<FillTuple, string>.mk_empty(FillTuple.key_fn);
var fills_buffer = Observable.Create<TableUpdate<FillTuple, string>>(o => {
// this is what .Cache() does, but is a little cheaper, since we know subscriptions and callbacks occur on the same thread (i.e., on this Dispatcher).
o.OnNext(last_unfiltered_fills.as_snapshot());
_unfiltered_fills_changed += o.OnNext;
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
});
_dispose = new CompositeDisposable(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
fills_source.OnDispatcher().Subscribe(t => {
_rows.update(t);
last_unfiltered_fills = t;
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
_unfiltered_fills_changed?.Invoke(t);
}),
state_obs.OnDispatcher().Subscribe(s => {
if (s.grid != null && _settings_manager != null) {
@@ -116,13 +113,13 @@ namespace mbts.spark {
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
var fs = s.filters ?? new FilterStates {
var fs = s.filters ?? new FillsFilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
};
rows_manager.on_settings(fs, s.sorting_criterion);
_rows_manager.on_settings(fs, s.sorting_criterion);
}),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
@@ -135,14 +132,17 @@ namespace mbts.spark {
Width = width
}, filter);
void add_col_expl<TFilter>(Columns col_key, ColumnBase col, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter) =>
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FillsFilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
// discrete-values-filter factories
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter<T>(IReadOnlyCollection<T> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter_rx<T>(IObservable<IEnumerable<T>> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
}
// this event is used to forward updates from the main data to the filtered data that drives the rows, after resetting the total row count.
event Action<TableUpdate<FillTuple, string>> _unfiltered_fills_changed;
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public ObservableCollection<Row> rows => _rows;
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
public int total_row_count => _total_row_count;
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
@@ -154,26 +154,26 @@ namespace mbts.spark {
return grid_settings_manager.create();
}
public FillsFilterStates get_filters() =>
_rows_manager.get_filters();
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
_sort_manager.create();
_rows_manager.get_sorting_criterion();
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
_rows_manager.on_sorting(e);
static Columns _col_key(ColumnBase column) =>
((IKeyedColumn<Columns>)column).key;
public FilterStates get_filters() =>
_filters_manager.create();
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
if (!state.orders.is_null_or_empty()) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples.Filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples;
}
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
state.SelectManyInSeries(s => _get_observable(bus, model, s));
static IComparer<FillTuple> _get_column_comparer(Columns col) {
@@ -212,8 +212,9 @@ namespace mbts.spark {
}
public class Row : SecurityRow, IEquatable<Row>, IRowVM {
public readonly FillTuple fill_tuple;
readonly OrdersModel _model;
readonly FillTuple _fill_tuple;
readonly FillImmutable _fill;
readonly OrderRecordImmutable _order;
Brush _badge_fill;
@@ -221,7 +222,7 @@ namespace mbts.spark {
public Row(FillTuple fill_tuple, OrdersModel model) :
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
_fill_tuple = fill_tuple;
this.fill_tuple = fill_tuple;
_fill = fill_tuple.fill;
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
_model = model;
@@ -231,9 +232,9 @@ namespace mbts.spark {
public DateTime fill_time => _fill.Timestamp.to_local_time();
public TimeInForce time_in_force => _order.time_in_force();
public OrderType order_type => _order.order_type();
public string display_name => OrderUtils.display_name(_order, _fill_tuple.algo);
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(_order, _fill_tuple.algo_meta, order_metadata));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
public string account { get; private set; }
public string user => _order.user();
public Venue exchange => security.Exchange;
@@ -1,6 +1,7 @@
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using mbts.persist;
using mbts.ui;
@@ -13,21 +14,20 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using StartupData = bts.shell.FillsWindowFactory.StartupData;
namespace mbts.spark {
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
class FillsWindow : SparkWindow<FillsSettings>, ISharedWindow {
public const string window_type_name = "Fills";
readonly AllowOnce _init_guard = new AllowOnce("Fills");
readonly OrdersModel _orders_model;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
FillsVM _vm;
FillsView _view;
StartupData _state;
FillsSettings _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
_orders_model = orders_model;
_state = state;
@@ -35,15 +35,15 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
}
event Action<FillsWindowFactory.StartupData> _state_changed;
event Action<FillsSettings> _state_changed;
public override string window_type => window_type_name;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = new StartupData(_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());
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
@@ -53,7 +53,7 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var state_src = Observable.FromEvent<Action<FillsWindowFactory.StartupData>, FillsWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
_view = new FillsView { DataContext = _vm };
@@ -79,7 +79,7 @@ namespace mbts.spark {
};
_subscriptions = Disposable.Create(() =>
_state_changed?.Invoke(
_state = new StartupData(_state.orders,
_state = new FillsSettings(_state.orders,
_vm.get_sorting_criterion().ToList(),
_vm.get_filters(),
_vm.grid_settings_manager.create()
@@ -1,5 +1,6 @@
using bts.api;
using bts.api.orderman;
using bts.shell.fills;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -16,10 +17,31 @@ using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.FillsView;
namespace bts.shell {
class FillsWindowFactory : ShellWindowFactory<FillsWindowFactory.StartupData> {
class FillsWindowFactory : ShellWindowFactory<FillsSettings> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
/// Fills 'sorting-criterion' settings were compromised a while back (mid-late 2018).
/// The issue was that, when a column was sorted, its 'sort member path' was assigned to the SortingCriteria.col_name,
/// rather than the <see cref="Columns"/> enum/id string.
/// This dictionary serves as a reverse-lookup so the sort member path can be converted to the enum/id string.
static readonly IReadOnlyDictionary<string, Columns> _sort_member_to_id =
new Dictionary<string, Columns> {
{ nameof(FillsVM.Row.account), Columns.Account },
{ nameof(FillsVM.Row.exchange), Columns.Exchange },
{ nameof(FillsVM.Row.fill_key), Columns.FillKey },
{ nameof(FillsVM.Row.fill_kind), Columns.FillKind },
{ nameof(FillsVM.Row.fill_time), Columns.FillTime },
{ nameof(FillsVM.Row.order_key), Columns.OrderKey },
{ nameof(FillsVM.Row.order_type), Columns.OrderType },
{ nameof(FillsVM.Row.price), Columns.Price },
{ nameof(FillsVM.Row.security), Columns.Security },
{ nameof(FillsVM.Row.security_key), Columns.SecurityKey },
{ nameof(FillsVM.Row.side), Columns.Side },
{ nameof(FillsVM.Row.size), Columns.Size },
{ nameof(FillsVM.Row.time_in_force), Columns.TimeInForce },
{ nameof(FillsVM.Row.user), Columns.User }
};
public FillsWindowFactory(TradingShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model)
: base(module, "Fills") {
@@ -31,175 +53,19 @@ namespace bts.shell {
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(StartupData state) =>
public override Task<IShellWindow> load(FillsSettings state) =>
Task.FromResult(_create(state));
public async void show(StartupData state) =>
public async void show(FillsSettings state) =>
_bus.post(SparkTags.ShowWindow, await load(state));
IShellWindow _create(StartupData state) {
IShellWindow _create(FillsSettings state) {
GridSettingsHelper.normalize_sorting_criterion(state.sorting_criterion, _sort_member_to_id);
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.fills_grid,
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
}
[ProtoContract]
public class StartupData {
public const int Version = 1;
[ProtoMember(1)]
public uint[] orders;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criteria;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
public StartupData() { }
public StartupData(uint[] orders) : this(orders, null, null) { }
public StartupData(FilterStates filters) : this(null, null, filters) { }
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
this.orders = orders;
this._sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default() =>
new StartupData(null, null, new FilterStates(), null);
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
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(fill_time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return fill_time;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return side;
yield return quantity;
yield return price;
yield return fill_kind;
yield return fill_key;
yield return order_key;
yield return security_key;
}
}
public static class Mini {
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
new Dictionary<Columns, (bool is_visible, double? width_override)> {
{ Columns.Security, (true, null) },
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
{ Columns.Side, (true, ShortColumnWidths.Side) },
{ Columns.Size, (true, ShortColumnWidths.Size) },
{ Columns.Price, (true, ShortColumnWidths.Price) },
};
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
return true;
data = default;
return false;
}
static GridColumnSettings _convert(GridColumnSettings default_col) =>
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
default_col.with(is_visible: d.is_visible, width: d.width_override) :
default_col.with(is_visible: false);
}
}
}
[ProtoContract]
public class FilterStates {
[ProtoMember(1)]
[DIYGridColumnID(nameof(Columns.FillTime))]
public TimeFilterTuple fill_time { get; set; }
[ProtoMember(2)]
[DIYGridColumnID(nameof(Columns.TimeInForce))]
public TimeInForce[] time_in_force { get; set; }
[ProtoMember(3)]
[DIYGridColumnID(nameof(Columns.OrderType))]
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
[ProtoMember(4)]
[DIYGridColumnID(nameof(Columns.Account))]
public string[] account { get; set; }
[ProtoMember(5)]
[DIYGridColumnID(nameof(Columns.User))]
public string[] user { get; set; }
[ProtoMember(6)]
[DIYGridColumnID(nameof(Columns.Exchange))]
public Venue[] exchange { get; set; }
[ProtoMember(7)]
[DIYGridColumnID(nameof(Columns.Security))]
public SecurityCriteria security { get; set; }
[ProtoMember(8)]
[DIYGridColumnID(nameof(Columns.Side))]
public Side[] side { get; set; }
[ProtoMember(9)]
[DIYGridColumnID(nameof(Columns.Price))]
public DoubleMinMaxState price { get; set; }
[ProtoMember(10)]
[DIYGridColumnID(nameof(Columns.Size))]
public DoubleMinMaxState size { get; set; }
[ProtoMember(11)]
[DIYGridColumnID(nameof(Columns.FillKey))]
public string fill_key { get; set; }
[ProtoMember(12)]
[DIYGridColumnID(nameof(Columns.OrderKey))]
public string order_key { get; set; }
[ProtoMember(13)]
[DIYGridColumnID(nameof(Columns.SecurityKey))]
public string security_key { get; set; }
[ProtoMember(14)]
[DIYGridColumnID(nameof(Columns.FillKind))]
public Fill.Types.FillKind[] fill_kind { get; set; }
}
}
}
@@ -0,0 +1,95 @@
using bts.api;
using bts.api.orderman;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using ProtoBuf;
using System;
using Columns = mbts.ui.spark.FillsView.Columns;
namespace bts.shell.fills {
[ProtoContract]
class FillsFilterStates {
[ProtoMember(1)]
[DIYGridColumnID(nameof(Columns.FillTime))]
public TimeFilterTuple fill_time { get; set; }
[ProtoMember(2)]
[DIYGridColumnID(nameof(Columns.TimeInForce))]
public TimeInForce[] time_in_force { get; set; }
[ProtoMember(3)]
[DIYGridColumnID(nameof(Columns.OrderType))]
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
[ProtoMember(4)]
[DIYGridColumnID(nameof(Columns.Account))]
public string[] account { get; set; }
[ProtoMember(5)]
[DIYGridColumnID(nameof(Columns.User))]
public string[] user { get; set; }
[ProtoMember(6)]
[DIYGridColumnID(nameof(Columns.Exchange))]
public Venue[] exchange { get; set; }
[ProtoMember(7)]
[DIYGridColumnID(nameof(Columns.Security))]
public SecurityCriteria security { get; set; }
[ProtoMember(8)]
[DIYGridColumnID(nameof(Columns.Side))]
public Side[] side { get; set; }
[ProtoMember(9)]
[DIYGridColumnID(nameof(Columns.Price))]
public DoubleMinMaxState price { get; set; }
[ProtoMember(10)]
[DIYGridColumnID(nameof(Columns.Size))]
public DoubleMinMaxState size { get; set; }
[ProtoMember(11)]
[DIYGridColumnID(nameof(Columns.FillKey))]
public string fill_key { get; set; }
[ProtoMember(12)]
[DIYGridColumnID(nameof(Columns.OrderKey))]
public string order_key { get; set; }
[ProtoMember(13)]
[DIYGridColumnID(nameof(Columns.SecurityKey))]
public string security_key { get; set; }
[ProtoMember(14)]
[DIYGridColumnID(nameof(Columns.FillKind))]
public Fill.Types.FillKind[] fill_kind { get; set; }
}
static class FillsFilterStatesExtensions {
public static Func<FillTuple, bool> build_predicate(this FillsFilterStates fs) {
var pb = new ColumnPredicateBuilder<FillTuple>();
// fill time
pb.add_time_filter(fs.fill_time, ft => ft.fill_time());
// security
pb.add_security_criteria(fs.security, ft => ft.security);
// regex
pb.add_regex(fs.fill_key, ft => ft.fill.FillKey);
pb.add_regex(fs.order_key, ft => ft.order_key().ToString());
// discrete values
pb.add_discrete_values(fs.user, ft => ft.user());
pb.add_discrete_values(fs.account, ft => ft.account);
pb.add_discrete_values(fs.exchange, ft => ft.exchange());
pb.add_discrete_values(fs.time_in_force, ft => ft.time_in_force());
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
// numeric
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
return pb.to_predicate();
}
}
}
@@ -0,0 +1,109 @@
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using static bts.shell.FillsWindowFactory;
using static mbts.ui.controls.DIYGrid;
using Columns = mbts.ui.spark.FillsView.Columns;
namespace bts.shell.fills {
[ProtoContract]
class FillsSettings {
public const int Version = 1;
[ProtoMember(1)]
public uint[] orders;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criteria;
[ProtoMember(3)]
public FillsFilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
public FillsSettings() { }
public FillsSettings(uint[] orders) : this(orders, null, null) { }
public FillsSettings(FillsFilterStates filters) : this(null, null, filters) { }
public FillsSettings(uint[] orders, List<SortingCriteria> sorting_criteria, FillsFilterStates filters, GridSettings grid = default) {
this.orders = orders;
this._sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static FillsSettings Default() =>
new FillsSettings(null, null, new FillsFilterStates(), null);
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
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(fill_time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return fill_time;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return side;
yield return quantity;
yield return price;
yield return fill_kind;
yield return fill_key;
yield return order_key;
yield return security_key;
}
}
public static class Mini {
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
new Dictionary<Columns, (bool is_visible, double? width_override)> {
{ Columns.Security, (true, null) },
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
{ Columns.Side, (true, ShortColumnWidths.Side) },
{ Columns.Size, (true, ShortColumnWidths.Size) },
{ Columns.Price, (true, ShortColumnWidths.Price) },
};
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
return true;
data = default;
return false;
}
static GridColumnSettings _convert(GridColumnSettings default_col) =>
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
default_col.with(is_visible: d.is_visible, width: d.width_override) :
default_col.with(is_visible: false);
}
}
}
}
@@ -2,23 +2,23 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.md;
using mbts.co2;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
class LadderModel {
readonly OrdersModel _order_gateway;
public LadderModel(SecurityMD source, OrdersModel ordergw, Func<OrderSecurity, bool> order_filter = null) {
public LadderModel(SecurityMD source, OrdersModel ordergw, MBOService mbo, Func<OrderSecurity, bool> order_filter = null) {
security_md = source;
_order_gateway = ordergw;
orders = ordergw.on_security_working_orders(source.security.Id);
if (order_filter != null)
orders = orders.Filter(o => order_filter(new OrderSecurity(source.security, o)), o => o.Key);
orders = ordergw.on_security_working_orders_with_piq(source.security.Id);
this.mbo_service = mbo;
if (order_filter != null) {
orders = orders.Filter(order_filter, o => o.order.Key);
}
}
public enum CancelType { All, Bids, Asks }
@@ -27,7 +27,9 @@ namespace mbts.spark {
public SecurityImmutable security => security_md.security;
public MDConsumerHandle md => security_md.md;
public OrdersModel order_gateway => _order_gateway;
public IObservable<TableUpdate<OrderRecordImmutable, uint>> orders { get; }
public IObservable<TableUpdate<OrderSecurity, uint>> orders { get; }
public MBOService mbo_service { get; }
public Task cancel_orders(CancelType ct) {
switch (ct) {
@@ -119,7 +119,8 @@ namespace mbts.spark {
buy = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.MarketData.bid, false) },
sell = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.MarketData.ask, false) },
passive = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.passive_volume_bar, false) },
total = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.total_volume_bar), show_numbers = true }
total = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.total_volume_bar), show_numbers = true },
show_ltq = false
};
}
}
@@ -134,6 +135,8 @@ namespace mbts.spark {
public HistogramBarAnimation passive { get; set; }
[ProtoMember(5)]
public HistogramBarAnimation total{ get; set; }
[ProtoMember(6)]
public bool show_ltq { get; set; }
public bool Equals(VolumeColumnSettings other) =>
other != null &&
@@ -141,7 +144,8 @@ namespace mbts.spark {
buy.safe_equals(other.buy) &&
sell.safe_equals(other.sell) &&
passive.safe_equals(other.passive) &&
total.safe_equals(other.total);
total.safe_equals(other.total) &&
show_ltq.Equals(other.show_ltq);
public override bool Equals(object obj) => Equals(obj as VolumeColumnSettings);
@@ -151,7 +155,8 @@ namespace mbts.spark {
buy.GetHashCode(),
sell.GetHashCode(),
passive.GetHashCode(),
total.GetHashCode());
total.GetHashCode(),
show_ltq.GetHashCode());
}
[ProtoContract]
@@ -227,6 +232,7 @@ namespace mbts.spark {
// Default proto constructor
// Added this so that when clients upgrade to a version with new settings,
// the default values are applied correctly; thx to John for this tip
/// TODO: all default values should be consts, because they must be referenced in two places (in the default constructor, and the <see cref="DefaultValueAttribute"/>)
public LadderSettings() {
day_visible = true;
high_visible = true;
@@ -238,6 +244,7 @@ namespace mbts.spark {
tif_visible = true;
order_type_visible = true;
account_position_visible = true;
day_position_visible = true;
size_presets_visible = true;
cancel_buys_visible = true;
cancel_sells_visible = true;
@@ -301,6 +308,7 @@ namespace mbts.spark {
tif_visible = true,
order_type_visible = true,
account_position_visible = true,
day_position_visible = true,
size_presets_visible = true,
cancel_buys_visible = true,
cancel_sells_visible = true,
@@ -397,6 +405,8 @@ namespace mbts.spark {
public bool order_type_visible { get; set; }
[ProtoMember(36), DefaultValue(true)]
public bool account_position_visible { get; set; }
[ProtoMember(50), DefaultValue(true)]
public bool day_position_visible { get; set; }
[ProtoMember(37), DefaultValue(true)]
public bool size_presets_visible { get; set; }
[ProtoMember(38), DefaultValue(true)]
@@ -473,6 +483,7 @@ namespace mbts.spark {
tif_visible == other.tif_visible &&
order_type_visible == other.order_type_visible &&
account_position_visible == other.account_position_visible &&
day_position_visible == other.day_position_visible &&
size_presets_visible == other.size_presets_visible &&
cancel_buys_visible == other.cancel_buys_visible &&
cancel_sells_visible == other.cancel_sells_visible &&
@@ -488,6 +499,7 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as LadderSettings);
// TODO: remove mutable props from hash logic (or conversely, make them immutable).
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
row_height.GetHashCode(),
@@ -1,4 +1,5 @@
using bts.api;
using bts.md;
using mbts;
using mbts.config;
using mbts.persist;
@@ -21,17 +22,26 @@ namespace bts.shell {
readonly LadderConfig _ladder_conf;
readonly IBus _bus;
readonly IWindowManager _wm;
readonly MBOService _mbo;
readonly UserSettingsHub _settings_hub;
readonly OrdersModel _orders_model;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model)
public LadderWindowFactory(
IShellModule owner,
IBus bus,
IWindowManager wm,
UserSettingsHub settings_hub,
OrdersModel orders_model,
MBOService mbo)
: base(owner, "SparkLadder") {
_bus = bus;
_wm = wm;
_settings_hub = settings_hub;
_orders_model = orders_model;
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
_mbo = mbo;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
@@ -54,7 +64,7 @@ namespace bts.shell {
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
var view = new Lazy<UIElement>(() => {
var model = new LadderModel(md, _orders_model);
var model = new LadderModel(md, _orders_model, _mbo);
var settings_manager = new WindowSettingsManager<LadderSettings>(
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
us => sanitize_ladder_settings(us.ladder),
@@ -44,11 +44,14 @@ namespace mbts.spark {
double _real_price = double.NaN, _phantom_price = double.NaN;
uint filled, quantity, num_root_orders;
int working_quantity;
uint? _min_piq = UInt32.MaxValue;
public event Action piq_changed;
LadderModel model => _owner.model;
Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta)>();
Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)>();
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoData?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
_owner = owner;
_bus = bus;
_bs = bs;
@@ -114,7 +117,7 @@ namespace mbts.spark {
var single_order_metadata = _owner.model.order_gateway.factory.get_metadata(single_order);
border = single_order_metadata.Color.rgb_brush();
if (_orders.TryGetValue(single_order.Key, out var opm)) {
algo_studio_abbreviation = opm.meta?.abbreviation ?? string.Empty;
algo_studio_abbreviation = _sanitize_abbreviation(string.IsNullOrEmpty(opm.meta?.abbreviation) ? _generate_abbreviation_for_algo_studio_algo(opm.algo) : opm.meta?.abbreviation);
border = opm.meta?.badge_color?.rgb_color().rgb_brush() ?? border;
}
if (single_order.order_type().is_bts_algo_type()) {
@@ -131,6 +134,34 @@ namespace mbts.spark {
_set_badge_text();
string _generate_abbreviation_for_algo_studio_algo(AlgoData? a) {
char[] delim = { ' ' };
string candidate = string.Empty;
if (a.HasValue) {
var algo_name = a.Value.name;
var words = algo_name.Split(delim);
if (words.Length == 1) {
var single_word = words[0];
if (single_word.Length > 1) {
candidate = words[0];
} else {
candidate = single_word;
}
} else {
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
}
}
return candidate.ToUpper();
}
string _sanitize_abbreviation(string ab) {
string candidate = ab;
if (ab.Length > 2) {
candidate = candidate.Substring(0, 2);
}
return candidate.ToUpper();
}
}
private void _set_badge_text() {
@@ -138,12 +169,32 @@ namespace mbts.spark {
RaisePropertyChanged(nameof(text_lines));
}
public void add_order((OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta) new_op) {
/// <summary>
/// Add Order performs an add the first time it's called via the constructor. When it's called, directly
/// it really serves as an update/replace. Also note that since the min_piq is a separate piece of state representing
/// the lowest piq of the orders the badge represents, we must reevaluate if that has changed on said update.
/// </summary>
public void add_order((OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta) new_op) {
_orders[new_op.order.Key] = new_op;
// reevaluate min piq if this order's piq is less
if (new_op.p.HasValue) {
// if this lovm only represents 1 order and the piq is now different, regardless of what it is that's our piq
if (_orders.Count == 1 && new_op.p.Value != min_piq) {
min_piq = new_op.p.Value;
} else if (new_op.p.Value < min_piq) {
min_piq = new_op.p.Value;
}
}
}
public void remove_order(uint order_key) {
_orders.Remove(order_key);
// on a remove, potentially the order w/ the lowest piq has been removed, so
// if the min() is now > than the current min_piq, update the min_piq
var prospective_piq = _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
if (prospective_piq > min_piq) {
min_piq = prospective_piq;
}
}
//public void update(OrderRecordImmutable neworder) {
@@ -155,7 +206,16 @@ namespace mbts.spark {
public uint security_key => _owner.security.Id;
public double price { get => double.IsNaN(_phantom_price) ? _real_price : _phantom_price; }
public uint? min_piq => _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
public uint? min_piq {
get {
return _min_piq;
}
set {
if (Set(ref _min_piq, value)) {
piq_changed?.Invoke();
}
}
}
public double real_price => _real_price;
public Side side {
get {
@@ -52,6 +52,7 @@ namespace mbts.spark {
_tif_visibility = new OrderEntryPanelItemVisibilityVM("Time In Force"),
_order_type_visibility = new OrderEntryPanelItemVisibilityVM("Order Type"),
_account_position_visibility = new OrderEntryPanelItemVisibilityVM("Account Position"),
_day_position_visibility = new OrderEntryPanelItemVisibilityVM("Day Position"),
_size_preset_visibility = new OrderEntryPanelItemVisibilityVM("Size Presets"),
_auto_clear_visibility = new OrderEntryPanelItemVisibilityVM("Auto Clear"),
_clear_visibility = new OrderEntryPanelItemVisibilityVM("Clear"),
@@ -164,6 +165,7 @@ namespace mbts.spark {
yield return _tif_visibility;
yield return _order_type_visibility;
yield return _account_position_visibility;
yield return _day_position_visibility;
yield return _size_preset_visibility;
yield return _clear_visibility;
yield return _auto_clear_visibility;
@@ -232,6 +234,7 @@ namespace mbts.spark {
_tif_visibility.show = settings.tif_visible;
_order_type_visibility.show = settings.order_type_visible;
_account_position_visibility.show = settings.account_position_visible;
_day_position_visibility.show = settings.day_position_visible;
_size_preset_visibility.show = settings.size_presets_visible;
_clear_visibility.show = settings.clear_visible;
_auto_clear_visibility.show = settings.auto_clear_visible;
@@ -278,6 +281,7 @@ namespace mbts.spark {
clone.tif_visible = _tif_visibility.show;
clone.order_type_visible = _order_type_visibility.show;
clone.account_position_visible = _account_position_visibility.show;
clone.day_position_visible = _day_position_visibility.show;
clone.size_presets_visible = _size_preset_visibility.show;
clone.auto_clear_visible = _auto_clear_visibility.show;
clone.clear_visible = _clear_visibility.show;
@@ -449,6 +453,7 @@ namespace mbts.spark {
passive = new VolumeAtPriceAnimationVM("Passive", new VolumeAtPriceSampleData(this, 72, 127, 198));
total = new VolumeAtPriceAnimationVM("Total", new VolumeAtPriceSampleData(this, 81, 100, 144));
_any_visible = backing(nameof(any_visible), () => volume_components.Any(vc => vc.show_numbers || vc.bars.enabled));
_show_last_trade = backing(nameof(show_last_trade), false);
foreach (var vc in volume_components) {
vc.show_numbers_changed += _any_visible.refresh;
vc.bars.enabled_changed += _any_visible.refresh;
@@ -463,6 +468,9 @@ namespace mbts.spark {
public VolumeAtPriceAnimationVM sell { get; }
public VolumeAtPriceAnimationVM passive { get; }
public VolumeAtPriceAnimationVM total { get; }
readonly Property<bool> _show_last_trade;
public bool show_last_trade { get => _show_last_trade.Value; set => _show_last_trade.Value = value; }
public bool any_visible => volume_components.Any(vc => vc.show_numbers || vc.bars.enabled);
public IEnumerable<VolumeAtPriceAnimationVM> volume_components {
@@ -481,6 +489,7 @@ namespace mbts.spark {
sell.assign_from(settings.sell);
passive.assign_from(settings.passive);
total.assign_from(settings.total);
show_last_trade = settings.show_ltq;
}
public VolumeColumnSettings get_settings() =>
@@ -490,6 +499,7 @@ namespace mbts.spark {
sell = sell.get_settings(),
passive = passive.get_settings(),
total = total.get_settings(),
show_ltq = show_last_trade
};
}
@@ -25,14 +25,17 @@ namespace mbts.spark {
readonly IBus _bus;
readonly LadderModel _model;
readonly Property<double> _high, _low, _change, _open, _settlement, _position, _left_panel_width, _zoom_factor;
readonly Property<double> _high, _low, _change, _open, _settlement, _left_panel_width, _zoom_factor;
//readonly Property<double> _window_width, _window_height;
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width, _piq_column_width;
readonly Property<ulong> _volume;
readonly Property<bool> _auto_clear, _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
_show_tif, _show_order_type, _show_account_position, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
readonly Property<bool> _auto_clear;
readonly PropertyAutoWrapper<LadderSettings, bool> _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
double _position, _day_position;
bool _show_account_position, _show_day_position;
IWindowSettingsManager<LadderSettings> _settings_manager;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
@@ -77,46 +80,51 @@ namespace mbts.spark {
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
var account_positions = new KeyDrivenObservableSeries<string, double>(a => _model.order_gateway.security_total_position(security.Id, a));
_position = backing(nameof(position), account_positions.OnDispatcher());
_volume = backing(nameof(volume), 0ul);
_auto_clear = backing(nameof(auto_clear), false);
_status = backing(nameof(status_message), "");
_status_brush = backing(nameof(status_background), _transparent_status_brush);
// brush props
_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background.rgb_brush());
_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground.rgb_brush());
_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background.rgb_brush());
_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground.rgb_brush());
Property<Brush> brush_prop(string name, Func<LadderSettings, Brush> get_brush) =>
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_brush);
// bool visibility props
_show_day = bool_prop(nameof(show_day), s => s.day_visible);
_show_high = bool_prop(nameof(show_high), s => s.high_visible);
_show_low = bool_prop(nameof(show_low), s => s.low_visible);
_show_open = bool_prop(nameof(show_open), s => s.open_visible);
_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible);
_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible);
_show_account = bool_prop(nameof(show_account), s => s.account_visible);
_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible);
_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible);
_show_account_position = bool_prop(nameof(show_account_position), s => s.account_position_visible);
_show_size_preset = bool_prop(nameof(show_size_preset), s => s.size_presets_visible);
_show_auto_clear = bool_prop(nameof(show_auto_clear), s => s.auto_clear_visible);
_show_clear = bool_prop(nameof(show_clear), s => s.clear_visible);
_show_cancel_buys = bool_prop(nameof(show_cancel_buys), s => s.cancel_buys_visible);
_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible);
_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible);
_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible);
Property<bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_bool);
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
var settings_observer = new CompositeObserver<LadderSettings> {
// brush props
(_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background)).set,
(_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground)).set,
(_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background)).set,
(_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground)).set,
// bool visibility props
(_show_day = bool_prop(nameof(show_day), s => s.day_visible)).set,
(_show_high = bool_prop(nameof(show_high), s => s.high_visible)).set,
(_show_low = bool_prop(nameof(show_low), s => s.low_visible)).set,
(_show_open = bool_prop(nameof(show_open), s => s.open_visible)).set,
(_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible)).set,
(_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible)).set,
(_show_account = bool_prop(nameof(show_account), s => s.account_visible)).set,
(_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible)).set,
(_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible)).set,
(_show_size_preset = bool_prop(nameof(show_size_preset), s => s.size_presets_visible)).set,
(_show_auto_clear = bool_prop(nameof(show_auto_clear), s => s.auto_clear_visible)).set,
(_show_clear = bool_prop(nameof(show_clear), s => s.clear_visible)).set,
(_show_cancel_buys = bool_prop(nameof(show_cancel_buys), s => s.cancel_buys_visible)).set,
(_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible)).set,
(_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible)).set,
(_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible)).set,
s => {
if (Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) | Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position))) {
RaisePropertyChanged(nameof(position_display_text));
}
}
};
PropertyAutoWrapper<LadderSettings, bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
backing(name, init, get_bool, EqualityComparer.Create(get_bool));
PropertyAutoWrapper<LadderSettings, Brush> brush_prop(string name, Func<LadderSettings, uint> get_color_uint) =>
backing(name, init, s => get_color_uint(s).rgb_brush(), EqualityComparer.Create(get_color_uint));
//
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(position)));
send_order_command = new DelegateCommand<(double price, Side? side, bool size_from_alternate)>(send_order);
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : position)));
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
show_book_command = new DelegateCommand(_show_book);
show_ticket_command = new DelegateCommand(_show_ticket);
@@ -139,31 +147,17 @@ namespace mbts.spark {
orders = new ObservableCollection<LadderOrderVM>();
_dispose = Disposable.Create(
settings_manager.subscribe_current().OnDispatcher().Subscribe(settings_observer),
settings_command,
non_shared_wsm,
get_order_properties_editor_hooks(),
account_positions,
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
// we need to hook this up to model.orders which is filtered for just the security
model.order_gateway.working_orders_with_piq.OnDispatcher().Subscribe(working => {
model.orders.OnDispatcher().Subscribe(working => {
// Here we manage N orders to 1 price slot in the ladder
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
// In master we originally had something similar to this snippet to restrict the foreach to just the orders
// for this ladder. In 1944, at one point we made use of model.orders, which made use of the on_security_working_orders demuxer.
// When we merged PIQ into 1944, we moved from that demuxer to working_orders_with_piq, so we reintroduce this little snippet here
// to once again restrict the processing to those working orders that match the criteria. Check with John to see if we can
// make a demuxer from working_orders_with_piq, which is an IReadOnlyDictionary; that probably won't work, but I have no doubt
// there is a better way.
var ladders_orders =
working.Values
.Where(o =>
o.order.Def.SecurityKey == security.Id &&
(o.order.HasState && o.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible))
.ToReadOnlyDictionary(os => os.order.Key, os => os);
_cache = ladders_orders;
_cache = working.state;
foreach (var os in _cache.Values) {
var wo = os.order;
@@ -180,9 +174,9 @@ namespace mbts.spark {
}
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order((wo, os.piq, os.meta));
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
} else {
slot_vm = new LadderOrderVM((wo, os.piq, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
_slot_price_vs_orders[slot_price_target] = slot_vm;
}
@@ -229,13 +223,29 @@ namespace mbts.spark {
WorkLoop.Start($"Ladder '{security}'", _tick),
Disposable.Create(() => _on_disposed_impl?.Invoke(true))
);
IDisposable get_order_properties_editor_hooks() =>
order_properties_vm == null ?
Disposable.empty :
Disposable.Create(
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
order_properties_vm,
order_properties_vm.observe_account(true).Select(a => a?.name ?? string.Empty).Subscribe(account_positions));
IDisposable get_order_properties_editor_hooks() {
if (order_properties_vm == null)
return Disposable.empty;
var position_src = order_properties_vm.observe_account(true)
.Select(a => a?.name ?? string.Empty)
.SelectManyInSeries(a => _model.order_gateway.on_security_positions(security.Id)
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
return Disposable.Create(
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
order_properties_vm,
position_src.OnDispatcher().Subscribe(pos => {
double total, day;
if (pos.HasValue) {
total = pos.Value.total_pos();
day = pos.Value.day_pos();
} else {
total = day = double.NaN;
}
if ((Set(ref _position, total, nameof(position)) | Set(ref _day_position, day, nameof(day_position))) && (show_day_position || show_account_position))
RaisePropertyChanged(nameof(position_display_text));
}),
position_src.Connect());
}
}
public IBus bus => _bus;
@@ -246,7 +256,7 @@ namespace mbts.spark {
// we return only entries who have piqs and whose piqs are not uint.Max
// uint.Max happens when the server is restarted but the client is still up
// maybe long term we don't show this data while the exchange connection is down
public IEnumerable<(double, uint)> price_vs_piq => _slot_price_vs_orders.Values.Where(result => result.min_piq.HasValue && result.min_piq.Value != uint.MaxValue).Select(o => (o.price, o.min_piq.Value));
public IEnumerable<(double, uint)> price_vs_piq => _slot_price_vs_orders.Values.Where(result => result.min_piq.HasValue && result.min_piq.Value != uint.MaxValue).Select(o => (o.price, o.min_piq.Value));
public bool is_synthetic => _model.security.ContractCase == Security.ContractOneofCase.Synthetic;
Property<string> _status;
@@ -280,7 +290,8 @@ namespace mbts.spark {
public double change => _change.Value;
public double open => _open.Value;
public double settlement => _settlement.Value;
public double position => _position.Value;
public double position => _position;
public double day_position => _day_position;
public ulong volume => _volume.Value;
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
public CalculatorLogic size_calculator { get; }
@@ -291,30 +302,41 @@ namespace mbts.spark {
public ICommand send_order_command { get; }
public ICommand show_book_command { get; }
public ICommand show_ticket_command { get; }
public Brush bid_background => _bid_background.Value;
public Brush bid_foreground => _bid_foreground.Value;
public Brush ask_background => _ask_background.Value;
public Brush ask_foreground => _ask_foreground.Value;
public Brush bid_background => _bid_background.view;
public Brush bid_foreground => _bid_foreground.view;
public Brush ask_background => _ask_background.view;
public Brush ask_foreground => _ask_foreground.view;
public DialogCommand settings_command { get; }
public bool show_day { get => _show_day.Value; set => _show_day.set(value); }
public bool show_high { get => _show_high.Value; set => _show_high.set(value); }
public bool show_low { get => _show_low.Value; set => _show_low.set(value); }
public bool show_open { get => _show_open.Value; set => _show_open.set(value); }
public bool show_settle { get => _show_settle.Value; set => _show_settle.set(value); }
public bool show_volume { get => _show_volume.Value; set => _show_volume.set(value); }
public bool show_account { get => _show_account.Value; set => _show_account.set(value); }
public bool show_tif { get => _show_tif.Value; set => _show_tif.set(value); }
public bool show_order_type { get => _show_order_type.Value; set => _show_order_type.set(value); }
public bool show_account_position { get => _show_account_position.Value; set => _show_account_position.set(value); }
public bool show_size_preset { get => _show_size_preset.Value; set => _show_size_preset.set(value); }
public bool show_auto_clear { get => _show_auto_clear.Value; set => _show_auto_clear.set(value); }
public bool show_clear { get => _show_clear.Value; set => _show_clear.set(value); }
public bool show_cancel_buys { get => _show_cancel_buys.Value; set => _show_cancel_buys.set(value); }
public bool show_cancel_sells { get => _show_cancel_sells.Value; set => _show_cancel_sells.set(value); }
public bool show_cancel_all { get => _show_cancel_all.Value; set => _show_cancel_all.set(value); }
public bool show_zoom { get => _show_zoom.Value; set => _show_zoom.set(value); }
public bool show_day => _show_day.view;
public bool show_high => _show_high.view;
public bool show_low => _show_low.view;
public bool show_open => _show_open.view;
public bool show_settle => _show_settle.view;
public bool show_volume => _show_volume.view;
public bool show_account => _show_account.view;
public bool show_tif => _show_tif.view;
public bool show_order_type => _show_order_type.view;
public bool show_account_position => _show_account_position;
public bool show_day_position => _show_day_position;
public bool show_size_preset => _show_size_preset.view;
public bool show_auto_clear => _show_auto_clear.view;
public bool show_clear => _show_clear.view;
public bool show_cancel_buys => _show_cancel_buys.view;
public bool show_cancel_sells => _show_cancel_sells.view;
public bool show_cancel_all => _show_cancel_all.view;
public bool show_zoom => _show_zoom.view;
public double left_panel_width { get => _left_panel_width.Value; set => _left_panel_width.set(value); }
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
public string position_display_text {
get {
if (show_day_position) {
string day_pos_text = SparkUtils.fmt_position(day_position);
return show_account_position ? $"{day_pos_text} ({SparkUtils.fmt_position(position)})" : day_pos_text;
} else if (show_account_position) {
return SparkUtils.fmt_position(position);
} else return string.Empty;
}
}
//public double window_height { get => _window_height.Value; set => _window_height.set(value); }
//public double window_width { get => _window_width.Value; set => _window_width.set(value); }
public uint orders_column_width { get => _orders_column_width.Value; set => _orders_column_width.set(value); }
@@ -326,7 +348,7 @@ namespace mbts.spark {
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoDefinitionMetadata? meta) op) {
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.price();
foreach (var o in lineage(order.ParentKey)) {
@@ -362,34 +384,41 @@ namespace mbts.spark {
}
}
public async void send_order((double price, Side? side, bool size_from_alternate) args) {
public void send_order((double price, Side? side, MouseButton buton) args) {
if (args.buton == MouseButton.Middle) return;
var size_from_alternate = args.buton == MouseButton.Right;
if (order_properties_vm == null)
return;
var s = _settings_manager.current_settings();
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (args.size_from_alternate && s.alternate_order_size == 0))
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0)) {
return;
else if (!args.size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
} else {
var order = order_properties_vm.create(args.side.Value);
if (order.CustomDef != null) {
if (order.CustomDef.Stop != null) {
order.CustomDef.Stop.StopPrice = args.price;
} else if (order.CustomDef.StopLimit != null) {
// swap the prices here so that the level clicked
// in the ladder is the StopPrice (trigger) and the Price
// is what was specificed in the extra parameter box
double temp_price = order.CustomDef.StopLimit.StopPrice;
order.CustomDef.StopLimit.StopPrice = args.price;
args.price = temp_price;
}
}
uint size = args.size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
await _model.order_gateway.send_order(order, args.price, size);
if (auto_clear)
size_calculator.value = 0;
}
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
}
else {
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
send_order_async(args.side.Value, size, args.price).ignore();
}
}
public async Task send_order_async(Side side, uint size, double price) {
var order = order_properties_vm.create(side);
if (order.CustomDef != null) {
if (order.CustomDef.Stop != null) {
order.CustomDef.Stop.StopPrice = price;
} else if (order.CustomDef.StopLimit != null) {
// swap the prices here so that the level clicked
// in the ladder is the StopPrice (trigger) and the Price
// is what was specificed in the extra parameter box
double temp_price = order.CustomDef.StopLimit.StopPrice;
order.CustomDef.StopLimit.StopPrice = price;
price = temp_price;
}
}
await _model.order_gateway.send_order(order, price, size);
if (auto_clear)
size_calculator.value = 0;
}
void _update_header() {
@@ -413,12 +442,13 @@ namespace mbts.spark {
}
void _show_book() =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData {
filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
}
});
void _show_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
void _show_ticket() {
if (order_properties_vm != null)
@@ -8,6 +8,7 @@ using mbts.ui;
using mbts.ui.controls;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.spark.algo;
using mbts.ui.utility.text;
using mbts.utils;
using System;
@@ -17,8 +18,10 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
@@ -56,13 +59,18 @@ namespace mbts.spark {
LadderTactileAdorner _tactile_adorner;
ulong _prices_updated;
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
bool _price_dirty = false;
bool _price_dirty = false, _piq_dirty = false;
double[] _price_list = null;
IComparer<double> _price_comparer;
double _last_origin_price;
MarketBreakLines _mkt_break_lines;
NativePriceSize _last_trade;
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
int _old_tooltip_row = int.MaxValue;
double _old_last_trade_price = 0.0;
bool _tooltips_are_hooked_up = false;
ToolTip tooltip_view = new ToolTip();
IDisposable _parameterized_order_popup;
public LadderGridHost() {
AllowDrop = true;
@@ -257,11 +265,15 @@ namespace mbts.spark {
case NotifyCollectionChangedAction.Add:
foreach (LadderOrderVM item in e.NewItems) {
_ordershost.add_order(item);
item.piq_changed += _set_piq_dirty;
_set_piq_dirty();
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (LadderOrderVM item in e.OldItems) {
_ordershost.remove_order(item);
item.piq_changed -= _set_piq_dirty;
_set_piq_dirty();
}
break;
case NotifyCollectionChangedAction.Reset:
@@ -272,6 +284,9 @@ namespace mbts.spark {
throw new NotImplementedException();
}
}
void _set_piq_dirty() { _piq_dirty = true; }
#endregion
Column[] columns { get; set; } = new Column[0];
@@ -309,27 +324,141 @@ namespace mbts.spark {
}
protected override async void OnMouseDown(MouseButtonEventArgs e) {
if (_settings == null || e.Handled)
return;
Keyboard.ClearFocus();
Focus();
var point = e.GetPosition(this);
var column = _column_for_point(point);
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle)
_scroll_to_center();
else if (_settings != null) {
var price = price_for_point(point);
Side? bs = null;
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
var pbs = (price, bs, e.ChangedButton == MouseButton.Right);
if (Command != null && Command.CanExecute(pbs)) {
if (bs.HasValue) {
using (_tactile_adorner.show_busy(_row_for_point(point), bs.Value == Side.Buy ? ColumnType.Bid : ColumnType.Ask))
await Command.ExecuteAsync(pbs);
} else
Command.Execute(pbs);
var price = price_for_point(point);
Side? bs = null;
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
if (e.ChangedButton == MouseButton.Middle) {
if (bs.HasValue) {
_send_parameterized_order(point, bs.Value);
} else if (column.column_type == ColumnType.Price) {
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
show_mbo_popup(point).ignore();
} else {
_scroll_to_center();
}
}
}
base.OnMouseDown(e);
var pbs = (price, bs, e.ChangedButton);
if (Command is ICommand cmd && cmd.CanExecute(pbs)) {
if (bs.HasValue) {
using (_tactile_adorner.show_busy(_row_for_point(point), bs.Value == Side.Buy ? ColumnType.Bid : ColumnType.Ask))
await cmd.ExecuteAsync(pbs);
} else {
cmd.Execute(pbs);
}
}
}
void _send_parameterized_order(Point point, Side side) {
Disposable.dispose(ref _parameterized_order_popup);
if (side == Side.Undefined)
throw new ArgumentException("Side is undefined.", nameof(side));
if (!(_vm.size_calculator.value is uint size) || size == 0 || !(_vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? false))
return;
double price = _price_for_row(_row_for_point(point));
var grid = new Grid { DataContext = _vm };
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
var close_cmd = new DelegateCommand(() => Disposable.dispose(ref _parameterized_order_popup));
var send_order_cmd = new DelegateCommand(send_order);
LadderColumnSettings lcs = side == Side.Buy ? _settings.bid_column : _settings.ask_column;
grid.Children.Add(new AlgoParamsView { DataContext = _vm });
// dialog skirt
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new Button {
Background = lcs.background.rgb_brush(),
Command = send_order_cmd,
Content = $"{side} @ {security.spec_or_default().format(price)}",
Foreground = lcs.foreground.rgb_brush(),
Margin = new Thickness(4, 0, 4, 0),
Height = 22
},
new Button {
Content = "Cancel",
Command = close_cmd,
Margin = new Thickness(4, 0, 4, 0),
Height = 22
}
}
};
Grid.SetRow(dialog_skirt, 1);
grid.Children.Add(dialog_skirt);
// root
var root = new Border {
Background = BTSColors.common_background_brush,
BorderBrush = Brushes.DarkGray,
BorderThickness = new Thickness(1),
Child = grid
};
// popup
var popup = new Popup {
Child = root,
PlacementTarget = this,
PlacementRectangle = new Rect(point, point),
StaysOpen = false
};
popup.Closed += on_closed;
root.Loaded += (s, e) => {
if (_parameterized_order_popup == null)
_parameterized_order_popup = Disposable.Create(() => popup.IsOpen = false);
};
popup.PreviewMouseDown += (s, e) => {
if (e.ChangedButton == MouseButton.Middle) {
e.Handled = true;
Disposable.dispose(ref _parameterized_order_popup);
}
};
popup.IsOpen = true;
// local methods
void send_order() {
// Double check that size hasn't changed (if it has, do NOT send anything).
// While this should be impossible, sending an order with an unexpected size is dangerous enough to warrant redunant checks.
if (_vm.size_calculator.value == size) {
_vm.send_order_async(side, size, price).ignore();
} else {
Log.Ui.warn($"Parameterized algo popup '{side}' order was deliberately ignored by the ui: the 'quantity' box value changed during the lifetime of the popup (was {size} when the popup was first opened; currently is {_vm.size_calculator.value}).");
}
Disposable.dispose(ref _parameterized_order_popup);
}
void on_closed(object sender, EventArgs e) =>
_vm.order_properties_vm.algo_param_rows.revert_all();
}
private async Task show_mbo_popup(Point point) {
var query = MBOPopupModel.default_query.with(
security_id: _vm.model.security.Id,
price_level: price_for_point(point)
);
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query);
// Load the first set of orders so we know if there are any before showing the popup
var result = await model.load_orders();
if (result.success && result.value.orders.Count > 0) {
var view = new MBOPopupView(model, result.value);
var popup = new AutoClosePopup {
PlacementTarget = this,
PlacementRectangle = new Rect(point, point),
IsOpen = true,
Child = new Border {
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(0.5),
Child = new ScrollViewer {
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
Content = view,
}
}
};
view.RequestClose += (o, _) => popup.IsOpen = false;
}
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
@@ -368,6 +497,18 @@ namespace mbts.spark {
uint? _ask_size_from_row(int row) =>
_size_from_row(row, _price_list, _asks, _vm.security.ContractSpec);
uint _volume_from_row(int row) {
if (_price_list != null) {
var price = _price_list[row];
var vapls = _volume_container.read_new_value();
if (vapls != null) {
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
}
}
return 0u;
}
int _row_for_price(double price) => LadderRender.index_of(_price_list, price, _price_comparer);
#endregion
@@ -485,6 +626,7 @@ namespace mbts.spark {
_prices_updated = 0;
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
_price_dirty = true;
_piq_dirty = true;
_vm.on_tick += _tick;
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
_volume_sub = _vm.model.order_gateway.md_auxiliary_model.security_volume_at_price(security.Id).Subscribe(t => _volume_container.write_value(t.state.Values));
@@ -492,6 +634,70 @@ namespace mbts.spark {
}
}
#region tooltip handling
void _toggle_tooltips() {
if (_tooltips_are_hooked_up && !_settings.volume_column.show_ltq) {
this.MouseMove -= _mouse_move_handler;
_tooltips_are_hooked_up = false;
_hide_tool_tip();
// somehow even after hiding an unhooking the event handler, after toggling the setting off and
// then hovering over the Volume column, the tooltip will still display, so we explicitly set it
// to null here.
this.ToolTip = null;
}
if (_settings.volume_column.show_ltq && !_tooltips_are_hooked_up) {
this.MouseMove += _mouse_move_handler;
_tooltips_are_hooked_up = true;
}
}
void _mouse_move_handler(object sender, MouseEventArgs e) {
var pt = e.GetPosition(this);
if (_column_for_point(pt)?.column_type == ColumnType.Volume) {
var row = _row_for_point(pt);
// mark dirty so that we'll always get a value on our read
_volume_container.mark_dirty();
var volume = _volume_from_row(row);
if (volume > uint.MinValue && row != _old_tooltip_row) {
if (tooltip_view.IsOpen) {
_old_tooltip_row = row;
// we've changed rows, so hide the old tooltip
//System.Diagnostics.Debug.WriteLine(" Tooltip - Previous tooltip is visible, but we've changed rows");
_hide_tool_tip();
}
tooltip_view.Content = $"{volume}";
tooltip_view.IsOpen = true;
this.ToolTip = tooltip_view;
} else {
// got 0 volume or we're over the same row
if (volume == uint.MinValue) {
// The latest volume query resulted in a 0 volume
// Perhaps we read it too quickly, or perhaps we are on a row where there is no volume
// At any rate, we need this part hiding the tooltip so that we don't leave random tooltips around
// and we have to reset to old row so that when we go back to what could be our preivous row, the tooltip gets drawn again
//System.Diagnostics.Debug.WriteLine(" Tooltip - Volume is 0");
_hide_tool_tip();
//_old_tooltip_row = Int32.MaxValue;
}
}
} else {
//System.Diagnostics.Debug.WriteLine(" Tooltip - not over Volume column");
_hide_tool_tip();
}
}
void _hide_tool_tip() {
// I am leaving this here for posterity
// Apparently even when setting IsOpen to false first and then setting Content to null afterward,
// the tooltip will *still* display as empty for a brief moment, which looks odd. So, we just hide it now.
tooltip_view.IsOpen = false;
//tooltip_view.Content = null;
}
#endregion
void _on_vm_disposed(bool obj) =>
_dispose_resources();
@@ -580,8 +786,10 @@ namespace mbts.spark {
if (depth_changed)
_refresh_sizes();
}
// TODO - detect changes in PIQ to selectively redraw
_refresh_piq();
if (_piq_dirty) {
_refresh_piq();
_piq_dirty = false;
}
_refresh_volume_at_price();
_last_origin_price = _mkt_break_lines.origin;
_price_dirty = false;
@@ -622,8 +830,7 @@ namespace mbts.spark {
if (!_settings.piq_column.show)
return;
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
var piq_list = _vm.price_vs_piq;
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, piq_list.ToArray(), _settings.row_height, security.ContractSpec, _settings.piq_column);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column);
}
void _refresh_sizes() {
@@ -642,7 +849,18 @@ namespace mbts.spark {
var vapls = _volume_container.read_new_value();
if (vapls != null) {
Log.assert_d(_price_list != null);
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column);
var ltp = _last_trade.price;
var last_index = LadderRender.index_of(_price_list, ltp, _price_comparer);
string direction_arrow = string.Empty;
if (MathUtils.epsilon_equals(ltp, _old_last_trade_price)) {
// do nothing if in here b/c direction_arrow already string.empty
} else if (ltp > _old_last_trade_price) {
direction_arrow = "▲";
} else {
direction_arrow = "▼";
}
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _last_trade, direction_arrow);
_old_last_trade_price = ltp;
}
}
@@ -825,6 +1043,7 @@ namespace mbts.spark {
case ColumnType.PIQ: SetCurrentValue(PIQColumnWidthProperty, col.settings.width); break;
case ColumnType.Volume:
SetCurrentValue(VolumeColumnWidthProperty, col.settings.width);
_toggle_tooltips();
_volume_container.mark_dirty();
break;
}
@@ -72,8 +72,7 @@ namespace mbts.spark {
panel.Children.Remove(item);
}
}
} //else
//Log.Ui.warn($"Failed to remove order ({order.id}): order not in {nameof(LadderOrdersHost)}'s {nameof(_orders)}.");
}
}
public void clear_orders() {
@@ -192,11 +191,8 @@ namespace mbts.spark {
Margin = Badge.DefaultMargin,
Orientation = Orientation.Horizontal,
MaxWidth = _col_width
//MaxHeight = _row_height,
//ClipToBounds = true
};
//var view_box = new Viewbox { Child = panel, Width = _col_width, Height = _row_height };
_position_panel(panel, price);
_stacks[price] = new PriceCellComponents(panel);
Children.Add(panel);
@@ -194,7 +194,10 @@ namespace mbts.spark {
IEnumerable<VolumeAtPriceLevelImmutable> data,
double row_height,
ContractSpecificationImmutable spec,
VolumeColumnSettings settings
VolumeColumnSettings settings,
int last_index,
NativePriceSize last_trade,
string direction_arrow
) {
if (!data.Any()) {
_clear_visual(dv);
@@ -217,28 +220,47 @@ namespace mbts.spark {
_clear_visual(dv);
return;
}
double max = data.Max(v => v.Volume);
// inflate the max so that the graph never fills the available space
double max = data.Max(v => v.Volume) * 1.1;
Brush text_brush = settings.general.foreground.rgb_brush();
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
/// local methods
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
double y = row_height * r;
var last_trade_here = r == last_index;
// render the total first; each component (buy/sell/passive) will be rendered on top of the total
if (total_brush != null) {
// if we have to change the brush to indicate this is the last, save brush, draw, then restore brush
var temp = total_brush;
if (settings.show_ltq && last_trade_here) {
total_brush = BTSColors.yellow_brush;
}
render_bar(dc, 0, y, vap.Volume, total_brush, out double _);
total_brush = temp;
}
List<string> numbers_text = new List<string>();
double x_bar = 0;
foreach ((var get_volume, Brush bars, bool show_numbers) in components) {
uint v = get_volume(vap);
render_bar(dc, x_bar, y, v, bars, out double width);
double width;
if (settings.show_ltq && last_trade_here) {
render_bar(dc, x_bar, y, v, BTSColors.yellow_brush, out width);
} else {
render_bar(dc, x_bar, y, v, bars, out width);
}
x_bar += width;
if (show_numbers)
numbers_text.Add(v.ToString());
}
if (settings.total.show_numbers) {
numbers_text.Add(vap.Volume.ToString());
}
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
numbers_text.Add($"{direction_arrow} {last_trade.size} ");
}
if (numbers_text.Count > 0) {
string str = string.Join("/", numbers_text);
GlyphRun gr = glyph_context.get_glyph_run(str, settings.general.font_size, settings.general.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, y, true);
@@ -253,7 +275,9 @@ namespace mbts.spark {
}
double pct = v / max;
width = pct * settings.general.width;
dc.DrawRectangle(brush, null, new Rect(x, y, width, row_height - 1));
// 4 seems like a good number to move the y pos down by and similarly
// to shorten the total y coponent by (-4 for the shift of +4, and another -4 to give it some space);
dc.DrawRectangle(brush, null, new Rect(x, y + 4, width, row_height - 2 * 4));
}
}
@@ -288,22 +312,22 @@ namespace mbts.spark {
}
// this is basically a copy of redner_sizes up above
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, (double price, uint piq)[] piqs, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, IEnumerable<(double price, uint piq)> pvp, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
using (var dc = dv.RenderOpen()) {
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
if (!prices.within_range(origin_index) || piqs.is_null_or_empty())
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
return;
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
for (int i = 0; i < piqs.Length; i++) {
double price = piqs[i].price;
foreach (var entry in pvp) {
double price = entry.price;
// prices are in descending order, so get diff from curr_price - price
int tick_diff = spec.tickdiff_unchecked(curr_price, price);
curr_index += tick_diff;
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Length) {
var gr = format_text(piqs[i].piq.ToString(), curr_index, row_height, settings, glyph_context);
var gr = format_text(entry.piq.ToString(), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
@@ -13,6 +13,7 @@
TextElement.FontSize="{x:Static u:FontSizes.Small}"
>
<UserControl.Resources>
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
<sys:Double x:Key="HorizontalPush">4</sys:Double>
<sys:Double x:Key="HorizontalSplit">8</sys:Double>
<sys:Double x:Key="IndentChild">24</sys:Double>
@@ -141,8 +142,8 @@
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</TabControl.ItemContainerStyle>
<!-- Fonts and Colors Tab -->
<TabItem Header="Fonts and Colors" VerticalAlignment="Stretch">
<!-- Column Settings Tab -->
<TabItem Header="Column Settings" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
@@ -382,7 +383,8 @@
FontStyle="Italic"
Foreground="{Binding can_choose_alternate_implied_format, Converter={StaticResource IsEnabledForegroundConverter}}"
/>
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3">
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3"
Visibility="{Binding DataContext.is_synthetic, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}">
<GroupBox.Header>
<CheckBox
x:Name="show_name_change_chk"
@@ -478,6 +480,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="volume_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}" Margin="8,0">
<Grid.ColumnDefinitions>
@@ -586,6 +589,22 @@
</TextBlock.Style>
</TextBlock>
</WrapPanel>
<StackPanel Grid.Row="4" Margin="8,0" VerticalAlignment="Top">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border Margin="0,5,5,0">
<CheckBox Content="Show Last on Volume Column" FlowDirection="RightToLeft" DataContext="{Binding volume}" IsChecked="{Binding show_last_trade}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</CheckBox.Resources>
</CheckBox>
</Border>
</Grid>
</StackPanel>
</Grid>
</TabItem>
<!-- Size Presets Tab-->
@@ -149,11 +149,33 @@ namespace mbts.spark {
}
void _host_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
// When middle click over bid or ask columns:
// Non-parameterizalbe order: change cursor to No indicating sending an order is not possible
// Parameterizable order: if qty is null/0, flash and No cursor
var pos = e.GetPosition(_host);
var column_type = _host._column_for_point(pos).column_type;
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
if (e.MiddleButton == MouseButtonState.Pressed &&
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
if (!selected_algo_can_expand) {
_host.Cursor = Cursors.No;
}
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
_host._vm.size_calculator.flash_error();
_host.Cursor = Cursors.No;
}
}
_is_mouse_down = true;
InvalidateVisual();
}
void _host_MouseUp(object sender, MouseButtonEventArgs e) {
// This will reset the cursor appropriately if the middle mouse button
// has been pressed while over the bid or ask columns
var point = e.GetPosition(_host);
_set_current_row_and_column(point);
_is_mouse_down = false;
_is_mouse_dragging = false;
InvalidateVisual();
@@ -19,6 +19,7 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/Order/OrderDetailsPopupControl.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoParamsView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateFavoriteButtonsView.xaml"/>
</ResourceDictionary.MergedDictionaries>
<converters:BooleanToVisibilityConverter x:Key="BoolToInVisibility" VisibilityForTrue="Collapsed" VisibilityForFalse="Visible" />
@@ -31,7 +32,6 @@
</converters:BooleanToObjectConverter.ValueForFalse>
</converters:BooleanToObjectConverter>
<converters:EnumCharacterCasingConverter x:Key="casing_converter"/>
<converters:DoubleTextConverter x:Key="PositionTextConverter" Format="0" NaNText="-"/>
<converters:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
<converters:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
<converters:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
@@ -455,13 +455,33 @@
<Button
Margin="2"
Command="{Binding position_click_command}"
Content="{Binding position, Converter={StaticResource PositionTextConverter}}"
Content="{Binding position_display_text}"
FontWeight="Bold"
Foreground="{Binding position, Converter={StaticResource NumericSignBrushConverter}}"
Visibility="{Binding show_account_position, Converter={StaticResource BooleanToVisibilityConverter}}"
>
<Button.Visibility>
<MultiBinding>
<MultiBinding.Converter>
<converters:MultiValueConverterPipe>
<converters:BooleanOrConverter/>
<converters:BooleanToVisibilityConverter/>
</converters:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="show_day_position"/>
<Binding Path="show_account_position"/>
</MultiBinding>
</Button.Visibility>
<Button.ToolTip>
<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>
<!--<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>-->
<TextBlock TextAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{x:Static spark:LadderView.PositionToolTipConverter}">
<Binding Path="order_properties_vm.selected_account"/>
<Binding Path="show_day_position"/>
<Binding Path="show_account_position"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button.ToolTip>
</Button>
<!-- Qty textbox -->
@@ -1,4 +1,7 @@
using bts.collections;
using bts;
using bts.collections;
using bts.utils;
using mbts.ui.converters;
using mbts.utils;
using System.Linq;
using System.Windows;
@@ -8,6 +11,35 @@ using System.Windows.Input;
namespace mbts.spark {
partial class LadderView : UserControl {
public static IMultiValueConverter PositionToolTipConverter =
ValueConverter.create<AccountTuple?, bool, bool, string>((account, show_day_pos, show_total_pos) => {
const string day_pos_text = "day", total_pos_text = "total";
if (!account.HasValue)
return string.Empty;
var sb = new StringBuilderEx(account.Value.name);
string primary_pos;
if (show_day_pos) {
primary_pos = day_pos_text;
if (show_total_pos) {
sb.Add(" '", primary_pos, " ");
sb.Add("(", total_pos_text, ")' ");
} else {
sb.Add(" ", primary_pos, " ");
}
} else if (show_total_pos) {
primary_pos = total_pos_text;
sb.Add(" ", primary_pos, " ");
} else {
// unexpected code path (the button for which this tooltip is destined should be invisible in this case), but no need to throw an exception.
return string.Empty;
}
sb.Add("position\n(click to copy ");
if (show_day_pos && show_total_pos)
sb.Add(primary_pos, " position ");
sb.Add("to size)");
return sb.ToString();
});
readonly double _max_zoom, _min_zoom;
internal LadderView(LadderVM vm) {
@@ -0,0 +1,149 @@
using bts.api;
using bts.api.position;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace bts.shell.manual_fills {
class ManualFillRow : DisposableViewModelBase, IEquatable<ManualFillRow> {
public const int _uncommitted_record_id = -1;
static readonly PropertyInfo[] _snapshot_props =
Array.ConvertAll(new[] {
nameof(security),
nameof(account),
nameof(user),
nameof(side),
nameof(size),
nameof(price)
}, typeof(ManualFillRow).GetProperty);
readonly Property<bool> _is_dirty;
readonly Property<EditableItemState> _editable_item_state;
readonly DelegateProperty<bool> _is_valid;
SecurityImmutable? _security;
AccountTuple? _account;
UserTuple? _user;
Side _side;
uint _size;
double _price;
bool _is_selected;
ManualFillData? _data;
IDisposable _committed_snapshot;
public ManualFillRow(ManualFillData data) {
_data = data;
key = data.key;
_security = data.value.security;
_account = data.value.account;
_user = data.value.user;
_side = data.value.manual_fill.Side;
_size = data.value.manual_fill.Quantity;
_price = data.value.manual_fill.Price;
if (placeholder_key.HasValue) {
_is_dirty = backing(nameof(is_dirty), false);
_editable_item_state = backing(nameof(editable_item_state), EditableItemState.Uncommitted);
_committed_snapshot = ObjectSnapshot.create(this, _snapshot_props);
} else {
_is_dirty = backing(nameof(is_dirty), ObjectSnapshot.dirty_monitor(this, out _committed_snapshot, _snapshot_props));
_editable_item_state = backing(nameof(editable_item_state), _is_dirty, ch => ch.newval ? EditableItemState.Modified : EditableItemState.None);
}
_is_valid = backing(nameof(is_valid), () => _validate(this));
}
public event Action<bool> is_selected_changed;
// key/id
public ManualFillKey key { get; }
public long? placeholder_key => key.unpersisted;
public int record_id => key.persisted ?? _uncommitted_record_id;
public string record_id_display_text => key.persisted?.ToString();
// security/spec
public SecurityImmutable? security {
get => _security;
set {
if (_set(ref _security, value))
RaisePropertyChanged(nameof(spec));
}
}
public ContractSpecificationImmutable? spec => security.spec_or_default();
// account/user
public AccountTuple? account { get => _account; set => _set(ref _account, value); }
public UserTuple? user { get => _user; set => _set(ref _user, value); }
// side/size/price
public Side side { get => _side; set => _set(ref _side, value); }
public uint size { get => _size; set => _set(ref _size, value); }
public double price { get => _price; set => _set(ref _price, value); }
// is selected
public bool is_selected {
get => _is_selected;
set {
if (_set(ref _is_selected, value))
is_selected_changed?.Invoke(value);
}
}
// dirty/valid/editable-state
public bool is_dirty => _is_dirty.Value;
public bool is_valid => _is_valid.Value;
public EditableItemState editable_item_state => _editable_item_state.Value;
public ManualFillData data => _data ??
(_data = new ManualFillData(key, new ManualFillTuple(new ManualFillImmutable(to_man_fill()), security, account, user))).Value;
public void revert() =>
_committed_snapshot.Dispose();
public IObservable<bool> subscribe_is_dirty() =>
_is_dirty.observe_current(true);
public IObservable<bool> subscribe_is_selected() =>
Observable.Create<bool>(o => {
o.OnNext(is_selected);
is_selected_changed += o.OnNext;
return Disposable.Create(() => is_selected_changed -= o.OnNext);
});
public ManualFill to_man_fill() =>
new ManualFill {
RecordId = record_id,
AccountId = account?.id ?? 0,
AccountName = account?.name ?? string.Empty,
UserId = user?.id ?? 0,
Price = price,
Side = side,
Quantity = size,
SecurityKey = security?.Id ?? 0
};
public ManualFillTuple to_tuple() =>
new ManualFillTuple(new ManualFillImmutable(to_man_fill()), security, account, user);
public bool Equals(ManualFillRow other) =>
data == other?.data;
public override bool Equals(object obj) => Equals(obj as ManualFillRow);
public override int GetHashCode() => record_id;
static bool _validate(ManualFillRow row) =>
row.security.HasValue &&
row.account.HasValue &&
row.user.HasValue &&
row.side != Side.Undefined &&
!double.IsNaN(row._price);
bool _set<T>(ref T backing, T value, [CallerMemberName] string prop_name = "") =>
_set(ref backing, value, EqualityComparer<T>.Default, prop_name);
bool _set<T>(ref T backing, T value, IEqualityComparer<T> comparer, [CallerMemberName] string prop_name = "") {
if (Set(ref backing, value, comparer, prop_name)) {
_is_valid.refresh();
_data = null;
return true;
}
return false;
}
}
}
@@ -0,0 +1,84 @@
<UserControl
x:Class="mbts.ui.spark.ManualFillRowEditor"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
FocusVisualStyle="{x:Null}"
>
<UserControl.Resources>
<sys:Double x:Key="HorizontalPush">4</sys:Double>
<sys:Double x:Key="VerticalPush3">8</sys:Double>
<Style x:Key="HeaderText" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{x:Static u:BTSColors.header_text_brush}"/>
<Setter Property="Margin" Value="4"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.ColumnSpan="11" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{x:Static u:BTSColors.header_brush}"/>
<TextBlock Grid.Column="0" Text="Security" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="2" Text="Account" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="4" Text="User" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="6" Text="Side" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="8" Text="Qty" Style="{StaticResource HeaderText}"/>
<TextBlock Grid.Column="10" Text="Price" Style="{StaticResource HeaderText}"/>
<c:ContractPickerPopup
Grid.Row="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CloseOnCancel="True"
CloseOnOK="True"
Header="{Binding security}"
IsEditMode="False"
IsLazy="True"
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
SelectedSecurity="{Binding security}"
ShowDialogSkirt="True"
/>
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
</Grid>
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button Content="OK" Command="{x:Static usp:ManualFillRowEditor.CommitCommand}" CommandParameter="{Binding}" IsDefault="True"/>
<Button Content="Cancel" Command="{x:Static usp:ManualFillRowEditor.CancelCommand}" IsCancel="True"/>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</UserControl>
@@ -0,0 +1,22 @@
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for ManualFillRowEditor.xaml
/// </summary>
partial class ManualFillRowEditor : UserControl {
public const string
SecuritiesKey = nameof(SecuritiesKey),
AccountsKey = nameof(AccountsKey),
UsersKey = nameof(UsersKey),
SidesKey = nameof(SidesKey);
public static readonly RoutedCommand
CommitCommand = new RoutedCommand(),
CancelCommand = new RoutedCommand();
public ManualFillRowEditor() =>
InitializeComponent();
}
}
@@ -0,0 +1,39 @@
<Window
x:Class="mbts.ui.spark.ManualFillsImportConflictsDialog"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
u:WindowStyle.AllowResize="True"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="Import Fills Conflicts"
Style="{StaticResource BTSDialogTitleTemplate}"
>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
<Window.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<usp:ManualFillsImportConflictsView Margin="12,10,12,8"/>
<c:DialogSkirt Grid.Row="1">
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button Command="{Binding ok_cmd}">Continue</Button>
<Button Command="{Binding cancel_cmd}" IsCancel="True">Cancel</Button>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</Window.Content>
</Window>
@@ -0,0 +1,11 @@
using System.Windows;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for ManualFillsImportConflictsDialog.xaml
/// </summary>
partial class ManualFillsImportConflictsDialog : Window {
public ManualFillsImportConflictsDialog() =>
InitializeComponent();
}
}
@@ -0,0 +1,106 @@
using bts.api;
using bts.collections;
using mbts;
using mbts.collections;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Data;
using System.Windows.Input;
using Component = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
namespace bts.shell.manual_fills {
class ManualFillsImportVM : DisposableViewModelBase, IRequestViewClose {
readonly RefCountMap<ParsedManualFill> _conflicts = new RefCountMap<ParsedManualFill>();
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
public ManualFillsImportVM(int num_fills) {
rows_view = CollectionViewSource.GetDefaultView(rows);
ok_cmd = new DelegateCommand(() => import_request?.Invoke());
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()));
this.num_fills = num_fills;
_dispose = ObservableAutoWrapper.connect_item_hooks(rows, r =>
r.subscribe_has_error().Subscribe(has_error => {
if (has_error) {
if (r.affected_fills.Count(_conflicts.increment_adds_key) == 0)
return;
} else if (r.affected_fills.Count(_conflicts.decrement_removes_key) == 0)
return;
RaisePropertyChanged(nameof(num_successfully_parsed_fills));
}));
}
public event Action import_request;
public event EventHandler RequestClose;
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
public ICollectionView rows_view { get; }
public int num_fills { get; }
public int num_successfully_parsed_fills => num_fills - _conflicts.Count;
// misc items sources
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
public ObservableCollection<UserTuple?> users { get; } = new ObservableCollection<UserTuple?>();
public IReadOnlyDictionary<uint, SecurityMD> security_map {
get => _security_map;
set {
if (!ReferenceEquals(_security_map, value)) {
_security_map = value;
_resolve_securities();
RaisePropertyChanged();
}
}
}
// commands
public ICommand ok_cmd { get; }
public ICommand cancel_cmd { get; }
void _resolve_securities() {
if (_security_map == null)
return;
var reverse_lookup = ManualFillsParser.reverse_lookup(MappedDictionary.create(_security_map, v => v.security.ToString()));
foreach (var r in rows) {
if (r.component == Component.Security && r.has_error) {
var id = reverse_lookup(r.text);
if (id != 0 && _security_map.TryGetValue(id, out var smd))
r.value = smd.security;
}
}
}
public class Row : ViewModelBase, ITemplateSelectorKey {
readonly DelegateProperty<bool> _has_error;
object _value;
string _text;
public Row(Component component, string text, IReadOnlyCollection<ParsedManualFill> affected_fills, object error_value, object value) {
this.component = component;
this.text = text;
this.affected_fills = affected_fills;
_value = value;
_has_error = backing(nameof(has_error), () => Equals(this.value, error_value));
}
public Component component { get; }
public string text { get => _text; set => Set(ref _text, value); }
public IReadOnlyCollection<ParsedManualFill> affected_fills { get; }
public object value {
get => _value;
set {
if (Set(ref _value, value))
_has_error.refresh();
}
}
public bool has_error => _has_error.Value;
string ITemplateSelectorKey.key => component.ToString();
public IObservable<bool> subscribe_has_error() => _has_error.observe_current(true);
}
}
}
@@ -0,0 +1,144 @@
<UserControl
x:Class="mbts.ui.spark.ManualFillsImportConflictsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
MinWidth="400"
>
<UserControl.Resources>
<Style x:Key="VerticallyCenteredText" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!--Value Cell Template Selector-->
<u:KeyedTemplateSelector x:Key="ValueCellTemplateSelector">
<u:KeyedTemplateSelector.Default>
<DataTemplate>
<TextBlock Text="{Binding value}" VerticalAlignment="Center"/>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
<u:TemplateSelectorKeyValue Key="{x:Static Price, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
<DataTemplate>
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
<!--Value Cell Editing Template Selector-->
<u:KeyedTemplateSelector x:Key="ValueCellEditingTemplateSelector">
<u:TemplateSelectorKeyValue Key="{x:Static Security, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
<DataTemplate>
<c:ContractPickerPopup
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CloseOnCancel="True"
CloseOnOK="True"
Header="{Binding value}"
IsEditMode="False"
IsLazy="True"
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
SelectedSecurity="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ShowDialogSkirt="True"
/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static Account, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static User, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.users, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static Side, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.sides, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static Size, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
<DataTemplate>
<c:TickUpDownUInt Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static Price, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
<DataTemplate>
<usp:PriceEntryControl Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
</UserControl.Resources>
<!--Content-->
<Grid x:Name="content_root">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image VerticalAlignment="Center" Margin="0,0,8,0">
<Image.Source>
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
<Binding.Converter>
<cv:SystemIconConverter/>
</Binding.Converter>
</Binding>
</Image.Source>
</Image>
<Canvas x:Name="text_canvas" Grid.Column="1" HorizontalAlignment="Stretch"/>
<TextBlock Grid.Column="1" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=text_canvas}">
<Run Text="{Binding num_successfully_parsed_fills, Mode=OneWay}"/>
<Run Text="/"/>
<Run Text="{Binding num_fills, Mode=OneWay}"/>
<Run Text=" fills have been successfully converted."/>
<Run Text="Resolve conflicts below by selecting a value to assign each affected fill,"/>
<Run Text="or modify individual fills in the main window."/>
<LineBreak/>
<LineBreak/>
<Run Text="Click 'Continue' to proceed to the main window."/>
</TextBlock>
<DataGrid
x:Name="data_grid"
u:DataGridBehavior.SingleClickEdit="True"
Grid.Row="1"
Grid.ColumnSpan="2"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserSortColumns="True"
HeadersVisibility="All"
ItemsSource="{Binding rows_view}"
Margin="0,8,0,0"
MinRowHeight="17"
RowHeaderWidth="22"
>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Component" Binding="{Binding component}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
<DataGridTextColumn Header="Text" Binding="{Binding text}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
<DataGridTextColumn Header="Affected Fills" Binding="{Binding affected_fills.Count}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ValueCellTemplateSelector}" CellEditingTemplateSelector="{StaticResource ValueCellEditingTemplateSelector}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
@@ -0,0 +1,30 @@
using System.Windows.Controls;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for ManualFillsImportConflictsView.xaml
/// </summary>
partial class ManualFillsImportConflictsView : UserControl {
public ManualFillsImportConflictsView() =>
InitializeComponent();
public enum ManualFillComponent {
Security,
Account,
User,
Side,
Size,
Price
}
public static class ManualFillComponentNames {
public static readonly string
Security = ManualFillComponent.Security.ToString(),
Account = ManualFillComponent.Account.ToString(),
User = ManualFillComponent.User.ToString(),
Side = ManualFillComponent.Side.ToString(),
Size = ManualFillComponent.Size.ToString(),
Price = ManualFillComponent.Price.ToString();
}
}
}
@@ -0,0 +1,107 @@
using bts.api.core;
using bts.api.position;
using bts.collections;
using mbts.spark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace bts.shell.manual_fills {
struct ManualFillKey : IEquatable<ManualFillKey>, IComparable<ManualFillKey> {
public readonly int? persisted;
public readonly long? unpersisted;
public ManualFillKey(int persisted) {
this.persisted = persisted;
unpersisted = null;
}
public ManualFillKey(long unpersisted) {
this.unpersisted = unpersisted;
persisted = null;
}
public int CompareTo(ManualFillKey other) {
if (persisted.HasValue) {
if (other.persisted.HasValue) {
return persisted.Value.CompareTo(other.persisted.Value);
} else if (other.unpersisted.HasValue) {
return -1;
} else return 1;
} else if (unpersisted.HasValue) {
return other.unpersisted.HasValue ? unpersisted.Value.CompareTo(other.unpersisted.Value) : 1;
} else return !other.persisted.HasValue && !other.unpersisted.HasValue ? 0 : -1;
}
public override string ToString() => persisted.HasValue ? persisted.Value.ToString() : unpersisted.HasValue ? unpersisted.Value.ToString() : "(N/A)";
public bool Equals(ManualFillKey other) =>
persisted == other.persisted &&
unpersisted == other.unpersisted;
public override bool Equals(object obj) =>
obj is ManualFillKey other && Equals(other);
public override int GetHashCode() => persisted ?? unpersisted?.GetHashCode() ?? 0;
public static bool operator ==(ManualFillKey a, ManualFillKey b) => a.Equals(b);
public static bool operator !=(ManualFillKey a, ManualFillKey b) => !a.Equals(b);
}
struct ManualFillData : IEquatable<ManualFillData> {
public static readonly Func<ManualFillData, ManualFillKey> key_fn = mfd => mfd.key;
public readonly ManualFillKey key;
public readonly ManualFillTuple value;
public ManualFillData(ManualFillKey key, ManualFillTuple value) {
this.key = key;
this.value = value;
}
public bool Equals(ManualFillData other) =>
key == other.key &&
value.Equals(other.value);
public override bool Equals(object obj) =>
obj is ManualFillData other && Equals(other);
public override int GetHashCode() =>
key.GetHashCode();
public static bool operator ==(ManualFillData a, ManualFillData b) => a.Equals(b);
public static bool operator !=(ManualFillData a, ManualFillData b) => !a.Equals(b);
}
class ManualFillsModel {
readonly OrdersModel _model;
public ManualFillsModel(OrdersModel model)
: this(model, model.on_manual_fill_tuples,
model.factory.subscribe_accounts().Select(d => MappedDictionary.create(d, kv => new AccountTuple(kv.Key, kv.Value))).Table(u => u.id),
model.factory.subscribe_users().Select(d => MappedDictionary.create(d, kv => new UserTuple(kv.Key, kv.Value))).Table(a => a.id)) { }
public ManualFillsModel(OrdersModel model, IObservable<TableUpdate<ManualFillTuple, int>> manual_fills, IObservable<TableUpdate<AccountTuple, uint>> accounts, IObservable<TableUpdate<UserTuple, uint>> users) {
_model = model;
this.manual_fills = manual_fills.SelectKeyValue(
mft => new ManualFillData(new ManualFillKey(mft.manual_fill.RecordId), mft),
ManualFillData.key_fn);
this.accounts = accounts;
this.users = users;
}
public IObservable<TableUpdate<ManualFillData, ManualFillKey>> manual_fills { get; }
public IObservable<TableUpdate<AccountTuple, uint>> accounts { get; }
public IObservable<TableUpdate<UserTuple, uint>> users { get; }
public async Task delete_fills(IEnumerable<ManualFill> fills) =>
await _model.delete_manual_fills(fills);
public async Task<bool> create_fills(IEnumerable<ManualFill> fills) =>
await _model.create_manual_fills(fills);
public async Task<bool> update_fills(IEnumerable<ManualFill> fills) =>
await _model.update_manual_fills(fills);
}
}
@@ -0,0 +1,162 @@
using bts.api;
using bts.api.position;
using bts.collections;
using bts.utils;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Linq;
using ManualFillComponent = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
namespace bts.shell.manual_fills {
class ParsedManualFill {
public readonly Guid unpersisted_id = Guid.NewGuid();
public readonly ManualFill result = new ManualFill();
public (string text, bool success) security { get; set; }
public (string text, bool success) account { get; set; }
public (string text, bool success) user { get; set; }
public (string text, bool success) side { get; set; }
public (string text, bool success) quantity { get; set; }
public (string text, bool success) price { get; set; }
public bool Equals(ParsedManualFill other) => other != null && unpersisted_id == other.unpersisted_id;
public override bool Equals(object obj) => Equals(obj as ParsedManualFill);
public override int GetHashCode() => unpersisted_id.GetHashCode();
}
static class ManualFillsParser {
public const string file_extension = "csv";
public static Action<ManualFill, object> assign_component(ManualFillComponent component) {
switch (component) {
case ManualFillComponent.Security: return (mf, val) => mf.SecurityKey = ConvertUtils.try_convert<SecurityImmutable?>(val)?.Id ?? 0;
case ManualFillComponent.Account: return (mf, val) => mf.AccountId = ConvertUtils.try_convert<AccountTuple?>(val)?.id ?? 0;
case ManualFillComponent.User: return (mf, val) => mf.UserId = ConvertUtils.try_convert<UserTuple?>(val)?.id ?? 0;
case ManualFillComponent.Side: return (mf, val) => mf.Side = ConvertUtils.try_convert<Side>(val, Side.Undefined);
case ManualFillComponent.Size: return (mf, val) => mf.Quantity = ConvertUtils.try_convert<uint>(val);
case ManualFillComponent.Price: return (mf, val) => mf.Price = ConvertUtils.try_convert(val, double.NaN);
default: throw new NotImplementedException();
}
}
/// Note: the returned dictionary will have entries for each <see cref="ManualFillComponent"/>, regardless of whether or not that component has any conflicts.
public static Dictionary<ManualFillComponent, Dictionary<string, List<ParsedManualFill>>> get_conflicts(IEnumerable<ParsedManualFill> parsed_fills) {
var missing_components = EnumHelper.get_values<ManualFillComponent>().ToDictionary(c => c, c => new Dictionary<string, List<ParsedManualFill>>());
var mk_list = new Func<string, List<ParsedManualFill>>(_ => new List<ParsedManualFill>());
foreach (var pmf in parsed_fills) {
if (!pmf.security.success) {
add_missing(ManualFillComponent.Security, pmf.security.text, pmf);
}
if (!pmf.account.success) {
add_missing(ManualFillComponent.Account, pmf.account.text, pmf);
}
if (!pmf.user.success) {
add_missing(ManualFillComponent.User, pmf.user.text, pmf);
}
if (!pmf.quantity.success) {
add_missing(ManualFillComponent.Size, pmf.quantity.text, pmf);
}
if (!pmf.side.success) {
add_missing(ManualFillComponent.Side, pmf.side.text, pmf);
}
if (!pmf.price.success) {
add_missing(ManualFillComponent.Price, pmf.price.text, pmf);
}
}
return missing_components;
void add_missing(ManualFillComponent component, string text, ParsedManualFill result) =>
missing_components[component].GetOrAdd(text ?? string.Empty, mk_list).Add(result);
}
// This implementation prioritizes simplicity over speed.
// The security desc lookup could be better optimized, but the speed at which fills are imported is probably not a paramount concern.
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, IReadOnlyDictionary<uint, SecurityImmutable> security_map, IReadOnlyDictionary<uint, string> account_map, IReadOnlyDictionary<uint, string> user_map) =>
parse_rows(rows,
reverse_lookup(MappedDictionary.create(security_map, kv => kv.Value.ToString())),
reverse_lookup(account_map),
reverse_lookup(user_map));
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, Func<string, uint> security_id_lookup, Func<string, uint> account_id_lookup, Func<string, uint> user_id_lookup) {
var columns = new[] {
(Headers.Security, Cells.security(security_id_lookup)),
(Headers.Account, Cells.account(account_id_lookup)),
(Headers.User, Cells.user(user_id_lookup)),
(Headers.Side, Cells.side),
(Headers.Quantity, Cells.quantity),
(Headers.Price, Cells.price)
}.ToDictionary(col => col.Item1, col => col.Item2.with_null_check());
return RowParser.parse_rows(rows, columns, Cells.delimiter);
}
public static Func<string, TKey> reverse_lookup<TKey>(IReadOnlyDictionary<TKey, string> map) {
var lookup = map.ToLookup(kv => kv.Value, kv => kv.Key);
return cell_text => lookup.Contains(cell_text) ? lookup[cell_text].First() : default;
}
static class Headers {
public const string
Security = "Security",
Account = "Account",
User = "User",
Side = "Side",
Quantity = "Quantity",
Price = "Price";
}
static class Cells {
public const char delimiter = ',';
public static RowParser.AssignColumnValue<ParsedManualFill> security(Func<string, uint> reverse_id_lookup) {
return impl;
void impl(ref ParsedManualFill pmf, string text) {
pmf.result.SecurityKey = reverse_id_lookup(text);
pmf.security = (text, pmf.result.SecurityKey != 0);
}
}
public static void side(ref ParsedManualFill pmf, string cell_text) {
switch (cell_text[0]) {
case 'B': pmf.result.Side = Side.Buy; break;
case 'S': pmf.result.Side = Side.Sell; break;
default: pmf.side = (cell_text, false); break;
}
pmf.side = (cell_text, true);
}
public static void quantity(ref ParsedManualFill pmf, string cell_text) {
if (uint.TryParse(cell_text, out uint v)) {
pmf.result.Quantity = v;
pmf.quantity = (cell_text, true);
} else {
pmf.quantity = (cell_text, false);
}
}
public static void price(ref ParsedManualFill pmf, string cell_text) {
if (double.TryParse(cell_text, out var v)) {
pmf.result.Price = v;
pmf.price = (cell_text, true);
} else {
pmf.price = (cell_text, false);
}
}
public static RowParser.AssignColumnValue<ParsedManualFill> account(Func<string, uint> reverse_id_lookup) {
return impl;
void impl(ref ParsedManualFill pmf, string text) {
pmf.result.AccountId = reverse_id_lookup(text);
pmf.account = (text, pmf.result.AccountId != 0);
}
}
public static RowParser.AssignColumnValue<ParsedManualFill> user(Func<string, uint> reverse_id_lookup) {
return impl;
void impl(ref ParsedManualFill pmf, string text) {
pmf.result.UserId = reverse_id_lookup(text);
pmf.user = (text, pmf.result.UserId != 0);
}
}
}
}
}
@@ -0,0 +1,23 @@
using mbts.ui;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace bts.shell.manual_fills {
[ProtoContract]
class ManualFillsSettings {
[ProtoMember(1)]
readonly SortingCriteria[] _sorting_criterion;
public static ManualFillsSettings Default =>
new ManualFillsSettings(new[] { new SortingCriteria(nameof(mbts.ui.spark.ManualFillsView.Columns.Security), true) });
public ManualFillsSettings() { }
public ManualFillsSettings(IEnumerable<SortingCriteria> sorting_criterion) =>
_sorting_criterion = sorting_criterion.ToArray();
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? Array.Empty<SortingCriteria>();
}
}
@@ -0,0 +1,222 @@
using bts.api;
using bts.api.core;
using bts.api.position;
using bts.collections;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
using Disposable = mbts.Disposable;
using ManualFillComponent = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
namespace bts.shell.manual_fills {
enum EditableItemState {
None, // Committed and not modified
Modified, // Committed and modified
Uncommitted // Uncommitted (modified n/a)
}
class ManualFillsVM : DisposableViewModelBase {
const int _max_unconfirmed_removes = 16;
static readonly IComparer<ManualFillRow> _row_key_comparer = Comparer<ManualFillRow>.Create((a, b) => a.key.CompareTo(b.key));
readonly IBus _bus;
readonly ManualFillsModel _model;
readonly ObservableCollection<ManualFillRow>
_persisted_rows = new ObservableCollection<ManualFillRow>(),
_placeholder_rows = new ObservableCollection<ManualFillRow>();
long _placeholder_id_counter = long.MinValue;
IDisposable _import_fills_hooks;
public static ManualFillsVM create(IBus bus, OrdersModel model) =>
new ManualFillsVM(bus, new ManualFillsModel(model));
public ManualFillsVM(IBus bus, ManualFillsModel model) {
_bus = bus;
_model = model;
var merged_rows = new ObservableAggregateCollection<ManualFillRow>(_persisted_rows, _placeholder_rows);
rows_view = CollectionViewSource.GetDefaultView(merged_rows);
// mons
var dirty_mon = new ReactiveThresholdMonitor<ManualFillRow>(r => r.subscribe_is_dirty());
var selection_mon = new ReactiveThresholdMonitor<ManualFillRow>(r => r.subscribe_is_selected());
var placeholder_mon = new ReactiveThresholdMonitor<ManualFillRow>();
// commands
commit_all_fills_cmd = ObservantAsyncCommand.create(() => _commit_async(merged_rows), out var commit_all_fils_observer);
commit_selected_fills_cmd = ObservantAsyncCommand.create(() => _commit_async(selection_mon.filtered_items), out var commit_selected_fills_observer);
revert_all_cmd = ObservantCommand.create(() => revert(dirty_mon.filtered_items.ToList()), out var revert_all_observer);
revert_selected_fills_cmd = ObservantCommand.create(() => revert(selection_mon.filtered_items.ToList()), out var revert_selected_fills_observer);
remove_selected_fills_cmd = ObservantAsyncCommand.create(remove_selected_fills_async, out var remove_selected_fills_observer);
add_fill_cmd = new DelegateCommand<ManualFillRow>(_add_fill);
import_fills_cmd = FileDialogCommand.open_file(_import_fills, extensions: ManualFillsParser.file_extension);
//
var dirty_or_new_src = dirty_mon.CombineLatest(placeholder_mon, LogicalDelegates.Or);
_dispose = Disposable.Create(
RXCollection.connect(_persisted_rows,
_model.manual_fills.OnDispatcher(),
Observable.Return(Comparer<ManualFillData>.Create((a, b) => a.key.CompareTo(b.key))),
mfd => new ManualFillRow(mfd),
mfr => mfr.data),
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
security_map = m;
RaisePropertyChanged(nameof(security_map));
}),
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
ObservableAutoWrapper.connect_item_hooks(_placeholder_rows, r => Disposable.Create(selection_mon.join(r), placeholder_mon.join(r, Observable.Return(true)))),
// command param subs
dirty_mon.Subscribe(revert_all_observer),
dirty_or_new_src.OnDispatcher().Subscribe(commit_all_fils_observer),
selection_mon.Subscribe(remove_selected_fills_observer),
selection_mon.CombineLatest(dirty_mon, LogicalDelegates.And).OnDispatcher().Subscribe(revert_selected_fills_observer),
selection_mon.CombineLatest(dirty_or_new_src, LogicalDelegates.And).OnDispatcher().Subscribe(commit_selected_fills_observer),
// accounts/users subs
RXCollection.connect(accounts, model.accounts.OnDispatcher(), a => new AccountTuple?(a), a => a.Value, Comparer<AccountTuple>.Default),
RXCollection.connect(users, model.users.OnDispatcher(), u => new UserTuple?(u), u => u.Value, Comparer<UserTuple>.Default),
Disposable.Create(() => Disposable.dispose(ref _import_fills_hooks))
);
// local methods
void revert(IReadOnlyList<ManualFillRow> rows) {
foreach (var r in rows)
r.revert();
}
async Task remove_selected_fills_async() {
if (selection_mon.filtered_items.Count < _max_unconfirmed_removes || BTSMessageBox.Confirm($"Are you sure you want to delete {selection_mon.filtered_items.Count} fills?", "Delete Fills")) {
var persisted_fills = new List<ManualFill>();
foreach (var r in selection_mon.filtered_items.ToList()) {
if (r.placeholder_key.HasValue)
_placeholder_rows.RemoveSorted(r, _row_key_comparer);
else persisted_fills.Add(r.to_man_fill());
}
await _model.delete_fills(persisted_fills);
}
}
}
public ICollectionView rows_view { get; }
// misc items sources
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
public ObservableCollection<UserTuple?> users { get; } = new ObservableCollection<UserTuple?>();
public IReadOnlyDictionary<uint, SecurityMD> security_map { get; private set; } = DictionaryHelper.empty<uint, SecurityMD>();
// commands
public ICommand add_fill_cmd { get; }
public ICommand commit_all_fills_cmd { get; }
public ICommand commit_selected_fills_cmd { get; }
public ICommand import_fills_cmd { get; }
public ICommand remove_selected_fills_cmd { get; }
public ICommand revert_all_cmd { get; }
public ICommand revert_selected_fills_cmd { get; }
public ManualFillRow row_factory() =>
new ManualFillRow(
new ManualFillData(
new ManualFillKey(_placeholder_id_counter++),
new ManualFillTuple(new ManualFillImmutable(new ManualFill {
Side = Side.Buy
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())));
void _add_fill(ManualFillRow new_row) =>
_placeholder_rows.InsertSorted((new_row ?? (new_row = row_factory())).placeholder_key.HasValue ?
new_row : throw new InvalidOperationException("New row is already persisted."),
_row_key_comparer);
async Task _commit_async(IEnumerable<ManualFillRow> rows) {
var updates = new List<ManualFill>();
var adds = new List<(ManualFill man_fill, long pl_key)>();
foreach (var r in rows) {
if (r.is_dirty) {
updates.Add(r.to_man_fill());
} else if (r.placeholder_key.HasValue) {
adds.Add((r.to_man_fill(), r.placeholder_key.Value));
}
}
if (updates.Count > 0)
await _model.update_fills(updates);
if (adds.Count > 0) {
await _model.create_fills(adds.WrapSelector(r => r.man_fill, null));
_placeholder_rows
.WrapSelector(r => r.placeholder_key.Value, null)
.RemoveSorted(adds.Select(r => r.pl_key));
}
}
void _import_fills(string file_name) {
try {
var lines = File.ReadAllLines(file_name);
var parsed_fills = ManualFillsParser.parse_rows(
lines,
MappedDictionary.create(security_map, kv => kv.Value.security),
accounts.ToDictionary(a => a.Value.id, a => a.Value.name),
users.ToDictionary(u => u.Value.id, u => u.Value.name)
).ToList();
var conflicts = ManualFillsParser.get_conflicts(parsed_fills);
if (conflicts.Values.Any(c => c.Count > 0)) {
// init vm
var vm = new ManualFillsImportVM(parsed_fills.Count) {
security_map = security_map
};
// add rows
foreach (var kv in conflicts) {
var assign_del = ManualFillsParser.assign_component(kv.Key);
foreach (var text_groups in kv.Value) {
var eval = get_error_value(kv.Key);
var row = new ManualFillsImportVM.Row(kv.Key, text_groups.Key, text_groups.Value, eval, eval);
vm.rows.Add(row);
vm.import_request += () => {
foreach (var pmf in text_groups.Value)
assign_del(pmf.result, row.value);
};
}
}
var win = new mbts.ui.spark.ManualFillsImportConflictsDialog {
DataContext = vm,
Title = ManualFillsWindowFactory.window_name,
Topmost = true
};
vm.RequestClose += (s, e) => Disposable.dispose(ref _import_fills_hooks);
vm.import_request += () => {
add_imported_fills();
Disposable.dispose(ref _import_fills_hooks);
};
Disposable.replace(ref _import_fills_hooks, Disposable.Create(
ObservableAutoWrapper.connect(accounts, vm.accounts),
ObservableAutoWrapper.connect(users, vm.users),
vm,
Disposable.Create(close)
));
win.move_to_mouse(-200, -75);
win.Show();
void close() => WindowUtilities.force_close(win);
object get_error_value(ManualFillComponent component) {
switch (component) {
case ManualFillComponent.Price: return double.NaN;
case ManualFillComponent.Size: return 0;
default: return null;
}
}
} else {
add_imported_fills();
}
void add_imported_fills() =>
_placeholder_rows.InsertSorted(parsed_fills.Select(pmf => new ManualFillRow(new ManualFillData(new ManualFillKey(_placeholder_id_counter++), new ManualFillTuple(new ManualFillImmutable(pmf.result),
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
users.FirstOrDefault(a => a?.id == pmf.result.UserId))))), _row_key_comparer);
} catch (Exception ex) {
BTSMessageBox.Error($"Error importing fills: {ex}.", ManualFillsWindowFactory.window_name);
}
}
}
}
@@ -0,0 +1,247 @@
<UserControl
x:Class="mbts.ui.spark.ManualFillsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
>
<AdornerDecorator>
<Grid>
<DataGrid
x:Name="data_grid"
u:DataGridBehavior.SingleClickEdit="True"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserSortColumns="True"
HeadersVisibility="All"
ItemsSource="{Binding rows_view}"
MinRowHeight="17"
RowHeaderWidth="22"
>
<!--Row Header Style-->
<DataGrid.RowHeaderStyle>
<Style TargetType="DataGridRowHeader">
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border x:Name="Root">
<u:StateManager.VisualState>
<MultiBinding Converter="{x:Static usp:ManualFillsView.RowHeaderVisualStateConverter}">
<Binding Path="editable_item_state"/>
<Binding Path="is_valid"/>
</MultiBinding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState Name="None"/>
<VisualState Name="Modified">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="pencil" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="0.7"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Fill has modified properties."/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Uncommitted">
<Storyboard>
<ColorAnimation Storyboard.TargetName="new_item" Storyboard.TargetProperty="(usp:ManualFillsView.ImageBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#B3006000"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="New Fill."/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Error">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="exclamation" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="1"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Fill has invalid properties."/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="ErrorUncommitted">
<Storyboard>
<ColorAnimation Storyboard.TargetName="new_item" Storyboard.TargetProperty="(usp:ManualFillsView.ImageBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#CCCD4848"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="New Fill has invalid properties."/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Image x:Name="new_item" usp:ManualFillsView.ImageBrush="Transparent" Height="14">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="{Binding Path='(usp:ManualFillsView.ImageBrush)', ElementName=new_item}" Geometry="{Binding Source={StaticResource {x:Static u:Icons.DocumentAddGeometryKey}}}"/>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<Image x:Name="exclamation" Height="14" Opacity="0">
<Image.Source>
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
<Binding.Converter>
<cv:SystemIconConverter/>
</Binding.Converter>
</Binding>
</Image.Source>
</Image>
<Image x:Name="pencil" Height="14" Source="{x:Static u:Icons.Pencil}" Opacity="0">
<Image.RenderTransform>
<RotateTransform Angle="45" CenterX="0" CenterY="7"/>
</Image.RenderTransform>
</Image>
</Grid>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowHeaderStyle>
<!--Cell Style-->
<DataGrid.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</DataGrid.CellStyle>
<!--Row Style-->
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<!--Record ID-->
<DataGridTextColumn c:DataGridEx.CellKey="{x:Static RecordID, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="ID" Binding="{Binding record_id_display_text}" SortMemberPath="record_id" IsReadOnly="True"/>
<!---Security-->
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Security, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Security" SortMemberPath="security">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding security}" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<c:ContractPickerPopup
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CloseOnCancel="True"
CloseOnOK="True"
Header="{Binding security}"
IsEditMode="False"
IsLazy="True"
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ShowDialogSkirt="True"
/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--Account-->
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Account, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Account" SortMemberPath="account">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding account}" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid>
<ComboBox
x:Name="mutable_view"
ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}"
SelectedItem="{Binding account, UpdateSourceTrigger=PropertyChanged}"
Visibility="Visible"
/>
<TextBlock
x:Name="readonly_view"
Text="{Binding account}"
VerticalAlignment="Center"
Visibility="Collapsed"
/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding placeholder_key}" Value="{x:Null}">
<Setter TargetName="mutable_view" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="readonly_view" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--User-->
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static User, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="User" SortMemberPath="user">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding user}" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.users, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding user, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--Side-->
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Side, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Side" SortMemberPath="side">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding side}" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.sides, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding side, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--Size-->
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Size, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Qty" SortMemberPath="side">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding size}" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<c:TickUpDownUInt MinWidth="40" Value="{Binding size, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--Price-->
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Price, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Price" SortMemberPath="price">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="price"/>
<Binding Path="spec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<usp:PriceEntryControl ContractSpec="{Binding spec}" Value="{Binding price, UpdateSourceTrigger=PropertyChanged}" AlwaysReformatOnSpecChange="True"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -0,0 +1,97 @@
using bts.shell.manual_fills;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.utils;
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for ManualFillsView.xaml
/// </summary>
partial class ManualFillsView : UserControl {
public static readonly IMultiValueConverter
RowHeaderVisualStateConverter = ValueConverter.create<EditableItemState, bool, string>(
(edit_state, is_valid) => is_valid ? edit_state.ToString() : edit_state == EditableItemState.Uncommitted ? "ErrorUncommitted" : "Error");
public enum Columns {
RecordID,
Security,
Account,
User,
Side,
Size,
Price
}
IDisposable _transaction;
public ManualFillsView() =>
InitializeComponent();
public static readonly DependencyProperty ImageBrushProperty =
DependencyProperty.RegisterAttached("ImageBrush", typeof(Brush), typeof(ManualFillsView));
public static Brush GetImageBrush(DependencyObject d) => d.GetValue(ImageBrushProperty) as Brush;
public static void SetImageBrush(DependencyObject d, Brush value) => d.SetValue(ImageBrushProperty, value);
public void begin_add_fill() {
if (!(DataContext is ManualFillsVM vm))
return;
Disposable.dispose(ref _transaction);
var row = vm.row_factory();
var row_editor = new ManualFillRowEditor { DataContext = row };
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
// dynamic resources
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
FocusManager.SetIsFocusScope(row_editor, true);
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
// popup adorner
var popup_adorner = new PopupAdorner(
data_grid, data_grid,
new Border {
Child = row_editor,
Background = BTSColors.common_background_brush,
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(1),
Padding = new Thickness(0, 6, 0, 0),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top
},
HorizontalPosition.Middle,
VerticalPosition.Middle,
0,
22) {
OpenRelativeToMouse = true,
EnableDragMove = true,
ClipToBounds = true
};
popup_adorner.attach();
popup_adorner.IsOpen = true;
row_editor.Focus();
_transaction = new CompositeDisposable(sec_hook) { destroy_popup };
// local methods
void commit_executed(object sender, ExecutedRoutedEventArgs e) {
vm.add_fill_cmd?.TryExecuteIfCan(row);
Disposable.dispose(ref _transaction);
}
void cancel_executed(object sender, ExecutedRoutedEventArgs e) {
row.revert();
Disposable.dispose(ref _transaction);
}
void destroy_popup() {
popup_adorner.IsOpen = false;
popup_adorner.detach();
}
}
}
}
@@ -0,0 +1,133 @@
using bts.api;
using bts.shell.manual_fills;
using bts.spark;
using mbts;
using mbts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace bts.shell {
class ManualFillsWindowFactory : ShellWindowFactory<ManualFillsSettings> {
public const string
window_type = "ManualFills",
window_name = "Manual Fills";
const double _window_min_width = 466;
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly ManualFillsConfig _manual_fills_conf = ManualFillsConfig.read();
public ManualFillsWindowFactory(IShellModule owner, IBus bus, OrdersModel orders_model) : base(owner, window_name) {
_bus = bus;
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.ShowManualFillsTag).Subscribe(open_window);
//
async void open_window(ManualFillsSettings state) =>
_bus.post(SparkTags.ShowWindow, await load(state));
}
public override Task<IShellWindow> load(ManualFillsSettings settings) {
return Task.FromResult<IShellWindow>(SparkWindow<ManualFillsSettings>.create(
window_type, window_name, _bus, this, Icons.DataEdit, _manual_fills_conf.thread_id, thread_init, _window_min_width));
//
(UIElement, IEnumerable<SparkCommand>, Func<ManualFillsSettings>, IDisposable) thread_init() {
var vm = ManualFillsVM.create(_bus, _orders_model);
var view = new ManualFillsView { DataContext = vm };
view.data_grid.Sorting += on_sorting;
IReadOnlyList<SortingCriteria> last_sorting_criterion = Array.Empty<SortingCriteria>();
using (apply_sort()) {
var col_sort_tuples =
from sc in settings.sorting_criterion
join col in view.data_grid.Columns on sc.col_name equals _col_id(col)
select (col, sc);
foreach (var (col, sc) in col_sort_tuples) {
col.SortDirection = sc.direction();
vm.rows_view.SortDescriptions.Add(new SortDescription(col.SortMemberPath, sc.direction()));
}
}
var cmds = new[] {
new SparkCommandCommand("Add New", Icons.AddBoxes, _manual_fills_conf.modal_transactions ? new DelegateCommand(() => view.begin_add_fill()) : vm.add_fill_cmd) { group = CommandGroups.Add },
new SparkCommandCommand("Import Fills", Icons.Import, vm.import_fills_cmd) { group = CommandGroups.Add },
new SparkCommandCommand("Revert Selected", Icons.UndoCircular,vm.revert_selected_fills_cmd) { group = CommandGroups.Revert, show_in_toolbar = false },
new SparkCommandCommand("Revert All", Icons.UndoCircular, vm.revert_all_cmd) { group = CommandGroups.Revert },
new SparkCommandCommand("Save Selected", Icons.Disk, vm.commit_selected_fills_cmd) { group = CommandGroups.Save, show_in_toolbar = false },
new SparkCommandCommand("Save All", Icons.Disk, vm.commit_all_fills_cmd) { group = CommandGroups.Save },
new SparkCommandCommand("Delete Selected", Icons.Trash, vm.remove_selected_fills_cmd)
};
return (view, cmds, get_settings, Disposable.Create(
vm.rows_view.subscribe_sorting_criterion(sm => view.data_grid.Columns.TryGetFirst(dc => dc.SortMemberPath == sm, out DataGridColumn match) ? _col_id(match) : null)
.Subscribe(sc => last_sorting_criterion = sc),
vm));
//
void on_sorting(object sender, DataGridSortingEventArgs e) {
e.Handled = true;
using (apply_sort()) {
var new_sort_desc = new SortDescription(e.Column.SortMemberPath, e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending);
e.Column.SortDirection = new_sort_desc.Direction;
if (KeyboardExtensions.IsShiftDown()) {
if (vm.rows_view.SortDescriptions.TryGetFirst(sd => sd.PropertyName == e.Column.SortMemberPath, out var match, out int index)) {
vm.rows_view.SortDescriptions[index] = new_sort_desc;
} else {
vm.rows_view.SortDescriptions.Add(new_sort_desc);
}
} else {
vm.rows_view.SortDescriptions.Clear();
foreach (var col in view.data_grid.Columns.Except(new[] { e.Column }))
col.SortDirection = null;
vm.rows_view.SortDescriptions.Add(new_sort_desc);
}
}
}
/// Ensures that the primary sort description property (i.e., at index 0) is the placeholder_key.
/// This behavior has the effect of grouping placeholder rows at the bottom, without using <see cref="ICollectionView.GroupDescriptions"/> (which is incompatible with row virtualization).
IDisposable apply_sort() =>
Disposable.Create(() => {
var pk_sort_desc = new SortDescription(nameof(ManualFillRow.placeholder_key), ListSortDirection.Ascending);
if (vm.rows_view.SortDescriptions.Count > 0) {
if (vm.rows_view.SortDescriptions[0].Equals(pk_sort_desc))
return;
vm.rows_view.SortDescriptions.removeFirst(sd => sd.PropertyName == pk_sort_desc.PropertyName);
}
vm.rows_view.SortDescriptions.Insert(0, pk_sort_desc);
});
ManualFillsSettings get_settings() =>
new ManualFillsSettings(last_sorting_criterion);
}
}
static string _col_id(DataGridColumn col) =>
DataGridEx.GetCellKey(col)?.ToString();
readonly struct ManualFillsConfig {
public readonly bool modal_transactions;
public readonly string thread_id;
ManualFillsConfig(string thread_id, bool modal_transactions) {
this.thread_id = thread_id;
this.modal_transactions = modal_transactions;
}
public static ManualFillsConfig read() => new ManualFillsConfig(
mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().manual_fills ? window_type : null,
mbts.config.Configuration.get<bts.config.ManualFillsOptions>().modal_transactions);
}
static class CommandGroups {
public const string
Add = "Add",
Save = "Save",
Revert = "Revert";
}
}
}
@@ -1,5 +1,6 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.shell.orderbook;
using bts.utils;
@@ -27,12 +28,13 @@ using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using static mbts.ui.controls.DIYGrid;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using System.Windows.Input;
using System.Reactive.Disposables;
namespace mbts.spark {
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
ListSortDirection? SortDirection { get; set; }
IDataGridColumnFilter<OrderSecurityAlgoMeta> filter { get; }
IDataGridColumnFilter<OrderSecurity> filter { get; }
IComparer<OrderBookRowVM> comparer { get; }
}
@@ -63,11 +65,12 @@ namespace mbts.spark {
partial class OrderBookVM {
public class OrderBookColumnCollection : ObservableCollection<ColumnBase>, IValueFactory<(FilterStates filters, IReadOnlyList<SortingCriteria> sorting)>, IDisposable {
readonly List<ICVOrderBookColumn> _active_comparers = new List<ICVOrderBookColumn>();
CompositeDisposable _resources = new CompositeDisposable();
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
// security
Add(security_column = new SecurityColumn(
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, SecurityImmutable, SecurityCriteria>(
DiscreteValuesFilterVM.Create<OrderSecurity, SecurityImmutable, SecurityCriteria>(
order_securities,
ort => ort.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
SecurityCriteria.from_securities,
@@ -82,7 +85,7 @@ namespace mbts.spark {
DefaultColumnHeaders.Symbol,
r => r.symbol,
DefaultColumnWidths.Symbol,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
ort => ort.security?.Symbol ?? string.Empty,
nameof(OrderBookRowVM.symbol)
@@ -95,7 +98,7 @@ namespace mbts.spark {
"Time",
r => r.time_created_text,
DefaultColumnWidths.Time,
new TimeFilterVM<OrderSecurityAlgoMeta>(
new TimeFilterVM<OrderSecurity>(
ort => ort.order.created(),
nameof(OrderBookRowVM.time_created)),
DataGridFilterKnownTemplates.TimeFilter,
@@ -106,7 +109,7 @@ namespace mbts.spark {
"Last Change",
r => r.last_change_text,
DefaultColumnWidths.Time,
new TimeFilterVM<OrderSecurityAlgoMeta>(
new TimeFilterVM<OrderSecurity>(
ort => ort.order.last_change(),
nameof(OrderBookRowVM.last_change)),
DataGridFilterKnownTemplates.TimeFilter,
@@ -117,7 +120,7 @@ namespace mbts.spark {
DefaultColumnHeaders.User,
r => r.user,
DefaultColumnWidths.User,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
model.factory.subscribe_users().Select(d => d.Values),
ort => ort.order.user(),
nameof(OrderBookRowVM.user)),
@@ -129,7 +132,7 @@ namespace mbts.spark {
DefaultColumnHeaders.Account,
r => r.account,
DefaultColumnWidths.Account,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
DiscreteValuesFilterVM.Create<OrderSecurity, string, string[]>(
model.factory.subscribe_accounts().Select(d => d.Values),
ort => ort.order.account(),
vals => vals.ToArray(),
@@ -143,7 +146,7 @@ namespace mbts.spark {
DefaultColumnHeaders.Exchange,
r => r.exchange.ToString(),
DefaultColumnWidths.Exchange,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
DiscreteValuesFilterVM.Create<OrderSecurity, Venue>(
EnumHelper.get_values_except(Venue.Unset).ToList(),
ort => ort.security.exchange_or_default(),
nameof(OrderBookRowVM.exchange)),
@@ -155,25 +158,48 @@ namespace mbts.spark {
DefaultColumnHeaders.TimeInForce,
r => r.time_in_force.ToString(),
DefaultColumnWidths.TimeInForce,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
DiscreteValuesFilterVM.Create<OrderSecurity, TimeInForce>(
EnumHelper.get_values<TimeInForce>().ToList(),
ort => ort.order.time_in_force(),
nameof(OrderBookRowVM.time_in_force)),
DataGridFilterKnownTemplates.EnumValues,
_wrap_comparer(r => r.time_in_force)));
// (algo items)
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher();
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
// order type
Add(order_type_column = new OrderBookOrderTypeColumn(
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderType>(
utilities.OrderDataHelper.all_order_types,
ort => ort.order.order_type(),
nameof(OrderBookRowVM.order_type))));
Columns.OrderType,
nameof(OrderBookRowVM.badge_fill),
nameof(OrderBookRowVM.display_name),
_wrap_comparer(r => r.order_type),
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
order_type_items_view.CustomSort = algo_items_comp;
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
// root algo type
Add(root_algo_type_column = new OrderBookOrderTypeColumn(
Columns.RootAlgoType,
nameof(OrderBookRowVM.root_algo_badge_fill),
nameof(OrderBookRowVM.root_algo_display_name),
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
o => new AlgoDataVM(o.root_algo),
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
DefaultColumnHeaders.RootAlgoType));
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
root_algo_type_items_view.CustomSort = algo_items_comp;
root_algo_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
// order sub type
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
Columns.SubType,
"Sub Type",
r => r.order_sub_type_text,
80,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
DiscreteValuesFilterVM.Create<OrderSecurity, OrderSubType>(
EnumHelper.get_values<OrderSubType>().ToList(),
ort => ort.order.sub_type(),
nameof(OrderBookRowVM.order_sub_type)),
@@ -181,27 +207,27 @@ namespace mbts.spark {
_wrap_comparer(r => r.order_sub_type)));
// side
Add(side_column = new SideColumn(
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Side>(
DiscreteValuesFilterVM.Create<OrderSecurity, Side>(
new[] { Side.Buy, Side.Sell },
ort=> ort.order.side(),
ort => ort.order.side(),
nameof(OrderBookRowVM.side))));
// quantity
Add(qty_column = new SizeColumn(
new UIntMinMax<OrderSecurityAlgoMeta>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
new UIntMinMax<OrderSecurity>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
// edit quantity column
// not usually part of the main order book, but it's useful to add it
// rather than debugging the popup window
//Add(edit_qty_column = new EditableQtyColumn());
// filled quantity
Add(filled_qty_column = new FilledColumn(
new UIntMinMax<OrderSecurityAlgoMeta>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
new UIntMinMax<OrderSecurity>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
// price
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.Price,
"Price",
r => r.price_text,
DefaultColumnWidths.Price,
new PriceMinMax<OrderSecurityAlgoMeta>(
new PriceMinMax<OrderSecurity>(
ort => ort.order.price(),
nameof(OrderBookRowVM.price)),
DataGridFilterKnownTemplates.DoubleMinMax,
@@ -217,7 +243,7 @@ namespace mbts.spark {
"Avg Price",
r => r.avg_price_text,
DefaultColumnWidths.Price,
new PriceMinMax<OrderSecurityAlgoMeta>(
new PriceMinMax<OrderSecurity>(
ort => ort.order.avg_price(),
nameof(OrderBookRowVM.avg_price)),
DataGridFilterKnownTemplates.DoubleMinMax,
@@ -228,18 +254,21 @@ namespace mbts.spark {
"Last Filled",
r => r.last_price_text,
DefaultColumnWidths.Price,
new PriceMinMax<OrderSecurityAlgoMeta>(
new PriceMinMax<OrderSecurity>(
ort => ort.order.last_price(),
nameof(OrderBookRowVM.last_price)),
DataGridFilterKnownTemplates.DoubleMinMax,
_wrap_comparer(r => r.last_price)));
// piq
Add(piq_column = new PiqColumn(
new UIntMinMax<OrderSecurity>(ort => ort.piq ?? uint.MaxValue, nameof(OrderBookRowVM.piq))));
// status
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
Columns.Status,
"Status",
r => r.status.ToString(),
80,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
DiscreteValuesFilterVM.Create<OrderSecurity, OrderStatus>(
EnumHelper.get_values<OrderStatus>().ToList(),
ort => ort.order.status(),
nameof(OrderBookRowVM.status)),
@@ -247,7 +276,7 @@ namespace mbts.spark {
_wrap_comparer(r => r.status)));
// last message
Add(last_message_column = new OrderBookLastMessageColumn(
new RegexFilterVM<OrderSecurityAlgoMeta>(
new RegexFilterVM<OrderSecurity>(
ort => ort.order.last_msg(),
nameof(OrderBookRowVM.last_msg))));
// security key
@@ -256,7 +285,7 @@ namespace mbts.spark {
DefaultColumnHeaders.SecurityKey,
r => r.security_key.ToString(),
DefaultColumnWidths.SecurityKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
new RegexFilterVM<OrderSecurity>(
ort => ort.order.security_key().ToString(),
nameof(OrderBookRowVM.security_key)),
DataGridFilterKnownTemplates.Regex,
@@ -267,7 +296,7 @@ namespace mbts.spark {
DefaultColumnHeaders.OrderKey,
r => r.order_key.ToString(),
DefaultColumnWidths.OrderKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
new RegexFilterVM<OrderSecurity>(
ort => ort.order.Key.ToString(),
nameof(OrderBookRowVM.order_key)),
DataGridFilterKnownTemplates.Regex,
@@ -278,7 +307,7 @@ namespace mbts.spark {
"Parent Key",
r => r.parent_order_key.ToString(),
DefaultColumnWidths.ParentKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
new RegexFilterVM<OrderSecurity>(
ort => ort.order.ParentKey.ToString(),
nameof(OrderBookRowVM.parent_order_key)),
DataGridFilterKnownTemplates.Regex,
@@ -289,7 +318,7 @@ namespace mbts.spark {
DefaultColumnHeaders.AttachedTargetOrderKey,
r => r.order.attached_target_order_key().ToString(),
DefaultColumnWidths.AttachedTargetOrderKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
new RegexFilterVM<OrderSecurity>(
ort => ort.order.attached_target_order_key().ToString(),
nameof(OrderBookRowVM.parent_order_key)),
DataGridFilterKnownTemplates.Regex,
@@ -307,6 +336,7 @@ namespace mbts.spark {
public SimpleTextColumn<Venue[]> exchange_column { get; }
public SimpleTextColumn<TimeInForce[]> time_in_force_column { get; }
public OrderBookOrderTypeColumn order_type_column { get; }
public OrderBookOrderTypeColumn root_algo_type_column { get; }
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
public SideColumn side_column { get; }
public SizeColumn qty_column { get; }
@@ -321,6 +351,7 @@ namespace mbts.spark {
public SimpleTextColumn<string> order_key_column { get; }
public SimpleTextColumn<string> parent_key_column { get; }
public SimpleTextColumn<string> attached_target_order_key_column { get; }
public PiqColumn piq_column { get; }
public FilterStates create_filter_states() =>
new FilterStates {
@@ -333,6 +364,7 @@ namespace mbts.spark {
exchanges = exchange_column.filter.get_state(),
tifs = time_in_force_column.filter.get_state(),
order_types = order_type_column.filter.get_state(),
root_algo_types = root_algo_type_column.filter.get_state(),
order_sub_types = sub_type_column.filter.get_state(),
sides = side_column.filter.get_state(),
qty = qty_column.filter.get_state(),
@@ -344,7 +376,8 @@ namespace mbts.spark {
exchange_message_regex = last_message_column.filter.get_state(),
last_change = last_change_column.filter.get_state(),
security_criteria = security_column.filter.get_state(),
symbols = symbol_column.filter.get_state()
symbols = symbol_column.filter.get_state(),
piq = piq_column.filter.get_state()
};
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() =>
@@ -362,6 +395,7 @@ namespace mbts.spark {
filled_qty_column.filter.load_state(fs.qty_filled);
side_column.filter.load_state(fs.sides);
order_type_column.filter.load_state(fs.order_types);
root_algo_type_column.filter.load_state(fs.root_algo_types);
sub_type_column.filter.load_state(fs.order_sub_types);
time_in_force_column.filter.load_state(fs.tifs);
exchange_column.filter.load_state(fs.exchanges);
@@ -373,6 +407,7 @@ namespace mbts.spark {
status_column.filter.load_state(fs.statuses);
last_message_column.filter.load_state(fs.exchange_message_regex);
last_change_column.filter.load_state(fs.last_change);
piq_column.filter.load_state(fs.piq);
}
_active_comparers.Clear();
foreach (var col in this)
@@ -393,9 +428,9 @@ namespace mbts.spark {
}
}
public Func<OrderSecurityAlgoMeta, bool> get_filter() {
public Func<OrderSecurity, bool> get_filter() {
var active_filters = this.OfType<ICVOrderBookColumn>().Where(c => c.filter.active.GetValueOrDefault()).Select(c => c.filter).ToArray();
return active_filters.Length == 0 ? null : new Func<OrderSecurityAlgoMeta, bool>(r => active_filters.All(f => f.filter(r)));
return active_filters.Length == 0 ? null : new Func<OrderSecurity, bool>(r => active_filters.All(f => f.filter(r)));
}
public OrderBookSortingData get_sorting_data() {
@@ -424,6 +459,7 @@ namespace mbts.spark {
}
public void Dispose() {
Disposable.dispose(ref _resources);
foreach (var col in this.OfType<ICVOrderBookColumn>())
(col.filter as IDisposable)?.Dispose();
Clear();
@@ -495,7 +531,7 @@ namespace mbts.spark {
GlyphContext _root_glyph_context, _child_glyph_context;
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
IDataGridColumnPersistentFilter<OrderSecurity, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
id = key.ToString();
Width = width;
Header = header;
@@ -512,10 +548,10 @@ namespace mbts.spark {
}
}
public IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter { get; }
public IDataGridColumnPersistentFilter<OrderSecurity, T> filter { get; }
public IComparer<OrderBookRowVM> comparer { get; }
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
IDataGridColumnFilter IFilterableColumn.filter => filter;
string IFilterableColumn.filter_template => _filter_template;
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
@@ -529,17 +565,17 @@ namespace mbts.spark {
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
IDataGridColumnPersistentFilter<OrderSecurity, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
: base(key, header, get_text, width, filter, filter_template, comparer) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
RenderUtils.get_foreground(row, font_brush), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
#region Explicit Column Defs
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurity, Side[]> filter)
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
@@ -548,7 +584,7 @@ namespace mbts.spark {
}
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
@@ -557,7 +593,7 @@ namespace mbts.spark {
}
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
@@ -566,21 +602,32 @@ namespace mbts.spark {
}
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
readonly BindingBase _foreground_binding;
ListSortDirection? _cached_sort_direction;
public OrderBookOrderTypeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, OrderType[]> filter)
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
: base(key, new Binding(badge_fill_binding_path), new Binding(display_name_binding_path), filter, header) {
id = key.ToString();
this.filter = filter;
font_weight_binding = RenderUtils.get_font_weight_binding();
_foreground_binding = RenderUtils.get_foreground_binding(this);
_cached_sort_direction = SortDirection;
this.comparer = row_comparer;
}
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, OrderType[]> filter { get; }
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.order_type);
public new IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter { get; }
public override string filter_template => DataGridFilterKnownTemplates.AlgoType;
public IComparer<OrderBookRowVM> comparer { get; }
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
public override FrameworkElement view_factory() {
var view = base.view_factory();
view.SetBinding(OrderTypeItem.ForegroundProperty, _foreground_binding);
return view;
}
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
_cached_sort_direction = new_value;
base.OnSortDirectionChanged(new_value);
@@ -590,7 +637,7 @@ namespace mbts.spark {
public class OrderBookLastMessageColumn : TextColumn<Columns>, ICVOrderBookColumn {
ListSortDirection? _cached_sort_direction;
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, string> filter)
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurity, string> filter)
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
id = key.ToString();
this.filter = filter;
@@ -599,10 +646,10 @@ namespace mbts.spark {
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
}
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, string> filter { get; }
public new IDataGridColumnPersistentFilter<OrderSecurity, string> filter { get; }
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.last_msg);
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
@@ -611,25 +658,35 @@ namespace mbts.spark {
}
}
public class PiqColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
: base(Columns.Piq, "PIQ", r => r.piq_text, DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.piq)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.piq_text, position, row_height,
RenderUtils.get_foreground(row, font_brush), null, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
}
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
readonly BindingBase _font_weight_binding = RenderUtils.get_font_weight_binding();
readonly BindingBase _font_weight_binding = RenderUtils.get_font_weight_binding(), _foreground_binding;
readonly IValueConverter _expander_visiblilty_converter = new ui.converters.BooleanToVisibilityConverter { VisibilityForFalse = Visibility.Hidden };
readonly IValueConverter _margin_converter = new AnonymousValueConverter<int, Thickness>(level => new Thickness(10 * level, 0, 0, 0));
ListSortDirection? _cached_sort_direction;
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, SecurityCriteria> filter) : base(Columns.Security) {
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter) : base(Columns.Security) {
id = key.ToString();
this.filter = filter;
_foreground_binding = RenderUtils.get_foreground_binding(this);
Header = DefaultColumnHeaders.Security;
Width = DefaultColumnWidths.Security;
_cached_sort_direction = SortDirection;
}
public IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, SecurityCriteria> filter { get; }
public IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter { get; }
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.security, SecurityHelper.Comparer.InstanceImmutable.to_nullable());
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
IDataGridColumnFilter IFilterableColumn.filter => filter;
string IFilterableColumn.filter_template => DataGridFilterKnownTemplates.DiscreteValues;
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
@@ -647,7 +704,7 @@ namespace mbts.spark {
security_view.SetBinding(TextBlock.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
security_view.SetBinding(TextBlock.TextProperty, nameof(OrderBookRowVM.security_text));
security_view.SetBinding(TextBlock.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
security_view.SetBinding(TextBlock.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
security_view.SetBinding(TextBlock.ForegroundProperty, _foreground_binding);
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
//
@@ -742,7 +799,7 @@ namespace mbts.spark {
// create the attach button and assign the menu to it
var attach_button = new Button() {
Name = "attach_button",
Content = "A",
Content = new Image() { Source = Icons.AttachAlgos },
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Focusable = false,
@@ -864,7 +921,7 @@ namespace mbts.spark {
var detach_button = new Button() {
Name = "detach_button",
Content = "D",
Content = new Image() { Source = Icons.DetachAlgos },
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Focusable = false,
@@ -1028,6 +1085,10 @@ namespace mbts.spark {
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
});
popup.Closed += (s, e) => {
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm)
ob_vm.is_attaching_orders = false;
};
var the_whole_thing = new Grid();
the_whole_thing.Children.Add(update_button);
@@ -1115,19 +1176,24 @@ namespace mbts.spark {
static class RenderUtils {
public static FontWeight
root_font_weight = FontWeights.Normal,
child_font_weight = FontWeights.Light;
child_font_weight = FontWeights.Normal;
static readonly IMultiValueConverter _foreground_converter =
ValueConverter.create<bool, Brush, Brush>(_get_foreground);
public static double font_size = FontSizes.Small;
public static readonly IValueConverter
_sub_type_text_converter = new ui.spark.converters.OrderSubTypeDisplayTextConverter(),
_side_brush_converter = new AnonymousValueConverter<Side, Brush>(s => s == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask),
_font_weight_converter = new AnonymousValueConverter<bool, FontWeight>(is_child => is_child ? FontWeights.Light : FontWeights.Normal);
_font_weight_converter = new AnonymousValueConverter<bool, FontWeight>(is_child => is_child ? child_font_weight : root_font_weight);
public static readonly IMultiValueConverter
_price_text_converter = new ui.spark.converters.TickDisplayConverter(),
_background_converter = new AnonymousMultiValueConverter<bool, OrderStatus, Brush>(
(selected, status) => selected || status != OrderStatus.Working ? Brushes.Transparent : BTSColors.Orders.working_order_background_brush);
static readonly bool _animate_child_order_rows = mbts.config.Configuration.get<bts.config.OrderBookOptions>().animate_child_order_rows;
public static BindingBase _get_background_binding() {
var mb = new MultiBinding { Converter = _background_converter };
mb.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
@@ -1135,9 +1201,29 @@ namespace mbts.spark {
return mb;
}
public static Brush get_side_brush(OrderBookRowVM r) => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
}
public static BindingBase get_foreground_binding(ColumnBase col) {
var col_font_brush_binding = new Binding(nameof(ColumnBase.font_brush)) { Source = col };
if (!_animate_child_order_rows)
return col_font_brush_binding;
var mb = new MultiBinding { Converter = _foreground_converter };
mb.Bindings.Add(new Binding(nameof(OrderBookRowVM.is_child)));
mb.Bindings.Add(col_font_brush_binding);
return mb;
}
public static Brush get_side_brush(OrderBookRowVM r) => get_foreground(r, r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
public static Brush get_foreground(OrderBookRowVM row, Brush brush) =>
_animate_child_order_rows ? _get_foreground(row.is_child, brush) : brush;
static Brush _get_foreground(bool is_child, Brush brush) {
if (is_child) {
brush = brush.Clone();
brush.Opacity = 0.75;
}
return brush;
}
}
}
}
@@ -14,34 +14,27 @@ using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace mbts.spark {
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurityAlgoMeta>, INotifyPropertyChanged {
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurity>, INotifyPropertyChanged {
const string _unknown_security_price_format_string = "0.#########";
const int _unknown_security_price_num_fractional_digits = 9;
readonly OrderBookVM _owner;
Brush _badge_fill;
Color? _root_algo_badge_color_backing;
Brush _badge_fill, _root_algo_badge_fill;
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd, _cancel_cmd, _detach_command, _attach_command, _update_command, _modify_order_quantity_andor_price, _revert_prospective_order_quantity_andor_price;
bool _is_parent, _is_expanded, _is_selected, _is_attachable, _is_detachable;
int _depth_level;
SecurityImmutable? _security => order_tuple.security;
// for mini-ob
uint _prospective_quantity;
double _prospective_price;
public OrderBookRowVM(OrderBookVM owner, OrderSecurityAlgoMeta order_tuple, bool is_expanded) {
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded) {
_owner = owner;
this.order_tuple = order_tuple;
is_visible = !is_child;
_is_expanded = is_expanded;
prospective_quantity = this.quantity;
prospective_price = this.price;
this.invalidated += OrderBookRowVM_invalidated;
}
private void OrderBookRowVM_invalidated() {
modify_order_quantity_andor_price?.TryRaiseCanExecuteChanged();
}
//// This takes care of the case where the OB has all rows visible
@@ -50,22 +43,22 @@ namespace mbts.spark {
//// when the OrderBookVM.selected_order_changes, this emulates the selected
//// order changing since the current_row setter invokes the selected_order_changed
//// handler
public IObservable<OrderSecurityAlgoMeta> subscribe_data() {
return Observable.FromEvent<Action, OrderSecurityAlgoMeta>(
cb => () => cb(order_tuple),
cb => invalidated += cb,
public IObservable<OrderSecurity> subscribe_data() {
return Observable.FromEvent<Action, OrderSecurity>(
cb => () => cb(order_tuple),
cb => invalidated += cb,
cb => invalidated -= cb);
}
public event Action invalidated;
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
public event Action<(OrderSecurity oldval, OrderSecurity newval)> updated;
public event PropertyChangedEventHandler PropertyChanged;
// Core
public OrderSecurityAlgoMeta order_tuple { get; private set; }
public OrderSecurity order_tuple { get; private set; }
public OrderRecordImmutable order => order_tuple.order;
public OrderMetadata order_metadata => _owner.get_metadata(this);
public Brush badge_fill => _badge_fill ?? (_badge_fill = order_tuple.badge_fill(order_metadata));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.meta, order_metadata));
public uint order_key => order.Key;
public uint parent_order_key => order.ParentKey;
public uint security_key => order.security_key();
@@ -98,11 +91,19 @@ namespace mbts.spark {
public uint quantity => order.quantity();
public uint filled_quantity => order.filled();
public string last_msg => order.last_msg();
public uint piq => order_tuple.piq ?? uint.MaxValue;
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.ToString();
public bool is_non_terminal => order.is_non_terminal();
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
// Root Algo
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root_algo.value.CustomCase;
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
public string root_algo_display_name => order_tuple.root_algo.name;
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
public bool is_child => order.has_parent();
@@ -135,45 +136,51 @@ namespace mbts.spark {
}
}
}
public bool is_qty_dirty => this.prospective_quantity != this.quantity;
public bool is_qty_dirty => this.prospective_quantity != this.quantity;
public bool is_price_dirty => this.prospective_price != this.price;
// Commands
public ICommand toggle_command => _toggle_expanded_cmd ?? (
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
public ICommand show_order_modify_cmd =>
public ICommand show_order_modify_cmd =>
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
public ICommand cancel_command => _cancel_cmd ??
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working || status == OrderStatus.Disconnected));
public ICommand modify_order_quantity_andor_price => _modify_order_quantity_andor_price ??
(_modify_order_quantity_andor_price = new DelegateCommand(
() => {
if (is_qty_dirty && is_price_dirty) {
_owner.modify_order_price_and_quantity(this, prospective_price, prospective_quantity);
} else if (is_qty_dirty) {
_owner.modify_order_quantity(this, prospective_quantity);
} else if (is_price_dirty) {
_owner.modify_order_price(this, this.prospective_price);
public ICommand modify_order_quantity_andor_price {
get {
if (_modify_order_quantity_andor_price == null) {
_modify_order_quantity_andor_price = new DelegateCommand(execute, can_execute);
invalidated += _modify_order_quantity_andor_price.RaiseCanExecuteChanged;
// impl
void execute() {
if (is_qty_dirty && is_price_dirty) {
_owner.modify_order_price_and_quantity(this, prospective_price, prospective_quantity);
} else if (is_qty_dirty) {
_owner.modify_order_quantity(this, prospective_quantity);
} else if (is_price_dirty) {
_owner.modify_order_price(this, this.prospective_price);
}
_unselect_me();
}
_unselect_me();
},
bool can_execute() => status == OrderStatus.Working && (is_price_dirty || is_qty_dirty);
}
return _modify_order_quantity_andor_price;
}
}
public ICommand revert_prospective_order_quantity_andor_price => _revert_prospective_order_quantity_andor_price ??
(_revert_prospective_order_quantity_andor_price = new DelegateCommand(
() => {
prospective_price = price;
prospective_quantity = quantity;
},
() => this.status == OrderStatus.Working && (is_price_dirty || is_qty_dirty)
)
);
public ICommand revert_prospective_order_quantity_andor_price => _revert_prospective_order_quantity_andor_price ??
(_revert_prospective_order_quantity_andor_price = new DelegateCommand(
() => {
prospective_price = price;
prospective_quantity = quantity;
},
() => this.status == OrderStatus.Working && (is_price_dirty || is_qty_dirty)
)
);
public ICommand detach_command => _detach_command ??
(_detach_command = new DelegateCommand(() => {
_owner.detach_orders(this);
@@ -190,14 +197,26 @@ namespace mbts.spark {
public ICommand update_command => _update_command ?? (
_update_command = new DelegateCommand(() => {
_select_me();
_select_me();
})
);
// private props
Color _root_algo_badge_color {
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata)).Value;
set {
if (_root_algo_badge_color_backing != value) {
_root_algo_badge_color_backing = value;
_root_algo_badge_fill = null;
}
}
}
#region IHierarchicalRow
bool IHierarchicalRow.is_parent => is_parent;
bool IHierarchicalRow.is_expanded => is_expanded;
int IHierarchicalRow.depth_level => depth_level;
OrderSecurityAlgoMeta IHierarchicalRow<OrderSecurityAlgoMeta>.value => order_tuple;
OrderSecurity IHierarchicalRow<OrderSecurity>.value => order_tuple;
void IHierarchicalRow.on_is_parent_changed(bool is_parent) => this.is_parent = is_parent;
/// Do NOT actually call the <see cref="is_expanded"/> setter (it has side effects) - the row collection calls this method when the row is expanded/collapsed by some **external means, outside of this class.
@@ -209,7 +228,7 @@ namespace mbts.spark {
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.collapse_row(TKey)"/>
void IHierarchicalRow.on_is_expanded_changed(bool is_expanded) => Set(ref _is_expanded, is_expanded, nameof(is_expanded));
void IHierarchicalRow.on_depth_level_changed(int depth_level) => this.depth_level = depth_level;
void IHierarchicalRow<OrderSecurityAlgoMeta>.on_value_changed(OrderSecurityAlgoMeta value) => update(value);
void IHierarchicalRow<OrderSecurity>.on_value_changed(OrderSecurity value) => update(value);
#endregion
private void _unselect_me() {
@@ -227,7 +246,7 @@ namespace mbts.spark {
internal void expand_internal() => Set(ref _is_expanded, true, nameof(is_expanded));
internal void collapse_internal() => Set(ref _is_expanded, false, nameof(is_expanded));
public void update(OrderSecurityAlgoMeta order_tuple) {
public void update(OrderSecurity order_tuple) {
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
@@ -240,11 +259,11 @@ namespace mbts.spark {
// quantity
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));
@@ -262,6 +281,15 @@ namespace mbts.spark {
// display name
if (old_order_tuple.display_name() != order_tuple.display_name())
RaisePropertyChanged(nameof(display_name));
// root badge fill
if (_root_algo_badge_color_backing.HasValue) {
// only need to invalidate it if it had a value (it is lazily instantiated)
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata);
RaisePropertyChanged(nameof(root_algo_badge_fill));
}
// root display name
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
RaisePropertyChanged(nameof(root_algo_display_name));
// status
if ((Columns.Status & dirty) != 0)
RaisePropertyChanged(nameof(status));
@@ -288,7 +316,9 @@ namespace mbts.spark {
// this will update the cancel column in the mini ob
cancel_command.TryRaiseCanExecuteChanged();
}
if ((Columns.Piq & dirty) != 0) {
RaisePropertyChanged(nameof(piq), nameof(piq_text));
}
// always refresh attach and detach b/c
// conditionally raise invalidated
if ((dirty & ViewRefresh.invalidate_mask) != 0)
@@ -349,13 +379,14 @@ namespace mbts.spark {
Columns.Status |
Columns.ParentKey |
Columns.AttachedTargetOrderKey |
Columns.LastMessage,
Columns.LastMessage |
Columns.Piq,
filled_status_desc_mask =
Columns.Filled |
Columns.AvgFilled |
Columns.Status;
public static Columns get_dirty_cells(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) {
public static Columns get_dirty_cells(OrderSecurity oldval, OrderSecurity newval) {
var dirty = (Columns)0;
if (oldval.security != newval.security)
dirty |= Columns.Security;
@@ -379,8 +410,11 @@ namespace mbts.spark {
dirty |= Columns.Status;
if (oldval.order.last_msg() != newval.order.last_msg())
dirty |= Columns.LastMessage;
if (oldval.piq != newval.piq) {
dirty |= Columns.Piq;
}
return dirty;
}
}
}
}
}
@@ -4,10 +4,10 @@ using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.utils;
using mbts.collections;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
@@ -41,10 +41,10 @@ namespace mbts.spark {
readonly OrdersModel _model;
readonly Property<OrderBookRowVM> _current_row;
readonly DelegateProperty<bool> _show_order_params, _show_ladder, _show_fills, _can_attach_selected_orders, _can_detach_selected_orders;
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityAlgoMeta, uint> _row_collection;
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint> _row_collection;
readonly OrderBookColumnCollection _columns;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsWindowFactory.StartupData.MiniGridSettings, FillsWindowFactory.StartupData.MiniGridSettings);
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
readonly SelectedOrderParamsMon _selected_params_mon;
readonly RightPanelModel _right_panel = new RightPanelModel();
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
@@ -57,14 +57,21 @@ namespace mbts.spark {
_show_right_panel = OrderBookStartupData.Default.show_right_panel,
_is_attaching_orders,
_is_row_collection_populated;
IReadOnlyDictionary<uint, OrderSecurityAlgoMeta> _cached_orders = DictionaryHelper.empty<uint, OrderSecurityAlgoMeta>();
IReadOnlyDictionary<uint, OrderSecurity> _cached_orders = DictionaryHelper.empty<uint, OrderSecurity>();
IReadOnlyDictionary<uint, bool> _cached_attachable = DictionaryHelper.empty<uint, bool>(), _cached_detachable = DictionaryHelper.empty<uint, bool>();
HashSet<uint>
_attachable_selected_orders = new HashSet<uint>(),
_detachable_selected_orders = new HashSet<uint>();
FilterData _cached_filter_data;
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool rows_are_initially_expanded = false) {
public OrderBookVM(
IObservable<string> owner_title,
IBus bus,
INotificationService notification_service,
OrdersModel model,
IWindowSettingsManager<OrderBookGridSettings> settings_manager,
bool rows_are_initially_expanded = false) {
_bus = bus;
_model = model;
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
@@ -90,7 +97,7 @@ namespace mbts.spark {
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(x => (x.added.Values, x.removed.Values)));
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
columns = new ObservableAppendCollection<DIYGrid.ColumnBase>(_columns);
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityAlgoMeta, uint>(
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint>(
o => new OrderBookRowVM(this, o, rows_are_initially_expanded) {
is_attachable = _cached_attachable.TryGetValue(o.order.Key, out bool ia) && ia,
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
@@ -139,6 +146,8 @@ namespace mbts.spark {
IDisposable attach_vm_hooks = null;
begin_attaching += on_begin_attaching;
end_attaching += on_end_attaching;
// switch between orders and/or orders w/ piq
var orders_source = OrdersModel.piq_enabled ? model.on_order_tuples_with_root_and_piq : model.on_order_tuples_with_root;
concat_disposable(
connect_order_mod_vm(),
_can_attach_selected_orders.observe_current().Subscribe(send_attached_orders_async_cmd, begin_attach_cmd),
@@ -160,7 +169,7 @@ namespace mbts.spark {
security_plurality = p;
RaisePropertyChanged(nameof(security_plurality));
}),
model.on_order_tuples.OnDispatcher().Subscribe(t => {
orders_source.OnDispatcher().Subscribe(t => {
_cached_orders = t.state;
_row_collection.update(t.added, t.changed, t.removed.Select(o => o.order.Key));
}),
@@ -248,13 +257,20 @@ namespace mbts.spark {
var order_tups = selected_rows.Select(r => r.order_tuple).ToList();
var mod_algo_vm = attach_vm.algo_vm.algo_root;
await Task.WhenAll(order_tups.Select(t => {
var algo = t.order.Def.CustomDef.CloneToMutable();
var algo = t.order.Def.CustomDef.actual().CloneToMutable();
mod_algo_vm.assign_to(algo);
if (algo_template_key != 0) {
algo = new CustomOrderDefinition {
Composite = new CompositeOrderDefinition {
AlgoTemplateKey = algo_template_key,
Definition = algo
}
};
}
return _model.send_order(
t.order.Def.with(
attach_target_order_key: t.order.Key,
custom_def: algo,
algo_template_key: algo_template_key
custom_def: algo
),
double.IsNaN(price) ? t.order.price() : price,
size ?? t.order.quantity());
@@ -268,15 +284,14 @@ namespace mbts.spark {
Disposable.replace(ref attach_vm_hooks,
Disposable.Create(
connect_common_selected_params(attach_vm),
_selected_params_mon.subscribe_algos().Subscribe(algos => {
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
bool show_price, show_size;
using (var en = algos.Select(a => a.CustomCase).Distinct().GetEnumerator()) {
if (show_price = show_size = en.MoveNext()) {
do {
if (show_price) show_price = AttachConf.show_price(en.Current);
if (show_size) show_size = AttachConf.show_size(en.Current);
} while ((show_price || show_size) && en.MoveNext());
}
if (algo_root == null) {
show_price = false;
show_size = false;
} else {
show_price = AttachConf.show_price(algo_root.order_type);
show_size = AttachConf.show_size(algo_root.order_type);
}
attach_vm.price_permissions = bool_to_perm(show_price);
attach_vm.size_permissions = bool_to_perm(show_size);
@@ -285,7 +300,7 @@ namespace mbts.spark {
}),
// custom templates (created by user)
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots as ObservableKeyedCollection<IAlgoVM, AlgoKey>, u, mk_factory(true)))));
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_factory(true)))));
// vm factory
Func<AlgoData, AlgoDefinitionVM> mk_factory(bool connect) =>
fd => {
@@ -529,13 +544,27 @@ namespace mbts.spark {
public void begin_attach_algos() => is_attaching_orders = true;
public bool try_get_parent(OrderBookRowVM row, out OrderSecurityAlgoMeta parent) =>
public bool try_get_parent(OrderBookRowVM row, out OrderSecurity parent) =>
_cached_orders.TryGetValue(row.parent_order_key, out parent);
public OrderRecordImmutable get_root(OrderRecordImmutable r) {
while (r.ParentKey != 0 && _cached_orders.TryGetValue(r.ParentKey, out var parent_osam)) {
r = parent_osam.order;
}
return r;
}
internal void detach_orders(OrderBookRowVM order) =>
_detach_orders_async(new[] { order }).ignore();
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
public OrderSecurity get_ordersecurityalgometa(OrderRecordImmutable order) {
_cached_orders.TryGetValue(order.Key, out var osam);
return osam;
}
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) => _model.factory.get_metadata(order_type);
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
internal OrderBookRowVM get_parent(OrderBookRowVM row) => _row_collection.get_parent(row.order_key);
@@ -598,7 +627,11 @@ namespace mbts.spark {
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderBookRowVM row) {
var mod_algo = row.order.Def.CustomDef.CloneToMutable();
mod_algo_vm.assign_to(mod_algo);
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
} else {
mod_algo_vm.assign_to(mod_algo);
}
var mod_algoi = new CustomOrderDefinitionImmutable(mod_algo);
return (row.order, OrderDataHelper.EqualsHelpers.equals(row.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
}
@@ -840,10 +873,9 @@ namespace mbts.spark {
revert();
}
public void revert() {
foreach (var r in param_rows)
r.param.revert();
}
public void revert() =>
param_rows.revert_all();
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
void _reset_ladder_vm() {
@@ -851,7 +883,10 @@ namespace mbts.spark {
return;
Disposable.replace(ref _ladder_vm,
_security_md.HasValue ?
LadderVM.create_readonly(_owner._bus, new LadderModel(security_md.Value, _owner._model, new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
LadderVM.create_readonly(
_owner._bus,
new LadderModel(security_md.Value, _owner._model, _owner._model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
null);
RaisePropertyChanged(nameof(ladder_vm));
}
@@ -873,7 +908,7 @@ namespace mbts.spark {
this.column_filters = column_filters;
}
public Func<OrderSecurityAlgoMeta, bool> build_predicate() {
public Func<OrderSecurity, bool> build_predicate() {
var pred = column_filters.build_predicate();
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
if (pred == null) {
@@ -882,7 +917,7 @@ namespace mbts.spark {
var col_pred = pred;
pred = o => tree_disp_pred(o) && col_pred(o);
}
bool tree_disp_pred(OrderSecurityAlgoMeta o) =>
bool tree_disp_pred(OrderSecurity o) =>
!o.order.has_parent() ||
o.order.is_exchange_order() ||
o.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
@@ -917,7 +952,7 @@ namespace mbts.spark {
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta>();
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
readonly DynamicOrderHelper _dyn_helper;
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
@@ -986,22 +1021,22 @@ namespace mbts.spark {
_security_md_map_changed?.Invoke();
}
void _on_row_updated((OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) ch) {
void _on_row_updated((OrderSecurity oldval, OrderSecurity newval) ch) {
using (begin_udpate()) {
_group_mon.remove(ch.oldval);
_group_mon.add(ch.newval);
}
}
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgoMeta osam) =>
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
AlgoSecurityKey _algo_key(OrderSecurityAlgoMeta osam) =>
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
SecurityMD? _get_sec_md(OrderSecurityAlgoMeta osam) =>
SecurityMD? _get_sec_md(OrderSecurity osam) =>
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
struct AlgoSecurityKey {
@@ -1104,4 +1139,4 @@ namespace mbts.spark {
!_algo_type_conf.TryGetValue(algo_type, out var result) || result.show_size;
}
}
}
}
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.view;
@@ -68,7 +69,7 @@ namespace mbts.spark {
_vm.load_from(_state);
_view = new OrderBookView(_vm);
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
var toggle_show_right_panel_command = new SparkCommandCommand(
get_toggle_show_right_panel_cmd_title(),
@@ -1,4 +1,5 @@
using mbts.ui;
using bts.shell.fills;
using mbts.ui;
using ProtoBuf;
using System.Collections.Generic;
using GridSettings = mbts.ui.settings.GridSettings;
@@ -9,11 +10,11 @@ namespace bts.shell.orderbook {
[ProtoMember(1)]
public GridSettings general { get; set; }
[ProtoMember(2)]
public FillsWindowFactory.FilterStates filters { get; set; }
public FillsFilterStates filters { get; set; }
[ProtoMember(3)]
public List<SortingCriteria> sorting_criterion { get; set; }
public FillsWindowFactory.StartupData to_startup_data() =>
new FillsWindowFactory.StartupData(null, sorting_criterion, filters, general);
public FillsSettings to_startup_data() =>
new FillsSettings(null, sorting_criterion, filters, general);
}
}
@@ -7,8 +7,6 @@ using mbts.spark.filters;
using mbts.ui;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace bts.shell.orderbook {
[ProtoContract]
@@ -27,8 +25,12 @@ namespace bts.shell.orderbook {
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
[ProtoMember(8)]
public CustomOrderDefinition.CustomOneofCase[] order_types;
//[ProtoMember(8)]
//public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(23)]
public AlgoID[] order_types;
[ProtoMember(24)]
public AlgoID[] root_algo_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
@@ -56,7 +58,9 @@ namespace bts.shell.orderbook {
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
public string attached_target_order_key_regex;
[ProtoMember(25)]
public UIntMinMaxState piq;
public bool Equals(FilterStates other) =>
other != null &&
@@ -70,6 +74,7 @@ namespace bts.shell.orderbook {
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
@@ -80,7 +85,8 @@ namespace bts.shell.orderbook {
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
piq.safe_equals(other.piq);
public override bool Equals(object obj) => Equals(obj as FilterStates);
@@ -96,6 +102,7 @@ namespace bts.shell.orderbook {
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
@@ -106,89 +113,42 @@ namespace bts.shell.orderbook {
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols));
EqualityComparer.safe_aggregate_hash(symbols),
piq?.GetHashCode() ?? 0);
}
static class FilterStatesExtensions {
public static Func<OrderSecurityAlgoMeta, bool> build_predicate(this FilterStates fs) {
var predicates = new List<Func<OrderSecurityAlgoMeta, bool>>();
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<OrderSecurity>();
// security
if (fs.security_criteria?.is_defined() ?? false)
predicates.Add(o => o.security.HasValue && ContractPickerModel.security_in_criteria(fs.security_criteria, o.security.Value));
pb.add_security_criteria(fs.security_criteria, o => o.security);
// regex
add_regex(fs.order_key_regex, o => o.order.Key.ToString());
add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
// time
add_time_filter(fs.time_created, o => o.order.created());
add_time_filter(fs.last_change, o => o.order.last_change());
pb.add_time_filter(fs.time_created, o => o.order.created());
pb.add_time_filter(fs.last_change, o => o.order.last_change());
// discrete values
add_discrete_values(fs.users, o => o.order.user());
add_discrete_values(fs.accounts, o => o.order.account());
add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
add_discrete_values(fs.tifs, o => o.order.time_in_force());
add_discrete_values(fs.order_types, o => o.order.order_type());
add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
add_discrete_values(fs.sides, o => o.order.side());
add_discrete_values(fs.statuses, o => o.order.status());
add_discrete_values(fs.symbols, o => o.security?.Symbol);
pb.add_discrete_values(fs.users, o => o.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.account());
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.status());
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
// numeric
add_numeric(fs.qty, o => o.order.quantity());
add_numeric(fs.price, o => o.order.price());
add_numeric(fs.qty_filled, o => o.order.filled());
add_numeric(fs.avg_price, o => o.order.avg_price());
add_numeric(fs.last_price, o => o.order.last_price());
return predicates.Count == 0 ? null : new Func<OrderSecurityAlgoMeta, bool>(o => predicates.All(p => p(o)));
// local methods (impl)
void add_regex(string str, Func<OrderSecurityAlgoMeta, string> selector) {
var regex = RegexFilterUtils.build_regex(str);
if (regex == null)
return;
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => regex.IsMatch(selector(o) ?? string.Empty);
}
void add_time_filter(TimeFilterTuple t, Func<OrderSecurityAlgoMeta, DateTime> selector) {
if (!t.is_active)
return;
var dt_pred = TimeFilterUtils.build_predicate(t);
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => dt_pred(selector(o));
}
void add_discrete_values<T>(T[] values, Func<OrderSecurityAlgoMeta, T> selector) {
if (values.is_null_or_empty())
return;
if (values.Length < 16) {
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => values.Contains(selector(o));
} else {
var set = new HashSet<T>(values);
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => set.Contains(selector(o));
}
}
void add_numeric<T>(IMinMaxState<T> value, Func<OrderSecurityAlgoMeta, T> selector) where T : struct, IComparable<T> {
if (value == null)
return;
if (value.minimum.active) {
var min = value.minimum.value.Value;
if (value.maximum.active) {
var max = value.maximum.value.Value;
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) {
var v = selector(o);
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
}
} else {
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => min.CompareTo(selector(o)) <= 0;
}
} else if (value.maximum.active) {
var max = value.maximum.value.Value;
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => max.CompareTo(selector(o)) >= 0;
}
}
pb.add_numeric(fs.qty, o => o.order.quantity());
pb.add_numeric(fs.price, o => o.order.price());
pb.add_numeric(fs.qty_filled, o => o.order.filled());
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.last_price());
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
return pb.to_predicate();
}
}
}
}
@@ -1,209 +1,223 @@
using bts.api.orderman;
using mbts;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
[ProtoContract]
class OrderBookStartupData {
const bool
_default_show_right_panel = true,
_default_show_order_params = true;
const double
_default_right_panel_width = 400,
_default_order_params_height = 250,
_default_fills_height = 300,
_default_ladder_height = 350;
[ProtoMember(16)]
OrderBookRightPanelSettings _right_panel;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
// right panel (host)
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
// order params
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
public double order_params_height { get; set; }
// ladder
public bool show_ladder { get; set; }
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
// fills
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid { get; set; }
// orderbook grid
[ProtoMember(17)]
public OrderBookGridSettings orderbook_grid { get; set; }
// fills settings
[ProtoMember(26)]
public OrderBookFillsGridSettings fills_settings { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
public OrderBookStartupData() {
show_right_panel = _default_show_right_panel;
show_order_params = _default_show_order_params;
right_panel_width = _default_right_panel_width;
order_params_height = _default_order_params_height;
fills_height = _default_fills_height;
ladder_height = _default_ladder_height;
}
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.show_right_panel = show_right_panel;
this.right_panel_width = right_panel_width;
this.fills_height = fills_height;
this.show_fills = show_fills;
this.orderbook_grid = orderbook_grid;
#pragma warning disable CS0612 // Type or member is obsolete
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() {
if (use_defaults)
return null;
if (orderbook_grid == null)
orderbook_grid = new OrderBookGridSettings(grid.clone(), _predicate_animations);
return orderbook_grid;
}
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
DefaultGrid,
_default_show_right_panel,
_default_right_panel_width,
_default_fills_height,
use_defaults: true) {
show_order_params = _default_show_order_params,
order_params_height = _default_order_params_height,
fills_settings = DefaultFillsGrid
};
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
public static OrderBookFillsGridSettings DefaultFillsGrid {
get {
var default_fills = FillsWindowFactory.StartupData.Default();
var rv = new OrderBookFillsGridSettings {
sorting_criterion = default_fills.sorting_criterion.ToList(),
filters = default_fills.filters,
general = FillsWindowFactory.StartupData.MiniGridSettings
};
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion));
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Right);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
yield return new SortingCriteria(time_created.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return symbol;
yield return time_created;
yield return last_change;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return sub_type;
yield return side;
yield return quantity;
yield return price;
yield return filled_quantity;
yield return avg_price;
yield return last_price;
yield return status;
yield return last_message;
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in all) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
yield return col_setting;
}
}
}
}
}
}
using bts.api.orderman;
using bts.shell.fills;
using mbts;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
[ProtoContract]
class OrderBookStartupData {
const bool
_default_show_right_panel = true,
_default_show_order_params = true;
const double
_default_right_panel_width = 400,
_default_order_params_height = 250,
_default_fills_height = 300,
_default_ladder_height = 350;
[ProtoMember(16)]
OrderBookRightPanelSettings _right_panel;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
// right panel (host)
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
// order params
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
public double order_params_height { get; set; }
// ladder
public bool show_ladder { get; set; }
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
// fills
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid { get; set; }
// orderbook grid
[ProtoMember(17)]
public OrderBookGridSettings orderbook_grid { get; set; }
// fills settings
[ProtoMember(26)]
public OrderBookFillsGridSettings fills_settings { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
/// </summary>
[Obsolete]
public OrderBookStartupData() {
show_right_panel = _default_show_right_panel;
show_order_params = _default_show_order_params;
right_panel_width = _default_right_panel_width;
order_params_height = _default_order_params_height;
fills_height = _default_fills_height;
ladder_height = _default_ladder_height;
}
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.show_right_panel = show_right_panel;
this.right_panel_width = right_panel_width;
this.fills_height = fills_height;
this.show_fills = show_fills;
this.orderbook_grid = orderbook_grid;
#pragma warning disable CS0612 // Type or member is obsolete
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() {
if (use_defaults)
return null;
if (orderbook_grid == null) {
if (grid == null)
grid = _default_general_grid;
orderbook_grid = new OrderBookGridSettings(grid.clone(), _predicate_animations);
}
return orderbook_grid;
}
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
DefaultGrid,
_default_show_right_panel,
_default_right_panel_width,
_default_fills_height,
use_defaults: true) {
show_order_params = _default_show_order_params,
order_params_height = _default_order_params_height,
fills_settings = DefaultFillsGrid
};
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
public static OrderBookFillsGridSettings DefaultFillsGrid {
get {
var default_fills = FillsSettings.Default();
var rv = new OrderBookFillsGridSettings {
sorting_criterion = default_fills.sorting_criterion.ToList(),
filters = default_fills.filters,
general = FillsSettings.MiniGridSettings
};
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion)) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
yield return new SortingCriteria(time_created.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return symbol;
yield return time_created;
yield return last_change;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return root_algo_type;
yield return sub_type;
yield return side;
yield return quantity;
yield return price;
yield return piq;
yield return filled_quantity;
yield return avg_price;
yield return last_price;
yield return status;
yield return last_message;
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in all) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
yield return col_setting;
}
}
}
}
}
}
}
@@ -4,13 +4,12 @@
x:Name="_grid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
InactiveSelectionOpacity="0.3"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
@@ -109,63 +108,4 @@
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
</c:DIYGrid.InputBindings>
<c:DIYGrid.RowStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Style.Triggers>
<!-- Completed order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Completed, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=completed_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!-- Error, RejectedBySpark, Disconnected order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Error, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum RejectedBySpark, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Disconnected, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!-- Cancel order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Canceled, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=canceled_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</c:DIYGrid.RowStyle>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<sp:OrderBookRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
@@ -1,4 +1,11 @@
using bts.api.orderman;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.spark {
internal partial class OrderBookGridView : DIYGrid {
@@ -41,11 +48,54 @@ namespace mbts.spark {
Attach = 1 << 25,
Detach = 1 << 26,
Update = 1 << 27,
Commit = 1 << 28
Commit = 1 << 28,
RootAlgoType = 1 << 29,
Piq = 1 << 30
}
public OrderBookGridView() =>
public OrderBookGridView() {
InitializeComponent();
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
}
static class RowBackgroundHelper {
public static readonly BindingBase row_background_binding;
static RowBackgroundHelper() {
var bg_binding = new MultiBinding();
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().status_color_opacity_when_selected;
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
});
} else {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
if (is_selected)
return null;
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) : null;
});
}
row_background_binding = bg_binding;
}
static Color? _status_color(OrderStatus status) {
switch (status) {
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
case OrderStatus.Disconnected:
case OrderStatus.RejectedBySpark:
case OrderStatus.Error:
return BTSColors.Orders.error_order_background;
default: return null;
}
}
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
@@ -174,9 +174,12 @@
SelectionHighlight="{Binding grid_settings_manager.core_manager.selection_highlight, Mode=OneWay}"
>
<FrameworkElement.Margin>
<Binding Source="{StaticResource GridSplitterWidth}">
<Binding Path="show_right_panel">
<Binding.Converter>
<cv:DoubleToThicknessConverter RightMultiplier="-1"/>
<cv:ValueConverterPipe>
<cv:BooleanToObjectConverter ValueForFalse="{u:Double 0}" ValueForTrue="{StaticResource GridSplitterWidth}"/>
<cv:DoubleToThicknessConverter RightMultiplier="-1"/>
</cv:ValueConverterPipe>
</Binding.Converter>
</Binding>
</FrameworkElement.Margin>
@@ -545,7 +548,7 @@
Grid.Column="1"
Grid.Row="3"
Panel.ZIndex="1"
Width="{StaticResource GridSplitterWidth}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
BorderThickness="0,1,0,0"
@@ -576,7 +579,7 @@
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
</Grid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom" Grid.ColumnSpan="2">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Orders:"/>
@@ -53,12 +53,19 @@ namespace mbts.spark {
d |= VisualStateNames.SlaveData.Data.Ladder;
return VisualStateNames.SlaveData.fmt_name(d);
});
static readonly IMultiValueConverter
_right_panel_expander_margin_converter = ValueConverter.create<bool, Visibility, Thickness>(
(is_expanded, scrollbar_vis) =>
!is_expanded && scrollbar_vis == Visibility.Visible ?
new Thickness(0, DIYGrid.DefaultColumnHeaderHeight, SystemParameters.VerticalScrollBarWidth, 0) :
new Thickness(0));
IDisposable _attach_op;
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
DataContext = vm;
bind_right_panel_expander_margin();
var algo_editor_cm = new ContextMenu();
algo_editor_cm.Items.AddRange(order_mod_menu());
algo_editor.ContextMenu = algo_editor_cm;
@@ -138,6 +145,19 @@ namespace mbts.spark {
}
}
}
async void bind_right_panel_expander_margin() {
if (!orders_grid.IsLoaded)
await orders_grid.LoadedAsync();
if (orders_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rows_presenter)) {
if (rows_presenter.try_find_descendent_of_type(out ScrollViewer scroll_viewer)) {
var mb = new MultiBinding { Converter = _right_panel_expander_margin_converter };
mb.Bindings.Add(new Binding(nameof(OrderBookVM.show_right_panel)) { Source = vm });
mb.Bindings.Add(new Binding(nameof(ScrollViewer.ComputedVerticalScrollBarVisibility)) { Source = scroll_viewer });
right_panel_expander_toggle.SetBinding(MarginProperty, mb);
}
}
}
}
public new OrderBookVM DataContext {
@@ -19,12 +19,12 @@ using static mbts.ui.controls.DIYGrid;
namespace bts.shell {
internal class PAndLWindowFactory : ShellWindowFactory<PAndLWindowFactory.PAndLStartupData> {
readonly IBus _bus;
readonly PAndLModel _model;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, PAndLModel model) : base(owner, "SparkPAndL") {
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, "SparkPAndL") {
_bus = bus;
_model = model;
_orders_model = orders_model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
}
@@ -45,7 +45,7 @@ namespace bts.shell {
state.grid,
get: us => us.pnl_grid,
set: (us, gs) => us.with(provided_pnl_gridsettings: gs));
return new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
return new PAndLWindow(_orders_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
}
[ProtoContract]
@@ -1,4 +1,6 @@
using bts.api;
using bts.shell.fills;
using bts.shell.manual_fills;
using bts.spark;
using mbts.persist;
using mbts.spark;
@@ -6,6 +8,7 @@ using mbts.ui;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media;
using static mbts.ui.shell.ProtoBufSerializationHelper;
@@ -18,15 +21,24 @@ namespace bts.shell {
FillsWindowFactory _fwf;
PAndLWindowFactory _plwf;
AlgoStudioWindowFactory _aswf;
ManualFillsWindowFactory _mfwf;
[ImportingConstructor]
public TradingShellModule(IBus bus, IWindowManager wm, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache,
UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model, ErrorReporter error_reporter) {
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model);
public TradingShellModule(
IBus bus,
IWindowManager wm,
INotificationService notification_service,
INotificationServiceDataCache notification_service_data_cache,
UserSettingsHub settings_hub,
OrdersModel orders_model,
md.MBOService mbo,
ErrorReporter error_reporter) {
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, mbo);
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
_plwf = new PAndLWindowFactory(this, bus, settings_hub, pandl_model);
_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
_mfwf = new ManualFillsWindowFactory(this, bus, orders_model);
}
public ImageSource icon => Icons.Trades;
@@ -37,11 +49,12 @@ namespace bts.shell {
public IEnumerable<SparkCommand> actions => new [] {
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
SparkCommands.ShowFills(null).complete(() => new FillsWindowFactory.StartupData()),
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()),
SparkCommands.PickLadder,
SparkCommands.PickOrderTicket,
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData())
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
};
IEnumerable<IShellWindowFactory> window_factories() {
@@ -50,11 +63,11 @@ namespace bts.shell {
yield return _fwf;
yield return _plwf;
yield return _aswf;
yield return _mfwf;
}
public Task<IShellWindow> create(Stream load_stream) {
return dispatch_to_factory(load_stream, window_factories().ToLArray());
}
public Task<IShellWindow> create(Stream load_stream) =>
dispatch_to_factory(load_stream, window_factories().ToArray());
public void Dispose() {
foreach (var i in window_factories())
+23 -2
View File
@@ -7,16 +7,17 @@ using static bts.api.orderman.Fill.Types;
namespace mbts.spark {
class FillTuple : IEquatable<FillTuple> {
public static readonly Func<FillTuple, string> key_fn = ft => ft.fill.FillKey;
public readonly FillImmutable fill;
public readonly OrderRecordImmutable? order;
public readonly SecurityImmutable? security;
public readonly AlgoData? algo;
public readonly AlgoDefinitionMetadata? algo_meta;
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurityAlgoMeta? order_tuple) {
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurity? order_tuple) {
this.fill = fill.val;
this.security = fill.sec;
if (order_tuple is OrderSecurityAlgoMeta osam) {
if (order_tuple is OrderSecurity osam) {
order = osam.order;
algo = osam.algo;
algo_meta = osam.meta;
@@ -45,4 +46,24 @@ namespace mbts.spark {
order.HasValue ? order.Value.Key.GetHashCode() : 0,
security.HasValue ? security.Value.Id.GetHashCode() : 0);
}
static class FillTupleExtensions {
public static DateTime fill_time(this FillTuple fill_tuple) =>
fill_tuple.fill.Timestamp.to_local_time();
public static TimeInForce time_in_force(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.time_in_force() : TimeInForce.Undefined;
public static CustomOrderDefinition.CustomOneofCase order_type(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.order_type() : CustomOrderDefinition.CustomOneofCase.None;
public static Venue exchange(this FillTuple fill_tuple) =>
fill_tuple.security.HasValue ? fill_tuple.security.Value.Exchange : Venue.Unset;
public static uint order_key(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.Key : 0u;
public static string user(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
}
}
+23 -15
View File
@@ -49,7 +49,7 @@ namespace mbts.spark.utilities {
public OrderFactory(AdminModel admin_model, DynamicOrderHelper dynamic_helper) {
_admin_model = admin_model;
_dynamic_helper = dynamic_helper;
var algo_name_index = new HashSet<OneOf<CustomOrderDefinition.CustomOneofCase, string>>(OneOf<CustomOrderDefinition.CustomOneofCase,string>.ValueEqualityComparer);
var algo_name_index = new HashSet<OneOf<CustomOrderDefinition.CustomOneofCase, string>>(OneOf<CustomOrderDefinition.CustomOneofCase, string>.ValueEqualityComparer);
// add standard types
algo_name_index.AddRange(
EnumHelper.get_values_except(CustomOrderDefinition.CustomOneofCase.None)
@@ -58,7 +58,7 @@ namespace mbts.spark.utilities {
algo_name_index.AddRange(dynamic_helper.dynamic_types
.Select(dt => new OneOf<CustomOrderDefinition.CustomOneofCase, string>(dt)));
// build abbreviation index
_algo_abbreviation_index = DisplayNameHelper.get_word_tree_abbreviations(algo_name_index, oneof => oneof.value.ToString(), OneOf<CustomOrderDefinition.CustomOneofCase,string>.ValueEqualityComparer);
_algo_abbreviation_index = DisplayNameHelper.get_word_tree_abbreviations(algo_name_index, oneof => oneof.value.ToString(), OneOf<CustomOrderDefinition.CustomOneofCase, string>.ValueEqualityComparer);
}
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_accounts() => _admin_model.on_accounts;
@@ -70,7 +70,7 @@ namespace mbts.spark.utilities {
public CustomOrderDefinition.CustomOneofCase default_order_type() =>
CustomOrderDefinition.CustomOneofCase.Limit;
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security, bool is_child=false) {
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security, bool is_child = false) {
if (is_child) return CustomOrderDefinition.CustomOneofCase.Limit;
switch (security.ContractCase) {
case Security.ContractOneofCase.Synthetic: return CustomOrderDefinition.CustomOneofCase.Spreader;
@@ -78,7 +78,7 @@ namespace mbts.spark.utilities {
}
}
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable? security, bool is_child=false) =>
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable? security, bool is_child = false) =>
security.HasValue ? default_order_type(security.Value, is_child) : default_order_type();
public TimeInForce default_time_in_force() =>
@@ -103,13 +103,13 @@ namespace mbts.spark.utilities {
}
}
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security, bool is_child=false) =>
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security, bool is_child = false) =>
security.HasValue ? default_order_types(security.Value, is_child) : default_order_types();
public IEnumerable<TimeInForce> default_time_in_forces() =>
EnumHelper.get_values_except(TimeInForce.Undefined);
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, bool is_child=false) =>
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, bool is_child = false) =>
default_time_in_forces();
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, CustomOrderDefinition.CustomOneofCase order_type, bool is_child = false) {
@@ -120,7 +120,7 @@ namespace mbts.spark.utilities {
return tifs;
}
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable? security, bool is_child=false) =>
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable? security, bool is_child = false) =>
security.HasValue ? default_time_in_forces(security.Value, is_child) : default_time_in_forces();
public LadderSettings default_ladder_settings(SecurityImmutable security) {
@@ -201,6 +201,7 @@ namespace mbts.spark.utilities {
public LimitOrderDefinition limit_order() => OrderDataHelper.Defaults.LimitOrderDefinition();
public StopOrderDefinition stop_order() => OrderDataHelper.Defaults.StopOrderDefinition();
public StopLimitOrderDefinition stop_limit_order() => OrderDataHelper.Defaults.StopLimitOrderDefinition();
public GLBXIcebergOrderDefinition glbx_iceberg_order() => OrderDataHelper.Defaults.GLBXIcebergOrderDefinition();
public IcebergOrderDefinition iceberg_order() => OrderDataHelper.Defaults.IcebergOrderDefinition();
public TimeBoxOrderDefinition time_box_order() => OrderDataHelper.Defaults.TimeBoxOrderDefinition();
public TickOrderDefinition tick_order() => OrderDataHelper.Defaults.TickOrderDefinition();
@@ -226,14 +227,15 @@ namespace mbts.spark.utilities {
public CustomOrderDefinition custom_order(CustomOrderDefinition.CustomOneofCase order_type) {
var rv = new CustomOrderDefinition();
switch(order_type) {
switch (order_type) {
case CustomOrderDefinition.CustomOneofCase.Market: rv.Market = market_order(); break;
case CustomOrderDefinition.CustomOneofCase.Limit: rv.Limit = limit_order(); break;
case CustomOrderDefinition.CustomOneofCase.Stop: rv.Stop = stop_order(); break;
case CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = stop_limit_order(); break;
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: rv.GlbxIceberg = glbx_iceberg_order(); break;
case CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = iceberg_order(); break;
case CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = time_box_order(); break;
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
case CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = lean_order(); break;
case CustomOrderDefinition.CustomOneofCase.Join: rv.Join = join_order(); break;
case CustomOrderDefinition.CustomOneofCase.Top: rv.Top = top_order(); break;
@@ -244,7 +246,7 @@ namespace mbts.spark.utilities {
case CustomOrderDefinition.CustomOneofCase.Trigger: rv.Trigger = trigger_order(); break;
case CustomOrderDefinition.CustomOneofCase.Spreader: rv.Spreader = spreader_order(); break;
case CustomOrderDefinition.CustomOneofCase.Dynamic: rv.Dynamic = dynamic_order(); break;
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
}
return rv;
}
@@ -257,6 +259,7 @@ namespace mbts.spark.utilities {
case CustomOrderDefinition.CustomOneofCase.Limit: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.green.rgb_color(), NoProps = true };
case CustomOrderDefinition.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.red_orange.rgb_color(), NoProps = true };
case CustomOrderDefinition.CustomOneofCase.StopLimit: return new OrderMetadata { KeyBinding = "SL", Color = BTSColors.pale_green.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return new OrderMetadata { KeyBinding = "GB", Color = BTSColors.Orders.glbx_iceberg_order };
case CustomOrderDefinition.CustomOneofCase.Iceberg: return new OrderMetadata { KeyBinding = "I", Color = BTSColors.pale_blue.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.TimeBox: return new OrderMetadata { KeyBinding = "TB", Color = BTSColors.purple.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.Tick: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.tick_order };
@@ -266,7 +269,7 @@ namespace mbts.spark.utilities {
case CustomOrderDefinition.CustomOneofCase.Cover: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.cover_order };
case CustomOrderDefinition.CustomOneofCase.Oco: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.oco_order };
case CustomOrderDefinition.CustomOneofCase.Pricer: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.pricer_order };
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.tick_order };
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.off_order };
case CustomOrderDefinition.CustomOneofCase.Trigger: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.trigger_order };
case CustomOrderDefinition.CustomOneofCase.Spreader: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.spreader_order };
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.top_order };
@@ -418,6 +421,7 @@ namespace mbts.spark.utilities {
case CustomOrderDefinition.CustomOneofCase.Limit: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.green.rgb_color(), NoProps = true };
case CustomOrderDefinition.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.red_orange.rgb_color(), NoProps = true };
case CustomOrderDefinition.CustomOneofCase.StopLimit: return new OrderMetadata { KeyBinding = "SL", Color = BTSColors.pale_green.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return new OrderMetadata { KeyBinding = "GB", Color = BTSColors.Orders.glbx_iceberg_order };
case CustomOrderDefinition.CustomOneofCase.Iceberg: return new OrderMetadata { KeyBinding = "I", Color = BTSColors.pale_blue.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.TimeBox: return new OrderMetadata { KeyBinding = "TB", Color = BTSColors.purple.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.Tick: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.tick_order };
@@ -427,7 +431,7 @@ namespace mbts.spark.utilities {
case CustomOrderDefinition.CustomOneofCase.Cover: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.cover_order };
case CustomOrderDefinition.CustomOneofCase.Oco: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.oco_order };
case CustomOrderDefinition.CustomOneofCase.Pricer: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.pricer_order };
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.tick_order };
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.off_order };
case CustomOrderDefinition.CustomOneofCase.Trigger: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.trigger_order };
case CustomOrderDefinition.CustomOneofCase.Spreader: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.spreader_order };
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.top_order };
@@ -440,6 +444,7 @@ namespace mbts.spark.utilities {
switch (algo_type) {
case CustomOrderDefinition.CustomOneofCase.Limit: return "LM";
case CustomOrderDefinition.CustomOneofCase.Cover: return "CV";
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return "GB";
case CustomOrderDefinition.CustomOneofCase.Iceberg: return "IB";
case CustomOrderDefinition.CustomOneofCase.Lean: return "LN";
case CustomOrderDefinition.CustomOneofCase.Market: return "MA";
@@ -594,6 +599,7 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
@@ -636,12 +642,12 @@ namespace mbts.spark.utilities {
var tgt_child = cad.get_child_from_impl(tgt_type_def);
copy_values(src_child, ref tgt_child);
}
}
}
public static CustomOrderDefinition.CustomOneofCase get_order_type(IMessage order_type_def) =>
order_type_def != null && _algo_type_case_index.TryGetValue(order_type_def.Descriptor.ClrType, out CustomOrderDefinition.CustomOneofCase algo_case) ?
algo_case : CustomOrderDefinition.CustomOneofCase.None;
#region Private Members
// get child
static CustomOrderDefinitionImmutable? _get_iceberg_child(IcebergOrderDefinitionImmutable iceberg) => iceberg.HasChild ? iceberg.Child : (CustomOrderDefinitionImmutable?)null;
@@ -857,7 +863,7 @@ namespace mbts.spark.utilities {
int phase_mult = 1;
switch (def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Cover:
add_component(def.Cover.HedgeTicks);
add_component(def.Cover.HedgeTicks.GetHashCode());
add_component(_get_hash(_get_cover_child(def.Cover)));
add_component(_get_hash(_get_cover_hedge(def.Cover)));
break;
@@ -981,6 +987,7 @@ namespace mbts.spark.utilities {
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Stop: rv.Stop = StopOrderDefinition(); break;
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = StopLimitOrderDefinition(); break;
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = IcebergOrderDefinition(); break;
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.GlbxIceberg: rv.GlbxIceberg = GLBXIcebergOrderDefinition(); break;
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = TimeBoxOrderDefinition(); break;
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = TickOrderDefinition(); break;
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = LeanOrderDefinition(); break;
@@ -1004,6 +1011,7 @@ namespace mbts.spark.utilities {
public static LimitOrderDefinition LimitOrderDefinition() => new LimitOrderDefinition { };
public static StopOrderDefinition StopOrderDefinition() => new StopOrderDefinition { };
public static StopLimitOrderDefinition StopLimitOrderDefinition() => new StopLimitOrderDefinition { };
public static GLBXIcebergOrderDefinition GLBXIcebergOrderDefinition() => new GLBXIcebergOrderDefinition { MaxShowQuantity = 1 };
public static IcebergOrderDefinition IcebergOrderDefinition() => new IcebergOrderDefinition { Child = CustomOrderDefinition(), ShowQuantity = 1 };
public static TimeBoxOrderDefinition TimeBoxOrderDefinition() => new TimeBoxOrderDefinition { Child = CustomOrderDefinition(), Start = Timestamp.FromDateTime(DateTime.UtcNow), Stop = Timestamp.FromDateTime(DateTime.UtcNow) };
public static TickOrderDefinition TickOrderDefinition() => new TickOrderDefinition { Child = CustomOrderDefinition(), CrossThreshold = QuantityMeasure };
@@ -153,9 +153,21 @@ namespace mbts.spark {
d => d.load_committed(),
d => d == null ? Observable.Return(false) : d.subscribe_is_definition_dirty(),
out IObserver<AlgoDefinitionVM> revert_definition_observer);
// algo params dialog skirt
set_default_algo_params_cmd = ObservantCommand.create(() => {
algo_param_rows.commit_all();
_close_algo_popup();
}, out IObserver<bool> set_default_algo_params_observer);
revert_algo_params_cmd = ObservantCommand.create(() => {
algo_param_rows.revert_all();
_close_algo_popup();
}, out IObserver<bool> revert_algo_params_observer);
var algo_params_dirty_mon = new ReactiveThresholdMonitor<AlgoParamRowVM>(r => r.param.subscribe_is_dirty());
var nullable_security_md_source = security_md_source.Select(smd => new SecurityMD?(smd)).RefCountReplay(1);
//
_dispose = new CompositeDisposable(
algo_params_dirty_mon.Subscribe(set_default_algo_params_observer, revert_algo_params_observer),
ObservableAutoWrapper.connect_item_hooks(_algo_param_rows, algo_params_dirty_mon.join),
_selected_algo_definition.subscribe_changed(_selected_algo_definition_Changed),
as_demuxer().subscribe<AlgoDefinitionTemplateVM>(nameof(selected_algo_template)).Subscribe(
undo_observer,
@@ -358,6 +370,8 @@ namespace mbts.spark {
public ICommand save_definition_cmd { get; }
public ICommand save_template_cmd { get; }
public ICommand revert_definition_cmd { get; }
public ICommand set_default_algo_params_cmd { get; }
public ICommand revert_algo_params_cmd { get; }
public ICommand undo_cmd { get; }
public ICommand redo_cmd { get; }
public ICommand undo_definition_cmd { get; }
@@ -386,9 +400,22 @@ namespace mbts.spark {
if (_model.algo_model.templates_enabled()) {
selected_algo_template.algo_root.assign_to(order.CustomDef);
} else {
selected_algo_definition.algo_root.assign_to(order.CustomDef);
if (selected_algo_definition.key.mode == AlgoKey.Mode.Persisted)
order.AlgoTemplateKey = selected_algo_definition.key.persisted.id_uint;
var algo = new CustomOrderDefinition();
selected_algo_definition.algo_root.assign_to(algo);
switch (selected_algo_definition.key.mode) {
case AlgoKey.Mode.Persisted:
order.CustomDef = new CustomOrderDefinition {
Composite = new CompositeOrderDefinition {
AlgoTemplateKey = selected_algo_definition.key.persisted.id_uint,
Definition = algo
}
};
selected_algo_definition.algo_root.assign_to(order.CustomDef.Composite.Definition);
break;
default:
order.CustomDef = algo;
break;
}
}
return order;
}
@@ -411,6 +438,11 @@ namespace mbts.spark {
static IDisposable _connect_algo_param_rows(AlgoDefinitionVM def, ObservableAggregateCollection<AlgoParamRowVM> rows) =>
AlgoParamRowsHelper.connect(def, rows, false);
void _close_algo_popup() {
if (selected_algo_definition?.algo_root is IAlgoVM a)
a.is_expanded = false;
}
int? _get_favorite_algo_index(AlgoKey key) =>
key.mode == AlgoKey.Mode.Persisted && _favorite_algos_index.TryGetValue(key, out int i) ? i : (int?)null;
+69 -13
View File
@@ -3,13 +3,15 @@ using mbts.spark;
using System;
namespace bts.api.orderman {
readonly struct OrderSecurity: IEquatable<OrderSecurity> {
readonly struct OrderSecurity : IEquatable<OrderSecurity> {
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order) {
this.security = sec;
this.order = order;
piq = null;
algo = null;
meta = null;
root_algo = new AlgoData(order.Def.CustomDef);
root_meta = null;
}
public OrderSecurity((SecurityImmutable? sec, OrderRecordImmutable order) t) {
security = t.sec;
@@ -17,6 +19,8 @@ namespace bts.api.orderman {
piq = null;
algo = null;
meta = null;
root_algo = new AlgoData(order.Def.CustomDef);
root_meta = null;
}
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq) {
this.security = sec;
@@ -24,6 +28,8 @@ namespace bts.api.orderman {
this.piq = piq;
algo = null;
meta = null;
root_algo = new AlgoData(order.Def.CustomDef);
root_meta = null;
}
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta) {
this.security = sec;
@@ -31,24 +37,55 @@ namespace bts.api.orderman {
this.piq = piq;
this.algo = algo;
this.meta = meta;
root_algo = algo ?? new AlgoData(order.Def.CustomDef);
root_meta = meta;
}
//public OrderSecurity(OrderSecurity t) {
// security = t.security;
// order = t.order;
// piq = t.piq;
//}
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? rootalgo = null, AlgoDefinitionMetadata? rootmeta = null) {
this.security = sec;
this.order = order;
this.piq = piq;
this.algo = algo;
this.meta = meta;
if (rootalgo.HasValue) {
this.root_algo = rootalgo.Value;
this.root_meta = rootmeta;
} else {
this.root_algo = algo ?? new AlgoData(order.Def.CustomDef);
this.root_meta = meta;
}
}
public static implicit operator OrderSecurity(ValueTuple<SecurityImmutable?, OrderRecordImmutable> x) => new OrderSecurity(x);
public SecurityImmutable? security { get; }
public OrderRecordImmutable order { get; }
public uint? piq { get; }
public AlgoData? algo { get; }
public AlgoDefinitionMetadata? meta { get; }
public AlgoData root_algo { get; }
public AlgoDefinitionMetadata? root_meta { get; }
public OrderSecurity with(AlgoData? root_algo, AlgoDefinitionMetadata? root_meta = null) =>
new OrderSecurity(security, order, piq, algo, meta, root_algo, root_meta);
public override bool Equals(object obj) => (obj is OrderSecurity os) && Equals(os);
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security && piq == other.piq;
public override int GetHashCode() => (security, order, piq).GetHashCode();
public bool Equals(OrderSecurity other) =>
order == other.order &&
security == other.security &&
piq == other.piq &&
algo == other.algo &&
meta == other.meta &&
root_algo == other.root_algo;
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
order.GetHashCode(),
security?.GetHashCode() ?? 0,
piq?.GetHashCode() ?? 0,
algo?.GetHashCode() ?? 0,
meta?.GetHashCode() ?? 0,
root_algo.GetHashCode());
public static bool operator == (OrderSecurity left, OrderSecurity right) => left.Equals(right);
public static bool operator != (OrderSecurity left, OrderSecurity right) => !left.Equals(right);
@@ -58,21 +95,39 @@ namespace bts.api.orderman {
struct OrderSecurityAlgoMeta : IEquatable<OrderSecurityAlgoMeta> {
public readonly OrderRecordImmutable order;
public readonly SecurityImmutable? security;
/// TODO: refactor <see cref="algo"/> to 'studio_algo' or something that indicates it does not bear the same relation to this order, that <see cref="root_algo"/> does to the root order.
/// The former is meant to hold studio algo information, and nothing else. Naturally, it indicates whether or not this is a studio order by being non-null (<see cref="is_studio_algo"/>).
/// The latter, however, can be any kind of algo (studio, exchange, etc.); it holds information about the root-most algo in this order's branch.
public readonly AlgoData? algo;
public readonly AlgoDefinitionMetadata? meta;
public readonly AlgoData root_algo;
public readonly AlgoDefinitionMetadata? root_meta;
public OrderSecurityAlgoMeta(OrderRecordImmutable order, SecurityImmutable? security, AlgoData? algo, AlgoDefinitionMetadata? meta) {
public OrderSecurityAlgoMeta(OrderRecordImmutable order, SecurityImmutable? security, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? root_algo = null, AlgoDefinitionMetadata? root_meta = null) {
this.order = order;
this.security = security;
this.algo = algo;
this.meta = meta;
if (root_algo.HasValue) {
this.root_algo = root_algo.Value;
this.root_meta = root_meta;
} else {
this.root_algo = algo ?? new AlgoData(order.Def.CustomDef);
this.root_meta = meta;
}
}
public bool is_studio_algo => algo.HasValue;
public OrderSecurityAlgoMeta with(AlgoData? root_algo, AlgoDefinitionMetadata? root_meta = null) =>
new OrderSecurityAlgoMeta(order, security, algo, meta, root_algo, root_meta);
public bool Equals(OrderSecurityAlgoMeta other) =>
order == other.order &&
security == other.security &&
algo == other.algo &&
meta == other.meta;
meta == other.meta &&
root_algo == other.root_algo;
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
@@ -81,9 +136,10 @@ namespace bts.api.orderman {
order.GetHashCode(),
security?.GetHashCode() ?? 0,
algo?.GetHashCode() ?? 0,
meta?.GetHashCode() ?? 0);
meta?.GetHashCode() ?? 0,
root_algo.GetHashCode());
public static bool operator ==(OrderSecurityAlgoMeta a, OrderSecurityAlgoMeta b) => a.Equals(b);
public static bool operator !=(OrderSecurityAlgoMeta a, OrderSecurityAlgoMeta b) => !a.Equals(b);
}
}
}
@@ -3,13 +3,13 @@
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class CoverOrderVM : OrderTypeVMBase<CoverOrderDefinition, CoverOrderDefinitionImmutable> {
readonly Property<int?> _hedge_ticks;
readonly Property<uint?> _hedge_ticks;
public CoverOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) =>
_hedge_ticks = add_nullable_property(nameof(hedge_ticks), CoverOrderDefinition.HedgeTicksFieldNumber, 1, true);
_hedge_ticks = add_nullable_property(nameof(hedge_ticks), CoverOrderDefinition.HedgeTicksFieldNumber, 1u, true);
public int? hedge_ticks { get => _hedge_ticks.Value; set => _hedge_ticks.set(value); }
public uint? hedge_ticks { get => _hedge_ticks.Value; set => _hedge_ticks.set(value); }
protected override void validate() =>
assert(hedge_ticks.HasValue, nameof(hedge_ticks), "Invalid hedge ticks.");
@@ -0,0 +1,16 @@
using bts.api.orderman;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class GLBXIcebergOrderVM : OrderTypeVMBase<GLBXIcebergOrderDefinition, GLBXIcebergOrderDefinitionImmutable> {
readonly Property<uint?> _max_show_quantity;
public GLBXIcebergOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_max_show_quantity = add_nullable_property<uint>(nameof(max_show_quantity), GLBXIcebergOrderDefinition.MaxShowQuantityFieldNumber);
}
public uint? max_show_quantity { get => _max_show_quantity.Value; set => _max_show_quantity.set(value); }
}
}
}
@@ -53,11 +53,15 @@ namespace mbts.spark {
public static IAlgoVM pricer(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new PricerOrderVM(key, display_name, editing_permissions);
public static IAlgoVM offset(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new OffsetOrderVM(key, display_name, editing_permissions);
public static IAlgoVM trigger(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new TriggerOrderVM(key, display_name, editing_permissions);
public static IAlgoVM glbx_iceberg(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new GLBXIcebergOrderVM(key, display_name, editing_permissions);
public static IAlgoVM iceberg(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new IcebergOrderVM(key, display_name, editing_permissions);
public static IAlgoVM spreader(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new SpreaderOrderVM(key, display_name, editing_permissions);
public static IAlgoVM dynamic(AlgoKey key, string display_name, string dynamic_type, AlgoEditingPermissions editing_permissions) => new DynamicOrderVM(key, dynamic_type, display_name, editing_permissions);
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
if (def_data.value.CustomCase == AlgoType.Composite) {
def_data = new AlgoData(def_data.value.Composite.Definition, def_data.name, def_data.key);
}
var vm = create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
vm.load_from(def_data.value);
return vm;
@@ -80,6 +84,7 @@ namespace mbts.spark {
case AlgoType.Pricer: return pricer(key, name, editing_permissions);
case AlgoType.Offset: vm = offset(key, name, editing_permissions); break;
case AlgoType.Trigger: vm = trigger(key, name, editing_permissions); break;
case AlgoType.GlbxIceberg: vm = glbx_iceberg(key, name, editing_permissions); break;
case AlgoType.Iceberg: vm = iceberg(key, name, editing_permissions); break;
case AlgoType.Spreader: vm = spreader(key, name, editing_permissions); break;
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
@@ -235,8 +240,11 @@ namespace mbts.spark {
!string.IsNullOrEmpty(display_name) && key.Key == (Key.A + display_name[0] - 'A');
public void load_from(AlgoDefi value) {
if (value.CustomCase != order_type)
return;
if (value.CustomCase != order_type) {
value = value.actual();
if (value.CustomCase != order_type)
return;
}
using (_def_change_notifier.defer_execution()) {
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
var security_src = Observable.Create<SecurityImmutable?>(o => {
@@ -268,8 +276,11 @@ namespace mbts.spark {
}
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
if (defi.CustomCase != order_type)
return null;
if (defi.CustomCase != order_type) {
defi = defi.actual();
if (defi.CustomCase != order_type)
return null;
}
using (_def_change_notifier.defer_execution()) {
// TODO: each level of the order hierarchy could load in parallel
await on_load_from_async(defi);
@@ -1048,4 +1059,4 @@ namespace mbts.spark {
}
#endregion
}
}
}
@@ -5,16 +5,13 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
readonly Property<uint?> _watch_level_tick_offset;
readonly Property<QuantityMeasureImmutable?> _top_threshold;
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_watch_level_tick_offset = add_nullable_property(nameof(watch_level_tick_offset), TopDef.WatchLevelTickOffsetFieldNumber, 0u);
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
}
public uint? watch_level_tick_offset { get => _watch_level_tick_offset.Value; set => _watch_level_tick_offset.set(value); }
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
}
}
+42 -13
View File
@@ -79,9 +79,41 @@ namespace mbts.spark {
public static CustomOrderDefinition.CustomOneofCase order_type(this OrderRecordImmutable order) =>
order.HasDef && order.Def.HasCustomDef ? order.Def.CustomDef.CustomCase : CustomOrderDefinition.CustomOneofCase.None;
/// <summary>
/// Unwraps all <see cref="CompositeOrderDefinition"/>s (if any) to return the underlying order type.
/// </summary>
/// <param name="order">The order who's underlying algo will be returned.</param>
/// <returns>The first non-composite algo definition in the specified <see cref="CustomOrderDefinition"/>.</returns>
public static CustomOrderDefinition.CustomOneofCase actual_order_type(this OrderRecordImmutable order) {
if (!order.HasDef || !order.Def.HasCustomDef)
return CustomOrderDefinition.CustomOneofCase.None;
var cdef = order.Def.CustomDef;
while (cdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
if (!cdef.Composite.HasDefinition)
return CustomOrderDefinition.CustomOneofCase.None;
cdef = cdef.Composite.Definition;
}
return cdef.CustomCase;
}
public static string dynamic_order_type_display_name(this OrderRecordImmutable order) =>
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasDynamic ? order.Def.CustomDef.Dynamic.DynamicOrderType : string.Empty;
public static uint algo_template_key(this OrderRecordImmutable order) =>
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite ?
order.Def.CustomDef.Composite.AlgoTemplateKey : 0u;
/// <summary>
/// If the definitions is a <see cref="CompositeOrderDefinition"/>, returns the underlying algo, otherwise returns <paramref name="custom_def"/> iteself.
/// </summary>
/// <param name="custom_def">The definition to for which the underlying algo will be returned.</param>
/// <returns>The first non-composite algo definition in the specified <see cref="CustomOrderDefinition"/>.</returns>
public static CustomOrderDefinitionImmutable actual(this CustomOrderDefinitionImmutable custom_def) {
while (custom_def.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
custom_def = custom_def.Composite.Definition;
return custom_def;
}
public static OrderSubType sub_type(this OrderRecordImmutable order) =>
order.HasDef ? order.Def.SubType : OrderSubType.None;
@@ -97,7 +129,8 @@ namespace mbts.spark {
case CustomOrderDefinition.CustomOneofCase.Limit:
case CustomOrderDefinition.CustomOneofCase.Market:
case CustomOrderDefinition.CustomOneofCase.Stop:
case CustomOrderDefinition.CustomOneofCase.StopLimit: return true;
case CustomOrderDefinition.CustomOneofCase.StopLimit:
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return true;
}
}
return false;
@@ -123,9 +156,11 @@ namespace mbts.spark {
public static string display_name(this OrderSecurityAlgoMeta order_tuple) =>
display_name(order_tuple.order, order_tuple.algo);
public static string display_name(this OrderSecurity order_tuple) =>
display_name(order_tuple.order, order_tuple.algo);
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
if (!order.has_parent() && algo.HasValue) {
if (algo.HasValue) {
return algo.Value.name;
} else if (order.HasDef && order.Def.HasCustomDef) {
return order.Def.CustomDef.HasDynamic ?
@@ -225,18 +260,12 @@ namespace mbts.spark {
}
return false;
}
public static Brush badge_fill(this OrderSecurityAlgoMeta order_sam, OrderMetadata order_meta) =>
badge_fill(order_sam.order, order_sam.meta, order_meta);
public static Brush badge_fill(OrderRecordImmutable order, AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) {
/// TODO: figure out why <see cref="OrderDefinition.AlgoTemplateKey"/> is propagated to all descendant-algos (should really just belong to the algo-node/CustomDef that is the root of the AlgoTemplate).
/// In the meantime, the following 'has-no-parent' conditional will suffice as a workaround so child-algos will be colored properly (according to their own <see cref="CustomOrderDefinition.CustomOneofCase"/>).
if (!order.has_parent()) {
if (algo_meta?.badge_color is Color c)
return c.to_brush();
}
return order_meta.Color.rgb_brush();
}
public static Brush badge_fill(AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) =>
new SolidColorBrush(badge_color(algo_meta, order_meta));
public static Color badge_color(AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) =>
algo_meta?.badge_color ?? order_meta.Color.rgb_color();
public static string order_collection_summary(double best_price, int best_price_volume, int total_volume, ContractSpecificationImmutable spec) {
if (total_volume == 0)
+120 -37
View File
@@ -18,7 +18,6 @@ using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using OrderUpdate = bts.api.core.TableUpdate<(bts.api.SecurityImmutable? sec, bts.api.orderman.OrderRecordImmutable order), uint>;
using OrderUpdatePiq = bts.api.core.TableUpdate<bts.api.orderman.OrderSecurity, uint>;
@@ -27,6 +26,7 @@ namespace mbts.spark {
[Export]
class OrdersModel : IDisposable {
public readonly MarketDataAuxiliaryModel md_auxiliary_model;
public static readonly bool piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
readonly IBus _bus;
readonly OrderFactory _factory;
@@ -37,10 +37,10 @@ namespace mbts.spark {
readonly ErrorReporter _error;
readonly DynamicOrderHelper _dynamic_helper;
readonly OrderActivitySummarizer _activity;
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer, _security_working_orders_demuxer;
readonly TableUpdateDemuxer<PositionUnitImmutable, int, uint> _security_positions_demuxer;
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer;
readonly TableUpdateDemuxer<OrderSecurity, uint, uint> _security_working_orders_with_piq_demuxer;
readonly TableUpdateDemuxer<(SecurityImmutable?, PositionUnitImmutable), int, uint> _security_positions_demuxer;
IDisposable _dispose;
static readonly bool _piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
[ImportingConstructor]
OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, MBOService mbo_service,
@@ -67,7 +67,7 @@ namespace mbts.spark {
.Table()
.Cache();
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).Table().Cache();
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).join(sstore, pos => pos.SecurityId).Table().Cache();
working_orders =
on_orders.Scan(
ImmutableDictionary<uint,OrderSecurity>.Empty,
@@ -82,28 +82,28 @@ namespace mbts.spark {
_activity = new OrderActivitySummarizer(bus, on_orders);
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
_security_working_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
_security_positions_demuxer = new TableUpdateDemuxer<PositionUnitImmutable, int, uint>(o => o.RecordId, p => p.SecurityId);
var algo_meta_join = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
_security_working_orders_with_piq_demuxer = new TableUpdateDemuxer<OrderSecurity, uint, uint>(o => o.order.Key, o => o.order.security_key());
_security_positions_demuxer = new TableUpdateDemuxer<(SecurityImmutable?, PositionUnitImmutable pos), int, uint>(o => o.pos.RecordId, p => p.pos.SecurityId);
studio_algos_with_meta = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
algo_model.metadata_service.subscribe_metadata().Table(m => m.id),
d => d.key,
m => m.id,
(algo, k, meta) => (algo, meta),
(algo, k) => (algo, null),
t => t.algo.key);
t => t.algo.key)
.Cache();
var order_secs_algo_meta_join = on_orders.LeftJoin(
algo_meta_join,
studio_algos_with_meta,
t => t.order.Key,
am => am.algo.key,
t => new AlgoKey(new bts.persist.algo.AlgoPersistedKey(t.order.Def.AlgoTemplateKey)),
(os, okey, am, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, am.algo, am.meta),
(os, okey, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, null, null),
t => new AlgoKey(new bts.persist.algo.AlgoPersistedKey(t.order.algo_template_key())),
(os, okey, am, mkey) => new OrderSecurity(os.sec, os.order, null, am.algo, am.meta),
(os, okey, mkey) => new OrderSecurity(os.sec, os.order, null, null, null),
ot => ot.order.Key);
on_order_tuples = order_secs_algo_meta_join.Cache();
// need to bring back the config key check
on_order_tuples_with_piq = on_order_tuples.LeftJoin<
OrderSecurityAlgoMeta,
OrderSecurity,
OrderPIQImmutable,
OrderSecurity,
uint
@@ -114,13 +114,19 @@ namespace mbts.spark {
(osam, _) => new OrderSecurity(osam.security, osam.order, null, osam.algo, osam.meta),
osp => osp.order.Key)
.Cache();
working_orders_with_piq =
on_order_tuples_with_piq.Scan(
ImmutableDictionary<uint, OrderSecurity>.Empty,
(workers, table) => _update_working_orders(workers, table))
.Replay(1).RefCount();
// order tuples with root
on_order_tuples_with_root = on_order_tuples.RootDependent(
o => o.order.Key,
o => o.order.ParentKey,
(osam, root_osam) => osam.with(root_osam.algo ?? new AlgoData(root_osam.order.Def.CustomDef), root_osam.meta)
).Cache();
// order tuples with root and piq
on_order_tuples_with_root_and_piq = on_order_tuples_with_piq.RootDependent(
o => o.order.Key,
o => o.order.ParentKey,
(osam, root_osam) => osam.with(root_osam.algo ?? new AlgoData(root_osam.order.Def.CustomDef), root_osam.meta)
).Cache();
// fills
var on_fills =
_service.subscribei_fills()
.Naggle(interval, scheduler)
@@ -136,6 +142,28 @@ namespace mbts.spark {
(f, fk, ok) => new FillTuple(f, null),
f => f.fill.FillKey);
on_fill_tuples = fill_order_tuples_join.Cache();
// manual fills
on_manual_fills_securities = _position_service.subscribei_manual_fills()
.Naggle(interval, scheduler)
.join(sstore, mf => mf.SecurityKey)
.Table().Cache();
var manual_fill_tuples_account_join = on_manual_fills_securities.LeftJoin(
admin_model.on_accounts.Select(d => MappedDictionary.create(d, kv => new AccountTuple(kv.Key, kv.Value))).Table(a => a.id),
mfs => mfs.Item2.RecordId,
AccountTuple.key_fn,
mfs => mfs.Item2.AccountId,
(mfs, mf_id, a, a_id) => new ManualFillTuple(mfs.Item2, mfs.Item1, a, null),
(mfs, mf_id, a_id) => new ManualFillTuple(mfs.Item2, mfs.Item1, null, null),
ManualFillTuple.key_fn);
on_manual_fill_tuples = manual_fill_tuples_account_join.LeftJoin(
admin_model.on_users.Select(d => MappedDictionary.create(d, kv => new UserTuple(kv.Key, kv.Value))).Table(a => a.id),
ManualFillTuple.key_fn,
UserTuple.key_fn,
mft => mft.manual_fill.UserId,
(mft, mft_id, u, u_id) => new ManualFillTuple(mft.manual_fill, mft.security, mft.account, u),
(mft, mft_id, u_id) => mft,
ManualFillTuple.key_fn)
.Cache();
// can attach
on_orders_can_attach = on_orders.ParentDependent(
t => t.order.Key,
@@ -152,10 +180,9 @@ namespace mbts.spark {
.Cache();
_dispose = Disposable.Create(
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
on_orders
.Filter(order => order.order.is_active() && order.order.HasState && order.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible, key => key.order.Key)
.Select(t => t.order, o => o.Key)
.Subscribe(_security_working_orders_demuxer),
on_order_tuples_with_piq
.Filter(os => os.order.is_active() && os.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible, key => key.order.Key)
.Subscribe(_security_working_orders_with_piq_demuxer),
on_positions.Subscribe(_security_positions_demuxer),
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
@@ -171,25 +198,30 @@ namespace mbts.spark {
public DynamicOrderHelper dynamic_helper => _dynamic_helper;
public IObservable<OrderUpdate> on_orders { get; }
public IObservable<OrderUpdatePiq> on_order_tuples_with_piq { get; }
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples { get; }
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples { get; }
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples_with_root { get; }
public IObservable<TableUpdate<OrderSecurity, uint>> on_order_tuples_with_root_and_piq { get; }
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_detach { get; }
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_attach { get; }
public IObservable<IIncrementalUpdate<SecurityImmutable, uint>> on_order_securities { get; }
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples { get; }
public IObservable<IReadOnlyDictionary<uint,OrderSecurity>> working_orders { get; }
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders_with_piq { get; }
public IObservable<TableUpdate<PositionUnitImmutable, int>> on_positions { get; }
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_positions { get; }
public IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> studio_algos_with_meta { get; }
public IObservable<TableUpdate<(SecurityImmutable?, ManualFillImmutable), int>> on_manual_fills_securities { get; }
public IObservable<TableUpdate<ManualFillTuple, int>> on_manual_fill_tuples { get; }
public AdminModel admin_model { get; }
public MBOService mbo_service => _mbo_service;
public IAlgoStudioModel algo_model { get; }
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_security_working_orders(uint security) =>
_security_working_orders_demuxer.subscribe(security);
public IObservable<TableUpdate<OrderSecurity, uint>> on_security_working_orders_with_piq(uint security) =>
_security_working_orders_with_piq_demuxer.subscribe(security);
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
_security_orders_demuxer.subscribe(security);
public IObservable<TableUpdate<PositionUnitImmutable, int>> on_security_positions(uint security) =>
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_security_positions(uint security) =>
_security_positions_demuxer.subscribe(security);
#endregion
@@ -228,9 +260,9 @@ namespace mbts.spark {
// order already filled to completion
return false;
}
var size = new_size ?? (uint)order.working_quantity();
var size = new_size ?? order.quantity();
var price = new_price ?? order.price();
if (size == order.working_quantity() && price == order.price() && !new_algo.HasValue) {
if (size == order.quantity() && price == order.price() && !new_algo.HasValue) {
// no parameters would be changed by this update
return false;
}
@@ -259,6 +291,45 @@ namespace mbts.spark {
(uint? key, string error) _handle_error(Result<Response> result, string template) =>
_error.handle_error(result, template, r => r.ErrorCode, e => e == OrderError.Ok, r => new uint?(r.OrderKey));
#region Manual Fillls
public async Task<bool> create_manual_fills(IEnumerable<ManualFill> fills) {
return await create_manual_fills(_create_fill_batch(fills));
}
public async Task<bool> create_manual_fills(ManualFillBatchImmutable fills) {
var result = await _position_service.create_manual_fill_async(fills);
return _handle_error(result);
}
public async Task<bool> update_manual_fills(IEnumerable<ManualFill> fills) {
return await update_manual_fills(_create_fill_batch(fills));
}
public async Task<bool> update_manual_fills(ManualFillBatchImmutable fills) {
var result = await _position_service.update_manual_fill_async(fills);
return _handle_error(result);
}
public async Task<bool> delete_manual_fills(IEnumerable<ManualFill> fills) {
return await delete_manual_fills(_create_fill_batch(fills));
}
public async Task<bool> delete_manual_fills(ManualFillBatchImmutable fills) {
var result = await _position_service.remove_manual_fill_async(fills);
return _handle_error(result);
}
ManualFillBatchImmutable _create_fill_batch(IEnumerable<ManualFill> fills) {
var batch = new ManualFillBatch();
batch.ManualFill.AddRange(fills);
return new ManualFillBatchImmutable(batch);
}
bool _handle_error(Result<PositionServiceResponse> result) {
if (!result.success)
_error.report_error(result.error_message);
return result.value.Response.All(r => r.Ok);
}
#endregion // Manual Fills
#endregion
public void Dispose() =>
@@ -354,10 +425,10 @@ namespace mbts.spark {
public static IObservable<double> security_total_position(this OrdersModel orders_model, uint security_key, string account) =>
orders_model.on_security_positions(security_key)
.Select(t => {
.Select((t,k) => {
foreach (var kv in t.state) {
if (kv.Value.Account == account)
return kv.Value.total_pos();
if (kv.Value.Item2.Account == account)
return kv.Value.Item2.total_pos();
}
return double.NaN;
}).DistinctUntilChanged()
@@ -437,6 +508,18 @@ namespace mbts.spark {
return rod;
}).Select(r => r.last_update);
public static IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> subscribe_all_algos(this OrdersModel orders_model) =>
orders_model.studio_algos_with_meta.MergeTable(Observable.Return(
new TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
ImmutableDictionary<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>.Empty.AddRange(orders_model.factory.default_algos(OrderDataHelper.all_order_types).Select(kv => new KeyValuePair<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>(kv.Key, (kv.Value, null)))),
Array.Empty<(AlgoData,AlgoDefinitionMetadata?)>(),
Array.Empty<Change<(AlgoData,AlgoDefinitionMetadata?)>>(),
Array.Empty<(AlgoData,AlgoDefinitionMetadata?)>(),
d => d.algo.key
).as_snapshot()));
//public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_all_algos_with_meta(this OrdersModel orders_model) =>
public static async Task<OrderRecordImmutable?> order_from_key(this OrdersModel orders_model, uint order_key) {
(var tu, var success) = await orders_model.on_orders.FirstAsyncTask(t => t.state.ContainsKey(order_key), TimeSpan.FromSeconds(3));
if (success && tu.state.TryGetValue(order_key, out var rec)) {
+34 -3
View File
@@ -18,9 +18,9 @@
<RootNamespace>bts</RootNamespace>
<LangVersion>7.2</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<Version>1.0.0</Version>
</PropertyGroup>
<WarningsAsErrors />
<Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../spark_api/spark_api.csproj" />
<ProjectReference Include="../spark_core/spark_core.csproj" />
@@ -81,16 +81,47 @@
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorGridView\AlgoEditorGridView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderSettingsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderSettingsView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\MiniLadderView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\MiniOrderBook\MiniOrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\MiniOrderBook\OrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookGridView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookToolBar.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderParamsPanel.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="view\Controls\DIYGrid\Settings\SimpleGridSettingsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
</ItemGroup>
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
</Target>
</Project>
+8 -4
View File
@@ -1,15 +1,16 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.fills;
using bts.shell.manual_fills;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts;
using System;
using System.Collections.Generic;
using System.Windows.Input;
namespace bts.spark
{
namespace bts.spark {
static class SparkCommands {
// Market
public static PartialCommand<SecurityCriteria> ShowMarketView =>
@@ -27,9 +28,12 @@ namespace bts.spark
SparkCommand.create("Order Book", Icons.Book, SparkTags.ShowOrderBookTag);
// Fills
public static PartialCommand<FillsWindowFactory.StartupData> ShowFills(KeyGesture key_gesture = null) =>
public static PartialCommand<FillsSettings> ShowFills(KeyGesture key_gesture = null) =>
SparkCommand.create("Fills", Icons.Payment, SparkTags.ShowFillsTag, key_gesture);
public static PartialCommand<ManualFillsSettings> ShowManualFills(KeyGesture key_gesture = null) =>
SparkCommand.create("Manual Fills", Icons.DataEdit, SparkTags.ShowManualFillsTag, key_gesture);
public static PartialCommand<PAndLWindowFactory.PAndLStartupData> ShowPAndL =>
SparkCommand.create("P&L", Icons.Accounting, SparkTags.ShowPAndLTag);
+4 -1
View File
@@ -1,6 +1,8 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.fills;
using bts.shell.manual_fills;
using bts.spark;
using mbts.ui;
using System;
@@ -51,7 +53,8 @@ namespace mbts.spark
#endregion
#region Spark Window
public static ITag<FillsWindowFactory.StartupData> ShowFillsTag { get; } = new SparkTag<FillsWindowFactory.StartupData>(nameof(ShowFillsTag));
public static ITag<FillsSettings> ShowFillsTag { get; } = new SparkTag<FillsSettings>(nameof(ShowFillsTag));
public static ITag<ManualFillsSettings> ShowManualFillsTag { get; } = new SparkTag<ManualFillsSettings>(nameof(ShowManualFillsTag));
// TODO: maybe get rid of the singlular ShowLadderTag, and just use the plural ShowLaddersTag.
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
// For now, both tags go through the same logic in the LadderWindowFactory anyways.
@@ -188,7 +188,7 @@ namespace mbts.collections {
}
public IObservableCollection<T> items { get; }
public ObservableCollection<T> selected_items { get; } = new ObservableCollection<T>();
public ObservableCollectionEx<T> selected_items { get; } = new ObservableCollectionEx<T>();
public IEnumerable<TKey> selected_keys => selected_items.Select(_key_selector);
public bool try_select(TKey key) {
@@ -83,10 +83,10 @@ namespace mbts.ui.utility {
}
}
public static IDisposable connect<T, TResult>(ObservableCollection<T> source, ObservableCollection<TResult> target, Func<T, TResult> create, Action<TResult> on_removed = null) =>
public static IDisposable connect<T, TResult>(ObservableCollection<T> source, IList<TResult> target, Func<T, TResult> create, Action<TResult> on_removed = null) =>
connect<ObservableCollection<T>, T, TResult>(source, target, create, null);
public static IDisposable connect<TCollection, T, TResult>(TCollection source, ObservableCollection<TResult> target, Func<T, TResult> create, Action<TResult> on_removed = null)
public static IDisposable connect<TCollection, T, TResult>(TCollection source, IList<TResult> target, Func<T, TResult> create, Action<TResult> on_removed = null)
where TCollection : IReadOnlyList<T>, INotifyCollectionChanged {
Action reset_cb;
Action<IList, int> removed_cb,
@@ -105,7 +105,8 @@ namespace mbts.ui.utility {
if (i < 0) {
throw new InvalidOperationException("Source collection invoked a CollectionChanged 'Removed' event without a valid 'OldStartingIndex.'");
} else {
target.RemoveRange(i, old_items.Count);
for (int j = 0; j < old_items.Count; j++)
target.RemoveAt(i);
}
};
} else {
@@ -42,7 +42,7 @@ namespace mbts.collections
foreach (TModel m in e.OldItems) _remove(m);
break;
case NotifyCollectionChangedAction.Reset:
var old_views = Values.ToLArray();
var old_views = Values.ToArray();
Clear();
foreach (TValue v in old_views) ValueDestroyed?.Invoke(v);
foreach (TModel m in _models) _add(m);
@@ -9,7 +9,7 @@ namespace mbts.collections {
int count { get; }
}
class RefCountMap<TKey> : IEnumerable<IKeyRefCount<TKey>>, ICollection {
class RefCountMap<TKey> : IReadOnlyCollection<IKeyRefCount<TKey>>, ICollection {
readonly IEqualityComparer<TKey> _key_comparer;
readonly Dictionary<TKey, KeyRefCount> _dictionary;
@@ -28,7 +28,7 @@ namespace mbts.collections {
public object SyncRoot => null;
public bool IsSynchronized => false;
public int this[TKey key] => _dictionary[key].count;
public ICollection<TKey> keys => _dictionary.Keys;
public IReadOnlyCollection<TKey> keys => _dictionary.Keys;
public bool contains_key(TKey key) => _dictionary.ContainsKey(key);
public void CopyTo(Array array, int index) =>
@@ -241,4 +241,4 @@ namespace mbts.collections {
return ref_count_map.Count > count_before;
}
}
}
}
@@ -73,7 +73,7 @@ namespace mbts.ui {
public AsyncCommand(Func<Task> execute)
: base(_ => execute()) { }
public AsyncCommand(Func<Task> execute, Func<bool> can_execute)
public AsyncCommand(Func<Task> execute, Func<bool> can_execute, bool allow_concurrent_executions = false)
: base(_ => execute(), _ => can_execute()) { }
public AsyncCommand(Func<object, Task> execute)
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
@@ -51,5 +52,41 @@ namespace mbts.ui {
public ObservantAsyncCommand(Func<object, Task> execute, bool allow_concurrent_executions = false)
: base(execute, allow_concurrent_executions) { }
public static ICommand create(Func<Task> execute, out IObserver<bool> observer) {
var cmd = new ObservantAsyncCommand(execute);
observer = cmd;
return cmd;
}
public static ICommand create<T>(Func<T, Task> execute, Func<T, bool> get_can_execute, out IObserver<T> observer, bool allow_concurrent_executions = false) {
T param = default;
bool can_execute = false;
var cmd = new AsyncCommand(() => execute(param), () => can_execute, allow_concurrent_executions);
observer = new AnonymousObserver<T>(p => {
param = p;
if (can_execute != get_can_execute(param))
cmd.RaiseCanExecuteChanged();
});
return cmd;
}
public static ICommand create<T>(Func<T, Task> execute, Func<T, IObservable<bool>> get_can_execute_source, out IObserver<T> observer, bool allow_concurrent_executions = false) {
T param = default;
bool can_execute = false;
var cmd = new AsyncCommand(() => execute(param), () => can_execute, allow_concurrent_executions);
IDisposable can_execute_sub = null;
observer = new AnonymousObserver<T>(p => {
param = p;
Disposable.dispose(ref can_execute_sub);
can_execute_sub = get_can_execute_source(p).Subscribe(ce => {
if (can_execute != ce) {
can_execute = ce;
cmd.RaiseCanExecuteChanged();
}
});
}, () => Disposable.dispose(ref can_execute_sub));
return cmd;
}
}
}
@@ -33,6 +33,12 @@ namespace mbts.ui {
public ObservantCommand(Action execute) : base(_ => execute()) { }
public ObservantCommand(Action<object> execute) : base(execute) { }
public static ICommand create(Action execute, out IObserver<bool> observer) {
var cmd = new ObservantCommand(execute);
observer = cmd;
return cmd;
}
public static ICommand create<T>(Action<T> execute, Func<T, bool> get_can_execute, out IObserver<T> observer) {
T param = default;
bool can_execute = false;
@@ -222,6 +222,7 @@ namespace mbts.ui {
public static BTSColor straddle_theovsmarket_positive { get { return BTSCoreColors.green_light; } }
// Order Types
public static BTSColor glbx_iceberg_order => new BTSColor(85, 85, 196);
public static BTSColor tick_order { get { return BTSColors.purple.rgb_color(); } }
public static BTSColor lean_order { get { return new BTSColor(255, 170, 210); } }
public static BTSColor join_order { get { return new BTSColor(200, 171, 232); } }
@@ -54,19 +54,16 @@ namespace System.Reflection
/// <summary>
/// Gets the generic type arguments of the generic class, or interface implemented by the current Type.
/// </summary>
/// <param name="type">The type that implements or is derived from the generic type (imp_generic_def).</param>
/// <param name="imp_generic_def">The generic type definition which the current type implements (if an interface) or is derived from (if a class).</param>
/// <param name="type">The type that implements or is derived from the generic type (<paramref name="generic_def"/>).</param>
/// <param name="generic_def">The generic type definition which the current type implements (if an interface) or is derived from (if a class).</param>
/// <returns>The generic type arguments of the generic type definition implemented or derived from by the current type.</returns>
/// <exception cref="System.InvalidOperationException">
/// The parameter generic_def does not represent a generic type definition. That is, System.Type.IsGenericTypeDefinition returns false.
/// </exception>
public static Type[] GetGenericTypeArgs(Type type, Type generic_def) {
if (!generic_def.IsGenericTypeDefinition) throw MyExceptions.not_generic_type_definition(generic_def);
Type[] rv = null;
rv = generic_def.IsInterface ? _get_generic_interface_type_args(type, generic_def) : _get_generic_type_args(type, generic_def);
if (rv == null) throw MyExceptions.does_not_implement_or_derive_from(type, generic_def);
else return rv;
}
public static Type[] GetGenericTypeArgs(Type type, Type generic_def) =>
!generic_def.IsGenericTypeDefinition ? throw MyExceptions.not_generic_type_definition(generic_def) :
(generic_def.IsInterface ? _get_generic_interface_type_args(type, generic_def) : _get_generic_type_args(type, generic_def)) ??
throw MyExceptions.does_not_implement_or_derive_from(type, generic_def);
public static IDisposable detect_property_changes<T>(T obj, Action<string> on_prop_changed, params string[] props) where T : class {
var snapshot = props.ToDictionary(p => p, p => {
@@ -11,4 +11,6 @@ namespace bts.shell
{
Task<Size> MeasureContentSize();
}
interface ISlowMeasureContentSize : IMeasureContentSize { }
}
@@ -17,7 +17,7 @@ namespace mbts.spark {
_ladder = LadderSettings.Default,
_notification = NotificationSettings.Default,
_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings,
_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings,
_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings,
_time_and_sales_grid = bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultGridSettings,
_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings,
_order_book_grid = bts.shell.orderbook.OrderBookStartupData.DefaultGrid
@@ -48,7 +48,7 @@ namespace mbts.spark {
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings);
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings);
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
public bts.shell.orderbook.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.orderbook.OrderBookStartupData.Default.grid_settings_wrapper());
@@ -40,7 +40,7 @@ namespace bts.utils {
public event Action<T> common_value_changed;
public event Action<Plurality> plurality_changed;
public ICollection<T> distinct_values => _values.keys;
public IReadOnlyCollection<T> distinct_values => _values.keys;
public T common_value => _values.keys.SafeSingleOrDefault(_default_value);
public Plurality plurality {
get {
+11 -1
View File
@@ -1,4 +1,14 @@
namespace mbts.utils {
using System;
namespace mbts.utils {
delegate bool TryGetValue<T>(out T value);
delegate bool TryGetValue<TKey, TValue>(TKey key, out TValue value);
static class LogicalDelegates {
public static readonly Func<bool, bool, bool>
And = (a, b) => a && b,
Or = (a, b) => a || b,
XOr = (a, b) => a ? !b : b,
None = (a, b) => !a && !b;
}
}
@@ -0,0 +1,57 @@
using System.Runtime.InteropServices;
namespace System.IO {
class NativeFileHelper {
const uint
FILE_ATTRIBUTE_READONLY = 0x00000001,
FILE_ATTRIBUTE_HIDDEN = 0x00000002,
FILE_ATTRIBUTE_SYSTEM = 0x00000004,
FILE_ATTRIBUTE_DIRECTORY = 0x00000010,
FILE_ATTRIBUTE_ARCHIVE = 0x00000020,
FILE_ATTRIBUTE_DEVICE = 0x00000040,
FILE_ATTRIBUTE_NORMAL = 0x00000080,
FILE_ATTRIBUTE_TEMPORARY = 0x00000100,
FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200,
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400,
FILE_ATTRIBUTE_COMPRESSED = 0x00000800,
FILE_ATTRIBUTE_OFFLINE = 0x00001000,
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000,
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000,
FILE_ATTRIBUTE_VIRTUAL = 0x00010000,
SHGFI_ICON = 0x000000100, // get icon
SHGFI_DISPLAYNAME = 0x000000200, // get display name
SHGFI_TYPENAME = 0x000000400, // get type name
SHGFI_ATTRIBUTES = 0x000000800, // get attributes
SHGFI_ICONLOCATION = 0x000001000, // get icon location
SHGFI_EXETYPE = 0x000002000, // return exe type
SHGFI_SYSICONINDEX = 0x000004000, // get system icon index
SHGFI_LINKOVERLAY = 0x000008000, // put a link overlay on icon
SHGFI_SELECTED = 0x000010000, // show icon in selected state
SHGFI_ATTR_SPECIFIED = 0x000020000, // get only specified attributes
SHGFI_LARGEICON = 0x000000000, // get large icon
SHGFI_SMALLICON = 0x000000001, // get small icon
SHGFI_OPENICON = 0x000000002, // get open icon
SHGFI_SHELLICONSIZE = 0x000000004, // get shell size icon
SHGFI_PIDL = 0x000000008, // pszPath is a pidl
SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute
public static string get_file_type_description(string file_type) =>
SHGetFileInfo(file_type, FILE_ATTRIBUTE_NORMAL, out SHFILEINFO shfi, (uint)Marshal.SizeOf(typeof(SHFILEINFO)), SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME) != IntPtr.Zero ?
shfi.szTypeName : null;
[DllImport("shell32")]
static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbFileInfo, uint flags);
[StructLayout(LayoutKind.Sequential)]
struct SHFILEINFO {
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
}
}
@@ -0,0 +1,103 @@
using bts.api.core;
using mbts;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
namespace bts.utils {
static class ObjectSnapshot {
public static IObservable<TableUpdate<(string name, object current, object original), string>> property_table<T>(T instance, params string[] prop_names) where T : INotifyPropertyChanged =>
property_table(instance, Array.ConvertAll(prop_names, Impl<T>.type.GetProperty));
public static IObservable<TableUpdate<(string name, object current, object original), string>> property_table<T>(T instance, params PropertyInfo[] props) where T : INotifyPropertyChanged =>
Observable.Create<TableUpdate<(string name, object current, object original), string>>(o => {
var cache = props.ToDictionary(p => p.Name, p => new PropNode(p, p.GetValue(instance)));
var table = TableUpdate<(string name, object current, object original), string>.mk_empty(t => t.name).with_added(cache.Values.Select(pn => (pn.prop.Name, pn.current, pn.original)));
instance.PropertyChanged += on_prop_changed;
return Disposable.Create(() => instance.PropertyChanged -= on_prop_changed);
void on_prop_changed(object sender, PropertyChangedEventArgs e) {
if (cache.TryGetValue(e.PropertyName, out PropNode pn)) {
object oldval = pn.current;
pn.current = pn.prop.GetValue(instance);
table = table.with_changes(new[] { new Change<(string, object, object)>((pn.prop.Name, oldval, pn.original), (pn.prop.Name, pn.current, pn.original)) });
o.OnNext(table);
}
}
});
public static IObservable<bool> dirty_monitor<T>(T instance, params string[] prop_names) where T : INotifyPropertyChanged =>
dirty_monitor(instance, Array.ConvertAll(prop_names, Impl<T>.type.GetProperty));
public static IObservable<bool> dirty_monitor<T>(T instance, out IDisposable snapshot, params string[] prop_names) where T : INotifyPropertyChanged =>
dirty_monitor(instance, out snapshot, Array.ConvertAll(prop_names, Impl<T>.type.GetProperty));
public static IObservable<bool> dirty_monitor<T>(T instance, out IDisposable snapshot, params PropertyInfo[] props) where T : INotifyPropertyChanged {
var prop_map = props.ToDictionary(p => p.Name, p => new PropNode(p, p.GetValue(instance)));
snapshot = Disposable.Create(() => {
foreach (var pn in prop_map.Values)
pn.prop.SetValue(instance, pn.original);
});
return _dirty_monitor(instance, prop_map);
}
public static IObservable<bool> dirty_monitor<T>(T instance, PropertyInfo[] props) where T : INotifyPropertyChanged =>
_dirty_monitor(instance, props.ToDictionary(p => p.Name, p => new PropNode(p, p.GetValue(instance))));
static IObservable<bool> _dirty_monitor<T>(T instance, Dictionary<string, PropNode> prop_map) where T : INotifyPropertyChanged =>
Observable.Create<bool>(o => {
var dirty_props = new HashSet<string>();
instance.PropertyChanged += on_prop_changed;
return Disposable.Create(() => instance.PropertyChanged -= on_prop_changed);
void on_prop_changed(object sender, PropertyChangedEventArgs e) {
if (prop_map.TryGetValue(e.PropertyName, out var p)) {
p.current = p.prop.GetValue(instance);
if (Equals(p.current, p.original) == p.is_dirty) {
p.is_dirty = !p.is_dirty;
if (p.is_dirty) {
if (dirty_props.Add(p.prop.Name) && dirty_props.Count == 1)
o.OnNext(true);
} else if (dirty_props.Remove(p.prop.Name) && dirty_props.Count == 0)
o.OnNext(false);
}
}
}
});
public static IDisposable create<T>(T instance) =>
create(instance, Impl<T>.mutable_props);
public static IDisposable create<T>(T instance, PropertyInfo[] props) {
var snapshot = Array.ConvertAll(props, p => p.GetValue(instance));
return Disposable.Create(() => {
for (int i = 0; i < props.Length; i++)
props[i].SetValue(instance, snapshot[i]);
});
}
static class Impl<T> {
public static readonly Type type = typeof(T);
public static readonly PropertyInfo[] mutable_props =
type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod() != null)
.ToArray();
}
class PropNode {
public readonly PropertyInfo prop;
public PropNode(PropertyInfo prop, object init) {
this.prop = prop;
original = init;
current = init;
}
public bool is_dirty { get; set; }
public object original { get; set; }
public object current { get; set; }
}
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
@@ -62,30 +63,37 @@ namespace mbts.utils {
class PropertyChannel<T> : IPropertyChannel, IObservable<T> {
T _last_value;
long _id_counter;
readonly Func<T> _get;
readonly List<IObserver<T>> _observers = new List<IObserver<T>>();
ImmutableDictionary<long, IObserver<T>> _observers = ImmutableDictionary<long, IObserver<T>>.Empty;
public PropertyChannel(Func<T> get) =>
_last_value = (_get = get)();
public void on_change() {
_last_value = _get();
foreach (var o in _observers)
foreach (var o in _observers.Values)
o.OnNext(_last_value);
}
public IDisposable Subscribe(IObserver<T> observer) {
_observers.Add(observer);
long id = _id_counter++;
// assume there is no way we ran out of ids, because that would be nuts.
_observers = _observers.Add(id, observer);
observer.OnNext(_last_value);
return Disposable.Create(() => _observers.Remove(observer));
return Disposable.Create(() => _observers = _observers.Remove(id));
}
IObservable<object> IPropertyChannel.subscribe() =>
this.Select(v => (object)v);
public void Dispose() {
foreach (var o in _observers)
o.OnCompleted();
_observers.Clear();
ImmutableDictionary<long, IObserver<T>> local;
do {
local = _observers;
foreach (var o in local.Values)
o.OnCompleted();
} while (!ReferenceEquals(local, _observers)); // remember: observers is immutable
_observers = _observers.Clear();
}
}
}
Loaded 100 of 313 files, more files were not shown because too many files have changed in this diff. Show more