Compare commits

..
652 Commits
Author SHA1 Message Date
Pedro Pinto 719d460372 Merged with master 2019-06-19 16:59:50 -04:00
John Colvin 7b0549c4cb (forgot to add 'GlbxIceberg' case when fitting non-synth algos to synth ladders) 2019-06-19 15:47:16 -05:00
John Colvin 2ee0568159 ShellSettingsDialog - added 'not applicable to custom' tooltip for security picker menu item 2019-06-19 15:37:46 -05:00
John Colvin df907fcb96 SPARK-2521 / SPARK-2522 rename 'Local' -> 'Custom' (take 2) 2019-06-19 15:37:46 -05:00
John Colvin b945f9f090 Ladder Profles - synth ladders convert all non-synth default_algos to 'Spreader' 2019-06-19 15:37:46 -05:00
Jake Norbut 4cebe8b6be updating mini ob manual test 2019-06-19 15:25:46 -05:00
Jake Norbut c899904f3a Merged in miniob_deselect (pull request #272)
fixing unselect all
2019-06-19 20:20:50 +00:00
Jake Norbut 8e6ccd5b1b fixing unselect all 2019-06-19 15:16:42 -05:00
Pedro Pinto 007054f2b8 Updated release notes. 2019-06-19 15:45:54 -04:00
Jake Norbut 43822134e3 Merged in mini_ob_ireqeustrowselection (pull request #271)
miniob / DIYGrid - keep selected_rows in sync with row_vm.IsSelected

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-06-19 19:26:08 +00:00
John Colvin 68c1415cf8 Merged in SPARK-2489-ladder-settings (pull request #270)
SPARK-2489 ladder settings

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-06-19 19:16:25 +00:00
John Colvin d9b25fb778 threadstatic fields cannot be initialized inline 2019-06-19 14:10:50 -05:00
John Colvin 0a3e12e012 SPARK-2521 / SPARK-2522 rename 'Local' -> 'Custom' 2019-06-19 14:10:50 -05:00
John Colvin 8b33c7fb5c Ladder Profile's 'default_algo' is fit to the security type (allows profiles to be shared between outrights/synths) 2019-06-19 14:10:50 -05:00
Pedro Pinto 831b042c35 Updated release notes. 2019-06-19 15:04:29 -04:00
Pedro Pinto c9af55f59d Fixed clang build. 2019-06-19 14:56:07 -04:00
Pedro Pinto 056176ff47 Hide Cover in the lader when its done with its child. 2019-06-19 14:55:39 -04:00
Pedro Pinto 297a925f22 Fixed a bug in cover where cover would not terminate properly. 2019-06-19 14:54:16 -04:00
John Colvin 11ca6326f3 refactor 'AlgoID.from_order_def' -> 'AlgoID.from_custom_def' 2019-06-19 13:19:09 -05:00
Jake Norbut efa78ee5fc OrderBookVM now implements IRequestRowSelection and appropriately invokes it via all_rows_selected setter 2019-06-19 09:15:38 -05:00
Jake Norbut c52b98d17c DIYGrid now hooks up to request_selection event and passes that along to the underlying row presenter's SetSelectedItems 2019-06-19 09:13:55 -05:00
Jake Norbut 06506be398 Exposing a public SetSelectedItems that calls the protected ListBox equivalent 2019-06-19 09:11:34 -05:00
Jake Norbut d951942a95 Creating new interface to handle the select all checkboxbox in the mini ob the right way 2019-06-19 09:06:28 -05:00
Michael Harding 1f99cc68f8 Re: the 5:30 problem 2019-06-19 09:51:59 -04:00
Michael Harding d4f27a65f5 Harden system tests against 5:30PM 2019-06-19 09:37:15 -04:00
John Colvin 2b3901fdbe dispose LadderSettingsDialog viewmodel when window closes 2019-06-18 22:44:16 -05:00
John Colvin fa51d7f441 LadderSettingsView - abstract magic const out into StaticResource 2019-06-18 22:14:29 -05:00
John Colvin 2bc54b0dbb LadderSettingsView - 'Market Price/Size' tab Grid.IsSharedSizeScope=True 2019-06-18 19:52:38 -05:00
John Colvin 5b767482d7 LadderSettingsView - rearrange GroupBoxes in 'Left Panel' tab 2019-06-18 19:52:20 -05:00
John Colvin 8594708073 LadderVM.get_save_state - null check for order_properties_vm 2019-06-18 18:47:24 -05:00
John Colvin ac4cdc0e2d Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-18 18:09:08 -05:00
John Colvin c66dfdaf27 LadderSettingsView - add 'Auto Clear' value checkbox, minor style tweaks 2019-06-18 18:00:18 -05:00
Pedro Pinto dcd42367e1 Updated release notes. 2019-06-18 17:14:55 -04:00
Jake Norbut 1fd4212a4e Merged in mini_ob_perf (pull request #266)
mini-ob - delaying popup Child control creation until approparite button is actually clicked

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 21:12:13 +00:00
Pedro Pinto aa5b88e1a2 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-06-18 17:07:16 -04:00
Pedro Pinto 3f86968937 Rejected stop and stop limit orders until we can fix them. 2019-06-18 17:06:55 -04:00
Kevin Darby 0385376d04 Merged in fix_auth_tests (pull request #267)
fix auth tests

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 20:55:38 +00:00
Kevin Darby 914ec09b4f fix auth tests 2019-06-18 16:44:54 -04:00
Kevin Darby fd106559da Merged in key_codes (pull request #253)
RTM: Key codes
2019-06-18 20:16:31 +00:00
Pedro Pinto a1270fb928 Updated release notes. 2019-06-18 16:15:49 -04:00
John Colvin 1f8b0bf1e6 in profile settings dialog, revert restores the 'persisted' profile (not necessarily the same value from when the dialog was opened) 2019-06-18 15:03:55 -05:00
John Colvin 292acd64e6 Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-18 14:44:26 -05:00
John Colvin 453f70259a AlgoID.from_algo_key was losing dynamic type name 2019-06-18 14:24:44 -05:00
John Colvin 18a80516d5 SPARK-2489 / SPARK-2499 eliminate 'window specific settings,' treat all props like regular settings 2019-06-18 14:24:43 -05:00
Kevin Darby 1ebd0e8d62 Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-18 15:08:13 -04:00
Kevin Darby c09ef31061 Added a warning message and facility to allow concurrent logins in a keycode 2019-06-18 15:07:08 -04:00
John Colvin 613ba8004d LadderView - make Min/MaxZoomFactor consts (were StaticResources) 2019-06-18 13:55:08 -05:00
John Colvin a1ffc24ed9 LadderVM - size_calculator uses shared settings subscription 2019-06-18 13:55:07 -05:00
John Colvin c40055dd2f SPARK-2493 Add multiple profiles for ladder settings 2019-06-18 13:55:07 -05:00
Jake Norbut 163b77ff84 mini-ob - delaying popup Child control creation until approparite button is actually clicked 2019-06-18 13:29:49 -05:00
Pedro Pinto 1b1129320a Merged in SP-22-expose-hidden-badge-facility-in-ap (pull request #265)
RTM. SP-22 expose hidden badge facility in ap

Approved-by: Taha Afzal <taha.afzal@bluetradesys.com>
2019-06-18 17:39:21 +00:00
Kevin Darby 589476eb02 Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-18 13:34:06 -04:00
Pedro Pinto fd0e1dfb0e Updated release notes. 2019-06-18 12:01:09 -04:00
Pedro Pinto e95eb4a749 Merged with master 2019-06-18 11:59:30 -04:00
Michael Harding 8fb13bd3c3 Merged in spreader_lean_qty (pull request #264)
Spreader lean qty
2019-06-18 14:17:21 +00:00
Jake Norbut 9cfa229b04 Merged in SPARK-2519 (pull request #262)
RTM - mini ob - select/unselect all checkbox in header

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 14:10:06 +00:00
Jake Norbut 72e7901145 Merged in SPARK-2509 (pull request #260)
RTM - SPARK-2509 - distinguish buy and sell better in fills window

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-18 14:09:25 +00:00
Michael Harding c2788e4d61 Harden wait_for_orders_service_ready 2019-06-18 09:58:54 -04:00
Michael Harding 0b5d2bd4dc Fix null security Id 2019-06-18 09:58:08 -04:00
Jake Norbut 98006a07e8 PR - refactoring - eliminating init_brush in favor of implicit BTSColor to frozen Brush operator 2019-06-18 08:04:15 -05:00
Jake Norbut 420273b861 Merge branch 'SPARK-2509' of bitbucket.org:bluetradesys/spark into SPARK-2509 2019-06-18 07:53:34 -05:00
Jake Norbut 09ed017bf2 BTSColors - no need to explicity freeze when we have an implicit operator that can do so, thx Eric 2019-06-18 07:52:55 -05:00
Pedro Pinto 8116700737 Updated release notes. 2019-06-17 14:31:39 -04:00
Pedro Pinto f902f163de Merge branch 'master' into SPARK-2509 2019-06-17 14:30:33 -04:00
Pedro Pinto d06bef463b Merged SP-42-manual-fills-should-not-go-into-so into master 2019-06-17 14:07:02 -04:00
Pedro Pinto 38db7b28e0 Updated manual test. 2019-06-17 14:05:11 -04:00
John Colvin 485991b046 LadderSettingsVM - assume algos in settings proto exist (avoid 'deleting' favs when create is called before first algo-TableUpdate arrives) 2019-06-17 12:32:57 -05:00
Michael Harding 63cd653ffd Merged master into spreader_lean_qty 2019-06-17 13:10:59 -04:00
Michael Harding cb0ec5f3b9 match sparkdata discovery 2019-06-17 13:08:59 -04:00
Taha Afzal 64e7921280 Lean Algo hides its badge once it has fired 2019-06-17 13:07:16 -04:00
Michael Harding e5d31d3c89 match sparkdata discovery, remove use_onixs: false 2019-06-17 12:58:21 -04:00
Jake Norbut 219da9b22a mini ob - select/unselect all checkbox in header 2019-06-17 11:50:43 -05:00
Pedro Pinto b3bff8ecc3 Updated release notes. 2019-06-17 11:02:01 -04:00
Pedro Pinto c1fb53ff06 Merge branch 'master' into SP-42-manual-fills-should-not-go-into-so 2019-06-17 11:01:11 -04:00
Michael Harding 220af155a9 Merged in Documentation (pull request #259)
Move system requirements to Docs
2019-06-17 14:58:07 +00:00
Pedro Pinto e7286e305d Updated release notes. 2019-06-17 10:57:33 -04:00
Pedro Pinto 770b857f90 Merge branch 'master' into Documentation 2019-06-17 10:56:35 -04:00
Jake Norbut 4b9e4b25c7 Merged in miniob_vnext (pull request #258)
Mini OrderBook client demo feedback
2019-06-17 14:53:50 +00:00
Pedro Pinto 0f87973ee2 Updated release notes. 2019-06-17 10:53:22 -04:00
Pedro Pinto b6790aa7f5 Merge branch 'master' into miniob_vnext 2019-06-17 10:44:57 -04:00
Pedro Pinto 6adbfda6c4 Merged in SP-40-cover-should-be-more-decoupled-fro (pull request #261)
RTM. SP-40 cover should be more decoupled fro

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-17 14:42:51 +00:00
Pedro Pinto fef5b4edaa Updated release notes. 2019-06-17 10:42:05 -04:00
Pedro Pinto e8cccdb1c0 Changed manual fills to show in day rather than SOD. 2019-06-17 10:15:52 -04:00
Pedro Pinto 19dc7f8915 Fixed a bug in visibiliy control API and changed Join to hide itself 2019-06-17 09:59:03 -04:00
Pedro Pinto 69f61757c4 Added API support for ladder visibility control. 2019-06-17 09:47:02 -04:00
John Colvin 666765ffbb LadderSettingsVM implements IObservable<LadderSettings> 2019-06-16 20:43:57 -05:00
John Colvin 82a9b4521a DisposableViewModelBase - in subscribe(), use the _property_changed_demuxer via the property (which lazily inits the backing) 2019-06-16 20:33:35 -05:00
John Colvin 7c329ceebe DisposableViewModelBase - initialize '_observable_properties' lazily in property getter 2019-06-16 20:33:35 -05:00
Pedro Pinto 66d05c63af Made cover cancel behaviour parameterizable.
This implements the server side only, the UI does not expose this
configuration so effectively the behaviour right now is
hard-coded until we get around to adding the UI.
2019-06-14 14:25:04 -04:00
Pedro Pinto bc3ab563bf Changed Cover to detach its hedges when canceled. 2019-06-14 10:58:33 -04:00
Pedro Pinto ed23bc7e2b Enhanced the API to allow algos to implement custom cancel handling 2019-06-14 10:50:34 -04:00
Pedro Pinto 3dda3e7f75 Allow cover order to continue even if hedgding child is canceled. 2019-06-14 10:44:38 -04:00
Pedro Pinto ff461d42b8 Merge branch 'master' into prod 2019-06-14 10:13:40 -04:00
Jake Norbut d678d202c1 PR rename on_row_updated 2019-06-14 09:04:09 -05:00
Jake Norbut ad0b1756b1 PR rename on_row_unselected 2019-06-14 09:03:07 -05:00
Jake Norbut f34f718c72 PR rename on_row_selected 2019-06-14 09:02:24 -05:00
Jake Norbut 213fb1de59 PR refactoring cancel 2019-06-14 09:00:30 -05:00
Jake Norbut e8d799a0f6 PR refactoring detach 2019-06-14 09:00:17 -05:00
John Colvin bef46390be ContractPickerPopup - remove automatic Header binding, so callers may override it 2019-06-13 16:54:42 -05:00
Jake Norbut 4edd804b3c forgot to remove this when I took the resource out 2019-06-13 16:15:22 -05:00
Jake Norbut 86733747f1 getting rid of the ListBoxItem template and just going with the converter 2019-06-13 15:58:40 -05:00
Jake Norbut 5202300fb1 ActualWidth for total visible width down in the HighPerformanceRow per John 2019-06-13 15:58:12 -05:00
Jake Norbut 7aae61decd Cleanup related to new RenderTextColumn func; deleting classes, etc 2019-06-13 14:14:45 -05:00
Jake Norbut 64f78e6077 RenderColumn now accepct a Func for background brush 2019-06-13 14:11:53 -05:00
Jake Norbut 318ea5909c adding new brushes to be references from xaml; misc helper function 2019-06-13 13:32:29 -05:00
Jake Norbut 83190b437a defining a new RowStyle for the fills grid that uses a converter based on side 2019-06-13 13:31:33 -05:00
Michael Harding 5a54b37af7 Move system requirements to Docs 2019-06-13 13:07:42 -04:00
Michael Harding 12dd3909bf Fix documentation deploy 2019-06-13 12:49:13 -04:00
Michael Harding 64fd8addb2 Update Lean qty factor shell script 2019-06-13 09:38:11 -04:00
John Colvin 5ddb0f92ea ObservantCommand impl was not updating 'CanExecute' 2019-06-13 00:26:05 -05:00
John Colvin 69f3f1f467 normalize line endings in Converters.xaml (why does it feel like i've done this before?) 2019-06-12 23:47:26 -05:00
John Colvin be42f9e583 general purpose ToolBar view 2019-06-12 23:46:50 -05:00
John Colvin 6d069f5174 added ListExtensions.WrapSkip() (returns an IList impl that skips the first n items in the source list) 2019-06-12 23:46:14 -05:00
John Colvin 7a31adcc1e added DictionaryHelper.key_comparer<TKey, TValue>() 2019-06-12 23:45:52 -05:00
John Colvin 512175966d KeyedStyleSelector._try_get_item_key returns false if key is found but null 2019-06-12 23:45:19 -05:00
John Colvin 67de7c5a33 clean up Spark.csproj 2019-06-12 23:44:52 -05:00
John Colvin 8add53a559 move/rename 'WindowSettingsDialogVM' -> 'ShellSettingsDialogVM' 2019-06-12 23:44:18 -05:00
John Colvin 3e7e572763 move/rename 'SimpleGridSettingsDialog' -> 'ShellSettingsDialog' 2019-06-12 23:43:43 -05:00
Jake Norbut 0d020e9821 updating manual test 2019-06-12 14:48:36 -05:00
Michael Harding 4490ac303e SPARK-2438: Lean Qty test 2019-06-12 15:41:16 -04:00
Jake Norbut fb1d70b08c Merge branch 'master' of bitbucket.org:bluetradesys/spark into miniob_vnext 2019-06-12 14:02:56 -05:00
Jake Norbut c2f6b01567 debugging comment 2019-06-12 10:54:24 -05:00
Jake Norbut 4a758c7554 misc comment cleanup, line endings or something 2019-06-12 10:54:02 -05:00
Jake Norbut 5c79a08f0e popup close event no longer messes with selected_rows 2019-06-12 10:53:15 -05:00
Jake Norbut 4b7adbe0c9 adding tooltip to update button 2019-06-12 09:59:48 -05:00
Jake Norbut 164c88fa60 popup debug comments 2019-06-12 09:56:03 -05:00
Jake Norbut b57d6cce85 select/unselect me only needs to manipulate row.is_selected now 2019-06-12 09:52:22 -05:00
Jake Norbut e47706eb1b guaranteeing on_updating is hooked up to only a single callback; this was a major pain to debug, thx john 2019-06-12 09:49:00 -05:00
Jake Norbut c2e91b03f6 Adding similar IsSelected setter to main OB just in case ... 2019-06-12 09:40:35 -05:00
Jake Norbut 114c9f3c27 Adding resurrected check column back to mini ob, bound to is_selected 2019-06-12 08:28:56 -05:00
Jake Norbut 40805be11e Bringing back the check column in case of SelectionHighlight shenanigans 2019-06-12 08:27:39 -05:00
Jake Norbut b21cd840c2 The first time I met the blues, I was losing my SelectionHighlight; too beat down to remove this Setter that was added over many hours of debugging 2019-06-12 08:23:55 -05:00
Jake Norbut 4854a06eda fix SelectionHighlight databinding expression in mini ob 2019-06-12 08:12:48 -05:00
Jake Norbut 44e7cfb4a5 setting is_selected and clearing selected_rows when update popup closes 2019-06-11 14:06:08 -05:00
Jake Norbut a7da930e69 synchronize selected_rows with _un/select_me() 2019-06-11 14:02:23 -05:00
Jake Norbut d64df27188 don't change is_selected state here b/c it leads to discrepancies when attaching/updating 2019-06-11 12:27:51 -05:00
Jake Norbut 93534eb801 changing row selection while in change breaks the main ob 2019-06-11 10:57:07 -05:00
John Colvin 0af5b78318 Merged in SPARK-2489-ladder-settings (pull request #257)
RTM SPARK-2489 ladder settings

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-10 21:31:24 +00:00
Pedro Pinto c652032ddf Updated release notes. 2019-06-10 17:27:46 -04:00
John Colvin 7bd868b753 Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-10 15:23:58 -05:00
John Colvin 1bc4510862 LadderSettingsView - touch up 'Market Price And Size Display' tab 2019-06-10 15:12:38 -05:00
Kevin Darby e452413b92 Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-10 16:04:43 -04:00
Kevin Darby 62c7886807 used os::host_ips() 2019-06-10 15:57:53 -04:00
John Colvin b1fb964648 Merged in SPARK-2508-fills-symbol-column (pull request #256)
RTM SPARK-2508 fills symbol column
2019-06-10 19:45:37 +00:00
Pedro Pinto ee19f7c7da Updated release notes. 2019-06-10 15:44:00 -04:00
Jake Norbut a0bf7779f5 mini ob - changes from John; closer to working but not yet, likely related to mark_commited/revert not getting wired up/called 2019-06-10 14:35:06 -05:00
John Colvin cd41632b53 LadderSettingsView - touch up 'Order Quantities' tab 2019-06-10 14:27:54 -05:00
Kevin Darby 95a5791867 Jake was worried about using the same keycode for multiple servers. To combat this I look at ifconfig for
an ip to appear on the list that matches what is optionally encoded in the keycode
2019-06-10 14:51:07 -04:00
John Colvin ecd384501e align LadderSettingsView (content in settings dialog) with hamburger button 2019-06-10 12:22:05 -05:00
John Colvin 0483460ac9 Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-10 12:00:08 -05:00
Michael Harding 666bd07411 Merge branch 'master' into spreader_lean_qty 2019-06-10 12:56:44 -04:00
John Colvin 0bc24fa7b7 SPARK-2508 Fills - add 'Symbol' column 2019-06-10 11:48:09 -05:00
Kevin Darby af365177d0 PR feedback and a manual test 2019-06-10 12:46:37 -04:00
John Colvin ac60a1447e stack FillsView.Columns enum values vertically (easier to merge column changes) 2019-06-10 11:32:35 -05:00
Kevin Darby e8b5d4f8fd Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-10 12:00:06 -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
Kevin Darby cb157dbce1 Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-07 18:15:26 -04: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
Jake Norbut 370b4bcde9 coording root row with selected params mon 2019-06-07 16:19:16 -05:00
Jake Norbut 6756b23104 miniob - attach and update select the row if they're not already selected 2019-06-07 16:17:42 -05:00
Jake Norbut c5f7ee0ed3 reassigning the miniob update button to the update_command property so select the row if it's not already selected 2019-06-07 16:16:27 -05: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
Jake Norbut 16eeddec7d OperableHierarchicalCollectionModel - fixing get_parent 2019-06-07 16:10:17 -05: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
John Colvin 445bbb10f5 LadderSettingsView - normalize spacing 2019-06-07 13:42:27 -05:00
John Colvin 70c006f797 SPARK-2489 / SPARK-2502 move 'Alternate Order Qty' into 'Order Quantities' tab 2019-06-07 13:42:27 -05: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
Jake Norbut 307d3d9466 eliminating new mini ob checkbox col and just operating based off of the standard diygrid selected_rows 2019-06-07 10:33:26 -05:00
John Colvin 3a5810d8ac LadderGridHost.OnMouseDown impl should return after handling middle click 2019-06-07 10:12:10 -05:00
Jake Norbut 9ba2ba1d75 refactoring modify_selected_orders to accept a IEnumerable>OrderBookRowVM>; creating a new command for minob that iterates over checked orders to modify 2019-06-07 09:25:25 -05:00
John Colvin 29c772cbb6 SPARK-2489 / SPARK-2501 rename 'Add Size' -> 'Add Quantity' 2019-06-06 23:41:45 -05:00
John Colvin 0b03180c4d SPARK-2489 / SPARK-2500 rename 'Size Presets' -> 'Order Quantities' 2019-06-06 23:39:02 -05:00
John Colvin 1518906b7f SPARK-2489 / SPARK-2498 remove 'Order Badge Style' option 2019-06-06 23:35:45 -05:00
John Colvin 31fe708907 touch up LadderSettingsView 2019-06-06 23:35:37 -05:00
John Colvin 4b2f39c536 LadderSettingsView - drop 'Show/Hide' from 'Advanced' hyperlinks 2019-06-06 22:42:55 -05:00
John Colvin 040960d33b SPARK-2489 / SPARK-2495 merge 'Volume Display' tab into the 'Ladder Columns' tab 2019-06-06 19:31:36 -05:00
Taha Afzal a4d63a1421 this should fix the confusion 2019-06-06 19:13:46 -04:00
Kevin Darby a28d5883f7 better to zero out the market data if not entitled so they don't trade off of it 2019-06-06 17:19:35 -04:00
Kevin Darby 8a1d49733e keycodes work, need some documentation 2019-06-06 16:40:32 -04:00
Kevin Darby 0da6b2cc3e Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-06 16:40:05 -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
Jake Norbut 3f245cd886 SPARK-2497 - mini ob mass detach 2019-06-06 14:20:00 -05:00
Jake Norbut 570f85377a part of SPARK-2485 - miniob - update buttons not visible unless leaf under control of an algo 2019-06-06 14:00:06 -05:00
Kevin Darby 8afb35e564 don't compile crypto on win 2019-06-06 14:54:33 -04:00
Jake Norbut a38a6057e0 part of SPARK-2485 - mini ob only shows exchange orders + spreader rows 2019-06-06 13:32:48 -05:00
John Colvin dcd8984b33 SPARK-2489 / SPARK-2494 rename 'Column Settings' tab 'Ladder Columns' 2019-06-06 12:35:09 -05:00
Kevin Darby 6647b28a9f keycode generation, encrypt, decrypt works 2019-06-06 13:30:54 -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
Jake Norbut fb314f0eb1 SPARK-2491 - mass cancel 2019-06-05 14:01:47 -05: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
Kevin Darby 8a965d41ff keycodes. boost stil can't handle dates 2019-05-30 18:24:24 -04: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
Michael Harding 7d75d87e6b Merge branch 'master' into key_codes 2019-05-28 09:45:17 -04:00
Michael Harding f13a3325bc Add Crypto 2019-05-28 09:44:29 -04: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
388 changed files with 17534 additions and 4694 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
},
]
}
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+126 -1
View File
@@ -4,6 +4,131 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.3.0
### User Visible Changes
- Introduced multiple settings profiles for the ladder
- This allows multiple standard configuration for the ladder, optionally with security specific variations
- Made Cover order more independent from its hedging chidren:
- Cover continues to operate even if an hedging child is canceled
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
- Changed manual fills to appear in day PnL rather than in the SOD records
- Add buy/sell coloring to fill rows in fills window
- Streamlined the mini order book:
- Display of the mini order book popup is quicker
- Only exchange orders are displayed
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
- A checkbox column has been added to make clearer which rows are selected
### API Changes
- Enhanced the API to allow algos to implement custom cancel handling
- Allowed algos to control their visibility in the ladder through the API
### Internal Changes
- Added system requirements to Spark documentation
- Implemented keycodes
- Temporarily rejected stop and stop limit orders, pending bug fixes
## 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
- Added security symbol column to fills window
- Re-organized ladder settings dialog to make it clearer and easier to use
## 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
@@ -251,7 +376,7 @@ insert_anchor_links = "right"
- This is configurable from a ladder's settings as well
### Customized settings in all windows
- All windows now contain a settings page where grid settings (font color, size, etc) are applied
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
- These settings can be saved on a per-window basis or as a Default for newly-launched windows␍
## 1.3.1 Through 1.3.4
@@ -0,0 +1,55 @@
+++
title = "System Requirements"
weight = 4
sort_by = "weight"
insert_anchor_links = "right"
+++
### Minimum Workstation
* New XPS Tower
* 8th Generation Intel® Core™ i7-8700 6-Core Processor (12M Cache, up to 4.6 GHz)
* 16GB, 2666MHz, DDR4 up to 64GB
* Windows 10 Professional 64-bit
* NVIDIA® GeForce® GTX 1070 with 8GB GDDR5 Graphics Memory
* 1 x 2.5" 256GB SATA Solid State Drive
### Recommended Workstation
* Similar or better than:
* Dell Precision Tower 5820
* Intel Xeon W-2145 3.7GHz, 4.5GHz Turbo, 8C, 11M Cache, HT, (140W) DDR4-2666
* Windows 10 Professional 64-bit
* NVIDIA® Quadro® P1000, 4GB, 4 mDP
* 16GB (4x4GB) 2133MHz DDR4 RDIMM ECC
* 2 x 2.5" 256GB SATA Solid State Drive running RAID1
### BTS Recommended Server Specifications
* Dell R640
* 2x Intel Xeon Gold 6148 2.40GHz 20-Core Processors. For better performance BTS recommends Intel® Xeon® Platinum 8268 2.9G, 24 Core CPU
* 192GB RAM 2666MHz DDR4
* 3x 480GB SSD RAID 5 HBA Minicard
* iDRAC 9 Enterprise
* Embedded Broadcom NIC
* Solarflare SFN8522-R2
* Redundant Power Supply
### BTS Minimum Server Specifications
* Dell R620
* Chassis with up to 8, 2.5” Hard Drives, up to 2 PCIe Slots (With Optional Riser)
* 2 x Intel® E5-2670 (16 total cores/ 32 total threads)
* 64GB total memory
* RAID 1 for H330/H730/H730P
* PERC H730 RAID Controller, 1GB NV Cache
* 3 x 300GB 10K RPM SATA 6Gbps 2.5in Hot-plug Hard Drive (RAID 5)
* iDRAC8 Enterprise, integrated Dell Remote Access Controller, Enterprise (varies by hosting company)
* Solarflare Ultra SFN7122F Dual-Port 10GbE (10 GB connectivity requirements, copper vs optical, varies by hosting company).
* Dual, Hot-plug, Redundant Power Supply (1+1), 750W
* Performance BIOS Setting
* ReadyRails Sliding Rails Without Cable Management Arm (varies by hosting company)
### Edge Tablet Requirements
* Similar or better than:
* Microsoft Surface Pro 4 Or Motion R12
* Intel i7
* 8 GB DDR4 RAM
-5
View File
@@ -1,7 +1,6 @@
#/bin/bash
#python -m mkdocs build -d site
cp -r ../../src/sdk/doc/* ./content/bts_spark_sdk/Server/
./run_zola.sh build
scp -r ./public bts@bluetradesys.com:/home/bts/doc/
@@ -14,7 +13,3 @@ sudo mv ./public/* /var/www/documentation/
EOF
rm -r public
cd ./content/bts_spark_sdk/server/
find . -not -name '_index.md' -delete
cd ../..
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Windows;
using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
[Export(typeof(IShellDaemon))]
@@ -48,14 +49,14 @@ namespace bts.shell {
void _show_order_ticket(SecurityMD smd) =>
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd,
Observable.Return(_model.factory.default_ladder_settings(smd.security)))
Observable.Return(LadderSettings.Default))
});
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
Observable.Return(_model.factory.default_ladder_settings(smd.security).calculator_buttons))
Observable.Return(LadderSettings.Default.calculator_buttons))
});
}
@@ -66,7 +67,7 @@ namespace bts.shell {
_show_order_window(new OrderModificationDialog {
DataContext = new ExistingOrderTicketVM(_model,
Observable.Return((md,ori.order)),
Observable.Return(_model.factory.default_ladder_settings(ori.sec.Value).calculator_buttons)
Observable.Return(LadderSettings.Default.calculator_buttons)
)
});
}
@@ -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();
}
+27 -4
View File
@@ -22,12 +22,15 @@ namespace mbts.ui {
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
AttachAlgosKey = nameof(AttachAlgosKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
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 +40,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 +124,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 +148,14 @@ 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 CopyWF => create_icon(CopyWFGeometryKey);
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 +173,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 +202,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);
+21 -50
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,13 @@
</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>
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
</sys:String>
<!--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 +115,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 +301,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,27 +56,95 @@ 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(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.market_view_grid,
UserSettings.FileExtensions.GRID,
@@ -82,13 +156,32 @@ namespace mbts.spark {
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
var dialog = new ShellSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
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() {
@@ -49,13 +49,8 @@ namespace mbts.spark {
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new TimeAndSalesColumn(
col_key
, (r) => _get_aggressor_brush(r)
, filter
, filter_template
)
{ Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
@@ -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;
@@ -69,7 +70,7 @@ namespace mbts.spark {
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(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
@@ -81,7 +82,7 @@ namespace mbts.spark {
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
var dialog = new ShellSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -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,13 +68,14 @@ 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(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
), 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 ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.synthetic_management_grid,
UserSettings.FileExtensions.GRID,
@@ -86,7 +87,7 @@ namespace mbts.spark {
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -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,91 @@
using bts.api.orderman;
using bts.persist.algo;
using bts.utils;
using mbts.ui.utility;
using ProtoBuf;
using System;
using System.Linq;
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 static readonly AlgoID[] exchange_algo_keys = utilities.OrderDataHelper.outright_order_types
.Concat(new CustomOrderDefinition.CustomOneofCase[] { CustomOrderDefinition.CustomOneofCase.Spreader })
.Select(cooc => new AlgoID(cooc))
.ToArray();
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_custom_def(CustomOrderDefinitionImmutable custom_def) {
switch (custom_def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(custom_def.Composite.AlgoTemplateKey);
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(custom_def.Dynamic.DynamicOrderType);
default: return new AlgoID(custom_def.CustomCase);
}
}
public static AlgoID from_algo_key(AlgoKey key, IMaskIndexInt32<string> dynamic_type_index) {
switch (key.mode) {
case AlgoKey.Mode.Factory:
return AlgoStructureSerializer.to_algo_id(key.factory.structure, dynamic_type_index);
case AlgoKey.Mode.Persisted:
return new AlgoID(key.persisted.id_uint);
}
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
}
public static AlgoKey to_algo_key(AlgoID id, IMaskIndexInt32<string> dynamic_type_index) {
if (id._factory_key.HasValue)
return new AlgoKey(id._factory_key.Value);
if (id._persisted_key.HasValue)
return new AlgoKey(new AlgoPersistedKey(id._persisted_key.Value));
if (id._dynamic_key != null)
return new AlgoKey(new AlgoNameStructureKey(id._dynamic_key, AlgoStructureSerializer.structure_key(dynamic_type_index.to_int(id._dynamic_key))));
return default;
}
public bool Equals(AlgoID other) =>
_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 +165,4 @@ namespace mbts.spark {
InvalidOperationException _unexpected_mode_ex() => new InvalidOperationException($"Unexpected Mode '{mode}.'");
}
}
}
@@ -70,6 +70,12 @@ 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(int dynamic_type) =>
new AlgoStructureKey(new[] { dynamic_type | _dynamic_mask });
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
@@ -100,6 +106,13 @@ namespace mbts.spark {
return mdef;
}
public static AlgoID to_algo_id(AlgoStructureKey key, IMaskIndexInt32<string> dynamic_type_index) {
if (key.structure.Count == 0)
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
int i = key.structure[0];
return BitHelper.remove_flags(ref i, _dynamic_mask) ? new AlgoID(dynamic_type_index.from_int(i)) : new AlgoID((CustomOrderDefinition.CustomOneofCase)i);
}
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
if (structure == null || structure.Length == 0)
structure = new int[1];
@@ -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>(
@@ -64,6 +61,14 @@ namespace mbts.spark {
equality_comparer: SecurityDataHelper.EqualityComparer.IDComparer,
comparer: Comparer<SecurityImmutable>.Default
), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
// symbol
add_col(Columns.Symbol, DefaultColumnHeaders.Symbol, r => r.security.Symbol,
DiscreteValuesFilterVM.Create<FillTuple, string>(
co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
ft => ft.security?.Symbol ?? string.Empty,
nameof(OrderBookRowVM.symbol)),
DataGridFilterKnownTemplates.DiscreteValues,
DefaultColumnWidths.Symbol);
// order type
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
add_col_expl(Columns.OrderType,
@@ -86,29 +91,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 +121,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 +140,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 +162,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) {
@@ -190,6 +198,7 @@ namespace mbts.spark {
case Columns.SecurityKey: return wrap(ft => ft.fill.SecurityKey);
case Columns.Side: return wrap_default(ft => ft.fill.Side);
case Columns.Size: return wrap(ft => ft.fill.LastShares);
case Columns.Symbol: return wrap(ft => ft.security?.Symbol ?? string.Empty);
case Columns.TimeInForce: return wrap_default(ft => ft.order?.time_in_force() ?? TimeInForce.Undefined);
case Columns.User: return wrap(ft => ft.order?.user() ?? string.Empty);
default: goto case Columns.FillTime;
@@ -212,8 +221,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 +231,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 +241,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;
@@ -6,6 +6,10 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:u="clr-namespace:mbts.ui"
xmlns:api="clr-namespace:bts.api;assembly=spark_api"
xmlns:bts="clr-namespace:mbts.ui.controls"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
@@ -15,6 +19,10 @@
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
<cv:ValueMap x:Key="SideToBrushConverter" Default="{u:Brush Transparent}">
<cv:KeyValue Key="{u:Enum Buy, Type=api:Side}" Value="{x:Static semi_transparent_bid_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
<cv:KeyValue Key="{u:Enum Sell, Type=api:Side}" Value="{x:Static semi_transparent_ask_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
</cv:ValueMap>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
@@ -41,10 +49,10 @@
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
/>
</c:DIYGrid.CommandBindings>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:FillsRowView/>
</DataTemplate>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:FillsRowView Background="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
@@ -7,7 +7,23 @@ namespace mbts.ui.spark {
/// Interaction logic for FillsView.xaml
/// </summary>
partial class FillsView : UserControl {
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKind, FillKey, OrderKey, SecurityKey }
public enum Columns {
FillTime,
User,
Account,
Exchange,
Security,
Symbol,
TimeInForce,
OrderType,
Side,
Price,
Size,
FillKind,
FillKey,
OrderKey,
SecurityKey
}
public FillsView() =>
InitializeComponent();
@@ -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,14 +53,14 @@ 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 };
_commands = new List<SparkCommand> {
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
@@ -72,14 +72,14 @@ namespace mbts.spark {
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = $"{window_type_name} Grid Settings", DataContext = vm };
var dialog = new ShellSettingsDialog { Title = $"{window_type_name} Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
_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,100 @@
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; }
[ProtoMember(15)]
[DIYGridColumnID(nameof(Columns.Symbol))]
public string[] symbol { 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);
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
// 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,111 @@
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 symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
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 symbol;
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;
@@ -279,8 +286,8 @@ namespace mbts.spark {
volume_column = VolumeColumnSettings.Default,
calculator_buttons = new[] { 1, 5, 10, 25, 50, 100 },
scroll_bar_visible = false,
default_order_type = CustomOrderDefinition.CustomOneofCase.Limit,
default_time_in_force = TimeInForce.Day,
default_algo = new AlgoID(CustomOrderDefinition.CustomOneofCase.Limit),
animate_recenter = true,
badge_style = BadgeStyle.Normal,
bid_break_line = new LadderMarketBreakLine { color = 0, show = 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,
@@ -319,7 +327,15 @@ namespace mbts.spark {
public enum BadgeStyle { Normal, Alternate }
public enum NetChangeTerms { None, Dollars, Ticks }
// TODO: rename to PriceDisplayFormat (today is merge monday, since this isn't a functional change, it is not worth the potential merge headaches right now)
public enum NetChangeTerms {
[Description("Standard Ticks")]
None,
[Description("Net Change Dollars")]
Dollars,
[Description("Net Change Ticks")]
Ticks
}
[ProtoMember(1)]
public double row_height { get; set; }
@@ -346,6 +362,10 @@ namespace mbts.spark {
public bool scroll_bar_visible { get; set; }
// USED [ProtoMember(11)]
// USED [ProtoMember(12)]
[ProtoMember(51)]
public uint default_account { get; set; }
[ProtoMember(52)]
public AlgoID default_algo { get; set; }
[ProtoMember(13)]
public CustomOrderDefinition.CustomOneofCase default_order_type { get; set; }
[ProtoMember(14)]
@@ -356,6 +376,8 @@ namespace mbts.spark {
public bool animate_recenter { get; set; } = true;
[ProtoMember(17)]
public BadgeStyle badge_style { get; set; }
[ProtoMember(53)]
public bool auto_clear { get; set; }
[ProtoMember(18)]
public LadderMarketBreakLine bid_break_line { get; set; }
@@ -397,6 +419,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)]
@@ -449,11 +473,13 @@ namespace mbts.spark {
piq_column.safe_equals(other.piq_column) &&
calculator_buttons.safe_equals(other.calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
scroll_bar_visible == other.scroll_bar_visible &&
default_order_type == other.default_order_type &&
default_account == other.default_account &&
default_algo == other.default_algo &&
default_time_in_force == other.default_time_in_force &&
size == other.size &&
animate_recenter == other.animate_recenter &&
badge_style == other.badge_style &&
auto_clear == other.auto_clear &&
bid_break_line.safe_equals(other.bid_break_line) &&
ask_break_line.safe_equals(other.ask_break_line) &&
mid_break_line.safe_equals(other.mid_break_line) &&
@@ -473,6 +499,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 +515,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(),
@@ -500,7 +528,6 @@ namespace mbts.spark {
EqualityComparer.safe_get_hash_code(piq_column),
EqualityComparer.safe_get_hash_code(calculator_buttons),
scroll_bar_visible.GetHashCode(),
default_order_type.GetHashCode(),
default_time_in_force.GetHashCode(),
size.GetHashCode(),
animate_recenter.GetHashCode(),
@@ -1,15 +1,19 @@
using bts.api;
using bts.md;
using mbts;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.spark;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -21,17 +25,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 })),
@@ -42,37 +55,175 @@ namespace bts.shell {
public async override Task<IShellWindow> load(LadderSaveState state) {
await Wait.until_true(() => _securities.ContainsKey(state.security_key), _ladder_conf.security_timeout);
if (_securities.TryGetValue(state.security_key, out SecurityMD security)) {
return create_ladder_window(security, state.settings);
return create_ladder_window(security, state.settings, state.settings_id);
}
return null;
}
static LadderSaveState _get_state(LadderVM vm) =>
new LadderSaveState { security_key = vm.security.Id, settings = vm.get_save_state() };
LadderSaveState _get_state(LadderVM vm, uint id) {
LadderSettings s = vm.get_save_state();
if (id != 0) {
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
// profile was deleted
id = 0;
} else {
var curr_profile = _fit_to_sec(result.value, vm.security.ContractCase);
if (!s.Equals(curr_profile)) {
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
id = 0;
}
}
}
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
}
int _ladder_count = 0;
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
IDisposable active_settings_dialog = null;
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
var view = new Lazy<UIElement>(() => {
var model = new LadderModel(md, _orders_model);
var settings_manager = new WindowSettingsManager<LadderSettings>(
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
us => sanitize_ladder_settings(us.ladder),
(us, v) => us.with(sanitize_ladder_settings(v)));
var init = state_settings ?? settings_manager.default_settings;
init.use_defaults |= state_settings == null;
var vm = LadderVM.create(_bus, model, settings_manager, init);
// resources
var model = new LadderModel(md, _orders_model, _mbo);
var profile_service = new SettingsProfileService<LadderSettings>(_settings_hub,
us => ImmutableDictionary<uint, SettingsProfile<LadderSettings>>.Empty.AddRange(us.ladder_profiles.ToDictionary(
lp => lp.id,
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, sanitize_ladder_settings(lp.value), lp.security_criteria))),
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
LadderSettings last_value = fit_to_sec(state_settings);
var settings_subject = new BehaviorSubject<LadderSettings>(last_value);
// vm
var vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_subject.Where(s => s != null));
vm.order_properties_vm.open_algo_popup_on_select = _ladder_conf.open_algo_popup_on_select;
vm.order_properties_vm.use_algo_param_grid = _ladder_conf.use_algo_param_grid;
vm.request_settings_dialog += open_settings_dialog;
bool initialized = false;
var settings_subscription = _settings_hub.subscribe_default_settings().Subscribe(on_user_settings);
vm.OnDisposed += disposing => {
Disposable.dispose(ref active_settings_dialog);
Disposable.dispose(ref settings_subject);
};
// view
var lv = new LadderView(vm);
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(vm.model.order_gateway.algo_model.ui_configuration))
lv.Resources[kv.Key] = kv.Value;
return lv;
// local methods
LadderSettings fit_to_sec(LadderSettings s) =>
_fit_to_sec(s, md.security.ContractCase);
void on_user_settings(UserSettings us) {
if (initialized) {
if (settings_id == 0) {
// not on a profile, don't respond to default settings changes
return;
} else if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
if (!last_value.Equals(vm.get_save_state())) {
settings_id = 0;
} else {
settings_subject.OnNext(last_value = fit_to_sec(profile.value));
}
} else {
// profile was deleted; keep the current settings and switch to the 'Local' profile.
settings_id = 0;
}
} else if (_settings_hub.initialized) {
initialized = true;
if (settings_id != 0) {
if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
last_value = fit_to_sec(profile.value);
} else {
settings_id = 0;
}
}
if (settings_id == 0 && last_value == null) {
foreach (var lp in _settings_hub.current.ladder_profiles) {
// TODO: Use a kind of 'match priority' to resolve conflicts when the security is included in more than 1 profile's security criteria.
// In the meantime, simply use the first match.
if (lp.security_criteria.includes(md.security)) {
last_value = fit_to_sec(lp.value);
settings_id = lp.id;
break;
}
}
if (last_value == null)
last_value = fit_to_sec(_settings_hub.current.ladder);
}
settings_subject.OnNext(last_value);
}
}
void open_settings_dialog() {
// dialog vm
var dialog_vm = new ProfiledShellSettingsDialogVM<LadderSettings, LadderSettingsVM, LadderSettingsProfile>(
profile_service,
us => us.ladder ?? LadderSettings.Default,
s => {
var svm = new LadderSettingsVM(model.order_gateway, vm.security);
svm.load(s);
return svm;
},
fit_to_sec,
vm.get_save_state(),
UserSettings.FileExtensions.LADDER,
settings_id);
dialog_vm.applied += apply;
// dialog
var dialog = new ShellSettingsDialog {
DataContext = dialog_vm,
Title = $"Ladder Settings - '{vm.security}'"
};
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
dialog_vm.RequestClose += on_request_close;
dialog.Closed += on_closed;
Disposable.replace(ref active_settings_dialog, Disposable.Create(destroy_all));
dialog.move_to_mouse(-500, -500);
dialog.Show();
// local methods
void apply(SettingsProfile<LadderSettings> profile) {
settings_id = profile.id;
last_value = profile.value;
settings_subject.OnNext(profile.value);
}
// idempotent
void destroy_all() {
destroy_window();
destroy_vm();
}
// idempotent
void destroy_window() {
if (dialog == null) return;
try {
var temp = dialog;
dialog = null;
temp.Closed -= on_closed;
temp.Close();
} catch (Exception ex) {
on_error("closing window", ex);
}
}
// idempotent
void destroy_vm() {
if (dialog_vm == null) return;
try {
var temp = dialog_vm;
dialog_vm = null;
temp.applied -= apply;
temp.RequestClose -= on_request_close;
temp.Dispose();
} catch (Exception ex) {
on_error("disposing vm", ex);
}
}
void on_closed(object sender, EventArgs e) => destroy_all();
void on_request_close(object sender, EventArgs e) => destroy_all();
void on_error(string action_desc, Exception error) =>
Log.Ui.warn($"LadderSettingsDialog error {action_desc}: {error}.");
}
});
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{id:00}" : "";
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{thread_id:00}" : "";
return ShellWindow.create(
module, md.security.ToString(), _window_type, view,
(v,s) => save(_get_state(((LadderView)v).vm), s),
(v,s) => save(_get_state(((LadderView)v).vm, settings_id), s),
icon: Icons.Ladder,
thread_id: thread);
// if there are new props, make sure they are not null, invalid, etc.
@@ -115,26 +266,19 @@ namespace bts.shell {
}
}
// TODO: whenever we re-implement the linked ladder, need to add some field to the LadderSaveState to indicate the owning window
//void show_linked(IObservable<SecurityMD> keystream) {
// var view = new LadderView();
// IDisposable sub = null;
// var window =
// ShellWindow.create(module, "loading", view,
// Disposable.Create(() => {
// (view.DataContext as LadderVM)?.Dispose();
// sub?.Dispose();
// }), s => {
// var vm = view.DataContext as LadderVM;
// if (vm != null) save(_get_state(vm), s);
// }, Icons.Ladder);
// sub =
// keystream
// .Select(smd => new LadderVM(_bus, new LadderModel(smd, _orders_model),
// Observable.Return(_orders_model.factory.default_ladder_settings(smd.security)))
// ).Aggregate((old, vm) => { old.Dispose(); return vm; })
// .Subscribe(vm => { view.DataContext = vm; window.Title = vm.security.ToString(); });
//}
LadderSettings _fit_to_sec(LadderSettings s, Security.ContractOneofCase sec_type) {
if (s == null) return null;
var algo_key = AlgoID.to_algo_key(s.default_algo, _orders_model.dynamic_helper.dynamic_type_index);
if (algo_key.mode == AlgoKey.Mode.Factory) {
var fitted_algo = OrderDataHelper.fit_to_type(algo_key.factory.structure.build_algo(_orders_model.dynamic_helper), sec_type, true);
if (fitted_algo.HasValue) {
// be sure to clone ('s' may be shared)
s = SparkUtils.clone_proto(s);
s.default_algo = AlgoID.from_custom_def(fitted_algo.Value);
}
}
return s;
}
readonly struct LadderConfig {
public readonly uint max_open_ladders;
@@ -157,5 +301,7 @@ namespace bts.shell {
public UInt32 security_key;
[ProtoMember(2)]
public mbts.spark.LadderSettings settings;
[ProtoMember(3)]
public uint settings_id;
}
}
@@ -1,5 +1,6 @@
using bts.api;
using bts.api.orderman;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using System;
@@ -44,11 +45,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 +118,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 +135,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 +170,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 +207,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 {
@@ -237,7 +298,8 @@ namespace mbts.spark {
new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_owner.security),
price = price_filter,
statuses = new[] { OrderStatus.Working }
statuses = new[] { OrderStatus.Working },
order_types = AlgoID.exchange_algo_keys
},
bts.shell.orderbook.OrderBookStartupData.default_mini_grid,
use_defaults: false
@@ -275,6 +337,7 @@ namespace mbts.spark {
}
obvm.columns.Insert(0, new OrderBookVM.UpdateColumn(typeof(MiniOrderBookView)));
obvm.columns.Insert(0, new OrderBookVM.CancelColumn(nameof(OrderBookRowVM.cancel_command)));
obvm.columns.Insert(0, new OrderBookVM.CheckColumn(nameof(OrderBookRowVM.is_selected)));
obvm.load_from(state);
return obvm;
@@ -1,4 +1,6 @@
using bts.api;
using bts;
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.collections;
@@ -8,10 +10,14 @@ using mbts.ui.controls.color_picker;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
@@ -19,19 +25,30 @@ using System.Windows.Media;
using TextUtils = mbts.ui.utility.text.TextUtils;
namespace mbts.spark {
class LadderSettingsVM : ViewModelBase, IWindowSettingsVM<LadderSettings>, ITemplateSelectorKey, IDisposable {
class LadderSettingsVM : DisposableViewModelBase, IWindowSettingsVM<LadderSettings>, IValueFactory<LadderSettings>, IObservable<LadderSettings>, ITemplateSelectorKey {
readonly Property<LadderSettings.BadgeStyle> _badge_style;
readonly Property<uint> _alternate_order_size;
readonly DelegateProperty<double> _row_height;
Func<double> _get_order_entry_panel_width, _get_zoom_factor;
readonly UserProperty<AccountTuple?> _default_account;
readonly UserProperty<AlgoDefinitionVM> _default_algo;
readonly DeferrableActionWrapper _update;
readonly ObservableKeyedCollection<AccountTuple?, uint> _accounts = new ObservableKeyedCollection<AccountTuple?, uint>(AccountTuple.nullable_key_fn);
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
readonly OrdersModel _model;
uint? _desired_account;
AlgoID? _desired_algo;
TimeInForce _default_time_in_force;
LadderSettings.NetChangeTerms _price_format;
double _order_entry_panel_width, _zoom_factor;
bool _auto_clear;
LadderSettings _current;
LadderColumnSettingsVM
_orders = new OrdersColumnSettingsVM(),
_bid = new LadderColumnSettingsVM("Bid"),
_price = new LadderColumnSettingsVM("Price"),
_ask = new LadderColumnSettingsVM("Ask"),
_last = new LadderColumnSettingsVM("Last", true),
_piq = new LadderColumnSettingsVM("PIQ", true);
_last = new LadderColumnSettingsVM("Last", LadderSettingsView.LastColumnTemplateKey, true),
_piq = new LadderColumnSettingsVM("PIQ", can_toggle_show: true);
VolumeColumnVM _volume = new VolumeColumnVM();
LadderMarketBreakLineVM
_bid_mkt_line = new LadderMarketBreakLineVM("Bid"),
@@ -52,31 +69,25 @@ 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"),
_cancel_buys_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Buys"),
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All");
readonly Property<bool>
_show_in_net_change,
_show_in_net_change_dollars,
_show_in_net_change_ticks;
public LadderSettingsVM(OrdersModel model, bool is_synthetic, Func<double> get_left_panel_width, Func<double> get_zoom_factor) {
this.is_synthetic = is_synthetic;
favorite_algos_vm = new FavoriteAlgosVM(model);
public LadderSettingsVM(OrdersModel model, SecurityImmutable security) {
_model = model;
is_synthetic = security.ContractCase == Security.ContractOneofCase.Synthetic;
favorite_algos_vm = new FavoriteAlgosVM(_algo_definitions);
size_presets = new ObservableEditableCollection<IMutableValueWrapperVM<uint>>(_create_size_preset_vm);
size_presets.CollectionChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
_badge_style = backing<LadderSettings.BadgeStyle>(nameof(badge_style));
_badge_style.Changed += _badge_style_Changed;
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
_get_order_entry_panel_width = get_left_panel_width;
_get_zoom_factor = get_zoom_factor;
_show_in_net_change = backing<bool>(nameof(show_in_net_change));
_show_in_net_change_dollars = backing<bool>(nameof(show_in_net_change_dollars));
_show_in_net_change_ticks = backing<bool>(nameof(show_in_net_change_ticks));
_default_account = user_backing<AccountTuple?>(nameof(default_account));
_default_algo = user_backing<AlgoDefinitionVM>(nameof(default_algo));
// be sure to initialize backings before this measure_row_height call
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
_orders.font_size_changed += on_font_size_changed;
@@ -85,10 +96,47 @@ namespace mbts.spark {
_ask.font_size_changed += on_font_size_changed;
_last.font_size_changed += on_font_size_changed;
_piq.font_size_changed += on_font_size_changed;
// accounts
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
CustomSort = Comparer<AccountTuple>.Default.to_nullable().to_non_generic()
};
// update
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
foreach (var c in _observable_components)
c.PropertyChanged += on_prop_changed;
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
_dispose = new CompositeDisposable(
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
_try_select_desired_account();
}),
ObservableAutoWrapper.connect_item_hooks(favorite_algos_vm.items, connect_favorite_algo),
_model.algo_model.definitions.OnDispatcher().Subscribe(update => {
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, algo_factory);
_try_select_desired_algo();
})
) { dispose_resources };
// local methods
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
void on_prop_changed(object sender, PropertyChangedEventArgs e) => _update.execute();
void on_favorite_algo_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => _update.execute();
IDisposable connect_favorite_algo(FavoriteAlgoItem item) {
item.PropertyChanged += on_prop_changed;
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
}
void dispose_resources() {
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
favorite_algos_vm.Dispose();
foreach (var c in _observable_components)
c.PropertyChanged -= on_prop_changed;
}
AccountTuple? account_factory(KeyValuePair<uint, string> kv) => new AccountTuple(kv.Key, kv.Value);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, false);
}
public event Action result_changed;
private void _badge_style_Changed(LadderSettings.BadgeStyle oldval, LadderSettings.BadgeStyle newval) {
if (newval == LadderSettings.BadgeStyle.Alternate && _orders.font_size == 10) {
_orders.font_size = 7;
@@ -109,6 +157,23 @@ namespace mbts.spark {
}
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
// price format
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
// time in force
public TimeInForce default_time_in_force { get => _default_time_in_force; set => Set(ref _default_time_in_force, value); }
public IEnumerable<TimeInForce> time_in_force_options => EnumHelper.get_values_except(TimeInForce.Undefined);
// account
public AccountTuple? default_account { get => _default_account.Value; set => _default_account.Value = value; }
public ICollectionView<AccountTuple?> accounts_view { get; }
// algo
public AlgoDefinitionVM default_algo { get => _default_algo.Value; set => _default_algo.Value = value; }
public IReadOnlyObservableCollection<AlgoDefinitionVM> algo_definitions => _algo_definitions;
// left-panel (components)
public bool auto_clear { get => _auto_clear; set => Set(ref _auto_clear, value); }
public double order_entry_panel_width { get => _order_entry_panel_width; set => Set(ref _order_entry_panel_width, value); }
public double zoom_factor { get => _zoom_factor; set => Set(ref _zoom_factor, value); }
// favorites
public FavoriteAlgosVM favorite_algos_vm { get; }
public double row_height => _row_height.Value;
@@ -164,6 +229,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;
@@ -173,78 +239,108 @@ namespace mbts.spark {
}
}
public bool show_in_net_change {
get => _show_in_net_change.Value;
set {
// when unchecking, clear out dollars and ticks
if (!value) {
show_in_net_change_dollars = false;
show_in_net_change_ticks = false;
}
// when checking, by defalt check dollars
if (value) {
show_in_net_change_dollars = true;
}
_show_in_net_change.Value = value;
}
}
public bool show_in_net_change_dollars {
get => _show_in_net_change_dollars.Value;
set => _show_in_net_change_dollars.Value = value;
}
public bool show_in_net_change_ticks {
get => _show_in_net_change_ticks.Value;
set => _show_in_net_change_ticks.Value = value;
}
public string key => GridSettingsTemplateKeys.Ladder;
IEnumerable<INotifyPropertyChanged> _observable_components {
get {
// columns
yield return _orders;
yield return _bid;
yield return _price;
yield return _ask;
yield return _last;
yield return _piq;
yield return _volume;
yield return _volume.general;
// market break lines
yield return _bid_mkt_line;
yield return _ask_mkt_line;
yield return _mid_mkt_line;
yield return _session_low_mkt_line;
yield return _session_high_mkt_line;
yield return _session_range_fill;
// left panel
yield return _zoom_slider_visbility;
yield return _day_visibility;
yield return _high_visibility;
yield return _low_visibility;
yield return _open_visibility;
yield return _settle_visibility;
yield return _volume_visibility;
yield return _account_visibility;
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 _auto_clear_visibility;
yield return _clear_visibility;
yield return _cancel_buys_visibility;
yield return _cancel_sells_visibility;
yield return _cancel_all_visibility;
// other
yield return size_display;
yield return favorite_algos_vm;
yield return this;
}
}
public void load(LadderSettings settings) {
_current = settings;
badge_style = settings.badge_style;
alternate_order_size = settings.alternate_order_size;
favorite_algos_vm.load_from(settings.favorite_algos);
size_display.load_from(settings.size_display);
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
_orders.assign_from(_current.orders_column);
_bid.assign_from(_current.bid_column);
_ask.assign_from(_current.ask_column);
_price.assign_from(_current.price_column);
_volume.assign_from(_current.volume_column);
_last.assign_from(_current.last_column);
_piq.assign_from(_current.piq_column);
_bid_mkt_line.assign_from(_current.bid_break_line);
_ask_mkt_line.assign_from(_current.ask_break_line);
_mid_mkt_line.assign_from(_current.mid_break_line);
_session_low_mkt_line.assign_from(_current.session_low_line);
_session_high_mkt_line.assign_from(_current.session_high_line);
_session_range_fill.assign_from(_current.session_range_fill);
_zoom_slider_visbility.show = settings.zoom_slider_visible;
_day_visibility.show = settings.day_visible;
_high_visibility.show = settings.high_visible;
_low_visibility.show = settings.low_visible;
_open_visibility.show = settings.open_visible;
_settle_visibility.show = settings.settle_visible;
_volume_visibility.show = settings.volume_visible;
_account_visibility.show = settings.account_visible;
_tif_visibility.show = settings.tif_visible;
_order_type_visibility.show = settings.order_type_visible;
_account_position_visibility.show = settings.account_position_visible;
_size_preset_visibility.show = settings.size_presets_visible;
_clear_visibility.show = settings.clear_visible;
_auto_clear_visibility.show = settings.auto_clear_visible;
_cancel_buys_visibility.show = settings.cancel_buys_visible;
_cancel_sells_visibility.show = settings.cancel_sells_visible;
_cancel_all_visibility.show = settings.cancel_all_visible;
// do not read the order entry panel width here from settings
// because it could have changed from the UI
// there might be a better way of doing this, like how
// does the widths of the ladder grid column pull this off?
show_in_net_change = settings.net_change != LadderSettings.NetChangeTerms.None;
show_in_net_change_dollars = settings.net_change == LadderSettings.NetChangeTerms.Dollars;
show_in_net_change_ticks = settings.net_change == LadderSettings.NetChangeTerms.Ticks;
using (_update.defer_execution()) {
_current = settings;
badge_style = settings.badge_style;
alternate_order_size = settings.alternate_order_size;
favorite_algos_vm.load_from(settings.favorite_algos);
size_display.load_from(settings.size_display);
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
// columns
_orders.assign_from(_current.orders_column);
_bid.assign_from(_current.bid_column);
_ask.assign_from(_current.ask_column);
_price.assign_from(_current.price_column);
_volume.assign_from(_current.volume_column);
_last.assign_from(_current.last_column);
_piq.assign_from(_current.piq_column);
// market break lines
_bid_mkt_line.assign_from(_current.bid_break_line);
_ask_mkt_line.assign_from(_current.ask_break_line);
_mid_mkt_line.assign_from(_current.mid_break_line);
_session_low_mkt_line.assign_from(_current.session_low_line);
_session_high_mkt_line.assign_from(_current.session_high_line);
_session_range_fill.assign_from(_current.session_range_fill);
// left-panel (defaults)
default_time_in_force = settings.default_time_in_force;
// - account
_desired_account = settings.default_account;
_try_select_desired_account(true);
// - algo
_desired_algo = settings.default_algo;
_try_select_desired_algo(true);
// - components
auto_clear = settings.auto_clear;
order_entry_panel_width = settings.order_entry_panel_initial_width;
zoom_factor = settings.zoom_factor;
// left-panel (show/hide)
_zoom_slider_visbility.show = settings.zoom_slider_visible;
_day_visibility.show = settings.day_visible;
_high_visibility.show = settings.high_visible;
_low_visibility.show = settings.low_visible;
_open_visibility.show = settings.open_visible;
_settle_visibility.show = settings.settle_visible;
_volume_visibility.show = settings.volume_visible;
_account_visibility.show = settings.account_visible;
_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;
_cancel_buys_visibility.show = settings.cancel_buys_visible;
_cancel_sells_visibility.show = settings.cancel_sells_visible;
_cancel_all_visibility.show = settings.cancel_all_visible;
price_format = settings.net_change;
}
}
public LadderSettings get_result() {
@@ -254,6 +350,7 @@ namespace mbts.spark {
clone.favorite_algos = favorite_algos_vm.create();
clone.size_display = size_display.create();
clone.calculator_buttons = size_presets.Select(vm => (int)vm.value).ToArray();
// columns, row height (the latter determined by columns font size)
clone.orders_column = _orders.get_settings();
clone.bid_column = _bid.get_settings();
clone.price_column = _price.get_settings();
@@ -261,13 +358,28 @@ namespace mbts.spark {
clone.volume_column = _volume.get_settings();
clone.last_column = _last.get_settings();
clone.piq_column = _piq.get_settings();
clone.row_height = LadderRender.measure_row_height(clone);
// market break lines
clone.bid_break_line = _bid_mkt_line.get_settings();
clone.ask_break_line = _ask_mkt_line.get_settings();
clone.mid_break_line = _mid_mkt_line.get_settings();
clone.session_low_line = _session_low_mkt_line.get_settings();
clone.session_high_line = _session_high_mkt_line.get_settings();
clone.session_range_fill = _session_range_fill.get_settings();
clone.row_height = LadderRender.measure_row_height(clone);
// left-panel (defaults)
clone.default_time_in_force = default_time_in_force;
// - account
if (default_account.HasValue)
clone.default_account = default_account.Value.id;
// - algo
clone.default_algo = default_algo != null ?
AlgoID.from_algo_key(default_algo.key, _model.dynamic_helper.dynamic_type_index) :
(_desired_algo ?? LadderSettings.Default.default_algo);
// - components
clone.auto_clear = auto_clear;
clone.order_entry_panel_initial_width = _order_entry_panel_width;
clone.zoom_factor = zoom_factor;
// left-panel (components)
clone.day_visible = _day_visibility.show;
clone.high_visible = _high_visibility.show;
clone.low_visible = _low_visibility.show;
@@ -278,31 +390,28 @@ 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;
clone.cancel_buys_visible = _cancel_buys_visibility.show;
clone.cancel_sells_visible = _cancel_sells_visibility.show;
clone.cancel_all_visible = _cancel_all_visibility.show;
clone.order_entry_panel_initial_width = _get_order_entry_panel_width();
clone.zoom_slider_visible = _zoom_slider_visbility.show;
clone.zoom_factor = _get_zoom_factor();
clone.net_change = _create_net_change_terms(show_in_net_change_dollars, show_in_net_change_ticks);
clone.net_change = price_format;
return clone;
LadderSettings.NetChangeTerms _create_net_change_terms(bool dollars, bool ticks) {
if (dollars) {
return LadderSettings.NetChangeTerms.Dollars;
}
if (ticks) {
return LadderSettings.NetChangeTerms.Ticks;
}
return LadderSettings.NetChangeTerms.None;
}
}
public void Dispose() => favorite_algos_vm.Dispose();
public IDisposable defer_updates() => _update.defer_execution();
IDisposable IObservable<LadderSettings>.Subscribe(IObserver<LadderSettings> observer) {
result_changed += notify;
return Disposable.Create(() => result_changed -= notify);
void notify() => observer.OnNext(get_result());
}
LadderSettings IValueFactory<LadderSettings>.create() => get_result();
void IValueFactory<LadderSettings>.load_from(LadderSettings value) => load(value);
IMutableValueWrapperVM<uint> _create_size_preset_vm() => _create_size_preset_vm(0);
IMutableValueWrapperVM<uint> _create_size_preset_vm(uint init) {
@@ -310,6 +419,20 @@ namespace mbts.spark {
vm.PropertyChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
return vm;
}
void _try_select_desired_account(bool bypass_setter = false) {
if (_desired_account.HasValue && (!_default_account.setter_called || bypass_setter) && _accounts.TryGetValue(_desired_account.Value, out var acc_tup)) {
_desired_account = null;
_default_account.bypass_setter(acc_tup);
}
}
void _try_select_desired_algo(bool bypass_setter = false) {
if (_desired_algo.HasValue && (!_default_algo.setter_called || bypass_setter) && _algo_definitions.TryGetValue(AlgoID.to_algo_key(_desired_algo.Value, _model.dynamic_helper.dynamic_type_index), out var vm)) {
_desired_algo = null;
_default_algo.bypass_setter(vm);
}
}
public class LadderColumnSettingsVM : ViewModelBase, ITemplateSelectorKey {
readonly Property<double> _font_size;
@@ -319,11 +442,13 @@ namespace mbts.spark {
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<string> _font_display_name;
readonly Property<bool> _show;
readonly bool _can_toggle_visibility;
readonly string _template_key;
uint _width;
public LadderColumnSettingsVM(string name, bool can_toggle_visibility = false) {
public LadderColumnSettingsVM(string name, string template_key = null, bool can_toggle_show = false) {
this.name = name;
_can_toggle_visibility = can_toggle_visibility;
_template_key = template_key;
this.can_toggle_show = can_toggle_show;
foreground_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
background_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
_show = backing(nameof(show), true);
@@ -336,24 +461,33 @@ namespace mbts.spark {
_font_display_name = backing(nameof(font_display_name),
() => TextUtils.display_name(font_family, font_weight, font_size), nameof(font_family), nameof(font_weight), nameof(font_size));
_font_size.Changed += (oldval, newval) => font_size_changed?.Invoke(oldval, newval);
toggle_show_details_cmd = new DelegateCommand(() => {
show_details = !show_details;
RaisePropertyChanged(nameof(show_details));
});
}
public event Action<double, double> font_size_changed;
public string name { get; }
public Typeface typeface { get => _typeface.Value; set => _typeface.Value = value; }
public FontFamily font_family => _font_family.Value;
public FontWeight font_weight => _font_weight.Value;
public string font_display_name => _font_display_name.Value;
public double font_size { get => _font_size.Value; set => _font_size.Value = value; }
public uint width { get; set; }
public uint width { get => _width; set => Set(ref _width, value); }
public bool show { get => _show.Value; set => _show.Value = value; }
public bool can_toggle_show { get; }
public bool show_details { get; private set; }
public Color foreground { get => _foreground.Value; set => _foreground.Value = value; }
public Color background { get => _background.Value; set => _background.Value = value; }
public ObservableCollection<ColorPaletteVM> foreground_palettes { get; }
public ObservableCollection<ColorPaletteVM> background_palettes { get; }
public event Action<double, double> font_size_changed;
public virtual string sample_text => "999-56789.01234";
string ITemplateSelectorKey.key => _can_toggle_visibility ? ui.spark.LadderSettingsView.ColumnVisibilityToggleTemplateKey : null;
public ICommand toggle_show_details_cmd { get; }
string ITemplateSelectorKey.key => _template_key;
public void assign_from(LadderColumnSettings settings) {
typeface = new Typeface(
@@ -366,7 +500,6 @@ namespace mbts.spark {
show = settings.show;
foreground = settings.foreground.rgb_color();
background = settings.background.rgb_color();
RaisePropertyChanged(nameof(width), nameof(show));
}
public LadderColumnSettings get_settings() {
@@ -391,7 +524,7 @@ namespace mbts.spark {
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
public Brush badge_background { get; } = Brushes.White;
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnSampleTemplateKey;
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnTemplateKey;
}
public class LadderMarketBreakLineVM : ViewModelBase {
@@ -449,6 +582,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;
@@ -458,11 +592,14 @@ namespace mbts.spark {
}
}
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", true);
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", LadderSettingsView.VolumeColumnTemplateKey, true);
public VolumeAtPriceAnimationVM buy { get; }
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 +618,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 +628,7 @@ namespace mbts.spark {
sell = sell.get_settings(),
passive = passive.get_settings(),
total = total.get_settings(),
show_ltq = show_last_trade
};
}
@@ -556,17 +695,15 @@ namespace mbts.spark {
}
public class FavoriteAlgosVM : DisposableViewModelBase, IValueFactoryVM<LadderFavoriteAlgosSettings> {
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
int _column_count = 2;
public FavoriteAlgosVM(OrdersModel model) {
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
_algo_definitions = algo_definitions;
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
_dispose = model.algo_model.definitions.OnDispatcher().Subscribe(update => {
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, d => AlgoDefinitionVM.create(model, d, false));
});
}
public ObservableCollection<FavoriteAlgoItem> items { get; }
@@ -586,11 +723,17 @@ namespace mbts.spark {
}
public LadderFavoriteAlgosSettings create() =>
new LadderFavoriteAlgosSettings(_raw_items.Where(fai => fai.algo_definition != null).Select(fai => fai.algo_definition.key).ToArray(), column_count);
new LadderFavoriteAlgosSettings(_raw_items
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray(),
column_count);
FavoriteAlgoItem _item_factory() =>
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic(),
Filter = obj => obj is AlgoDefinitionVM vm && vm.key.is_persisted
});
}
@@ -601,7 +744,7 @@ namespace mbts.spark {
protected FavoriteAlgoItem() { }
public FavoriteAlgoItem(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> algo_definitions, ICollectionView<AlgoDefinitionVM> algo_definitions_view) {
public FavoriteAlgoItem(IObservableCollection<AlgoDefinitionVM> algo_definitions, ICollectionView<AlgoDefinitionVM> algo_definitions_view) {
selector = new KeyedItemSelector<AlgoDefinitionVM, AlgoKey>(this, nameof(algo_definition), algo_definitions, d => d.key);
this.algo_definitions_view = algo_definitions_view;
}
@@ -614,4 +757,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -1,13 +1,11 @@
using bts.api;
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -25,40 +23,40 @@ 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;
IWindowSettingsManager<LadderSettings> _settings_manager;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
double _position, _day_position;
bool _show_account_position, _show_day_position;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
IEqualityComparer<double> _equality;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
LadderSettings _current_settings;
public static LadderVM create(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) {
var non_shared_wsm = settings_manager.as_non_shared();
return new LadderVM(bus, model, settings_manager, non_shared_wsm, init, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))));
}
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
public static LadderVM create_readonly(IBus bus, LadderModel model) {
var default_settings = LadderSettings.Default;
return create_readonly(bus, model, new WindowSettingsManager<LadderSettings>.DisconnectedImpl(default_settings), default_settings);
}
public static LadderVM create_readonly(IBus bus, LadderModel model, IObservable<LadderSettings> settings) =>
create_readonly(bus, model, LadderSettings.Default, settings);
public static LadderVM create_readonly(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) =>
new LadderVM(bus, model, settings_manager, settings_manager.as_non_shared(), init);
public static LadderVM create_readonly(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
new LadderVM(bus, model, init, settings);
LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, IWindowSettingsManagerNonShared<LadderSettings> non_shared_wsm, LadderSettings init, OrderPropertiesEditorVM order_properties_vm = null) {
LadderVM(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings, OrderPropertiesEditorVM order_properties_vm = null) {
_bus = bus;
_model = model;
_current_settings = init;
this.settings = settings;
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
_settings_manager = non_shared_wsm;
this.order_properties_vm = order_properties_vm;
_high = backing(nameof(high), double.NaN);
_low = backing(nameof(low), double.NaN);
@@ -77,93 +75,82 @@ 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);
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
_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> {
/// first things first: assign our <see cref="_current_settings"/>
(s => _current_settings = s),
// 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));
}
size_calculator.buttons = s.calculator_buttons;
_auto_clear.set(s.auto_clear);
// column widths
_orders_column_width.set(s.orders_column.width);
_bid_column_width.set(s.bid_column.width);
_price_column_width.set(s.price_column.width);
_ask_column_width.set(s.ask_column.width);
_last_column_width.set(s.last_column.width);
_piq_column_width.set(s.piq_column.width);
_volume_column_width.set(s.volume_column.general.width);
// left panel/zoom
_left_panel_width.set(s.order_entry_panel_initial_width);
_zoom_factor.set(s.zoom_factor);
}
};
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);
settings_command = new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<LadderSettings, LadderSettingsVM>(
settings_manager, s => s.ladder, UserSettings.FileExtensions.LADDER,
s => {
var svm = new LadderSettingsVM(model.order_gateway, security.ContractCase == Security.ContractOneofCase.Synthetic, () => left_panel_width, () => zoom_factor);
svm.load(s);
return svm;
}, s => s.with_window_specifics(get_save_state().get_window_specifics()));
var dialog = new ui.spark.SimpleGridSettingsDialog {
DataContext = vm,
Title = $"Ladder Settings - '{security}'"
};
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}, -500, -500);
settings_command = new DelegateCommand(() => request_settings_dialog?.Invoke());
orders = new ObservableCollection<LadderOrderVM>();
_dispose = Disposable.Create(
settings_command,
non_shared_wsm,
settings.OnDispatcher().Subscribe(settings_observer),
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 +167,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.Select(s => s.badge_style).OnDispatcher());
_slot_price_vs_orders[slot_price_target] = slot_vm;
}
@@ -229,24 +216,40 @@ 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;
public event Action on_tick;
public event Action on_tick, request_settings_dialog;
public MDConsumerHandle md => _model.md;
// 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,10 +283,11 @@ 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; }
public CalculatorLogic size_calculator { get; } = new CalculatorLogic(0);
/// Important: <see cref="order_properties_vm"/> can be null.
public OrderPropertiesEditorVM order_properties_vm { get; }
public ObservableCollection<LadderOrderVM> orders { get; }
@@ -291,30 +295,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 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 ICommand settings_command { get; }
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 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); }
@@ -325,8 +340,8 @@ namespace mbts.spark {
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
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 IObservable<LadderSettings> settings { get; }
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)) {
@@ -344,10 +359,26 @@ namespace mbts.spark {
}
public LadderSettings get_save_state() {
var state = _settings_manager.current_settings();
state = state.with_window_specifics(new LadderSettings.WindowSpecifics(left_panel_width, orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width, zoom_factor, piq_column_width));
state.use_defaults = _settings_manager.instance_settings == null;
return state;
var rv = _current_settings.with_window_specifics(
new LadderSettings.WindowSpecifics(
left_panel_width,
orders_column_width,
bid_column_width,
price_column_width,
ask_column_width,
last_column_width,
volume_column_width,
zoom_factor,
piq_column_width));
rv.auto_clear = auto_clear;
if (order_properties_vm != null) {
rv.default_time_in_force = order_properties_vm.selected_time_in_force;
if (order_properties_vm.selected_account.HasValue)
rv.default_account = order_properties_vm.selected_account.Value.id;
if (order_properties_vm.selected_algo_definition != null)
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key, _model.order_gateway.dynamic_helper.dynamic_type_index);
}
return rv;
}
public void key_down(KeyEventArgs e) {
@@ -362,34 +393,39 @@ namespace mbts.spark {
}
}
public async void send_order((double price, Side? side, bool size_from_alternate) args) {
if (order_properties_vm == null)
public void send_order((double price, Side? side, MouseButton button) args) {
if (order_properties_vm == null || args.button == MouseButton.Middle)
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))
var size_from_alternate = args.button == MouseButton.Right;
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && _current_settings.alternate_order_size == 0)) {
return;
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 ? _current_settings.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 +449,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,143 @@ 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);
return;
} 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();
}
return;
}
}
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 +499,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 +628,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 +636,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 +788,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 +832,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 +851,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 +1045,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() {
@@ -140,6 +139,7 @@ namespace mbts.spark {
// properly Dispose of it later when the popup is closed.
DataContext = obvm,
Child = view,
// StaysOpen true for debugging
StaysOpen = false,
PlacementTarget = popup_placement_target,
Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom,
@@ -192,11 +192,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;
}
File diff suppressed because it is too large. Load diff
@@ -10,10 +10,15 @@ namespace mbts.ui.spark {
/// </summary>
partial class LadderSettingsView : UserControl {
public const string
OrdersColumnSampleTemplateKey = nameof(OrdersColumnSampleTemplateKey),
ColumnVisibilityToggleTemplateKey = nameof(ColumnVisibilityToggleTemplateKey),
OrdersColumnTemplateKey = nameof(OrdersColumnTemplateKey),
VolumeColumnTemplateKey = nameof(VolumeColumnTemplateKey),
LastColumnTemplateKey = nameof(LastColumnTemplateKey),
FavoriteAlgoAddButtonTemplateKey = nameof(FavoriteAlgoAddButtonTemplateKey);
public static readonly IMultiValueConverter LadderColumnToggleShowToolTipConverter =
ValueConverter.create<bool, bool, string, string>((can_toggle_show, show, col_name) =>
can_toggle_show ? $"{(show ? "Hide" : "Show")} {col_name}" : $"{col_name} cannot be {(show ? "hidden" : "shown")}.");
public LadderSettingsView() =>
InitializeComponent();
@@ -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" />
@@ -288,8 +288,11 @@
/>
</Grid.LayoutTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
MinWidth="60" MaxWidth="150"/>
<ColumnDefinition
Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
MinWidth="{x:Static spark:LadderView.MinLeftPanelWidth}"
MaxWidth="{x:Static spark:LadderView.MaxLeftPanelWidth}"
/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
@@ -309,11 +312,14 @@
</Style>
</StackPanel.Resources>
<StackPanel x:Name="_slider_stack_panel" Margin="5,5,5,0">
<Slider x:Name="_ui_scaler" Value="{Binding zoom_factor}"
Minimum="{StaticResource MinZoomFactor}"
Maximum="{StaticResource MaxZoomFactor}"
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Slider
x:Name="_ui_scaler"
Value="{Binding zoom_factor}"
Minimum="{x:Static spark:LadderView.MinZoomFactor}"
Maximum="{x:Static spark:LadderView.MaxZoomFactor}"
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
<Grid Margin="2,4,2,0">
<Grid.RowDefinitions>
@@ -455,13 +461,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,7 +11,40 @@ using System.Windows.Input;
namespace mbts.spark {
partial class LadderView : UserControl {
readonly double _max_zoom, _min_zoom;
public const double
MinLeftPanelWidth = 60,
MaxLeftPanelWidth = 150,
MinZoomFactor = 0.5,
MaxZoomFactor = 1.5;
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();
});
internal LadderView(LadderVM vm) {
InitializeComponent();
@@ -18,19 +54,6 @@ namespace mbts.spark {
this.vm = vm;
DataContext = vm;
DataContextChanged += datacontext_changed;
if (TryFindResource("MaxZoomFactor") is double found_max) {
_max_zoom = found_max;
} else {
// is there a better way of not specifying the constant twice ?
// once in the resource dictionary here and again in the xaml, seems like there has to be a better way
_max_zoom = 1.5;
}
// ditto
if (TryFindResource("MinZoomFactor") is double found_min) {
_min_zoom = found_min;
} else {
_min_zoom = 0.5;
}
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
this.PreviewMouseDown += LadderView_PreviewMouseDown;
@@ -46,7 +69,7 @@ namespace mbts.spark {
var delta = (e.Delta > 0) ? 0.1 : -0.1;
var proposed_scaler_value = _ui_scaler.Value + delta;
// Here we enforce the same contraints that are on the gui slider regarding min and max
if (_min_zoom < proposed_scaler_value && proposed_scaler_value < _max_zoom) {
if (MinZoomFactor < proposed_scaler_value && proposed_scaler_value < MaxZoomFactor) {
_ui_scaler.Value = proposed_scaler_value;
}
}
@@ -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,85 @@
<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"
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
/>
<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,248 @@
<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"
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
/>
</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";
}
}
}
@@ -99,7 +99,16 @@
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
<!-- We added this IsSelected after seeing some strangeness when bringing up the Mini OB,
then clicking the attach button, then performing a click to modify a param in the popup, the grid
would lose the appearance of its selected row. I think this was actually a result of a bad path
in the SelectionHighlight expression below, but that leaves me questioning how if it was bad (and
it was, I confirmed in Snoop) did the Grid successfully show a highlighted row in the first place.
At any rate, I have a strong suspicion this Setter is no longer necessary, but I am too exhausted
and scared to remove it at this point.
-->
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay}" />
</Style>
<DataTemplate x:Key="{x:Static sp:MiniOrderBookView.RowTemplateKey}">
<sp:MiniOrderBookRowView/>
@@ -121,10 +130,9 @@
Rows="{Binding rows}"
RowStyle="{StaticResource {x:Static sp:MiniOrderBookView.RowStyleKey}}"
RowTemplate="{StaticResource {x:Static sp:MiniOrderBookView.RowTemplateKey}}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
SelectionHighlight="{Binding grid_settings_manager.core_manager.selection_highlight, Mode=OneWay}"
RowsHorizontalScrollBarVisibility="Collapsed"
RowHeight="20"
SelectionMode="Single"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
@@ -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, model.dynamic_helper.dynamic_type_index)).OnDispatcher();
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
// order type
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, model.dynamic_helper.dynamic_type_index), out var order_type_filter_observer, out var order_type_items_view)));
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
order_type_items_view.CustomSort = algo_items_comp;
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
// 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, model.dynamic_helper.dynamic_type_index), out var root_algo_type_observer, out var root_algo_type_items_view),
DefaultColumnHeaders.RootAlgoType));
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
root_algo_type_items_view.CustomSort = algo_items_comp;
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);
//
@@ -667,6 +724,33 @@ namespace mbts.spark {
}
}
public class CheckColumn : FrameworkElementColumnBase<Columns> {
readonly BindingBase _checked_binding;
public CheckColumn(string checked_property) : base(Columns.Check) {
id = key.ToString();
Width = 30;
_checked_binding = new Binding(checked_property);
var header_cb = new CheckBox();
header_cb.ToolTip = "Select / Unselect All";
header_cb.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(OrderBookVM.all_rows_selected)));
this.Header = header_cb;
}
public override FrameworkElement view_factory() {
var grid = new Grid() {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
var cb = new CheckBox();
cb.Name = "check_col";
cb.SetBinding(CheckBox.IsCheckedProperty, _checked_binding);
grid.Children.Add(cb);
return grid;
}
}
public class CancelColumn : FrameworkElementColumnBase<Columns> {
readonly BindingBase _command_binding;
@@ -742,7 +826,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,
@@ -755,7 +839,10 @@ namespace mbts.spark {
VerticalAlignment = VerticalAlignment.Center,
};
attach_grid.Children.Add(attach_button);
attach_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
attach_button.Click += (s, e) => {
popup.Child = create_popup_child();
popup.IsOpen = !popup.IsOpen;
};
var orderbook_vm_datacontext_binding = new Binding("DataContext");
orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
@@ -772,84 +859,9 @@ namespace mbts.spark {
var row_vm_attach_command_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.attach_command)));
row_vm_attach_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
attach_button.SetBinding(Button.CommandProperty, row_vm_attach_command_binding);
attach_button.SetBinding(Button.CommandProperty, row_vm_attach_command_binding);
#region OrderParamsPanel
// finally, set the order_params DataContext to that of the ContextMenu
var order_params = AlgoEditorGridView.create_attach_view();
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.attach_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
#endregion
#region Dialog Skirt
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var send_attach_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.send_attached_orders_cmd))) {
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
send_attach_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, send_attach_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "attach_border",
Child = grid,
};
#endregion
popup.Child = border;
// set to true for debugging
popup.StaysOpen = false;
popup.PlacementTarget = attach_button;
popup.Placement = PlacementMode.Bottom;
@@ -857,14 +869,13 @@ namespace mbts.spark {
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
});
#endregion
#region Detach Button
var detach_button = new Button() {
Name = "detach_button",
Content = "D",
Content = new Image() { Source = Icons.DetachAlgos },
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Focusable = false,
@@ -893,6 +904,83 @@ namespace mbts.spark {
the_whole_thing.Children.Add(popup);
return the_whole_thing;
UIElement create_popup_child() {
#region OrderParamsPanel
// finally, set the order_params DataContext to that of the ContextMenu
var order_params = AlgoEditorGridView.create_attach_view();
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.attach_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
#endregion
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var send_attach_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.send_attached_orders_cmd))) {
// set to true for debugging
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
send_attach_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, send_attach_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}
};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "attach_border",
Child = grid,
};
return border;
}
}
}
@@ -913,17 +1001,21 @@ namespace mbts.spark {
public override FrameworkElement view_factory() {
var popup = new System.Windows.Controls.Primitives.Popup();
#region Attach Button
#region Update Button
// create the attach button and assign the menu to it
// create the update button and assign the menu to it
var update_button = new Button() {
Name = "update_button",
Content = "···",
Margin = new Thickness(2),
VerticalAlignment = VerticalAlignment.Center,
Focusable = false
Focusable = false,
ToolTip = "Update"
};
update_button.Click += (s, e) => {
popup.Child = create_popup_child();
popup.IsOpen = !popup.IsOpen;
};
update_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
var orderbook_vm_datacontext_binding = new Binding("DataContext");
orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
@@ -938,89 +1030,14 @@ namespace mbts.spark {
row_vm_update_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
update_button.SetBinding(Button.CommandProperty, row_vm_update_command_binding);
// update button is only visible for orders under the control of an algo
var update_button_visibility_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.root_algo_order_type))) {
Converter = ValueConverter.create<CustomOrderDefinition.CustomOneofCase, Visibility>(ord_type => utilities.OrderDataHelper.outright_order_types.Contains(ord_type) ? Visibility.Hidden : Visibility.Visible)
};
update_button_visibility_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
update_button.SetBinding(Button.VisibilityProperty, update_button_visibility_binding);
#endregion
#region OrderParamsPanel
// finally, set the order_params DataContext to that of the ContextMenu
var order_params = new AlgoEditorGridView {
Margin = new Thickness(6),
CanUserChangeAlgoRoot = false,
};
;
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.order_mod_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
#endregion
#region Dialog Skirt
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var modify_selected_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.modify_selected_orders_cmd))) {
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
modify_selected_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, modify_selected_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}
};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "update_border",
Child = grid,
};
#endregion
popup.Child = border;
popup.StaysOpen = false;
popup.PlacementTarget = update_button;
popup.Placement = PlacementMode.Bottom;
@@ -1028,12 +1045,94 @@ 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);
the_whole_thing.Children.Add(popup);
return the_whole_thing;
UIElement create_popup_child() {
var order_params = new AlgoEditorGridView {
Margin = new Thickness(6),
CanUserChangeAlgoRoot = false,
};
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.order_mod_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var modify_selected_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.modify_selected_orders_cmd))) {
// set to true for debugging
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
modify_selected_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, modify_selected_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}
};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "update_border",
Child = grid,
};
return border;
}
}
}
@@ -1115,19 +1214,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 +1239,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,29 @@ 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;
Action<bool> _on_selection_changed;
public OrderBookRowVM(OrderBookVM owner, OrderSecurityAlgoMeta order_tuple, bool is_expanded) {
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
_owner = owner;
this.order_tuple = order_tuple;
is_visible = !is_child;
_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();
_on_selection_changed = on_selection_changed;
}
//// This takes care of the case where the OB has all rows visible
@@ -50,22 +45,22 @@ namespace mbts.spark {
//// when the OrderBookVM.selected_order_changes, this emulates the selected
//// order changing since the current_row setter invokes the selected_order_changed
//// handler
public IObservable<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 +93,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();
@@ -117,7 +120,11 @@ namespace mbts.spark {
public int depth_level { get => _depth_level; set => Set(ref _depth_level, value); }
public bool is_selected {
get => _is_selected;
set => Set(ref _is_selected, value);
set {
if (Set(ref _is_selected, value)) {
_on_selection_changed(value);
}
}
}
public uint prospective_quantity {
get => _prospective_quantity;
@@ -135,45 +142,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);
@@ -183,21 +196,37 @@ namespace mbts.spark {
public ICommand attach_command => _attach_command ?? (
_attach_command = new DelegateCommand(() => {
// the _select me will trigger the OBVM.attach_vm to get primed
_select_me();
if (!is_selected) {
_select_me();
}
_owner.begin_attach_algos();
})
);
public ICommand update_command => _update_command ?? (
_update_command = new DelegateCommand(() => {
_select_me();
if (!is_selected) {
_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,17 +238,15 @@ 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() {
this.is_selected = false;
_owner.current_row = null;
}
private void _select_me() {
this.is_selected = true;
_owner.current_row = this;
}
// These fns present a way for the orderbook to avoid noisy, intermittent collection changes during "Expand/Collapse All" ops,
@@ -227,7 +254,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 +267,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 +289,19 @@ 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));
// root order type
if (old_order_tuple.root_algo.value.CustomCase != order_tuple.root_algo.value.CustomCase) {
RaisePropertyChanged(nameof(root_algo_order_type));
}
// status
if ((Columns.Status & dirty) != 0)
RaisePropertyChanged(nameof(status));
@@ -288,7 +328,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 +391,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 +422,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;
@@ -18,6 +18,7 @@ using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
@@ -31,7 +32,7 @@ using System.Windows.Input;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace mbts.spark {
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting {
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting, IRequestRowSelection {
const double
_min_right_panel_width = 200,
_min_order_params_height = 300,
@@ -41,10 +42,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;
@@ -56,15 +57,24 @@ namespace mbts.spark {
bool
_show_right_panel = OrderBookStartupData.Default.show_right_panel,
_is_attaching_orders,
_is_row_collection_populated;
IReadOnlyDictionary<uint, OrderSecurityAlgoMeta> _cached_orders = DictionaryHelper.empty<uint, OrderSecurityAlgoMeta>();
_is_row_collection_populated,
_all_rows_selected;
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;
Dictionary<uint, IDisposable> _on_updated_single_callback_gatekeeper = new Dictionary<uint, IDisposable>();
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,8 +100,18 @@ 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>(
o => new OrderBookRowVM(this, o, rows_are_initially_expanded) {
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint>(
o => new OrderBookRowVM(this, o, rows_are_initially_expanded, (row_is_selected) => {
// all are selected and one is checked off
if (all_rows_selected && !row_is_selected) {
_all_rows_selected = false;
RaisePropertyChanged(nameof(this.all_rows_selected));
// not all selected and now the last one gets turned on
} else if (!all_rows_selected && _row_collection.All(r => r.is_selected)) {
_all_rows_selected = true;
RaisePropertyChanged(nameof(this.all_rows_selected));
}
}) {
is_attachable = _cached_attachable.TryGetValue(o.order.Key, out bool ia) && ia,
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
},
@@ -118,7 +138,7 @@ namespace mbts.spark {
toggle_show_fills_cmd = new DelegateCommand(() => show_fills = !show_fills);
launch_fills_settings_cmd = concated_disposable(new DialogCommand(() => {
const string title = "Order Book 'Mini' Fills Grid";
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_fills_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
@@ -130,7 +150,7 @@ namespace mbts.spark {
),
show_save_as_defaults: false);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = title, DataContext = vm };
var dialog = new ShellSettingsDialog { Title = title, DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
@@ -139,6 +159,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 +182,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 +270,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 +297,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 +313,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 => {
@@ -319,6 +347,7 @@ namespace mbts.spark {
public event Action<OrderRecordImmutable?> selected_order_changed;
public event Action begin_attaching, end_attaching;
public event Action<(IEnumerable, bool)> request_selection;
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
public ObservableCollection<DIYGrid.ColumnBase> core_columns => _columns;
@@ -404,6 +433,13 @@ namespace mbts.spark {
public bool can_attach_selected_orders => _can_attach_selected_orders.Value;
public bool can_detach_selected_orders => _can_detach_selected_orders.Value;
public Plurality security_plurality { get; private set; }
public bool all_rows_selected {
get => _all_rows_selected;
set {
Set(ref _all_rows_selected, value);
request_selection?.Invoke((_row_collection, _all_rows_selected));
}
}
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
@@ -415,19 +451,43 @@ namespace mbts.spark {
if (e.RemovedItems != null) {
foreach (var row_vm in e.RemovedItems.OfType<OrderBookRowVM>()) {
row_vm.is_selected = false;
_selected_params_mon.on_row_unselected(row_vm);
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
_attachable_selected_orders.Remove(row_vm.order_key);
_detachable_selected_orders.Remove(row_vm.order_key);
}
}
if (e.AddedItems != null) {
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
var root = _get_root(row_vm.order_tuple);
row_vm.is_selected = true;
_selected_params_mon.on_row_selected(row_vm);
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
row_vm.updated += on_updated;
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
}
_selected_params_mon.on_order_selected(root);
if (row_vm.is_attachable)
_attachable_selected_orders.Add(row_vm.order_key);
if (row_vm.is_detachable)
_detachable_selected_orders.Add(row_vm.order_key);
void on_updated((OrderSecurity oldval, OrderSecurity newval) change) {
if (!row_vm.is_selected) {
// self destruct
if (_on_updated_single_callback_gatekeeper.TryGetValue(row_vm.order_key, out var unsubscribe_updated_disposable)) {
unsubscribe_updated_disposable.Dispose();
_on_updated_single_callback_gatekeeper.Remove(row_vm.order_key);
}
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
return;
}
var new_root = _get_root(change.newval);
if (!new_root.Equals(root)) {
// unselect and select in one smooth motion via on_row_updated
// because we thought there may be gremlins afoot when doing
// on_row_unselected and then on_row_selected
_selected_params_mon.on_order_updated((root, new_root));
root = new_root;
}
}
}
}
RowSelectionHelper.apply_changes(selected_rows, e);
@@ -505,11 +565,11 @@ namespace mbts.spark {
if (!new_sort.Equals(old_sort)) {
/// Both filter and sort have changed, use <see cref="OperableHierarchicalCollection{TRow, T, TKey}.defer_refresh"/> to avoid syncing twice.
using (_row_collection.defer_refresh()) {
_row_collection.filter = new_filter.build_predicate();
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
_row_collection.comparer = new_sort.comparer;
}
} else {
_row_collection.filter = new_filter.build_predicate();
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
}
} else if (!new_sort.Equals(old_sort))
_row_collection.comparer = new_sort.comparer;
@@ -529,21 +589,50 @@ 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);
internal void detach_orders(OrderBookRowVM order) =>
_detach_orders_async(new[] { order }).ignore();
OrderBookRowVM _get_root_row(OrderBookRowVM row) {
while (row.parent_order_key != 0 && _row_collection.get_parent(row.parent_order_key) is OrderBookRowVM prow)
row = prow;
return row;
}
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
OrderSecurity _get_root(OrderSecurity os) {
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
os = pos;
return os;
}
internal void detach_orders(OrderBookRowVM row) {
if (row.is_selected) {
_detach_orders_async(selected_rows).ignore();
} else {
_detach_orders_async(new[] { row }).ignore();
}
}
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);
internal void show_order_modify(OrderBookRowVM row) =>
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
internal void cancel_order(OrderBookRowVM row) =>
_bus.post(SparkTags.CancelOrders, new List<uint> { row.order_key });
internal void cancel_order(OrderBookRowVM row) {
if (row.is_selected) {
_bus.post(SparkTags.CancelOrders, selected_rows.Select(r => r.order_key).ToList());
} else {
_bus.post(SparkTags.CancelOrders, new List<uint> { row.order_key });
}
}
internal void modify_order_quantity(OrderBookRowVM row, uint size) =>
_bus.post(SparkTags.UpdateOrder, (row.order_key, row.price, size));
@@ -566,7 +655,7 @@ namespace mbts.spark {
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
if (!fd.Equals(_cached_filter_data)) {
_cached_filter_data = fd;
_row_collection.filter = fd.build_predicate();
_row_collection.filter = fd.build_predicate(_model.dynamic_helper.dynamic_type_index);
}
}
@@ -582,6 +671,7 @@ namespace mbts.spark {
}
}
// Note: modifications are performed on the selected orders' ROOT orders.
async Task _modify_selected_orders_async() {
double? price = DoubleHelper.nan_nullable(order_mod_vm.price);
uint? size = order_mod_vm.size;
@@ -594,13 +684,17 @@ namespace mbts.spark {
// their values will be cleared (in the param grid gui), and the modifications will not be applied.
// FYI this behavior is implemented to support 'partial edits.' (e.g., changing the 'price' for multiple orders that do not have the same 'size').
// ----
var update_params = selected_rows.Select(update_params_factory).ToList();
var update_params = selected_rows.Select(r => update_params_factory(_get_root(r.order_tuple))).ToList();
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderBookRowVM row) {
var mod_algo = row.order.Def.CustomDef.CloneToMutable();
mod_algo_vm.assign_to(mod_algo);
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurity order_tuple) {
var mod_algo = order_tuple.order.Def.CustomDef.CloneToMutable();
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
} 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);
return (order_tuple.order, OrderDataHelper.EqualsHelpers.equals(order_tuple.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
}
} else await Task.WhenAll(selected_rows.Select(r => _model.update_order(r.order, price)));
}
@@ -840,10 +934,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,8 +944,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)))) :
null);
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))),
Observable.Return(LadderSettings.Default)) : null);
RaisePropertyChanged(nameof(ladder_vm));
}
void _assign_param_permissions<T>(IAlgoParamVM<T> param, ref AlgoParamPermissions permissions_backing, AlgoParamPermissions permissions) {
@@ -873,8 +968,8 @@ namespace mbts.spark {
this.column_filters = column_filters;
}
public Func<OrderSecurityAlgoMeta, bool> build_predicate() {
var pred = column_filters.build_predicate();
public Func<OrderSecurity, bool> build_predicate(IMaskIndexInt32<string> dynamic_type_index) {
var pred = column_filters.build_predicate(dynamic_type_index);
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
if (pred == null) {
pred = tree_disp_pred;
@@ -882,7 +977,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 +1012,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>();
@@ -971,37 +1066,33 @@ namespace mbts.spark {
public IDisposable begin_udpate() =>
_group_mon.defer_updates();
public void on_row_selected(OrderBookRowVM row) {
_group_mon.add(row.order_tuple);
row.updated += _on_row_updated;
}
public void on_order_selected(OrderSecurity order_tuple) =>
_group_mon.add(order_tuple);
public void on_row_unselected(OrderBookRowVM row) {
_group_mon.remove(row.order_tuple);
row.updated -= _on_row_updated;
}
public void on_order_unselected(OrderSecurity order_tuple) =>
_group_mon.remove(order_tuple);
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
_security_md_map = security_md_map;
_security_md_map_changed?.Invoke();
}
void _on_row_updated((OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) ch) {
public void on_order_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 +1195,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(),
@@ -160,7 +161,7 @@ namespace mbts.spark {
toggle_expand_all_command.group = CommandGroups.Auxiliary;
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
var gs_vm = new ShellSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
_settings_manager,
us => us.order_book_grid,
UserSettings.FileExtensions.GRID,
@@ -174,7 +175,7 @@ namespace mbts.spark {
}
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { DataContext = gs_vm };
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(gs_vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
Converter = ValueConverter.create<string, string>(str => (string.IsNullOrEmpty(str) ? _default_title : str) + " Settings")
});
@@ -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);
}
}
@@ -1,194 +1,155 @@
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
[ProtoMember(8)]
public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public UIntMinMaxState qty;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public UIntMinMaxState qty_filled;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
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(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
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);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols));
}
static class FilterStatesExtensions {
public static Func<OrderSecurityAlgoMeta, bool> build_predicate(this FilterStates fs) {
var predicates = new List<Func<OrderSecurityAlgoMeta, bool>>();
// security
if (fs.security_criteria?.is_defined() ?? false)
predicates.Add(o => o.security.HasValue && ContractPickerModel.security_in_criteria(fs.security_criteria, o.security.Value));
// 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());
// time
add_time_filter(fs.time_created, o => o.order.created());
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);
// 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;
}
}
}
}
}
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.utility;
using ProtoBuf;
using System;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
//[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)]
public UIntMinMaxState qty;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public UIntMinMaxState qty_filled;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
[ProtoMember(25)]
public UIntMinMaxState piq;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
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) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
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) &&
piq.safe_equals(other.piq);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols),
piq?.GetHashCode() ?? 0);
}
static class FilterStatesExtensions {
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs, IMaskIndexInt32<string> dynamic_type_index) {
var pb = new ColumnPredicateBuilder<OrderSecurity>();
// security
pb.add_security_criteria(fs.security_criteria, o => o.security);
// regex
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
// time
pb.add_time_filter(fs.time_created, o => o.order.created());
pb.add_time_filter(fs.last_change, o => o.order.last_change());
// discrete values
pb.add_discrete_values(fs.users, o => o.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.account());
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.Def.CustomDef));
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key, dynamic_type_index));
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.status());
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.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>
@@ -111,61 +110,7 @@
</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>
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay}" />
</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,55 @@ namespace mbts.spark {
Attach = 1 << 25,
Detach = 1 << 26,
Update = 1 << 27,
Commit = 1 << 28
Commit = 1 << 28,
RootAlgoType = 1 << 29,
Piq = 1 << 30,
Check = 1 << 31
}
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;
}
}
+33 -17
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";
@@ -511,7 +516,7 @@ namespace mbts.spark.utilities {
}
}
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type) {
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type, bool fit_all_to_spreader = false) {
var mdef = defi.CloneToMutable();
switch (security_type) {
case Security.ContractOneofCase.Synthetic:
@@ -537,7 +542,8 @@ namespace mbts.spark.utilities {
case CustomOrderDefinition.CustomOneofCase.Stop:
case CustomOrderDefinition.CustomOneofCase.StopLimit:
case CustomOrderDefinition.CustomOneofCase.Market:
def = null;
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg:
def = fit_all_to_spreader ? new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() } : null;
break;
default:
var desc_data = _algo_type_case_descriptor_data[def.CustomCase];
@@ -594,6 +600,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,
@@ -616,6 +623,13 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top
},
outright_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
};
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
@@ -636,12 +650,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 +871,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 +995,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 +1019,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 };
@@ -26,27 +26,26 @@ using System.Windows.Input;
namespace mbts.spark {
class InlineOrderProperties {
public readonly InlineUserProperty<AccountTuple> account;
public readonly InlineUserProperty<uint> account;
public readonly InlineUserProperty<TimeInForce> time_in_force;
public readonly InlineUserProperty<CustomOrderDefinition> order_type_def;
public readonly InlineUserProperty<AlgoID> algo;
public InlineOrderProperties(OrdersModel model, LadderSettings settings) : this(
new InlineUserProperty<AccountTuple>(),
new InlineUserProperty<uint>(settings.default_account, settings.default_account != 0),
new InlineUserProperty<TimeInForce>(settings.default_time_in_force, true),
new InlineUserProperty<CustomOrderDefinition>(model.factory.custom_order(settings.default_order_type), true)) { }
new InlineUserProperty<AlgoID>(settings.default_algo, true)) { }
public InlineOrderProperties(OrdersModel model, OrderDefinitionImmutable def) : this(
new InlineUserProperty<AccountTuple>(),
new InlineUserProperty<TimeInForce>(def.TimeInForce, true),
new InlineUserProperty<CustomOrderDefinition>(def.CustomDef.CloneToMutable(), true)) { }
new InlineUserProperty<uint>(def.AccountId, def.AccountId != 0),
new InlineUserProperty<TimeInForce>(def.TimeInForce, true)) { }
public InlineOrderProperties(
InlineUserProperty<AccountTuple> account=null,
InlineUserProperty<uint> account=null,
InlineUserProperty<TimeInForce> time_in_force=null,
InlineUserProperty<CustomOrderDefinition> order_type_def=null) {
this.account = account ?? new InlineUserProperty<AccountTuple>();
InlineUserProperty<AlgoID> algo = null) {
this.account = account ?? new InlineUserProperty<uint>();
this.time_in_force = time_in_force ?? new InlineUserProperty<TimeInForce>();
this.order_type_def = order_type_def ?? new InlineUserProperty<CustomOrderDefinition>();
this.algo = algo ?? new InlineUserProperty<AlgoID>();
}
}
@@ -102,14 +101,23 @@ namespace mbts.spark {
_favorite_algo_definitions_view.LiveFilteringProperties.Add(nameof(AlgoDefinitionVM.is_favorite));
_favorite_algo_definitions_view.IsLiveFiltering = true;
favorite_algo_definitions_view.Filter = d => d.is_favorite;
//_accounts
var inline_account_src = order_properties.AggregateLatest(
model.factory.subscribe_accounts(),
(last, change, cache) => convert_inline_account(change.New.account, cache),
(last, props, change) => convert_inline_account(props.account, change.New),
() => new InlineUserProperty<AccountTuple>(),
new InlineOrderProperties(),
DictionaryHelper.empty<uint, string>());
_accounts = backing(
nameof(selected_account),
model.factory.subscribe_account_tuples(),
order_properties.Select(ops => ops.account),
inline_account_src,
a => new AccountTuple?(a),
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
EqualityComparer<AccountTuple?>.Default,
new[] { new PropertyValidation<AccountTuple?>(a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account")) }.ToReadOnlyList());
// time in force
_time_in_force = backing(
nameof(selected_time_in_force),
// remember, both observables must fire once to start the CombineLatest sequence
@@ -153,9 +161,25 @@ 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);
var inline_algo_relay = new SelectionModelInlinePropRelay<AlgoDefinitionVM, AlgoDefinitionVM, AlgoID>(_selected_algo_definition, vm => AlgoID.from_algo_key(vm.key, _model.dynamic_helper.dynamic_type_index));
//
_dispose = new CompositeDisposable(
order_properties.OnDispatcher().Subscribe(props => {
inline_algo_relay.on_inline_prop(props.algo);
}),
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,
@@ -214,6 +238,7 @@ namespace mbts.spark {
Disposable.dispose(ref _algo_param_rows_hook);
}
};
// local methods
async Task save_template_async(AlgoDefinitionTemplateVM template) {
using (_preserve_selection_by_index())
await AlgoPersistenceHelper.persist_template(model, algo_definitions, template);
@@ -238,6 +263,10 @@ namespace mbts.spark {
break;
}
}
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map) =>
account_id.has_value && account_map.TryGetValue(account_id.value, out string name) ?
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), account_id.force_override) :
new InlineUserProperty<AccountTuple>();
}
IDisposable _template_collection_sync;
@@ -358,6 +387,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 +417,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 +455,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)) {
+5 -34
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" />
@@ -42,6 +42,7 @@
<PackageReference Include="System.Reactive" Version="3.1.1" />
<PackageReference Include="protobuf-net" Version="2.3.2" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="WPFTextBoxAutoComplete" Version="1.0.5" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml" Generator="MSBuild:Compile" />
@@ -52,12 +53,6 @@
<Resource Include="**\*.png" />
<ConfigDefaults Include="../../lib/config/defaults/*.conf" />
</ItemGroup>
<ItemGroup>
<None Remove="Sounds\game_sound_correct.wav" />
<None Remove="Sounds\whistle.wav" />
<None Remove="view\Controls\TickUpDown\TickUpDownNullableUIntCommit.xaml" />
<None Remove="view\Shell\Workspaces\WorkspacesView.xaml" />
</ItemGroup>
<ItemGroup>
<Resource Remove="logo.ico" />
</ItemGroup>
@@ -68,29 +63,5 @@
<EmbeddedResource Include="Sounds\game_sound_correct.wav" />
<EmbeddedResource Include="Sounds\whistle.wav" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controls\SecurityPicker\" />
<Folder Include="view\Extensions\ItemsControl\ObservableItemsSource\" />
</ItemGroup>
<ItemGroup>
<Compile Update="view\Controls\TickUpDown\TickUpDownNullableUIntCommit.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="view\Shell\Workspaces\WorkspacesDialogView.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<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="view\Controls\DIYGrid\Settings\SimpleGridSettingsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
</ItemGroup>
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
</Target>
<Target Name="diagnostic" AfterTargets="PreBuildEvent" />
</Project>
+1
View File
@@ -14,6 +14,7 @@
<ResourceDictionary Source="/view/Resources/Buttons.xaml" />
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
<ResourceDictionary Source="/view/Resources/MenuItems.xaml" />
<ResourceDictionary Source="/view/Controls/ToolBar/ToolBar.xaml" />
<ResourceDictionary Source="/view/Controls/EditableTextBlock/EditableTextBlock.xaml"/>
<ResourceDictionary Source="/view/Controls/DragDropItemsControl/DragDropItemsControl.xaml" />
<ResourceDictionary Source="/view/Controls/DragDropItemsControl/DraggableContentControl.xaml" />
+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.
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.collections {
class ItemSelector<T> : ViewModelBase {
@@ -128,7 +129,6 @@ namespace mbts.collections {
readonly Property<T> _value;
readonly Func<T, TKey> _key_selector;
readonly string _prop_name;
TKey? _desired_key;
public KeyedItemSelector(IRaiseNotifyPropertyChanged owner, string prop_name, IObservableCollection<T> items, Func<T, TKey> key) {
_owner = owner;
@@ -143,33 +143,42 @@ namespace mbts.collections {
get => _value.Value;
set {
_value.set(value);
if (_desired_key.HasValue) {
if (desired_key.HasValue) {
items.CollectionChanged -= _on_items_CollectionChanged;
_desired_key = null;
desired_key = null;
}
_owner.raise_notify_property_changed(_prop_name);
}
}
public TKey? desired_key { get; private set; }
public bool try_select(TKey key) {
if (items.TryGetFirst(i => _key_selector(i).Equals(key), out T result)) {
value = result;
return true;
} else {
if (!_desired_key.HasValue) {
if (!desired_key.HasValue) {
items.CollectionChanged += _on_items_CollectionChanged;
}
_desired_key = key;
desired_key = key;
return false;
}
}
public IObservable<T> subscribe_value() =>
Observable.Create<T>(o => {
o.OnNext(value);
_value.Changed += on_changed;
return Disposable.Create(() => _value.Changed -= on_changed);
void on_changed(T oldval, T newval) => o.OnNext(newval);
});
void _on_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Replace:
foreach (T i in e.NewItems)
if (_key_selector(i).Equals(_desired_key.Value)) {
if (_key_selector(i).Equals(desired_key.Value)) {
value = i;
return;
}
@@ -188,7 +197,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 {
@@ -128,6 +128,15 @@ namespace mbts.collections {
}
}
public void SyncWithDictionary(IReadOnlyDictionary<TKey, T> dictionary) {
foreach (var k in _map.Keys.Except(dictionary.Keys).ToList())
Remove(k);
foreach (var kv in dictionary) {
if (!ContainsKey(kv.Key))
Add(kv.Value);
}
}
public void SyncWithDictionary<TModel>(IReadOnlyDictionary<TKey, TModel> dictionary, Func<TModel, T> factory) {
foreach (var k in _map.Keys.Except(dictionary.Keys).ToList())
Remove(k);
@@ -206,6 +215,11 @@ namespace mbts.collections {
}
static class ObservableKeyedCollectionExtensions {
public static void AddRange<T, TKey>(this ObservableKeyedCollection<T, TKey> collection, IEnumerable<T> values) {
foreach (var v in values)
collection.Add(v);
}
public static IReadOnlyObservableKeyedCollection<TResult, TKey> WrapSelector<T, TKey, TResult>(this IReadOnlyObservableKeyedCollection<T, TKey> source, Func<T, TResult> selector) =>
new SelectorWrapper<T, TKey, TResult>(source, selector);
@@ -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)
Loaded 100 of 388 files, more files were not shown because too many files have changed in this diff. Show more