Compare commits

...
286 Commits
Author SHA1 Message Date
Kevin Darby e0fdf74a73 fix crash where account name > 12 chars, tested with long account name 2019-07-11 17:51:45 -04:00
Kevin Darby ebedd17129 Merge branch 'master' of bitbucket.org:bluetradesys/spark into excel_algos 2019-07-11 14:59:19 -04:00
Kevin Darby 0f8ed5dacb range to vector mapping 2019-07-11 14:15:06 -04:00
Pedro Pinto 8553229119 Merge branch 'master' into prod 2019-07-11 10:45:10 -04:00
Pedro Pinto 4903c12533 _index.md edited online with Bitbucket 2019-07-11 14:23:13 +00:00
Jake Norbut 88f23530c6 Merged in SP-119 (pull request #298)
RTM - SP-119 Ladder settings NullRef

Approved-by: John Colvin <john.colvin@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-11 14:18:45 +00:00
Kevin Darby 14f4452599 . 2019-07-10 23:21:56 -04:00
Kevin Darby 22002c43cf getting there 2019-07-10 18:43:09 -04:00
Kevin Darby 7551e23877 spreadsheet 2019-07-10 00:06:27 -04:00
Kevin Darby 22c92c3e17 ranges... 2019-07-10 00:05:24 -04:00
Kevin Darby 613823e7c5 arrays starting to work 2019-07-09 23:22:18 -04:00
Kevin Darby f0a9a2415d adding notion of bts_parameters 2019-07-09 18:31:05 -04:00
Kevin Darby 708e63e36e almost have C 2019-07-09 14:39:11 -04:00
Jake Norbut 76ff36e514 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-119 2019-07-09 11:54:46 -05:00
Jake Norbut 740c14c261 add LadderSettings santization on paths to VM instantiation 2019-07-09 11:52:41 -05:00
Kevin Darby 53990297e8 parsing right along 2019-07-08 23:54:55 -04:00
Kevin Darby 9570be8bfa it builds, ship it. 2019-07-08 23:16:31 -04:00
Kevin Darby 0b15570d00 more parser 2019-07-08 18:22:12 -04:00
Jake Norbut 33a71f5f47 Merged in SP-105_account-in-ladder (pull request #294)
RTM - SP-105 selected account in ladder

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 22:21:46 +00:00
Pedro Pinto 2940601f4b Updated release notes 2019-07-08 18:15:31 -04:00
Pedro Pinto 1f7939f49a Merge branch 'SP-105_account-in-ladder' of bitbucket.org:bluetradesys/spark into SP-105_account-in-ladder 2019-07-08 18:14:45 -04:00
Pedro Pinto 48e273a607 Merge remote-tracking branch 'origin' into SP-105_account-in-ladder 2019-07-08 17:50:43 -04:00
John Colvin e35634020d Merged in SP-89-offset-algo (pull request #286)
RTM SP-89 offset algo

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:49:12 +00:00
Pedro Pinto 1ede053f59 Merged with master 2019-07-08 17:48:16 -04:00
Pedro Pinto 93824786c7 Updated release notes. 2019-07-08 17:32:33 -04:00
Pedro Pinto 25b8c0a728 Merged with master 2019-07-08 17:31:22 -04:00
Jake Norbut 0e4fe1b47e filter the SelectorProperty source for updates that actually have items; this keeps the currently selected account through server restarts 2019-07-08 16:29:19 -05:00
Jake Norbut cd744963ff Revert "don't revert to default selected setter when server reboots"
This reverts commit d5fdcc6796.
2019-07-08 16:21:46 -05:00
Kevin Darby fb9d877938 Merged in dme (pull request #293)
RTM add mic for DME

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:08:53 +00:00
Pedro Pinto 087b001510 Updated release notes. 2019-07-08 17:03:56 -04:00
Pedro Pinto 02a7c0ddca Merge branch 'master' into dme 2019-07-08 17:01:50 -04:00
Kevin Darby 6afa1d581d Merged in span (pull request #263)
RTM: Span

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:01:01 +00:00
Taha Afzal 5e04abe573 Combining the account lookup for loss_limitm and span 2019-07-08 16:50:30 -04:00
Pedro Pinto 3422a44c87 Updated release notes. 2019-07-08 16:49:32 -04:00
Jake Norbut d5fdcc6796 don't revert to default selected setter when server reboots 2019-07-08 15:37:38 -05:00
Kevin Darby f12217d76c Remove mapping of DME mic per Pedro 2019-07-08 20:12:47 +00:00
Pedro Pinto d85a914046 Merge branch 'master' into span 2019-07-08 15:58:06 -04:00
Pedro Pinto 4fdc890cb5 Merged in ticks_off (pull request #296)
RTM Added ticks_off to top algo server side.

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-07-08 19:55:01 +00:00
Pedro Pinto 3ed0bd9052 Merged with master 2019-07-08 15:54:39 -04:00
Jake Norbut dbdc915ff2 populating mini_ob (LadderMiniOrderBookSettings) in LadderSettings.Default 2019-07-08 14:24:36 -05:00
Jake Norbut 10476c6662 null ref check on LadderSettings.mini_ob.value 2019-07-08 14:22:55 -05:00
Kevin Darby 5fbc1397b5 messing with proj 2019-07-08 14:59:12 -04:00
Kevin Darby 05e09cb977 Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-07-08 14:33:37 -04:00
Kevin Darby 38bd2ef321 Merge branch 'master' of bitbucket.org:bluetradesys/spark into excel_algos 2019-07-08 14:29:03 -04:00
Eric Harding dab4f5acbb Merged in SP-104-HCL (pull request #289)
[RTM] SP-104 HCL

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 18:28:44 +00:00
Eric Harding a20c53b23d Merged in SP-68-crash (pull request #292)
[RTM] SP-68 Fix crash on exit by not touching queue on windows.  insert_or_replace() is a no-op on windows anyway.

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 18:27:59 +00:00
Pedro Pinto 8206f457b5 Updated release notes. 2019-07-08 14:27:37 -04:00
Pedro Pinto d4e6d6293d Merge branch 'master' into SP-68-crash 2019-07-08 14:26:48 -04:00
Kevin Darby d1a2197ba4 Merged in fix_auth_tests2 (pull request #297)
RTM fix auth tests to respect difference betwen admin and trader login
2019-07-08 18:20:09 +00:00
Kevin Darby 369fb55e90 fix auth tests to respect difference betwen admin and trader login 2019-07-08 14:20:14 -04:00
Pedro Pinto 37dacff74d Fixed a bug in top update 2019-07-08 14:09:37 -04:00
Pedro Pinto d297b703c7 Merge branch 'master' into ticks_off 2019-07-08 13:36:53 -04:00
Kevin Darby 59c00203a9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into excel_algos 2019-07-08 13:10:23 -04:00
Kevin Darby 33f533af9b set span to default off, hook up admin boolean to enforce span behind config key 2019-07-08 13:08:18 -04:00
Kevin Darby 085d6402f4 Merged in SPARK-2524-keycode_fixes (pull request #290)
RTM fixed SPARK-2524, allowing admin logins to not require a keycode session

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 16:26:51 +00:00
Pedro Pinto f4395ef833 Updated release notes. 2019-07-08 12:26:27 -04:00
Pedro Pinto e7a9412124 Updated release notes. 2019-07-08 12:21:39 -04:00
Pedro Pinto da4ee38abd Merge branch 'master' into SPARK-2524-keycode_fixes 2019-07-08 12:20:50 -04:00
Kevin Darby 706e5bc73b excel 2019-07-08 11:22:53 -04:00
Pedro Pinto af39fac368 Merge branch 'master' into SPARK-2524-keycode_fixes 2019-07-08 11:18:39 -04:00
Michael Harding 4f997505f3 Enforce Margin Limit in Spark Admin 2019-07-08 11:12:55 -04:00
Jake Norbut fcbb66361d Merged in SP-103_no_reset_on_agg_side_change (pull request #288)
RTM - SP-103 don't reset last when aggressor side changes

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:46:17 +00:00
Pedro Pinto fa16321691 Updated release notes. 2019-07-08 10:45:56 -04:00
Pedro Pinto ceb3542e15 Merge branch 'master' into SP-103_no_reset_on_agg_side_change 2019-07-08 10:30:25 -04:00
Jake Norbut 32e55d07ee Merged in SP-110_volume_tooltip_nit (pull request #295)
RTM - display tooltip again when re-entering same cell where tooltip was previouly closed

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:28:34 +00:00
Pedro Pinto e079acf91f Updated release notes. 2019-07-08 10:28:18 -04:00
Pedro Pinto fecedb203a Merge branch 'master' into SP-110_volume_tooltip_nit 2019-07-08 10:26:59 -04:00
Jake Norbut 6ac05a47e1 Merged in SP-103_bars_not_req (pull request #291)
RTM - SP-103 bars not required to show last text

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:15:36 +00:00
Pedro Pinto 36ed80dc87 Updated release notes. 2019-07-08 10:15:14 -04:00
Kevin Darby 6cf395199e working on visitor 2019-07-06 23:25:51 -04:00
Kevin Darby 1707c3268f excel formula grammar, antlr4 integration. Successful parse tree 2019-07-05 18:24:41 -04:00
John Colvin f5bbc4245f SP-111 / SP-113 Add ticks off parameter to top algo UI 2019-07-05 16:12:32 -05:00
Pedro Pinto 96fc54ff0f Added ticks_off to top algo server side. 2019-07-05 14:35:18 -04:00
Kevin Darby 25b69e4f7d fix small bug and add manual test 2019-07-05 13:29:22 -04:00
Kevin Darby d871ffca6d temporarily tie span enforcement to config while michael works 2019-07-05 12:59:41 -04:00
Jake Norbut 213567bcdb display tooltip again when re-entering same cell where tooltip was previouly closed 2019-07-05 11:49:34 -05:00
Kevin Darby b7e5c3a374 Merge branch 'span' of bitbucket.org:bluetradesys/spark into span 2019-07-05 12:30:10 -04:00
Kevin Darby ae4a8bbf46 more span 2019-07-05 12:28:58 -04:00
Taha Afzal 850386c1d4 Margin enforced in the limiter 2019-07-05 12:05:16 -04:00
Kevin Darby 2d4e94a636 Types.fs edited online with Bitbucket 2019-07-05 15:39:35 +00:00
Kevin Darby 9b73ad2486 playing with excel parsing 2019-07-05 11:06:19 -04:00
Pedro Pinto e447096733 Routed excess liquidity to OrderManager. 2019-07-05 11:00:42 -04:00
Jake Norbut cd68b95a3e add a mechanism for ignoring InlineUserProperty's force_override property 2019-07-05 08:19:05 -05:00
Kevin Darby 205eebb7da excel formulae 2019-07-04 23:43:22 -04:00
Kevin Darby ef4dc1fc65 more excel 2019-07-04 23:25:28 -04:00
Michael Harding ebad5b389f Fix Save button 2019-07-03 16:01:50 -04:00
John Colvin 160c49514a Merge remote-tracking branch 'origin/master' into SP-89-offset-algo 2019-07-03 14:38:50 -05:00
Michael Harding 4f7fcc5634 Move loss limits to the top of the limits page 2019-07-03 14:54:40 -04:00
Michael Harding 500e5b5fe4 Polling and commas 2019-07-03 14:19:51 -04:00
Michael Harding 9d924fa4ba Margin table formatting 2019-07-03 10:14:01 -04:00
John Colvin 092e63e300 OffsetAlgo attach impl (take 2) 2019-07-03 00:07:23 -05:00
Kevin Darby 4654cd4739 changes in margin workflow for webui 2019-07-02 18:29:10 -04:00
Kevin Darby f2f06d76de PR feedback 2019-07-02 17:43:28 -04:00
Kevin Darby 6bc293a58e add mic for DME 2019-07-02 17:35:29 -04:00
Kevin Darby fdb462eda4 simplify margin server states 2019-07-02 17:34:46 -04:00
Kevin Darby cc579034b2 fix bug in margin service 2019-07-02 17:05:22 -04:00
John Colvin 4b76e91757 SP-89 (pr edit) - 'can_consume_attachment()' returns false for 'Offset' 2019-07-02 15:48:24 -05:00
Eric Harding 8d34279ab0 SP-68 Fix crash on exit by not touching queue on windows. insert_or_replace() is a no-op on windows anyway. 2019-07-02 16:26:34 -04:00
John Colvin 7973b927a2 SP-89 (pr edit) - fwd price updates to child; cache update qty 2019-07-02 15:22:54 -05:00
Kevin Darby 6997aed9bd fixed SPARK-2524, allowing admin logins to not require a keycode session 2019-07-02 15:05:23 -04:00
Michael Harding b5d7d98098 Add DayPnL to SA Margin tab 2019-07-02 15:04:55 -04:00
Jake Norbut 8f22a326ab last on volume foreground text is now white 2019-07-02 14:01:50 -05:00
Jake Norbut b2c3488c5e draw volume numbers when bars not present 2019-07-02 14:01:07 -05:00
Michael Harding 23f10eac70 Add accountPnl to emptyMargin 2019-07-02 14:10:27 -04:00
Kevin Darby cb31269a70 added pnl impact to margin 2019-07-02 13:14:46 -04:00
Jake Norbut 5b75b8718a updating comment to reflect change 2019-07-02 11:14:01 -05:00
Eric Harding 5a256d4634 SP-104 PEBKAC buy with negative mult is nonsense 2019-07-02 11:55:55 -04:00
Jake Norbut db2de3e8c4 remove last_side; change StructLayout Size accordingly 2019-07-02 10:53:11 -05:00
Jake Norbut b9dcad9549 don't reset last trade size when aggressor changes 2019-07-02 10:52:21 -05:00
Jake Norbut ce104479e1 removing last_side; incrementing shm_version 2019-07-02 10:51:06 -05:00
Eric Harding 2d56eedf3e Bugfix in synthetic_book_merge_tests 2019-07-02 10:37:47 -04:00
Eric Harding 049381908c update gitignore 2019-07-02 10:34:28 -04:00
John Colvin 0067e52155 SP-89 (pr edit) - wait 4 mkt 2 launch; attach impl 2019-07-02 00:07:17 -05:00
Kevin Darby 8ed6d297a9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-07-01 18:01:34 -04:00
Kevin Darby 474401cddc new spreadsheet 2019-07-01 17:59:18 -04:00
Taha Afzal a06df5099a Merged in SP-24-loss-limit (pull request #285)
RTM SP-24 loss limit

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 21:44:41 +00:00
Pedro Pinto 8ddd5bdbb5 Merged with master 2019-07-01 17:06:00 -04:00
Taha Afzal 5cbe00beab Spread PNL is not counted towards the Account PNL 2019-07-01 16:33:56 -04:00
Kevin Darby 3ebcf9a270 example of join in excel 2019-07-01 16:21:38 -04:00
Jake Norbut 40cd826260 Merged in SP-79 (pull request #280)
RTM - SP-79 arrow indicator on last disappearing too fast

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 20:03:34 +00:00
Pedro Pinto 8bd63f11b1 Updated release notes. 2019-07-01 16:03:13 -04:00
Pedro Pinto 2198a8d6ed Merge remote-tracking branch 'origin' into SP-79 2019-07-01 16:02:15 -04:00
Pedro Pinto 8023dc7aa8 Merge branch 'master' into SP-79 2019-07-01 16:01:53 -04:00
John Colvin e2b0da8103 Merged in SP-78-miniob-settings (pull request #282)
RTM SP-78 In miniob settings changing the column width directly through the grid (i.e. not through the dialog) has no effect beyond the current popup.

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 19:36:01 +00:00
Pedro Pinto aea233d537 Updated release notes. 2019-07-01 15:35:07 -04:00
Pedro Pinto 5756c090f3 Merge remote-tracking branch 'origin' into SP-78-miniob-settings 2019-07-01 15:28:59 -04:00
John Colvin 5ab0acb0ef Merged in SP-81-private-synths (pull request #284)
RTM SP-81 private synths

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 19:28:00 +00:00
Pedro Pinto 3950e5b634 Updated release notes. 2019-07-01 15:27:29 -04:00
Pedro Pinto 7ce8f1ed97 Updated release notes 2019-07-01 14:57:00 -04:00
Michael Harding 5d53492ab5 Merged SP-38-span-admin into span 2019-07-01 14:53:52 -04:00
Michael Harding 90fd5bd382 Handle save errors better 2019-07-01 14:52:40 -04:00
Michael Harding beff060a4f Update unchanged margin values 2019-07-01 14:27:44 -04:00
Michael Harding bc3418734f Span Columns and Formatting 2019-07-01 13:22:27 -04:00
Michael Harding ab255c8777 update ui.fsproj 2019-07-01 12:51:17 -04:00
Michael Harding cc2bde1064 Merged span into SP-38-span-admin 2019-07-01 12:43:16 -04:00
Michael Harding 94d66a8e99 Add Margin tab to Spark Admin 2019-07-01 12:40:16 -04:00
Taha Afzal 92f08b89cc Added a sub test to the clip limits test to verify that spread clip limits are independent of futures 2019-07-01 09:01:46 -04:00
Taha Afzal 02769479ea SP-74 bundled into this (it was a small change) 2019-06-30 22:45:29 -04:00
John Colvin 4ada322c56 bring back OffsetOrder gui in ladder/ticket 2019-06-30 12:56:59 -05:00
John Colvin 7de7f6e496 remove old OffsetOrder.cpp/h, replace with new OffsetAlgo in CMakeDeps 2019-06-30 12:47:05 -05:00
John Colvin 0700e9e80d SP-89 Port Offset order to new api 2019-06-29 16:10:34 -05:00
Taha Afzal ccca85d1c5 loss limit manual test updated 2019-06-27 15:46:44 -04:00
Taha Afzal 60e933fca9 SOD also indexes its data on account_id, Cleaned up Limiter's account_data (there was an overlap, after introducing Loss limit related PNL data) 2019-06-27 15:20:12 -04:00
Taha Afzal e2aa6034ab merging master 2019-06-26 20:22:05 -04:00
Taha Afzal 8b4c64e8f4 PositionServiceServer not indixes the state based on account_id istead of names 2019-06-26 19:34:46 -04:00
John Colvin b5ea399e7e WorkspacesDialogView - remove 'Is Shared' checkbox 2019-06-26 15:22:14 -05:00
John Colvin 62baa6aa31 MEFExport UserTuple does not need a contract name 2019-06-26 14:19:49 -05:00
John Colvin 87433aa7c3 add comment for 'KnownUsers' prop 2019-06-26 13:41:37 -05:00
John Colvin 3252cab4bb SP-80 / SP-88 Login dialog should not expose user names (because they are private) 2019-06-26 13:22:02 -05:00
John Colvin fbe9b570a3 (forgot to run 'git add' again) 2019-06-26 12:43:48 -05:00
John Colvin 1bcb209961 remove unused delegate type, and worthless commented line 2019-06-26 11:42:47 -05:00
John Colvin b0726ad3ea just noticed some iobservables in OrderBookVM without OnDispatcher()s (adding them preemptively) 2019-06-26 11:40:10 -05:00
John Colvin e0ba27a0a9 SpreaderModel - remove unneccesary 'async/await' wrappers 2019-06-26 11:40:07 -05:00
John Colvin 345e45c5bf MEFExport UserTuple 2019-06-26 10:28:05 -05:00
John Colvin 490add84d9 replace magic consts w/ regular consts 2019-06-25 23:19:20 -05:00
John Colvin 94fc2c4248 refactor LoginModel.Exports -> LoginContainer.Exports 2019-06-25 23:18:43 -05:00
John Colvin 8a7e2db4ac SP-80 / SP-81 Synths should be private 2019-06-25 19:01:57 -05:00
John Colvin 71c576bbc0 SP-80 / SP-82 Algos should be private 2019-06-25 17:05:41 -05:00
John Colvin 2e4dff4d91 SP-76 In miniob settings, the miniob's width seems to have a minimum that does not reflect the currently displayed columns 2019-06-25 16:20:08 -05:00
John Colvin 73336df814 SP-78 In miniob settings changing the column width directly through the grid (i.e. not through the dialog) has no effect beyond the current popup. 2019-06-25 16:04:24 -05:00
Michael Harding 633edb43eb wait longer for the system to have products 2019-06-25 14:36:52 -04:00
Pedro Pinto f4add8d464 Merged in fix_service_init_concurrency (pull request #281)
Fix service init concurrency

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-25 16:44:35 +00:00
Pedro Pinto 894335d019 Updated release notes 2019-06-25 12:29:56 -04:00
Pedro Pinto 0fc977f5d3 Merged with master 2019-06-25 12:29:02 -04:00
Pedro Pinto 3d70d35b99 Changed Run.cpp to defer qbus dispatch past service initialization. 2019-06-25 12:08:20 -04:00
Pedro Pinto 66307edf91 Added a deferred start option to qbus. 2019-06-25 12:08:00 -04:00
Taha Afzal e3e525819e Merged in SPARK-2506_qho_ib_2 (pull request #279)
RTM
2019-06-25 15:03:53 +00:00
Pedro Pinto ece25dd946 Updated release notes. 2019-06-25 10:04:14 -04:00
Pedro Pinto 24f824fd81 Downgraded logging message. 2019-06-25 10:04:04 -04:00
Pedro Pinto 7d992c84ab Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-25 09:53:45 -04:00
Pedro Pinto 1cce474485 Suppressed updates on completed/canceled orders 2019-06-25 09:51:51 -04:00
Taha Afzal 7186c0349b Added logs for order callbacks on QueueHolderOrder 2019-06-24 20:17:29 -04:00
John Colvin 3d829da004 Merged in SP-49-miniob-settings (pull request #277)
RTM SP-49 miniob settings
2019-06-24 23:48:57 +00:00
John Colvin f20fd2136e SP-78 miniob settings - column width adjustments made directly through the grid should be saved in settings 2019-06-24 17:50:30 -05:00
John Colvin 30ce85b70f fix typo in AlgoID.Equals(object) 2019-06-24 17:41:42 -05:00
John Colvin fc7a04a805 LadderSettings must assign show_warn_on_cross in the constructor with the default value since it is 'true' 2019-06-24 16:58:26 -05:00
Jake Norbut 6d96c0e2a0 fix cross check 2019-06-24 16:52:18 -05:00
Pedro Pinto ae913962b3 Updated release notes 2019-06-24 17:41:04 -04:00
Pedro Pinto b52f6fdc24 Merge remote-tracking branch 'origin' into SP-49-miniob-settings 2019-06-24 17:24:27 -04:00
Taha Afzal 29d14404e6 PositionService has access to AdminService to resolve accounts now 2019-06-24 17:15:04 -04:00
John Colvin d13f1b04ed LadderWindowFactory - actually dispose the settings subscription when ladder closes 2019-06-24 16:06:11 -05:00
John Colvin 8d234d9b8b LadderSettingsView - 'Auto Clear' default value checkbox's checkmark was backwards 2019-06-24 15:32:22 -05:00
John Colvin d261d6fc31 LadderSettingsView - fix 'Order Quantities' tab 'hint text' vertical alignment (apparently RowDefinition.Height is a just a suggestion to wpf - using MaxHeight now) 2019-06-24 14:52:23 -05:00
John Colvin 343bf25aaf ShellSettingsDialog - increase LadderSettingsView width to fit all tabs in 1 row 2019-06-24 14:47:42 -05:00
John Colvin 0f93757506 Merge remote-tracking branch 'origin/SP-51_cross_warning' into SP-49-miniob-settings 2019-06-24 14:42:26 -05:00
Taha Afzal 60b7966f2f Loss limit manual test 2019-06-24 15:33:47 -04:00
John Colvin 6cad454f9e Merged in ladder-settings-round2 (pull request #273)
RTM Ladder settings round2

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-24 19:32:05 +00:00
Pedro Pinto b3f6649e7f Updated release notes. 2019-06-24 15:30:26 -04:00
Jake Norbut 1059351334 adding the settings_subscription to be disposed; fixes swallowed NullRef; not directly related to this branch 2019-06-24 14:15:53 -05:00
John Colvin 3263cc840a SP-77 in miniob settings, changing the column order does not seem to have an effect 2019-06-24 14:15:50 -05:00
Pedro Pinto 8d0e93a311 Merge remote-tracking branch 'origin' into ladder-settings-round2 2019-06-24 15:04:29 -04:00
Jake Norbut 179ee000a3 adding case for when the ladder is first brought up and the market has not yet moved 2019-06-24 14:02:24 -05:00
Jake Norbut 43129427f6 fix arrow disappearing from non-Last-based renders 2019-06-24 13:20:38 -05:00
Pedro Pinto f49d73640e Merge remote-tracking branch 'origin' into SPARK-2506_qho_ib_2 2019-06-24 14:18:34 -04:00
Jake Norbut 7afe1e103d refactor string formatting out of LadderRender 2019-06-24 13:18:29 -05:00
Jake Norbut c1b701ff2b Merged in SP-73 (pull request #274)
RTM - add null check on LadderVM

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-06-24 18:16:37 +00:00
John Colvin 06ca15ccac Merged in SP-75-ladder-security (pull request #275)
RTM SP-75 OrderBook's Ladder uses actual selected order's to get security (not roots)

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-24 18:15:15 +00:00
Pedro Pinto 027efa9d2a Updated release notes 2019-06-24 14:14:35 -04:00
Pedro Pinto 026695d0d1 Merged with master 2019-06-24 14:01:09 -04:00
Jake Norbut 50775b3925 Merged in SP-50-prime (pull request #278)
SP-50 prime last trade accumulation

Approved-by: John Colvin <john.colvin@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-24 17:59:31 +00:00
Pedro Pinto c695b43647 Updated release notes. 2019-06-24 13:58:13 -04:00
John Colvin 56ace9514a MiniOrderBook - set 'Cancel' column [default]width to 37, give other 'magic consts' widths fields in DefaultColumnWidths 2019-06-24 11:48:06 -05:00
Taha Afzal 7c6a26ebd9 All hooked up, on updates, loss limit is checked only on size increases 2019-06-23 19:25:06 -04:00
John Colvin c6c1659953 ShellSettingsDialog - adjust dialog skrit margin to stretch to the edges of the window 2019-06-23 13:36:59 -05:00
John Colvin dc696e9468 ShellSettingsDialog uses 'Admin' icon (gears) 2019-06-23 13:19:18 -05:00
John Colvin a6189d910c ShellSettingsDialog - apply NegativeResizeBorderThickness margin to content root (not just dialog skirt) 2019-06-23 12:25:01 -05:00
John Colvin 049cc69a4f settings dialogs title matches actual shell window titles 2019-06-23 12:18:10 -05:00
Taha Afzal b2766c283d Setting account id in PositionUnit 2019-06-22 22:05:32 -04:00
John Colvin 0b14b048e4 fix ViewModelBase.backing<string>() conflicts between 'string default_value' arg and 'string[] params invalidating props' 2019-06-22 17:26:07 -05:00
John Colvin ffc03a4055 ExpanderToggleButton was getting clipped in GridSettingsEditor 2019-06-22 16:56:45 -05:00
John Colvin 992eb7b104 added 'ImmutableHeader' flag for MiniOB 'Commit' column 2019-06-22 16:47:37 -05:00
John Colvin 314eca0e89 GridSettings - fix DefaultValue attributes/list initialization 2019-06-22 16:41:17 -05:00
John Colvin 626ec79af4 SortingCriteria implements IEquatable 2019-06-22 16:38:02 -05:00
John Colvin 953272b231 GridSettings (Simple)ColumnVM - remove unneccesary Property<T>s, simplify flags logic 2019-06-22 16:38:02 -05:00
John Colvin 4c185093bb make ShellSettingsDialog resizable 2019-06-22 16:38:02 -05:00
Pedro Pinto 6b5b4ea2b1 Merge branch 'SP-24-loss-limit' of bitbucket.org:bluetradesys/spark into SP-24-loss-limit 2019-06-22 16:30:39 -04:00
Pedro Pinto 6f7c6db918 Updated fill account_id in FillStore. 2019-06-22 16:29:55 -04:00
Taha Afzal a1ead3d1c1 cpp routed correctly now 2019-06-22 14:24:15 -04:00
Taha Afzal 1fc35ee3cd Merge branch 'SP-24-loss-limit' of bitbucket.org:bluetradesys/spark into SP-24-loss-limit 2019-06-21 18:43:31 -04:00
John Colvin af576bbee7 SP-37 / SP-49 Add miniob settings to ladder settings 2019-06-21 17:29:16 -05:00
John Colvin 7b9ed4326a DragDropListBox - null check AdornerLayer 2019-06-21 17:27:42 -05:00
John Colvin 615e585c82 simplify mini ob editable price/qty column insertion 2019-06-21 17:21:32 -05:00
Taha Afzal a014dcc984 abstracted _get_handle in PositionServicePNLHelper::HandleIndex 2019-06-21 17:58:08 -04:00
John Colvin 42645f1246 OrderBookGridSettings.with() was dropping algo_tree_display_mode value 2019-06-21 16:26:29 -05:00
Taha Afzal 1c5507a3f4 QueueHolderOrder's QHOOrderManager kills the spurious price/qty updates 2019-06-21 16:09:19 -04:00
Eric Harding a92dd5a841 SP-50 last_trade accumulation 2019-06-21 15:21:57 -04:00
Eric Harding 91a6c946f8 SP-50 last_trade should accumulate trades at the same price 2019-06-21 14:25:15 -04:00
John Colvin cfbd7855a5 OrderBookVM - remove a few unused methods 2019-06-21 11:51:15 -05:00
John Colvin 00e9862844 refactor 'begin_update()' -> 'defer_updates()' for consistency's sake 2019-06-21 11:46:31 -05:00
John Colvin 4d413fb92c OrderBook - remove security->security_md conversion logic from OrderParamsAggregator, get rid of SelectedSecurityMon 2019-06-21 11:45:19 -05:00
Pedro Pinto 6e07a0c109 Fixed build. 2019-06-21 11:51:20 -04:00
Pedro Pinto e936b80dc6 Forwarded pnl updates to order manager. 2019-06-21 11:45:50 -04:00
Taha Afzal 4046f82e06 refactoring accidentally omitted conditional check 2019-06-21 08:35:40 -04:00
Taha Afzal 699a6526f5 Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-20 21:53:10 -04:00
Taha Afzal 12a4c67e4d Modified SingleLegQuoter::_compute_price_quantity to deal with show qty and qho 2019-06-20 21:52:06 -04:00
Jake Norbut e9562b55f3 pr refactoring 2019-06-20 20:16:32 -05:00
Jake Norbut 43735e1dab adding icon param Confirm overload 2019-06-20 20:16:12 -05:00
John Colvin 930723dd0e move ObservableExtensions into spark_core 2019-06-20 18:15:56 -05:00
John Colvin 3e4ee9d6b6 abstract SelectedParamsMon out of OrderBookVM into new class 'OrderParamsAggregator' 2019-06-20 17:11:55 -05:00
John Colvin c771aea311 RefCountMap implements IDictionary/IReadOnlyDictionary 2019-06-20 16:53:16 -05:00
Jake Norbut 7b35467d59 adding warn on cross message box for the approparite criteria 2019-06-20 15:12:01 -05:00
Jake Norbut 192354946d exposing a couple settings as readonly properties so we don't have to expose the entire settings object 2019-06-20 15:09:15 -05:00
Jake Norbut 1eacb7f0f0 adding new Safety Checks tab on which Warn on Market Cross is found 2019-06-20 15:07:16 -05:00
Jake Norbut d0695fd5d7 adding show_warm_on_cross to LadderSettingsVM 2019-06-20 15:05:46 -05:00
Jake Norbut ad8377d13f adding show_warn_on_cross to LadderSettings 2019-06-20 15:04:39 -05:00
Pedro Pinto 60ab67213f Ran clang format. 2019-06-20 15:57:09 -04:00
Pedro Pinto 4055a06601 Merged master 2019-06-20 15:28:27 -04:00
John Colvin 883582630d SP-75 OrderBook's Ladder uses actual selected order's to get security (not roots) 2019-06-20 14:01:47 -05:00
Jake Norbut e7728203cf PR feedback 2019-06-20 12:04:40 -05:00
Jake Norbut dd8586133f add null check on LadderGridHost and LadderVM 2019-06-20 10:18:39 -05:00
Kevin Darby e47af3d7bd update api docs 2019-06-20 10:34:06 -04:00
John Colvin f4aa5cfa8c sec picker in Ladder/Orderbook settings dialogs does not change security when cancel clicked 2019-06-20 00:10:52 -05:00
John Colvin bc3f2db0f9 add OK/Cancel buttons to sec pickers in ladder/orderbook settings dialogs 2019-06-19 19:56:20 -05:00
John Colvin ed08cb11d7 convert ContractPickerPopup ok/cancel actions to events 2019-06-19 19:43:53 -05:00
Taha Afzal 32eeeee94f Adding logs and removing the show qty hardcoded value in QHO 2019-06-19 16:00:23 -04:00
Kevin Darby c3c427742d Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-06-19 13:34:52 -04:00
Michael Harding 83c432d8e2 Merged in SP-34-loss_limit_admin (pull request #268)
SP-34: Add Loss Limit to Spark Admin

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 21:25:47 +00:00
Michael Harding 6a4930767b SP-34: Add Loss Limit to Spark Admin 2019-06-18 16:32:14 -04:00
Kevin Darby 685e69adcc Merge branch 'key_codes' of bitbucket.org:bluetradesys/spark into span 2019-06-18 15:21:10 -04:00
Taha Afzal fe2b167fd5 Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-18 12:06:23 -04:00
Taha Afzal 04fbcc49b1 Merge branch 'SPARK-2506_qho_ib_2' of bitbucket.org:bluetradesys/spark into SPARK-2506_qho_ib_2 2019-06-17 18:18:49 -04:00
Taha Afzal 32229e3236 removing the hard coded values now that it is hooked to the ui 2019-06-17 18:18:41 -04:00
Kevin Darby e9f61dc3c0 added get_excess_liquidity_report rpc for adminui 2019-06-17 18:15:05 -04:00
Jake Norbut f13949ed0f removing Minimum property on ShowQuantity template 2019-06-17 16:49:16 -05:00
Kevin Darby f329470165 added spreadsheet to help test span 2019-06-17 16:49:21 -04:00
Jake Norbut f2b7e692f8 ShowQuantity param now defaults to 1 and the associated TickUpDownUint has a min of 1 as well 2019-06-17 14:49:39 -05:00
Jake Norbut 2cff906c5f adding minimum maximum properties to TickUpDownUint; adding uint overload for within_range 2019-06-17 14:47:48 -05:00
Jake Norbut f711715350 don't allow parameter modification unless the checkbox is checked 2019-06-17 14:43:28 -05:00
Taha Afzal e76a72e3e6 Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-17 13:37:48 -04:00
Kevin Darby d7d319f1e3 fixed span timing 2019-06-17 10:43:27 -04:00
Taha Afzal cddadcb278 PositionService exposes new streaming calls pnl_data and pnl_account_data to get detailed and summary pnls 2019-06-16 22:02:16 -04:00
Kevin Darby 419833d398 slight timing issue with span 2019-06-14 18:10:41 -04:00
Jake Norbut 0f88144fd6 adding new ShowQuantity Enabled + Quantity models 2019-06-14 14:55:15 -05:00
Jake Norbut abe35bddf0 adding ShowQuantity (and params) 2019-06-14 14:54:22 -05:00
Jake Norbut cd262cd66c Adding Show Quantity ParameterSection 2019-06-14 14:52:59 -05:00
Jake Norbut 0133cc8aa5 Adding ShowQuantity Defaults 2019-06-14 14:52:23 -05:00
Kevin Darby 9883553fb5 MarginService 2019-06-14 11:21:09 -04:00
Kevin Darby be5d037ca9 span works again 2019-06-13 17:52:47 -04:00
Taha Afzal 21f5fd919d Changes in the API for Loss Limit and new parameter in the account to enforce that limit 2019-06-13 15:37:43 -04:00
Kevin Darby 159e8fba23 beginnings of span, should't be long 2019-06-12 18:17:09 -04:00
Taha Afzal d1b2dbd688 Hiding the QHO badge 2019-06-11 08:59:52 -04:00
Taha Afzal bebe0c2478 Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-10 20:36:05 -04:00
Taha Afzal 61126fb3d5 QHO order qty's are contrained by the show qty now 2019-06-10 20:32:15 -04:00
Taha Afzal b0886f6b3d First check in, needs to be retofitted to QHO now 2019-06-10 15:58:10 -04:00
Taha Afzal 7fd8eeb1ff Merge branch 'master' into spreader_show_qty 2019-06-10 08:37:32 -04:00
Taha Afzal 5c22b43eb5 show qty added 2019-05-24 11:10:14 -04:00
243 changed files with 7266 additions and 1907 deletions

No files matched your search

+1
View File
@@ -32,3 +32,4 @@ trace_data/
/src/lib/orderman/perf_analysis/last_pretty_log.csv
/src/lib/orderman/perf_analysis/log.csv
/doc/user/public/*
.ionide/*
+1 -1
View File
@@ -104,7 +104,7 @@
"request": "launch",
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
"args": [
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
+42 -1
View File
@@ -4,6 +4,46 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.6.1
### User Visible Changes
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
## 2.6.0
### User Visible Changes
- In the ladder made last trade more distinctive when overlayed on the volumn column
- In the ladder, corrected display of volume tool tip
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
- Added an option for the Top Algo to delete itself if within N ticks from top of book
- Address a rare issue that could lead to an error report on UI exit
- Added margin risk limits
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
- Avoided switching the current account and order type on open ladders when limits are changed
### Internal Changes
- Ensure that admin logins are always allowed to login, regardless of keycode status.
## 2.5.0
### User Visible Changes
- Added loss limit risk check
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
- Allowed the mini order book defaults for column widths to be changed from the mini order book
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
## 2.4.0
### User Visible Settings
- In the ladder, changed the last trade column to accumulate successive trades at the same price
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
- Added ladder settings for the mini order book
- Added a safety check to guard against unintentional market crosses
- Improved the Queue Holder mode in the spreader:
- Added a Show Quantity, allowing the spreader to keep queue position of the trailing orders as Fills (of size Show Qty) happen
- Improved spreader performance when in Queue Holder mode
### Internal changes
- Suppressed order callbacks for completed/canceled orders. This should eliminate some bogus error logs by spreader orders.
- Addressed a potential concurrency issues with service initialization.
## 2.3.0
### User Visible Changes
- Introduced multiple settings profiles for the ladder
@@ -376,7 +416,8 @@ 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
+6 -14
View File
@@ -7,12 +7,12 @@ using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using bts.api.core;
using mbts.ui.shell;
namespace mbts.spark {
[Export]
class AdminModel : IDisposable {
readonly string _user_name;
public string user_name => _user_name;
public readonly UserTuple user;
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
@@ -24,14 +24,14 @@ namespace mbts.spark {
IDisposable _dispose;
[ImportingConstructor]
AdminModel(AdminService service, [Import("username")]string user_name) {
_user_name = user_name;
AdminModel(AdminService service, [Import]UserTuple user) {
this.user = user;
_dispose = service.subscribei_admin_data().Select(update => {
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), _user_name));
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
IEnumerable<BTSUserImmutable> active_users =
from admin_data in update.all
from bts_user in admin_data.Users
where bts_user.Name == user_name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
select bts_user;
IEnumerable<BTSAccountImmutable> active_accounts =
@@ -62,14 +62,6 @@ namespace mbts.spark {
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
public UserTuple? get_current_user() {
foreach (var kv in _user_map) {
// just for good measure, there should never be > 1 element in _user_map, and it should always match _user_name
if (kv.Value == _user_name)
return new UserTuple(kv.Key, kv.Value);
}
return null;
}
public AccountTuple? get_default_account() {
foreach (var kv in _account_map) {
return new AccountTuple(kv.Key, kv.Value);
+2 -1
View File
@@ -3,6 +3,7 @@ using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -22,7 +23,7 @@ namespace bts.ui {
IDisposable _dispose;
[ImportingConstructor]
public AboutDaemon(IBus bus, [Import("username")]string username) {
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
_user = username;
_bus = bus;
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
+28 -2
View File
@@ -10,6 +10,7 @@ using System.Reactive.Linq;
using System.Windows.Threading;
using System.Windows;
using System.Threading.Tasks;
using mbts.ui.shell;
namespace bts.spark
{
@@ -20,7 +21,7 @@ namespace bts.spark
IDisposable _dispose;
[ImportingConstructor]
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
_svc = svc;
_username = username;
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
@@ -82,7 +83,6 @@ namespace bts.spark
}
}
//public static async Task<Result<T>>
public static async Task<(T value, bool success)> safe_execute<T>(this ErrorReporter error_reporter, Func<Task<Result<T>>> execute, Func<T, bool> get_success, Func<T, string> get_error_msg, string desc) {
try {
var result = await execute();
@@ -101,5 +101,31 @@ namespace bts.spark
void _report_error(string msg) =>
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
}
public static async Task<TResult> safe_execute<T, TResult>(
this ErrorReporter error_reporter,
Func<Task<Result<T>>> execute,
Func<T, bool> get_success,
Func<T, string> get_error_msg,
Func<T, bool, TResult> result_selector,
string desc) {
//
try {
var result = await execute();
if (!result.success) {
_report_error(result.error_message);
} else if (!get_success(result.value)) {
_report_error(get_error_msg(result.value));
} else {
return result_selector(result.value, true);
}
} catch (Exception ex) {
_report_error(ex.ToString());
}
return result_selector(default, false);
//
void _report_error(string msg) =>
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
}
}
}
+3 -2
View File
@@ -15,6 +15,7 @@ using System.Collections.Concurrent;
using System.Threading;
using bts.api.user;
using bts.md;
using mbts.ui.shell;
namespace bts {
[Export(typeof(IInitializable))]
@@ -27,8 +28,8 @@ namespace bts {
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
[ImportingConstructor]
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss) :
base(get_naggle(), MDSourceOptions.DeferInit) {
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
_bus = bus;
_token = token;
_ss = ss;
@@ -69,11 +69,11 @@ namespace bts {
cpv.Width = double.NaN;
cpv.Height = double.NaN;
vm.ok_action = () => {
vm.ok_action += () => {
window.Close();
message.complete(select_result(vm)).execute(_bus);
};
vm.cancel_action = () => window.Close();
vm.cancel_action += () => window.Close();
window.move_to_mouse();
window.Show();
}
@@ -3,6 +3,7 @@ using bts.metrics;
using mbts;
using mbts.config;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -23,7 +24,7 @@ namespace bts.perf {
Ping _pinger = new Ping();
[ImportingConstructor]
public ShellLatencyDaemon(ShellLatencyService service, [Import("username")] string user) {
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
_service = service;
_version = Configuration.get<bts.config.System>().version;
_username = user;
@@ -5,6 +5,7 @@ using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
@@ -18,7 +19,7 @@ namespace bts.shell
class MarketShellModule : IShellModule
{
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import("username")]string user_name) {
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
@@ -27,8 +27,8 @@ namespace mbts.spark {
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
// securities_connectable should NOT be connected already.
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IConnectableObservable<IEnumerable<SecurityMD>> securities_connectable, IWindowSettingsManager<GridSettings> settings_manager)
: base(RXCollection.Create(securities_connectable, SecurityDataHelper.EqualityComparer.MDComparer), null) {
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
_bus = bus;
_orders_model = orders_model;
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
@@ -163,7 +163,6 @@ namespace mbts.spark {
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
securities_connectable.Connect(),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
WorkLoop.Start("MarketView", _refresh)
);
@@ -7,6 +7,7 @@ using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -135,6 +136,7 @@ namespace mbts.spark {
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
_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),
@@ -151,12 +153,11 @@ namespace mbts.spark {
s => _vm.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select( c => c.create())),
this.observe_title()
s.with(_vm.columns.Select(c => c.create()))
)
);
// TODO: bind to main window's Title
var dialog = new ShellSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -7,6 +7,7 @@ using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -17,7 +18,6 @@ using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
@@ -77,12 +77,11 @@ namespace mbts.spark {
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select( c => c.create())),
this.observe_title()
s.with(_vm.value.columns.Select(c => c.create()))
)
);
// TODO: bind to main window's Title
var dialog = new ShellSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -59,6 +59,7 @@
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
ConditionalPriceTypeTemplate="{StaticResource SimpleValueTextTemplate}"
ShowQuantityTemplate="{StaticResource SimpleValueTextTemplate}"
>
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
<DataTemplate>
@@ -115,6 +116,16 @@
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
<usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
<DataTemplate>
<!-- Note: the motivation for this Template was to enforce a Minimum via the TickUpDownUInt -->
<!-- As of 2019/06/17, we believe such a Minimum/Maximum is not actually getting enforced and -->
<!-- furthermore is not the appropriate way to go about enforcing said value. It is near the end -->
<!-- of the cycle though so that is why we are leaving this Template in here for now until we -->
<!-- can come up with a better solution. -->
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}" />
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
<usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.MarketDataType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
@@ -240,7 +240,7 @@ namespace mbts.ui.spark {
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
var rv = new ContentControl();
rv.SetBinding(ContentControl.ContentProperty, new Binding());
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding() { Converter = cell_template_converter });
return rv;
}
}
@@ -306,17 +306,23 @@ namespace mbts.ui.spark {
public DataTemplate MarketDataTypeTemplate { get; set; }
public DataTemplate MarketDataCalculationTemplate { get; set; }
public DataTemplate ConditionalPriceTypeTemplate { get; set; }
public DataTemplate ShowQuantityTemplate { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
Type t = (Type)value;
if (t == typeof(double)) return DoubleTemplate;
else if (t == typeof(bool)) return BooleanTemplate;
else if (t == typeof(int)) return IntTemplate;
else if (t == typeof(uint)) return UIntTemplate;
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
else return null;
if (value is ICell cell) {
if (cell.row is ShowQuantityQuantityModel) {
return ShowQuantityTemplate;
}
Type t = cell.data_type;
if (t == typeof(double)) return DoubleTemplate;
else if (t == typeof(bool)) return BooleanTemplate;
else if (t == typeof(int)) return IntTemplate;
else if (t == typeof(uint)) return UIntTemplate;
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.synthetic;
using bts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -14,12 +15,14 @@ namespace mbts.spark {
readonly IBus _bus;
readonly SyntheticSecurityService _service;
readonly ErrorReporter _error;
readonly uint _user_id;
[ImportingConstructor]
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error) {
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
_bus = bus;
_service = service;
_error = error;
_user_id = user_id;
}
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
@@ -39,29 +42,40 @@ namespace mbts.spark {
return (result, success);
});
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
await lookup_strategy_async(security.Id);
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
lookup_strategy_async(security.Id);
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) {
var result = await _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } });
if (result.success) {
var metadata = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
if (metadata != null) {
return metadata.Strategy;
}
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) =>
_error.safe_execute(
() => _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } }),
mdc => mdc.Data.Count == 1 && mdc.Data[0].SecurityKey == id,
mdc => $"did not find strategy for security id '{id}.'",
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
"lookup strategy");
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
return null;
if (synthetic.Synthetic.UserId != _user_id) {
var msynth = synthetic.CloneToMutable();
msynth.Synthetic.UserId = _user_id;
synthetic = new SecurityImmutable(msynth);
}
return null;
return _error.safe_execute(
() => _service.create_synthetic_async(synthetic),
r => r.Error == CreateResult.Types.Error.Ok,
r => r.Error.ToString(),
(r, success) => success ? r.Key : (uint?)null,
"creating synthetic");
}
public async Task<uint?> create_synthetic(SecurityImmutable? synthetic) {
var result = await _service.create_synthetic_async(synthetic.Value);
return _error.handle_error(result, "Error creating synthetic: {0}", r => r.Error, e => e == CreateResult.Types.Error.Ok, r => new uint?(r.Key), () => null).result;
}
public async Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) {
var result = await _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() });
return _error.handle_error(result, "Error saving execution strategy: {0}", r => r.Error, e => e == UpdateResult.Types.Error.Ok);
}
public Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) =>
_error.safe_execute(
() => _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() }),
r => r.Error == UpdateResult.Types.Error.Ok,
r => r.Error.ToString(),
(r, success) => success,
"saving strategy");
public async Task<bool> verify_name_is_unique(string name) {
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
@@ -108,6 +108,9 @@ namespace bts.shell {
NTicks = 2,
PriceType = ConditionalQuoting.Types.PriceType.Leg
};
public static NullableQuantity ShowQuantity => new NullableQuantity {
Quantity = 1
};
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
#endregion
@@ -82,11 +82,10 @@ namespace mbts.spark {
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select( c => c.create())),
this.observe_title()
s.with(_vm.value.columns.Select(c => c.create()))
)
);
// TODO: bind to main window's Title
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
@@ -34,6 +34,8 @@ namespace mbts.spark {
public virtual Type data_type { get; } = typeof(T);
public virtual IRowModel row { get => _model; }
public T value {
get => _model.get_value(_leg_tup);
set {
@@ -64,6 +66,8 @@ namespace mbts.spark {
public Type data_type { get; } = typeof(QuantityMeasure);
public virtual IRowModel row { get => _model; }
public double ratio {
get => _get_qm().Ratio;
set {
@@ -0,0 +1,36 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark {
partial class SyntheticEditorVM {
class ShowQuantityEnabledModel : RowModelBase<bool> {
readonly ShowQuantityQuantityModel _quantity;
public ShowQuantityEnabledModel(ShowQuantityQuantityModel quantity) {
_quantity = quantity;
}
public override string display_name => string.Empty;
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.ShowQuantity != null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.QuoteParams.ShowQuantity == null) {
leg_tup.leg_params.QuoteParams.ShowQuantity = SyntheticDataHelper.Defaults.ShowQuantity;
_quantity.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.QuoteParams.ShowQuantity = null;
}
_quantity.set_enabled(leg_tup, value);
}
}
}
}
@@ -0,0 +1,43 @@
using bts.shell;
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
internal class ShowQuantityQuantityModel : RowModelBase<uint> {
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
public override string display_name => "Quantity";
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup).Quantity = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup) is NullableQuantity show_quantity ? show_quantity.Quantity : _value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.SecurityKey] = value;
if (_get_value(leg_tup) is NullableQuantity show_quantity) {
show_quantity.Quantity = value;
}
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup) != null;
NullableQuantity _get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.ShowQuantity;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ShowQuantity.Quantity);
}
}
}
@@ -32,6 +32,7 @@ namespace mbts.spark {
internal interface ICell {
event Action value_changed;
IRowModel row { get; }
Type data_type { get; }
bool enabled { get; }
}
@@ -40,7 +41,7 @@ namespace mbts.spark {
FontWeight font_weight { get; }
}
abstract class RowModelBase<T> : ViewModelBase, IRowModel {
internal abstract class RowModelBase<T> : ViewModelBase, IRowModel {
readonly Dictionary<uint, ReplaySubject<bool>> _enabled_store = new Dictionary<uint, ReplaySubject<bool>>();
readonly Dictionary<uint, Subject<T>> _override_value_store = new Dictionary<uint, Subject<T>>();
@@ -98,6 +99,10 @@ namespace mbts.spark {
}
private static class Subsections {
public static ParameterSection ShowQuantity =>
new ParameterSection("Show Quantity", true,
"The quantity shown when the order is placed.");
public static ParameterSection Throttle =>
new ParameterSection("Throttle", true,
"Reduces re-quoting updates by defining a region on either side " +
@@ -76,8 +76,8 @@ namespace mbts.spark {
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
}
}));
contract_picker_model.cancel_action = _close_new_leg_popup;
contract_picker_model.ok_action = () => {
contract_picker_model.cancel_action += _close_new_leg_popup;
contract_picker_model.ok_action += () => {
if (!leg_tuples.Any(l => l.leg.Security.Id == contract_picker_model.result.security.Id)) {
leg_tuples.Add(new LegSpreaderParamsTuple(
SyntheticDataHelper.Defaults.ContractLeg(contract_picker_model.result.security),
@@ -134,7 +134,7 @@ namespace mbts.spark {
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
uint? id = await model.create_synthetic(_synthetic);
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
if (id.HasValue && id != 0) {
if (await model.save_strategy(id.Value, get_strategy())) {
var (smd, success) = await SparkUtils.get_security_md_async(bus, id.Value);
@@ -177,6 +177,8 @@ namespace mbts.spark {
} else {
_can_add_leg = backing(nameof(can_add_leg), false);
}
var show_quantity_quantity = new ShowQuantityQuantityModel();
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
@@ -211,6 +213,8 @@ namespace mbts.spark {
//new QuoteHiddenModel(),
new QuoteQueueHolderModel(),
new MarketDataTypeModel(),
new ShowQuantityEnabledModel(show_quantity_quantity),
show_quantity_quantity,
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
quote_throttle_inside_ticks,
quote_throttle_outside_ticks,
@@ -1,14 +1,9 @@
using bts.api;
using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using ProtoBuf;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using System.Windows.Controls;
using mbts;
namespace bts.shell {
public class BrowserWindow : IShellWindow {
@@ -75,12 +70,15 @@ namespace bts.shell {
public string window_type => _window_type;
public IShellModule Owner {get; set; }
public ShellWindowUI GetContent() { return new ShellWindowUI(_browser); }
public void GetState(System.IO.Stream save_here) {
_isw.save(new BrowserWindowState() { uri = uri.ToString() }, save_here);
}
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
public void Dispose() {
if (_browser != null) {
_browser.Dispose();
@@ -102,5 +100,4 @@ namespace bts.shell {
}
#endregion
}
}
}
@@ -86,7 +86,7 @@ namespace mbts.spark {
} else if (t.definition != null) {
return t.definition.subscribe_is_definition_dirty();
} else return Observable.Return(false);
}).RefCount();
});
_commands.Add(new ReactiveSparkCommand(content_src.OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_template is AlgoDefinitionTemplateVM t)
_vm.save_template(t).ignore();
@@ -116,7 +116,6 @@ namespace mbts.spark {
});
_commands.Add(undo_spark_cmd);
_resources.Add(can_undo_source.Subscribe(undo_cmd));
_resources.Add(can_undo_source.Connect());
// redo
var redo_cmd = new ObservantCommand(() => _vm.content_source?.try_redo());
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
@@ -133,7 +132,6 @@ namespace mbts.spark {
});
_commands.Add(redo_spark_cmd);
_resources.Add(can_redo_source.Subscribe(redo_cmd));
_resources.Add(can_redo_source.Connect());
}
// remove
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
@@ -73,7 +73,7 @@ namespace mbts.spark {
_persisted_key == other._persisted_key;
public override bool Equals(object obj) =>
obj is AlgoID && Equals(obj);
obj is AlgoID other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
@@ -1,5 +1,4 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.persist.algo;
@@ -110,38 +109,24 @@ namespace mbts.spark {
public void Dispose() =>
Disposable.dispose(ref _dispose);
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) {
if (_try_get_user(out uint user_id)) {
return await _create_definition(definition, user_id);
} else {
_report_no_user_id(ActionMetadata.CreateDefinition);
}
return null;
}
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) =>
await _create_definition(definition, _admin_model.user.id);
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
new TemplateCreateRequest { UserId = user_id, NewTemplate = template.CloneToMutable() }),
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
if (success)
return _Utils.template_data(value.Record);
} else {
_report_no_user_id(ActionMetadata.CreateTemplate);
}
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
new TemplateCreateRequest { UserId = _admin_model.user.id, NewTemplate = template.CloneToMutable() }),
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
if (success)
return _Utils.template_data(value.Record);
return null;
}
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
if (success)
return _Utils.template_data(template);
} else {
_report_no_user_id(ActionMetadata.UpdateTemplate);
}
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
if (success)
return _Utils.template_data(template);
return null;
}
@@ -175,17 +160,6 @@ namespace mbts.spark {
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
bool _try_get_user(out uint id) {
var user = _admin_model.get_current_user();
if (user.HasValue) {
id = user.Value.id;
return true;
} else {
id = 0;
return false;
}
}
// used for error handling/reporting
interface IActionMetadata {
string desc { get; }
@@ -1,14 +1,11 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman.algo;
using bts.persist.algo;
using bts.spark;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -27,7 +24,12 @@ namespace mbts.spark {
_admin_model = admin_model;
_error_reporter = error_reporter;
this.ui_configuration = ui_configuration;
definitions = service.subscribei_algo_definitions().Filter(r => r.Metadata.IsActive).SelectKeyValue(_definition_data, d => d.key).Table().Cache();
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
@@ -39,15 +41,11 @@ namespace mbts.spark {
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = user_id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
} else {
_report_no_user_id("save template");
}
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
return null;
}
@@ -105,16 +103,5 @@ namespace mbts.spark {
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
bool _try_get_user(out uint id) {
var user = _admin_model.get_current_user();
if (user.HasValue) {
id = user.Value.id;
return true;
} else {
id = 0;
return false;
}
}
}
}
@@ -78,7 +78,7 @@ namespace mbts.spark {
_dispose = new System.Reactive.Disposables.CompositeDisposable(
as_demuxer().subscribe_many(_refresh_on_local_change.execute, nameof(name), nameof(abbreviation), nameof(is_default), nameof(is_favorite)),
owner.observe_algo_root().Subscribe(r => _algo_root.set(get_algo_root(r))),
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).RefCount().Subscribe(_ => _refresh_on_local_change.execute()),
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).Subscribe(_ => _refresh_on_local_change.execute()),
Disposable.wrap_collection(algo_map.Values)) {
() => {
_can_redo.Changed -= on_can_redo_changed;
@@ -54,7 +54,7 @@ namespace mbts.spark {
_model = model;
_selected_definition = backing<AlgoDefinitionVM>(nameof(selected_definition));
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).RefCount().OnDispatcher());
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).OnDispatcher());
_definitions_update_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(def => def.key);
_definitions_view = new ListCollectionView<AlgoDefinitionVM>(definitions) {
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
@@ -77,7 +77,7 @@ namespace mbts.spark {
definitions_view.Refresh();
}),
ObservableAutoWrapper.connect(observe_selected().Select(t => t.template?.edit_pane_visual_states ?? t.definition?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).RefCount().OnDispatcher()
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).OnDispatcher()
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
@@ -22,7 +22,6 @@ using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
using System.Windows.Media;
using static bts.api.orderman.Fill.Types;
@@ -129,8 +128,7 @@ namespace mbts.spark {
};
_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
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -181,7 +179,7 @@ namespace mbts.spark {
} else return model.on_fill_tuples;
}
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
static IObservable<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) {
@@ -6,6 +6,7 @@ using bts.spark;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -67,12 +68,11 @@ namespace mbts.spark {
s => _vm.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select( c => c.create())),
this.observe_title()
s.with(_vm.columns.Select(c => c.create()))
)
);
// TODO: bind to main window's Title
var dialog = new ShellSettingsDialog { Title = $"{window_type_name} Grid Settings", DataContext = vm };
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -9,6 +9,7 @@ using bts.proto;
using mbts.ui.spark;
using System.Collections.Generic;
using System.ComponentModel;
using mbts.ui.settings;
namespace mbts.spark {
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
@@ -220,6 +221,23 @@ namespace mbts.spark {
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
}
[ProtoContract]
readonly struct LadderMiniOrderBookSettings : IEquatable<LadderMiniOrderBookSettings> {
// this is the only member now, but more may be added later (that is why a GridSettings member wasn't added directly to LadderSettings).
[ProtoMember(1)]
public readonly GridSettings value;
public LadderMiniOrderBookSettings(GridSettings value) =>
this.value = value;
public bool Equals(LadderMiniOrderBookSettings other) => value == null ? other.value == null : value.Equals(other.value);
public override bool Equals(object obj) => obj is LadderMiniOrderBookSettings other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => a.Equals(b);
public static bool operator !=(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => !a.Equals(b);
}
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
// 1 important reason at this time:
// From the settings dialog, default settings will only be persisted when they are NOT EQUAL to the current default settings.
@@ -228,6 +246,7 @@ namespace mbts.spark {
class LadderSettings : IEquatable<LadderSettings> {
const double _default_order_entry_panel_width = 110.0;
const double _default_zoom_factor = 1.0;
const bool _default_show_warn_on_cross = true;
// Default proto constructor
// Added this so that when clients upgrade to a version with new settings,
@@ -255,6 +274,7 @@ namespace mbts.spark {
zoom_factor = _default_zoom_factor;
use_defaults = true;
favorite_algos = new LadderFavoriteAlgosSettings(null, 2);
show_warn_on_cross = _default_show_warn_on_cross;
}
public static LadderSettings Default {
@@ -319,6 +339,7 @@ namespace mbts.spark {
zoom_slider_visible = false,
zoom_factor = _default_zoom_factor,
net_change = NetChangeTerms.None,
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
};
rv.row_height = LadderRender.measure_row_height(rv);
return rv;
@@ -460,6 +481,47 @@ namespace mbts.spark {
get => _piq_column ?? (_piq_column = LadderSettings.Default.piq_column);
set => _piq_column = value;
}
[ProtoMember(54), DefaultValue(_default_show_warn_on_cross)]
public bool show_warn_on_cross { get; set; }
[ProtoMember(55)]
public LadderMiniOrderBookSettings mini_ob { get; set; }
public void sanitize() =>
sanitize(this);
// if there are new props, make sure they are not null, invalid, etc.
public static LadderSettings sanitize(LadderSettings s) {
if (s == null)
return null;
// these MarketBreakLines are new settings props, ensure they are not null
if (s.bid_break_line == null)
s.bid_break_line = Default.bid_break_line;
if (s.ask_break_line == null)
s.ask_break_line = Default.ask_break_line;
if (s.mid_break_line == null)
s.mid_break_line = Default.mid_break_line;
if (s.session_low_line == null)
s.session_low_line = Default.session_low_line;
if (s.session_high_line == null)
s.session_high_line = Default.session_high_line;
if (s.session_range_fill == null)
s.session_range_fill = Default.session_range_fill;
if (s.volume_column == null)
s.volume_column = Default.volume_column;
if (s.size_display == null)
s.size_display = MarketSizeDisplay.get_default();
if (s.size_display.normal == null)
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
if (s.mini_ob.value == null)
s.mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general);
#pragma warning disable CS0612 // Type or member is obsolete
if (s.mini_ob.value.sorting_criterion.Count == 0)
s.mini_ob = new LadderMiniOrderBookSettings(s.mini_ob.value.with(sorting_criterion: bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general.sorting_criterion));
#pragma warning restore CS0612 // Type or member is obsolete
s.row_height = LadderRender.measure_row_height(s);
return s;
}
public bool Equals(LadderSettings other) =>
other != null &&
@@ -511,7 +573,9 @@ namespace mbts.spark {
zoom_factor == other.zoom_factor &&
use_defaults == other.use_defaults &&
favorite_algos == other.favorite_algos &&
net_change == other.net_change;
net_change == other.net_change &&
show_warn_on_cross == other.show_warn_on_cross &&
mini_ob == other.mini_ob;
public override bool Equals(object obj) => Equals(obj as LadderSettings);
@@ -562,7 +626,8 @@ namespace mbts.spark {
zoom_factor.GetHashCode(),
use_defaults.GetHashCode(),
favorite_algos.GetHashCode(),
net_change.GetHashCode());
net_change.GetHashCode(),
show_warn_on_cross.GetHashCode());
public struct WindowSpecifics {
public readonly double order_entry_panel_width, zoom_factor;
@@ -6,6 +6,7 @@ using mbts.persist;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using ProtoBuf;
using System;
@@ -81,24 +82,45 @@ namespace bts.shell {
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
IDisposable active_settings_dialog = null;
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
// on_shell_window_vm should behave like an event (do not set to null)
Action<ShellWindowVM> on_shell_window_vm = _ => { };
ShellWindowVM shell_window_vm = null;
IShellWindow shell_window = null;
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
var view = new Lazy<UIElement>(() => {
// 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))),
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, LadderSettings.sanitize(lp.value), lp.security_criteria))),
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
state_settings?.sanitize();
/// When settings are adjusted outside of the settings dialog, the vm should raise the <see cref="LadderVM.settings_invalidated"/> event.
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
bool initialized = false, needs_resync = false;
LadderSettings last_value = fit_to_sec(state_settings);
var settings_subject = new BehaviorSubject<LadderSettings>(last_value);
LadderVM vm = null;
var settings_subject = new Subject<LadderSettings>();
var settings_source = Observable.Create<LadderSettings>(o => {
ensure_synced();
if (last_value != null)
o.OnNext(last_value);
return settings_subject.Subscribe(s => {
if (s != null)
o.OnNext(s);
});
});
// vm
var vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_subject.Where(s => s != null));
vm = LadderVM.create(_bus, model, LadderSettings.sanitize(last_value ?? _settings_hub.current.ladder), settings_source);
vm.settings_invalidated += on_settings_invalidated;
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 settings_subscription);
Disposable.dispose(ref active_settings_dialog);
Disposable.dispose(ref settings_subject);
};
@@ -111,8 +133,25 @@ namespace bts.shell {
LadderSettings fit_to_sec(LadderSettings s) =>
_fit_to_sec(s, md.security.ContractCase);
void on_settings_invalidated() =>
needs_resync = true;
void ensure_synced() {
if (!needs_resync)
return;
var curr = vm.get_save_state();
if (settings_id == 0) {
last_value = curr;
} else if (!curr.Equals(last_value)) {
settings_id = 0;
last_value = curr;
}
needs_resync = false;
}
void on_user_settings(UserSettings us) {
if (initialized) {
ensure_synced();
if (settings_id == 0) {
// not on a profile, don't respond to default settings changes
return;
@@ -168,10 +207,8 @@ namespace bts.shell {
settings_id);
dialog_vm.applied += apply;
// dialog
var dialog = new ShellSettingsDialog {
DataContext = dialog_vm,
Title = $"Ladder Settings - '{vm.security}'"
};
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
dialog_vm.SubscribeDisposed(title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
dialog_vm.RequestClose += on_request_close;
dialog.Closed += on_closed;
@@ -221,35 +258,15 @@ namespace bts.shell {
}
});
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{thread_id:00}" : "";
return ShellWindow.create(
return shell_window = ShellWindow.create(
module, md.security.ToString(), _window_type, view,
(v,s) => save(_get_state(((LadderView)v).vm, settings_id), s),
icon: Icons.Ladder,
thread_id: thread);
// if there are new props, make sure they are not null, invalid, etc.
LadderSettings sanitize_ladder_settings(LadderSettings s) {
// these MarketBreakLines are new settings props, ensure they are not null
if (s.bid_break_line == null)
s.bid_break_line = LadderSettings.Default.bid_break_line;
if (s.ask_break_line == null)
s.ask_break_line = LadderSettings.Default.ask_break_line;
if (s.mid_break_line == null)
s.mid_break_line = LadderSettings.Default.mid_break_line;
if (s.session_low_line == null)
s.session_low_line = LadderSettings.Default.session_low_line;
if (s.session_high_line == null)
s.session_high_line = LadderSettings.Default.session_high_line;
if (s.session_range_fill == null)
s.session_range_fill = LadderSettings.Default.session_range_fill;
if (s.volume_column == null)
s.volume_column = LadderSettings.Default.volume_column;
if (s.size_display == null)
s.size_display = MarketSizeDisplay.get_default();
if (s.size_display.normal == null)
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
s.row_height = LadderRender.measure_row_height(s);
return s;
}
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
icon: Icons.Ladder,
thread_id: thread,
vm => {
shell_window_vm = vm;
title_source.connect();
});
}
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
@@ -304,4 +321,4 @@ namespace bts.shell {
[ProtoMember(3)]
public uint settings_id;
}
}
}
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.orderman;
using mbts.spark.utilities;
using bts.shell.orderbook;
using mbts.persist;
using mbts.ui;
using mbts.ui.shell;
using System;
@@ -293,7 +294,6 @@ namespace mbts.spark {
price_filter.minimum.value = real_price;
price_filter.maximum.value = real_price;
var obvm = new OrderBookVM(null, _owner.bus, null, _owner.model.order_gateway, null, true);
var state = new bts.shell.orderbook.OrderBookStartupData(
new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_owner.security),
@@ -301,44 +301,22 @@ namespace mbts.spark {
statuses = new[] { OrderStatus.Working },
order_types = AlgoID.exchange_algo_keys
},
bts.shell.orderbook.OrderBookStartupData.default_mini_grid,
bts.shell.orderbook.OrderBookStartupData.default_mini_grid.with(_owner.current_settings.mini_ob.value),
use_defaults: false
);
// NOTE:
// we might need the ObservableAppend to InsertAt
// if these EditXXX ever support settings.
// The end result after this group of lines will be
// ----------------------------------------------
// | Cancel | Update | Qty | Price | Commit | Attach | Detach | ...
// ----------------------------------------------
//
var settings_man = new WindowSettingsManager<OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
var obvm = new OrderBookVM(_owner.bus, null, _owner.model.order_gateway, settings_man, true);
_owner.settings.Subscribe(s => settings_man.instance_settings = settings_man.instance_settings.with(s.mini_ob.value));
obvm.columns.Insert(0, new OrderBookVM.AttachDetachColumn(typeof(MiniOrderBookView)));
obvm.columns.Insert(0, new OrderBookVM.CommitColumn());
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Price.ToString());
var price_index = obvm.columns.indexOf(price_col);
if (price_index > -1) {
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditablePrice.ToString());
var existing_editprice_index = obvm.columns.indexOf(existing_editprice_col);
if (existing_editprice_index == -1) {
// insert the edit price column at the beginning
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
}
}
var qty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Size.ToString());
var qty_index = obvm.columns.indexOf(qty_col);
if (qty_index > -1) {
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditableQty.ToString());
var existing_editqty_index = obvm.columns.indexOf(existing_editqty_col);
if (existing_editqty_index == -1) {
// insert the edit quantity column at the beginning
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
}
}
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
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)));
// the CheckColumn must be inserted at the front of the ObservableAppendCollection,
// and not mixed with the other columns in said ObservableAppendCollection's underlying ObservableCollection (the settings dialog uses the underlying collection).
// The user cannot adjust this column in the settings dialog, and it should always be the left-most column.
obvm.columns.insert_front(new OrderBookVM.CheckColumn(nameof(OrderBookRowVM.is_selected)));
obvm.load_from(state);
return obvm;
}
@@ -2,6 +2,7 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.shell.orderbook;
using bts.utils;
using mbts.collections;
using mbts.ui;
@@ -40,7 +41,7 @@ namespace mbts.spark {
TimeInForce _default_time_in_force;
LadderSettings.NetChangeTerms _price_format;
double _order_entry_panel_width, _zoom_factor;
bool _auto_clear;
bool _auto_clear, _show_warn_on_cross;
LadderSettings _current;
LadderColumnSettingsVM
_orders = new OrdersColumnSettingsVM(),
@@ -102,6 +103,7 @@ namespace mbts.spark {
};
// update
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
mini_ob.result_changed += _update.execute;
foreach (var c in _observable_components)
c.PropertyChanged += on_prop_changed;
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
@@ -126,6 +128,8 @@ namespace mbts.spark {
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
}
void dispose_resources() {
mini_ob.result_changed -= _update.execute;
mini_ob.Dispose();
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
favorite_algos_vm.Dispose();
foreach (var c in _observable_components)
@@ -157,6 +161,7 @@ namespace mbts.spark {
}
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
// 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>();
@@ -173,6 +178,9 @@ namespace mbts.spark {
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); }
// safety checks
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
// favorites
public FavoriteAlgosVM favorite_algos_vm { get; }
@@ -293,6 +301,7 @@ namespace mbts.spark {
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)));
mini_ob.load_from(settings.mini_ob.value);
// columns
_orders.assign_from(_current.orders_column);
_bid.assign_from(_current.bid_column);
@@ -340,11 +349,25 @@ namespace mbts.spark {
_cancel_sells_visibility.show = settings.cancel_sells_visible;
_cancel_all_visibility.show = settings.cancel_all_visible;
price_format = settings.net_change;
// safety checks
show_warn_on_cross = settings.show_warn_on_cross;
}
}
public LadderSettings get_result() {
var clone = SparkUtils.clone_proto(_current);
LadderSettings clone;
if (_current == null) {
clone = new LadderSettings {
mini_ob = new LadderMiniOrderBookSettings(mini_ob.create())
};
} else {
clone = SparkUtils.clone_proto(_current);
#pragma warning disable CS0612 // Type or member is obsolete
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
mini_ob.create());
#pragma warning restore CS0612 // Type or member is obsolete
}
clone.badge_style = badge_style;
clone.alternate_order_size = alternate_order_size;
clone.favorite_algos = favorite_algos_vm.create();
@@ -399,6 +422,8 @@ namespace mbts.spark {
clone.cancel_all_visible = _cancel_all_visibility.show;
clone.zoom_slider_visible = _zoom_slider_visbility.show;
clone.net_change = price_format;
// safety checks
clone.show_warn_on_cross = show_warn_on_cross;
return clone;
}
@@ -459,7 +484,7 @@ namespace mbts.spark {
_foreground = backing(nameof(foreground), Colors.Black);
_background = backing(nameof(background), Colors.White);
_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));
() => TextUtils.display_name(font_family, font_weight, font_size), new[] { 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;
@@ -39,7 +39,6 @@ namespace mbts.spark {
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, 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))));
@@ -53,7 +52,7 @@ namespace mbts.spark {
LadderVM(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings, OrderPropertiesEditorVM order_properties_vm = null) {
_bus = bus;
_model = model;
_current_settings = init;
current_settings = init;
this.settings = settings;
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
@@ -82,8 +81,8 @@ namespace mbts.spark {
// 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),
/// 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,
@@ -236,14 +235,13 @@ namespace mbts.spark {
}
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, request_settings_dialog;
public event Action on_tick, request_settings_dialog, settings_invalidated;
public MDConsumerHandle md => _model.md;
// we return only entries who have piqs and whose piqs are not uint.Max
@@ -278,6 +276,7 @@ namespace mbts.spark {
remove => _on_disposed_impl -= value;
}
public SecurityImmutable security => _model.security;
public LadderSettings current_settings { get; private set; }
public double high => _high.Value;
public double low => _low.Value;
public double change => _change.Value;
@@ -339,6 +338,8 @@ namespace mbts.spark {
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
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); }
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings { get; }
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
@@ -359,7 +360,7 @@ namespace mbts.spark {
}
public LadderSettings get_save_state() {
var rv = _current_settings.with_window_specifics(
var rv = current_settings.with_window_specifics(
new LadderSettings.WindowSpecifics(
left_panel_width,
orders_column_width,
@@ -393,18 +394,24 @@ namespace mbts.spark {
}
}
public void on_mini_orderbook_closed(bts.shell.orderbook.OrderBookStartupData ob_state) {
current_settings = SparkUtils.clone_proto(current_settings);
current_settings.mini_ob = new LadderMiniOrderBookSettings(ob_state.orderbook_grid.general);
settings_invalidated?.Invoke();
}
public void send_order((double price, Side? side, MouseButton button) args) {
if (order_properties_vm == null || args.button == MouseButton.Middle)
return;
var size_from_alternate = args.button == MouseButton.Right;
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && _current_settings.alternate_order_size == 0)) {
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
return;
}
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
}
else {
uint size = size_from_alternate ? _current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
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();
}
}
@@ -0,0 +1,58 @@
using bts.api;
using bts.api.orderman;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
namespace mbts.spark {
class LadderVMContainer : ViewModelBase, IDisposable {
readonly IBus _bus;
readonly OrdersModel _model;
LadderVM _value;
SecurityMD? _security_md;
HashSet<uint> _selected_order_tree = new HashSet<uint>();
public LadderVMContainer(IBus bus, OrdersModel model) {
_bus = bus;
_model = model;
}
public SecurityMD? security_md {
get => _security_md;
set {
if (_security_md != value) {
_security_md = value;
_reset_ladder_vm();
RaisePropertyChanged(nameof(security_md), nameof(LadderVMContainer.value));
}
}
}
public LadderVM value => _value;
public void Dispose() {
if (_value != null) {
_value.Dispose();
_value = null;
RaisePropertyChanged(nameof(value));
}
}
public void set_orders(IEnumerable<uint> orders) {
_selected_order_tree = new HashSet<uint>(orders);
_reset_ladder_vm();
}
void _reset_ladder_vm() {
if (_value == null && !_security_md.HasValue)
return;
Disposable.replace(ref _value,
_security_md.HasValue ?
LadderVM.create_readonly(_bus,
new LadderModel(security_md.Value, _model, _model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
RaisePropertyChanged(nameof(value));
}
}
}
@@ -67,7 +67,8 @@ namespace mbts.spark {
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;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
bool _tooltips_are_hooked_up = false;
ToolTip tooltip_view = new ToolTip();
IDisposable _parameterized_order_popup;
@@ -339,7 +340,7 @@ namespace mbts.spark {
_send_parameterized_order(point, bs.Value);
return;
} else if (column.column_type == ColumnType.Price) {
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
show_mbo_popup(point).ignore();
} else {
@@ -347,6 +348,19 @@ namespace mbts.spark {
}
return;
}
} else if (_vm != null) {
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
// if warning enabled && (buy above opposite inside market || ask below opposite inside market)
// when 1-sided market, _get_top_price will return NaN, and so the price comparison is false
// meaning show no warning == correct
if (_vm.show_warn_on_cross_from_settings &&
(size > 0) &&
((bs == Side.Buy && price >= _get_top_price(_asks)) || (bs == Side.Sell && price <= _get_top_price(_bids)))) {
string prompt = $"Are you sure you want to CROSS the market to {bs.ToString().ToUpper()} {size} @ {security.spec_or_default().format(price)} ?";
if (!BTSMessageBox.Confirm(prompt, "Cross Market Alert", MessageBoxImage.Warning)) {
return;
}
}
}
var pbs = (price, bs, e.ChangedButton);
if (Command is ICommand cmd && cmd.CanExecute(pbs)) {
@@ -680,12 +694,14 @@ namespace mbts.spark {
// 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");
_old_tooltip_row = int.MaxValue;
_hide_tool_tip();
//_old_tooltip_row = Int32.MaxValue;
}
}
} else {
//System.Diagnostics.Debug.WriteLine(" Tooltip - not over Volume column");
_old_tooltip_row = int.MaxValue;
_hide_tool_tip();
}
}
@@ -848,21 +864,43 @@ namespace mbts.spark {
void _refresh_volume_at_price() {
if (!_settings.volume_column.general.show)
return;
// if the last trade has a new size then we need to explicitly
// mark the volume container as dirty because there is a chance
// we have arrived here off a size change and not a volume change
// or a random change, like applying new settings
bool last_trade_size_changed = _old_last_trade.HasValue ? _last_trade.size != _old_last_trade.Value.size : true;
if (last_trade_size_changed && _settings.volume_column.show_ltq) {
_volume_container.mark_dirty();
}
var vapls = _volume_container.read_new_value();
if (vapls != null) {
Log.assert_d(_price_list != null);
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;
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
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, _get_last_arrow_string());
_old_last_trade = _last_trade;
}
string _get_last_arrow_string() {
// if _old_last_trade doesn't have a value, this is the first time the ladder has been launched
// and so the direction should be drawn without an arrow since there is no basis for comparison
if (!_old_last_trade.HasValue) {
_direction_arrow = string.Empty;
// if the Lasts are different then we need to reformat the direction arrow in some fashion
} else if (_old_last_trade != _last_trade) {
var ltp = _last_trade.price;
if (MathUtils.epsilon_equals(ltp, _old_last_trade.Value.price)) {
// update on the same price, so size must be different
_direction_arrow = string.Empty;
} else if (ltp > _old_last_trade.Value.price) {
_direction_arrow = "▲";
} else {
_direction_arrow = "▼";
}
}
// regardless of if we've arrived here via a new Last (and new direction arrow)
// or we're reusing the old one, this format bit is the same
return $"{_direction_arrow} {_last_trade.size} ";
}
}
@@ -9,9 +9,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace mbts.spark {
@@ -156,7 +154,10 @@ namespace mbts.spark {
popup.SetBinding(System.Windows.Controls.Primitives.Popup.IsOpenProperty,
nameof(OrderBookVM.is_row_collection_populated)
);
popup.Closed += (sender, event_args) => (popup.DataContext as IDisposable)?.Dispose();
popup.Closed += (sender, event_args) => {
(_parent.DataContext as LadderVM)?.on_mini_orderbook_closed(obvm.create());
(popup.DataContext as IDisposable)?.Dispose();
};
popup.MouseUp += (sender, event_args) => {
if (event_args.ChangedButton == MouseButton.Middle) {
popup.IsOpen = false;
@@ -196,8 +196,7 @@ namespace mbts.spark {
ContractSpecificationImmutable spec,
VolumeColumnSettings settings,
int last_index,
NativePriceSize last_trade,
string direction_arrow
string last_size_and_direction
) {
if (!data.Any()) {
_clear_visual(dv);
@@ -216,16 +215,16 @@ namespace mbts.spark {
Brush total_brush = null;
if (settings.total.bars.result.HasValue) {
total_brush = new SolidColorBrush(settings.total.bars.result.Value);
} else if (components.Count == 0) {
} else if (components.Count == 0 && !settings.show_ltq) {
_clear_visual(dv);
return;
}
// 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) {
Brush text_brush = settings.general.foreground.rgb_brush();
double y = row_height * r;
var last_trade_here = r == last_index;
@@ -259,7 +258,8 @@ namespace mbts.spark {
}
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
numbers_text.Add($"{direction_arrow} {last_trade.size} ");
numbers_text.Add(last_size_and_direction);
text_brush = BTSCoreColors.white;
}
if (numbers_text.Count > 0) {
string str = string.Join("/", numbers_text);
@@ -30,7 +30,6 @@
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
<sys:Double x:Key="LadderColumnsSampleColumnWidth">153</sys:Double>
<!--Margins-->
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
@@ -190,7 +189,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
@@ -226,7 +225,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
@@ -272,7 +271,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<Grid>
@@ -329,7 +328,7 @@
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<Grid>
@@ -449,7 +448,7 @@
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
</UserControl.Resources>
<TabControl Width="702" MinHeight="275">
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="VerticalAlignment" Value="Stretch"/>
@@ -458,14 +457,14 @@
</TabControl.ItemContainerStyle>
<!-- Ladder Columns Tab -->
<TabItem Header="Ladder Columns" VerticalAlignment="Stretch">
<Grid Grid.IsSharedSizeScope="True">
<Grid Grid.IsSharedSizeScope="True" Margin="8,0">
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="fonts_and_colors_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}" Margin="8,0">
<Grid x:Name="fonts_and_colors_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="c0"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
@@ -473,7 +472,7 @@
<ColumnDefinition Width="Auto" SharedSizeGroup="c3"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c4"/>
<ColumnDefinition SharedSizeGroup="c5"/>
<ColumnDefinition SharedSizeGroup="c6"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Background" VerticalAlignment="Center"/>
@@ -482,8 +481,8 @@
<TextBlock Grid.Column="5" HorizontalAlignment="Center" Margin="6,0" Text="Font" VerticalAlignment="Center"/>
<TextBlock Grid.Column="6" HorizontalAlignment="Center" Margin="6,0" Text="Sample" VerticalAlignment="Center"/>
</Grid>
<ItemsControl Grid.Row="1" ItemsSource="{Binding column_items}" Margin="8,0" ItemTemplateSelector="{StaticResource LadderColumnTemplateSelector}"/>
<WrapPanel Grid.Row="3" Margin="8,0" Width="{Binding ActualWidth, ElementName=fonts_and_colors_headers, Mode=OneWay}" VerticalAlignment="Top">
<ItemsControl Grid.Row="1" ItemsSource="{Binding column_items}" ItemTemplateSelector="{StaticResource LadderColumnTemplateSelector}"/>
<WrapPanel Grid.Row="3" Width="{Binding ActualWidth, ElementName=fonts_and_colors_headers, Mode=OneWay}" VerticalAlignment="Top">
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource HintText}">
@@ -725,7 +724,7 @@
<Grid HorizontalAlignment="Left" Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}" MaxHeight="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -926,6 +925,11 @@
</ComboBox>
<!--Auto Clear-->
<CheckBox Grid.Row="10" Grid.ColumnSpan="3" HorizontalAlignment="Left" VerticalAlignment="Top" VerticalContentAlignment="Top" Margin="0,2,0,0" FlowDirection="RightToLeft" IsChecked="{Binding auto_clear}">
<CheckBox.Resources>
<Style TargetType="Path">
<Setter Property="FlowDirection" Value="LeftToRight"/>
</Style>
</CheckBox.Resources>
Auto Clear
</CheckBox>
</Grid>
@@ -980,5 +984,29 @@
-->
</Grid>
</TabItem>
<TabItem Header="Safety Checks" VerticalAlignment="Stretch">
<Grid HorizontalAlignment="Left" Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox Content="Warn on Market Cross" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight"/>
</Style>
</CheckBox.Resources>
</CheckBox>
</Grid>
</TabItem>
<TabItem Header="Mini Order Book">
<usp:GridSettingsView Height="790" Margin="6" DataContext="{Binding mini_ob}" GridSectionContent="{Binding}" GridSectionContentTemplate="{DynamicResource GridSectionCoreContentTemplate}"/>
</TabItem>
</TabControl>
</UserControl>
@@ -152,20 +152,21 @@ namespace mbts.spark {
// 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;
if (_host._vm != null) {
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();
}
@@ -63,9 +63,10 @@
IsEditMode="False"
IsLazy="True"
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
SelectedSecurity="{Binding security}"
SelectedSecurity="{Binding security, UpdateSourceTrigger=Explicit}"
ShowDialogSkirt="True"
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
/>
<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}"/>
@@ -14,7 +14,6 @@
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
mc:Ignorable="d"
MaxHeight="240"
MinWidth="875"
d:DesignHeight="600" d:DesignWidth="1080"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}">
@@ -1177,7 +1177,6 @@ namespace mbts.spark {
public class CommitColumn : FrameworkElementColumnBase<Columns> {
public CommitColumn() : base(Columns.Commit) {
id = key.ToString();
Header = "";
Width = 20;
}
@@ -20,7 +20,6 @@ using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@@ -38,6 +37,8 @@ namespace mbts.spark {
_min_order_params_height = 300,
_min_ladder_height = 100,
_min_fills_height = 100;
static readonly TimeSpan _selection_change_throttle = TimeSpan.FromSeconds(0.1);
readonly IBus _bus;
readonly OrdersModel _model;
readonly Property<OrderBookRowVM> _current_row;
@@ -46,7 +47,8 @@ namespace mbts.spark {
readonly OrderBookColumnCollection _columns;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
readonly SelectedOrderParamsMon _selected_params_mon;
readonly OrderParamsAggregator _selected_params_mon;
readonly CommonValueMonitor<SecurityImmutable?> _selected_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly RightPanelModel _right_panel = new RightPanelModel();
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
double
@@ -68,7 +70,6 @@ namespace mbts.spark {
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,
@@ -77,7 +78,8 @@ namespace mbts.spark {
_bus = bus;
_model = model;
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle);
ladder_vm = new LadderVMContainer(bus, model);
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, OrderDataHelper.Defaults.AlgoData(), editing_permissions: AlgoEditingPermissions.AllowEditValues));
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
order_mod_vm.algo_vm.algo_root = null;
@@ -145,8 +147,7 @@ namespace mbts.spark {
s => fills_vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(fills_vm.columns.Select(c => c.create())),
Observable.Return(title)
s.with(fills_vm.columns.Select(c => c.create()))
),
show_save_as_defaults: false);
// TODO: bind to main window's Title
@@ -154,7 +155,7 @@ namespace mbts.spark {
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
grid_settings_manager = new OrderBookGridSettingsManager(owner_title, bus, notification_service, model, rows, columns);
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, model, rows, core_columns);
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
IDisposable attach_vm_hooks = null;
begin_attaching += on_begin_attaching;
@@ -177,6 +178,11 @@ namespace mbts.spark {
if (ch.newval && fills_height < _min_fills_height)
fills_height = _min_fills_height;
}),
_selected_security_mon.SubscribeThrottled(_selection_change_throttle).to_security_md(bus).OnDispatcher().Subscribe(smd => ladder_vm.security_md = smd),
_subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
var hierarchical_order_keys = new HashSet<uint>(selected_hierarchical_rows.Select(r => r.order_key));
ladder_vm.set_orders(hierarchical_order_keys);
}),
_selected_params_mon.subscribe_all_active().Subscribe(on_all_selected_orders_active_changed),
_selected_params_mon.subscribe_security_plurality().Subscribe(p => {
security_plurality = p;
@@ -218,7 +224,6 @@ namespace mbts.spark {
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
order_mod_vm.algo_vm,
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(_selected_params_mon.on_security_md_map),
attach_vm,
_show_fills.observe_current(true)
.Subscribe(is_visible => {
@@ -326,8 +331,8 @@ namespace mbts.spark {
IDisposable connect_order_mod_vm() =>
Disposable.Create(
_selected_params_mon.subscribe_algos().Subscribe(order_mod_vm.algo_vm.load_many),
order_mod_vm.subscribe_is_dirty().Subscribe(_ => {
_selected_params_mon.subscribe_algos().OnDispatcher().Subscribe(order_mod_vm.algo_vm.load_many),
order_mod_vm.subscribe_is_dirty().OnDispatcher().Subscribe(_ => {
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
revert_selected_orders_cmd.RaiseCanExecuteChanged();
}),
@@ -335,10 +340,10 @@ namespace mbts.spark {
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
Disposable.Create(
_selected_params_mon.subscribe_security_md().Subscribe(newval => vm.security_md = newval),
_selected_params_mon.subscribe_price().Subscribe(vm.on_common_price),
_selected_params_mon.subscribe_size().Subscribe(vm.on_common_size),
_selected_params_mon.subscribe_account().Subscribe(newval => vm.selected_account = newval));
_selected_params_mon.subscribe_security().OnDispatcher().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
}
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
@@ -440,6 +445,7 @@ namespace mbts.spark {
request_selection?.Invoke((_row_collection, _all_rows_selected));
}
}
public LadderVMContainer ladder_vm { get; }
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
@@ -447,11 +453,13 @@ namespace mbts.spark {
public void row_selection_changed(SelectionChangedEventArgs e) {
try {
using (_selected_params_mon.begin_udpate()) {
using (_selected_params_mon.defer_updates())
using (_selected_security_mon.defer_updates()) {
if (e.RemovedItems != null) {
foreach (var row_vm in e.RemovedItems.OfType<OrderBookRowVM>()) {
row_vm.is_selected = false;
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
_selected_security_mon.remove(row_vm.security);
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
_attachable_selected_orders.Remove(row_vm.order_key);
_detachable_selected_orders.Remove(row_vm.order_key);
}
@@ -460,11 +468,12 @@ namespace mbts.spark {
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
var root = _get_root(row_vm.order_tuple);
row_vm.is_selected = true;
_selected_security_mon.add(row_vm.security);
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
row_vm.updated += on_updated;
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
}
_selected_params_mon.on_order_selected(root);
_selected_params_mon.Add(root);
if (row_vm.is_attachable)
_attachable_selected_orders.Add(row_vm.order_key);
if (row_vm.is_detachable)
@@ -476,16 +485,26 @@ namespace mbts.spark {
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));
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
_selected_security_mon.remove(row_vm.security);
return;
}
var new_root = _get_root(change.newval);
if (!new_root.Equals(root)) {
// 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;
using (_selected_params_mon.defer_updates()) {
// unselect and select in one smooth motion via on_row_updated
// because we thought there may be gremlins afoot when doing
// on_row_unselected and then on_row_selected
_selected_params_mon.Remove(root);
_selected_params_mon.Add(new_root);
root = new_root;
}
}
if (change.oldval.security?.Id != change.newval.security?.Id) {
using (_selected_security_mon.defer_updates()) {
_selected_security_mon.remove(change.oldval.security);
_selected_security_mon.add(change.newval.security);
}
}
}
}
@@ -589,21 +608,6 @@ namespace mbts.spark {
public void begin_attach_algos() => is_attaching_orders = true;
public bool try_get_parent(OrderBookRowVM row, out OrderSecurity parent) =>
_cached_orders.TryGetValue(row.parent_order_key, out parent);
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;
}
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();
@@ -612,17 +616,10 @@ namespace mbts.spark {
}
}
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);
@@ -651,6 +648,12 @@ namespace mbts.spark {
}
}
OrderSecurity _get_root(OrderSecurity os) {
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
os = pos;
return os;
}
void _on_filter_changed() {
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
if (!fd.Equals(_cached_filter_data)) {
@@ -737,52 +740,6 @@ namespace mbts.spark {
}
}
static IObservable<ISet<uint>> _subscribe_selected_order_keys(ObservableCollection<OrderBookRowVM> selected_rows) =>
Observable.Create<ISet<uint>>(o => {
bool waiting_for_mouse_up = false;
var sub_container = new DisposableContainer();
var builder = ImmutableHashSet<uint>.Empty.ToBuilder();
foreach (var row in selected_rows)
builder.Add(row.order_key);
var selected_keys = builder.ToImmutable();
o.OnNext(selected_keys);
return Disposable.Create(selected_rows.Subscribe(on_selection_changed), sub_container);
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
if (waiting_for_mouse_up)
return;
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Replace:
foreach (OrderBookRowVM row in e.NewItems)
selected_keys = selected_keys.Add(row.order_key);
break;
case NotifyCollectionChangedAction.Remove:
foreach (OrderBookRowVM row in e.OldItems)
selected_keys = selected_keys.Remove(row.order_key);
break;
case NotifyCollectionChangedAction.Reset:
selected_keys = ImmutableHashSet<uint>.Empty;
break;
default: return;
}
if (Mouse.LeftButton == MouseButtonState.Pressed) {
waiting_for_mouse_up = true;
wait_for_mouse_up();
async void wait_for_mouse_up() {
await Wait.until_true(() => Mouse.LeftButton == MouseButtonState.Released, TimeSpan.FromSeconds(60), 333);
builder = ImmutableHashSet<uint>.Empty.ToBuilder();
foreach (var row in selected_rows)
builder.Add(row.order_key);
selected_keys = builder.ToImmutable();
o.OnNext(selected_keys);
waiting_for_mouse_up = false;
}
} else {
o.OnNext(selected_keys);
}
}
});
public class OrderParamsVM : DisposableViewModelBase {
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly OrderBookVM _owner;
@@ -793,8 +750,6 @@ namespace mbts.spark {
AlgoParamPermissions
_price_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write,
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
HashSet<uint> _selected_order_tree = new HashSet<uint>();
LadderVM _ladder_vm;
SecurityMD? _security_md;
public OrderParamsVM(OrderBookVM owner)
@@ -845,13 +800,7 @@ namespace mbts.spark {
appended_param_rows.insert_front(new AlgoParamRowVM(root, _price_param, null, AlgoParamTemplateKeys.Price));
}
}),
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join),
owner._subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
_selected_order_tree = new HashSet<uint>(_owner.selected_hierarchical_rows.Select(r => r.order_key));
_reset_ladder_vm();
})) {
() => Disposable.dispose(ref _ladder_vm)
};
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
// payup methods
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
void payup_ask(int ticks) => payup(bbo.ask, ticks);
@@ -879,8 +828,7 @@ namespace mbts.spark {
get => _security_md;
set {
if (Set(ref _security_md, value)) {
_reset_ladder_vm();
RaisePropertyChanged(nameof(security), nameof(spec), nameof(ladder_vm));
RaisePropertyChanged(nameof(security), nameof(spec));
bbo.security_md = value;
_payup_bid_cmd.RaiseCanExecuteChanged();
_payup_ask_cmd.RaiseCanExecuteChanged();
@@ -904,7 +852,6 @@ namespace mbts.spark {
}
public AlgoDefinitionVM algo_vm { get; }
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
public LadderVM ladder_vm => _ladder_vm;
public BBOVM bbo { get; }
public ICommand send_order { get; }
public ICommand payup_bid_cmd => _payup_bid_cmd;
@@ -939,17 +886,6 @@ namespace mbts.spark {
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
void _reset_ladder_vm() {
if (_ladder_vm == null && !_security_md.HasValue)
return;
Disposable.replace(ref _ladder_vm,
_security_md.HasValue ?
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) {
if (permissions_backing != permissions) {
permissions_backing = permissions;
@@ -996,128 +932,6 @@ namespace mbts.spark {
EqualityComparer.aggregate_hash((int)algo_tree_display_mode, column_filters?.GetHashCode() ?? 0);
}
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class SelectedOrderParamsMon {
static readonly TimeSpan _throttle = TimeSpan.FromSeconds(0.1);
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
ask => ask,
EqualityComparer<AlgoSecurityKey>.Default,
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
readonly DynamicOrderHelper _dyn_helper;
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
public SelectedOrderParamsMon(DynamicOrderHelper dyn_helper) {
_dyn_helper = dyn_helper;
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
}
event Action _security_md_map_changed;
public SecurityImmutable? security => _security_mon.common_value;
public SecurityMD? security_md => security is SecurityImmutable sec && _security_md_map.TryGetValue(sec.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? size => _size_mon.common_value_nullable();
public AccountTuple? account => _account_mon.common_value_nullable();
public bool all_active => _all_active_mon.common_value;
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityMD?> subscribe_security_md() =>
Observable.Create<SecurityMD?>(o => {
SecurityMD? last_value = security_md;
o.OnNext(last_value);
_security_mon.common_value_changed += on_common_sec_changed;
_security_md_map_changed += refresh;
return Disposable.Create(() => {
_security_mon.common_value_changed -= on_common_sec_changed;
_security_md_map_changed -= refresh;
});
void on_common_sec_changed(SecurityImmutable? _) => refresh();
void refresh() {
var curr = security_md;
if (curr != last_value)
o.OnNext(last_value = curr);
}
});
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
public IDisposable begin_udpate() =>
_group_mon.defer_updates();
public void on_order_selected(OrderSecurity order_tuple) =>
_group_mon.add(order_tuple);
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();
}
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(OrderSecurity osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
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(OrderSecurity osam) =>
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
struct AlgoSecurityKey {
public readonly CustomOrderDefinitionImmutable algo;
public readonly SecurityImmutable? security;
public readonly AlgoStructureKey key;
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
this.algo = algo;
this.security = security;
this.key = key;
}
public bool Equals(AlgoSecurityKey other) =>
security == other.security &&
key == other.key;
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
public override int GetHashCode() => key.GetHashCode();
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
}
}
class RightPanelModel {
OrderBookRightPanelSettings.ViewMode _mode;
OrderBookRightPanelSettings.Components _all_selected, _hidden = OrderBookRightPanelSettings.Components.None;
@@ -1180,11 +994,13 @@ namespace mbts.spark {
var join = config.Configuration.get<bts.config.JoinAttachUIOptions>();
var lean = config.Configuration.get<bts.config.LeanAttachUIOptions>();
var top = config.Configuration.get<bts.config.TopAttachUIOptions>();
var offset = config.Configuration.get<bts.config.OffsetAttachUIOptions>();
_algo_type_conf = new Dictionary<CustomOrderDefinition.CustomOneofCase, (bool, bool)> {
{ CustomOrderDefinition.CustomOneofCase.Cover, (cover.show_price, cover.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Join, (join.show_price, join.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Lean, (lean.show_price, lean.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) }
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Offset, (offset.show_price, offset.show_quantity) }
};
}
@@ -7,8 +7,8 @@ using bts.spark;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -17,10 +17,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.orderbook.OrderBookGridSettings>;
namespace mbts.spark {
internal class OrderBookWindow : SparkWindow<OrderBookStartupData>, ISharedWindow {
@@ -65,7 +63,7 @@ namespace mbts.spark {
}
public void thread_init() {
_vm = new OrderBookVM(this.observe_title(), _bus, _notification_service, _model, _settings_manager);
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
_vm.load_from(_state);
_view = new OrderBookView(_vm);
@@ -171,14 +169,11 @@ namespace mbts.spark {
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.general = s.general.with(_vm.core_columns.Select(c => c.create()));
return _vm.grid_settings_manager.create_vm(s, this.observe_title());
return _vm.grid_settings_manager.create_vm(s);
}
);
// TODO: bind to main window's Title
gs_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
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")
});
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
@@ -43,7 +43,9 @@ namespace bts.shell.orderbook {
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
public OrderBookGridSettings with(GridSettings general) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations);
new OrderBookGridSettings(general ?? this.general, predicate_animations) {
algo_tree_display_mode = algo_tree_display_mode
};
public bool Equals(OrderBookGridSettings other) =>
other != null &&
@@ -14,16 +14,12 @@ using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace bts.shell.orderbook {
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
public static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> hard_column_flags;
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
AlgoTreeDisplayMode _algo_tree_display_mode;
public OrderBookGridSettingsManager(
IObservable<string> owner_title,
IBus bus,
INotificationService notification_service,
OrdersModel model,
IEnumerable<OrderBookRowVM> rows,
ObservableCollection<DIYGrid.ColumnBase> columns) {
static OrderBookGridSettingsManager() {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
// TODO: Remove the immutable font-weight flags when cell animation is ready
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
@@ -31,10 +27,20 @@ namespace bts.shell.orderbook {
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
// TODO: Remove the immutable font-color flags when cell animation is ready
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontWeight;
}
col_settings_flags[Columns.Commit.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableHeader;
hard_column_flags = col_settings_flags;
}
public OrderBookGridSettingsManager(
IBus bus,
INotificationService notification_service,
OrdersModel model,
IEnumerable<OrderBookRowVM> rows,
ObservableCollection<DIYGrid.ColumnBase> columns) {
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
owner_title, bus, model, rows, columns,
bus, model, rows, columns,
() => new OrderRecordTuplePredicate(),
() => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
@@ -53,7 +59,7 @@ namespace bts.shell.orderbook {
p => p.create_notification_predicate(notification_service.data_cache),
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
col_settings_flags);
hard_column_flags);
_core_man_as_observer = core_manager;
}
@@ -83,8 +89,8 @@ namespace bts.shell.orderbook {
return gs;
}
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs, IObservable<string> owner_title) =>
new OrderBookSettingsVM(core_manager.create_vm(gs.general, owner_title)) {
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) =>
new OrderBookSettingsVM(core_manager.create_vm(gs.general)) {
algo_tree_display_mode = gs.algo_tree_display_mode
};
@@ -137,12 +137,13 @@ namespace bts.shell.orderbook {
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, 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 {
// standard
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);
@@ -167,6 +168,13 @@ namespace bts.shell.orderbook {
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);
// editable
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Size, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
@@ -174,7 +182,7 @@ namespace bts.shell.orderbook {
}
}
public static IEnumerable<GridColumnSettings> all {
public static IEnumerable<GridColumnSettings> standard {
get {
yield return security;
yield return symbol;
@@ -203,13 +211,26 @@ namespace bts.shell.orderbook {
}
}
public static IEnumerable<GridColumnSettings> editable {
get {
yield return cancel;
yield return update;
yield return edit_quantity;
yield return edit_price;
yield return commit;
yield return attach_detach;
}
}
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) {
foreach (var col_setting in editable)
yield return col_setting;
foreach (var col_setting in standard) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
@@ -17,7 +17,8 @@ namespace bts.shell.orderbook {
this.core_settings_vm = core_settings_vm;
public OrderBookSettingsVM(
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
IBus bus,
OrdersModel orders_model,
Func<OrderRecordTuplePredicate> predicate_data_factory,
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
@@ -25,7 +26,7 @@ namespace bts.shell.orderbook {
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
owner_title, bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
}
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
@@ -554,7 +554,7 @@
BorderThickness="0,1,0,0"
ResizeBehavior="PreviousAndNext"
/>
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding order_mod_vm.ladder_vm}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding ladder_vm.value}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
<GridSplitter
x:Name="ladder_splitter"
Grid.Column="1"
@@ -117,12 +117,12 @@ namespace bts.shell {
var temp_sound_triggers = _sound_trigger_refs;
foreach (var kv in temp_sound_triggers) {
if (kv.Key.trigger(value)) {
sounds.AddRange(kv.Value.keys);
sounds.AddRange(kv.Value.Keys);
}
}
var temp_toast_triggers = _toast_trigger_refs;
foreach (var krc in temp_toast_triggers) {
if (krc.key.trigger(value) is IToastNotification tn && tn.pass)
foreach (var kv in temp_toast_triggers) {
if (kv.Key.trigger(value) is IToastNotification tn && tn.pass)
toast.Add(tn);
}
if (sounds.Count > 0 || toast.Count > 0)
+9 -10
View File
@@ -168,17 +168,14 @@ namespace mbts.spark.utilities {
// this will be the tag 50 on CME orders
// current user might be okay, but we might also want to get it
// from the store that serves the risk admin gui
ManualOrderIndicator = ManualOrderIndicator.Yes
ManualOrderIndicator = ManualOrderIndicator.Yes,
User = _admin_model.user.name,
UserId = _admin_model.user.id
};
if (account.HasValue) {
rv.Account = account.Value.name;
rv.AccountId = account.Value.id;
}
var user = _admin_model.get_current_user();
if (user.HasValue) {
rv.User = user.Value.name;
rv.UserId = user.Value.id;
}
return rv;
}
@@ -605,7 +602,8 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset
},
// The following spreader types are not yet ready for production use:
// CustomOrderDefinition.CustomOneofCase.Dynamic,
@@ -622,7 +620,8 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset
},
outright_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
@@ -718,7 +717,7 @@ namespace mbts.spark.utilities {
!b.HasChild);
public static bool top_equals(TopOrderDefinitionImmutable a, TopOrderDefinitionImmutable b) =>
a.WatchLevelTickOffset == b.WatchLevelTickOffset &&
a.TicksOff == b.TicksOff &&
(a.HasTopThreshold ?
b.HasTopThreshold && quantity_measure_equals(a.TopThreshold, b.TopThreshold) :
!b.HasTopThreshold) &&
@@ -909,7 +908,7 @@ namespace mbts.spark.utilities {
add_component(_get_hash(_get_tick_child(tick)));
break;
case CustomOrderDefinition.CustomOneofCase.Top:
add_component(def.Top.WatchLevelTickOffset.GetHashCode());
add_component(def.Top.TicksOff.GetHashCode());
add_component(def.Top.HasTopThreshold ? def.Top.TopThreshold.GetHashCode() : 0);
add_component(_get_top_child(def.Top).GetHashCode());
break;
@@ -0,0 +1,105 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class OrderParamsAggregator {
readonly TimeSpan _throttle;
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
ask => ask,
EqualityComparer<AlgoSecurityKey>.Default,
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
readonly DynamicOrderHelper _dyn_helper;
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle) {
_dyn_helper = dyn_helper;
_throttle = throttle;
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
}
public SecurityImmutable? security => _security_mon.common_value;
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? size => _size_mon.common_value_nullable();
public AccountTuple? account => _account_mon.common_value_nullable();
public bool all_active => _all_active_mon.common_value;
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
public IDisposable defer_updates() =>
_group_mon.defer_updates();
event Action<TableUpdate<OrderSecurity, uint>> _updated;
public void Update(TableUpdate<OrderSecurity, uint> table) {
_updated?.Invoke(table);
}
public void Add(OrderSecurity order_tuple) =>
_group_mon.add(order_tuple);
public void Remove(OrderSecurity order_tuple) =>
_group_mon.remove(order_tuple);
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(OrderSecurity osam) =>
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
struct AlgoSecurityKey {
public readonly CustomOrderDefinitionImmutable algo;
public readonly SecurityImmutable? security;
public readonly AlgoStructureKey key;
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
this.algo = algo;
this.security = security;
this.key = key;
}
public bool Equals(AlgoSecurityKey other) =>
security == other.security &&
key == other.key;
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
public override int GetHashCode() => key.GetHashCode();
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
}
}
}
@@ -105,13 +105,18 @@ namespace mbts.spark {
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),
(last, props, change) => convert_inline_account(props.account, change.New, false),
() => new InlineUserProperty<AccountTuple>(),
new InlineOrderProperties(),
DictionaryHelper.empty<uint, string>());
// Note that with the .Where on the account_tuples, we will ignore any
// updates for which all accounts have been removed.
// This was a safer change than changing SelectorProperty's IEnumberable.OnNext
// so that it would not reset the selected item on server reboots; i.e. this
// change only affects this IObservable.
_accounts = backing(
nameof(selected_account),
model.factory.subscribe_account_tuples(),
model.factory.subscribe_account_tuples().Where(items => items.Any()),
inline_account_src,
a => new AccountTuple?(a),
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
@@ -263,9 +268,18 @@ namespace mbts.spark {
break;
}
}
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map) =>
//
// Regarding the 3rd parameter, force_override_from_inline_property:
// When using an InlineUserProperty, in the context of assigning a default vs selected item,
// there is somewhat of a conflict between the IObserver<InlineUserProperty<TItem>> and the
// IObserver<IEnumerable<TItem>> OnNext methods. This 3rd paramter provides yet another override
// to say if the force_override should come from the inline property or if the force_override should
// be disbled, by way of the immediate return in ViewModelBase._try_auto_select when force_override
// is false. Our intent is then accomplished because we are no longer attempting to set a default/
// selected item when the account_tuples potentially changes
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map, bool force_override_from_inline_property = true) =>
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>(new AccountTuple(account_id.value, name), force_override_from_inline_property ? account_id.force_override : false) :
new InlineUserProperty<AccountTuple>();
}
@@ -5,13 +5,16 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
readonly Property<uint?> _ticks_off;
readonly Property<QuantityMeasureImmutable?> _top_threshold;
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_ticks_off = add_nullable_property<uint>(nameof(ticks_off), TopDef.TicksOffFieldNumber);
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
}
public uint? ticks_off { get => _ticks_off.Value; set => _ticks_off.set(value); }
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
}
}
@@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using mbts.ui.shell;
using System;
using System.Windows.Threading;
namespace mbts.ui.tests
{
class TestShellWindow : IShellWindow
{
namespace mbts.ui.tests {
class TestShellWindow : IShellWindow {
#region IShellWindow boilerplate
public string Title { get; set; }
public event Action TitleChanged = () => { };
@@ -20,6 +15,7 @@ namespace mbts.ui.tests
#endregion
public Dispatcher Dispatcher { get; private set; }
public ShellWindowUI GetContent() {
this.Dispatcher = Dispatcher.CurrentDispatcher;
var content = new System.Windows.Forms.Button() { Text = "Click Me!", Width = 100, Height = 100 };
@@ -27,10 +23,12 @@ namespace mbts.ui.tests
}
public bool IsDisposed { get; set; }
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
public void Dispose() { IsDisposed = true; }
}
class TestSlowWindow : TestShellWindow, ISlowWindow {
public void thread_init() { }
}
}
}
@@ -1,17 +1,17 @@
using System;
using bts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace mbts.collections {
interface IKeyRefCount<T> {
T key { get; }
int count { get; }
}
class RefCountMap<TKey> : IDictionary<TKey, int>, IReadOnlyDictionary<TKey, int> {
static readonly Func<KeyRefCount, int> _count_selector = krc => krc.count;
class RefCountMap<TKey> : IReadOnlyCollection<IKeyRefCount<TKey>>, ICollection {
readonly IEqualityComparer<TKey> _key_comparer;
readonly Dictionary<TKey, KeyRefCount> _dictionary;
readonly IReadOnlyCollection<int> _ro_values;
readonly ICollection<int> _values;
public RefCountMap()
: this(EqualityComparer<TKey>.Default) { }
@@ -22,31 +22,54 @@ namespace mbts.collections {
RefCountMap(Dictionary<TKey, KeyRefCount> dictionary, IEqualityComparer<TKey> key_comparer) {
_dictionary = dictionary;
_key_comparer = key_comparer;
CollectionHelper.wrap_selector(dictionary.Values, _count_selector, out _ro_values, out _values);
}
public int this[TKey key] {
get => _dictionary[key].count;
set {
if (!_dictionary.TryGetValue(key, out var krc))
_dictionary[key] = krc = new KeyRefCount(key);
krc.count = value;
}
}
public int Count => _dictionary.Count;
public object SyncRoot => null;
public IReadOnlyCollection<TKey> Keys => _dictionary.Keys;
public IReadOnlyCollection<int> Values => _ro_values;
public bool IsReadOnly => false;
public bool IsSynchronized => false;
public int this[TKey key] => _dictionary[key].count;
public IReadOnlyCollection<TKey> keys => _dictionary.Keys;
public bool contains_key(TKey key) => _dictionary.ContainsKey(key);
public object SyncRoot => null;
// explicit
ICollection<TKey> IDictionary<TKey, int>.Keys => _dictionary.Keys;
ICollection<int> IDictionary<TKey, int>.Values => _values;
IEnumerable<TKey> IReadOnlyDictionary<TKey, int>.Keys => _dictionary.Keys;
IEnumerable<int> IReadOnlyDictionary<TKey, int>.Values => _values;
public void CopyTo(Array array, int index) =>
((ICollection)_dictionary).CopyTo(array, index);
public void Add(TKey key, int value) =>
_dictionary.Add(key, new KeyRefCount(key) { count = value });
public IEnumerator<IKeyRefCount<TKey>> GetEnumerator() =>
_dictionary.Values.GetEnumerator();
public void Add(KeyValuePair<TKey, int> item) =>
_dictionary.Add(item.Key, new KeyRefCount(item.Key) { count = item.Value });
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
public void Clear() => _dictionary.Clear();
public void increment(TKey key) =>
increment(key, 1);
public RefCountMap<TKey> clone() =>
new RefCountMap<TKey>(new Dictionary<TKey, KeyRefCount>(_dictionary, _key_comparer), _key_comparer);
public void increment(TKey key, int count) {
if (!_dictionary.TryGetValue(key, out KeyRefCount krc))
_dictionary.Add(key, krc = new KeyRefCount(key) { count = count });
else krc.count += count;
public bool Contains(KeyValuePair<TKey, int> item) =>
_dictionary.TryGetValue(item.Key, out var krc) && krc.count == item.Value;
public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
public void CopyTo(KeyValuePair<TKey, int>[] array, int arrayIndex) {
foreach (var kv in _dictionary)
array[arrayIndex++] = new KeyValuePair<TKey, int>(kv.Key, kv.Value.count);
}
public void CopyTo(Array array, int index) {
foreach (var kv in _dictionary)
array.SetValue(new KeyValuePair<TKey, int>(kv.Key, kv.Value.count), index++);
}
public bool decrement(TKey key) =>
@@ -66,38 +89,41 @@ namespace mbts.collections {
return false;
}
public bool remove(TKey key) =>
_dictionary.Remove(key);
public IEnumerator<KeyValuePair<TKey, int>> GetEnumerator() {
foreach (var kv in _dictionary)
yield return new KeyValuePair<TKey, int>(kv.Key, kv.Value.count);
}
public void clear() => _dictionary.Clear();
public void increment(TKey key) =>
increment(key, 1);
public bool try_pop(TKey key, out IKeyRefCount<TKey> key_ref_count) {
if (try_lookup(key, out key_ref_count)) {
remove(key);
public void increment(TKey key, int count) {
if (!_dictionary.TryGetValue(key, out KeyRefCount krc))
_dictionary.Add(key, krc = new KeyRefCount(key) { count = count });
else krc.count += count;
}
public bool Remove(TKey key) => _dictionary.Remove(key);
public bool Remove(KeyValuePair<TKey, int> item) {
if (_dictionary.TryGetValue(item.Key, out var krc) && krc.count == item.Value) {
_dictionary.Remove(item.Key);
return true;
} else return false;
}
public bool try_lookup(TKey key, out IKeyRefCount<TKey> key_ref_count) {
if (_dictionary.TryGetValue(key, out KeyRefCount krc)) {
key_ref_count = krc;
public bool TryGetValue(TKey key, out int value) {
if (_dictionary.TryGetValue(key, out var krc)) {
value = krc.count;
return true;
}
key_ref_count = null;
value = 0;
return false;
}
// explicit
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void absorb(RefCountMap<TKey> other) {
foreach (var kv in other._dictionary) {
if (!_dictionary.TryGetValue(kv.Key, out KeyRefCount curr))
_dictionary.Add(kv.Key, kv.Value);
}
}
public RefCountMap<TKey> clone() =>
new RefCountMap<TKey>(new Dictionary<TKey, KeyRefCount>(_dictionary, _key_comparer), _key_comparer);
class KeyRefCount : IKeyRefCount<TKey> {
class KeyRefCount {
public KeyRefCount(TKey key) =>
this.key = key;
@@ -198,27 +224,7 @@ namespace mbts.collections {
_dictionary.Clear();
}
public bool try_pop(TKey key, out IKeyRefCount<TKey> key_ref_count) {
using (_execute_op()) {
if (try_lookup(key, out key_ref_count)) {
remove(key);
return true;
} else return false;
}
}
public bool try_lookup(TKey key, out IKeyRefCount<TKey> key_ref_count) {
using (_execute_op()) {
if (_dictionary.TryGetValue(key, out KeyRefCount krc)) {
key_ref_count = krc;
return true;
}
key_ref_count = null;
return false;
}
}
class KeyRefCount : IKeyRefCount<TKey> {
class KeyRefCount {
public KeyRefCount(TKey key) =>
this.key = key;
@@ -7,8 +7,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
@@ -240,84 +238,7 @@ namespace System.Collections.Specialized {
}
namespace System.Reactive.Linq {
public static class ObservableExtensions
{
/// <summary>
/// <para>Creates an "Observable Heartbeat" that will publish true/false to indicate whether or not the heartbeat is detected, it will only send updates when the 'heartbeat status' has changed.</para>
/// <para>Whenever the source produces an update, the heartbeat evaluates to true and the heartbeat duration clock is reset.</para>
/// <para>Whenever heartbeat duration clock expires, the heartbeat evaluates to false.</para>
/// </summary>
/// <typeparam name="T">Type of data produced by the source (bears no impact on the resultant observable heartbeat</typeparam>
/// <param name="source">The source that will indicate alive/dead by publishing updates within the duration period</param>
/// <param name="duration">The amount of time that must pass without receiving an update from the source to determine the heartbeat is not detected</param>
/// <returns>An "Observable Heartbeat"</returns>
public static IObservable<bool> AsHeartbeat<T>(this IObservable<T> source, TimeSpan duration) {
IObservable<bool> dead = source.Throttle(duration).Select(_ => false);
IObservable<bool> alive = source.Select(_ => true);
return dead.Merge(alive).DistinctUntilChanged();
}
// was sick of this scenario: observable_source.Select(enumerable => enumerable.FirstOrDefault(where)).Where(val => val != null)
// with structs it was even more annoying because they can't be null
public static IObservable<TResult> TrySelect<T, TResult>(this IObservable<T> source, Func<T, (TResult result, bool success)> try_select) =>
source.Select(try_select).Where(ts => ts.success).Select(ts => ts.result);
public static IObservable<(T1, T2)> CombineLatest<T1, T2>(this IObservable<T1> source, IObservable<T2> second) =>
source.CombineLatest(second, (v1, v2) => (v1, v2));
public static IObservable<T> BufferLatest<T>(this IObservable<T> source, TimeSpan throttle) =>
source.Buffer(throttle).Where(vals => vals.Count > 0).Select(vals => vals[vals.Count - 1]);
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source) =>
PairWithPrevious(source, default);
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
/// <summary>
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
/// each inner sequence is terminated when the next one begins.
/// <para/>In other words, observers are notified by the inner observerables in &quot;series,&quot; rather than &quot;parallel.&quot;
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="select_next"></param>
/// <param name="source_sub"></param>
/// <returns></returns>
public static IConnectableObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
new ConnectableSeriesSource<TSource>(source).create_connectable(select_next);
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, Func<T, bool> predicate, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value) =>
Observable.Create<T>(o => {
o.OnNext(get_value());
return source.Subscribe(o);
});
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value, Func<bool> can_get_value) =>
Observable.Create<T>(o => {
if (can_get_value())
o.OnNext(get_value());
return source.Subscribe(o);
});
public static IObservable<T> FromEvent<T>(Action<Action> add_handler, Action<Action> remove_handler, Func<T> selector) =>
Observable.Create<T>(o => {
callback();
add_handler(callback);
return Disposable.Create(() => remove_handler(callback));
void callback() => o.OnNext(selector());
});
static class SparkObservableExtensions {
// This is placed here to intentionally make ObserveOnDispatcher ambiguous with the one provided by Rx
public static IObservable<T> ObserveOnDispatcher<T>(this IObservable<T> source) {
throw new InvalidOperationException("This method can starve the UI of input messages. Please use OnDispatcher() instead");
@@ -326,47 +247,5 @@ namespace System.Reactive.Linq {
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
}
public static IDisposable Subscribe<T>(this IObservable<T> source, params IObserver<T>[] observers) =>
source.Subscribe(new CompositeObserver<T>(observers));
public static IObservable<T> RefCountReplay<T>(this IObservable<T> source, int buffer_size) {
if (buffer_size < 0) throw new ArgumentException("Buffer size is less than zero.", nameof(buffer_size));
if (buffer_size == 0) return source.Publish().RefCount();
int num_subscribers = 0;
ReplaySubject<T> replay = new ReplaySubject<T>(buffer_size);
IDisposable source_sub = null;
return Observable.Create<T>(o => {
if (Interlocked.Increment(ref num_subscribers) == 1)
source_sub = source.Subscribe(replay.OnNext);
IDisposable sub = replay.Subscribe(o);
return Disposable.Create(() => {
sub.Dispose();
if (Interlocked.Decrement(ref num_subscribers) == 0)
source_sub.Dispose();
});
});
}
/// <summary>
/// A less verbose way to call <see cref="Observable.FromEvent{Action{TEventArgs}, TEventArgs}(Action{Action{TEventArgs}}, Action{Action{TEventArgs}})"/>.
/// </summary>
public static IObservable<T> FromAction<T>(Action<T> event_source) =>
Observable.FromEvent<Action<T>, T>(cb => event_source += cb, cb => event_source -= cb);
/// <summary>
/// An inner subscription is made for each value in the source sequence using the specified delegate (<paramref name="subscribe_value"/>).
/// When a new value arrives, the subscription for the previous value is disposed before <paramref name="subscribe_value"/> is invoked with the new value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="subscribe_value"></param>
/// <returns></returns>
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
IDisposable value_sub = null;
return new Disposables.CompositeDisposable(source.Subscribe(val => Disposable.replace(ref value_sub, subscribe_value(val)))) {
() => Disposable.dispose(ref value_sub)
};
}
}
}
}
@@ -27,6 +27,8 @@ namespace mbts.ui {
// module to ask to restore the window after app restart.
IShellModule Owner { get; }
void on_shell_window_vm(ShellWindowVM shell_window_vm);
// Return the UI controls which will be displayed
ShellWindowUI GetContent();
@@ -113,5 +115,28 @@ namespace mbts.ui {
return src.Subscribe(o);
});
}
/// <summary>
/// Returns an IObservable sequence of <see cref="ShellWindowVM.Title"/>.
/// </summary>
public static IObservable<string> literal_title_source(IShellWindow shell_window, ShellWindowVM shell_window_vm) {
return Observable.Create<string>(o => {
shell_window_vm.TitleOverrideChanged += on_title_override_changed;
shell_window.TitleChanged += notify;
notify();
connect();
return Disposable.Create(disconnect);
void connect() {
shell_window_vm.Model.TitleChanged += notify;
shell_window_vm.TitleOverrideChanged += on_title_override_changed;
}
void disconnect() {
shell_window_vm.Model.TitleChanged -= notify;
shell_window_vm.TitleOverrideChanged -= on_title_override_changed;
}
void on_title_override_changed(string value) => notify();
void notify() => o.OnNext(shell_window_vm.Title);
});
}
}
}
@@ -32,10 +32,12 @@ namespace mbts.persist {
try {
s = await _persist.get<UserSettings>(NAME_SPACE, DEFAULT_KEY);
if (s != null) {
s.ladder.sanitize();
// ensure valid named ladder set (w/ distinct, non-zero ids)
var ladder_profile_map = new Dictionary<uint, LadderSettingsProfile>();
var unidentified_lps = new List<LadderSettingsProfile>();
foreach (var lp in s.ladder_profiles) {
lp.value.sanitize();
if (lp.id == 0) {
unidentified_lps.Add(lp);
} else ladder_profile_map[lp.id] = lp;
@@ -40,11 +40,11 @@ namespace bts.utils {
public event Action<T> common_value_changed;
public event Action<Plurality> plurality_changed;
public IReadOnlyCollection<T> distinct_values => _values.keys;
public T common_value => _values.keys.SafeSingleOrDefault(_default_value);
public IReadOnlyCollection<T> distinct_values => _values.Keys;
public T common_value => _values.Keys.SafeSingleOrDefault(_default_value);
public Plurality plurality {
get {
switch (_values.keys.Count) {
switch (_values.Count) {
case 0: return Plurality.None;
case 1: return Plurality.One;
default: return Plurality.Many;
@@ -115,7 +115,7 @@ namespace bts.utils {
_deferred_adds = new RefCountMap<T>(value_comparer);
_deferred_removes = new RefCountMap<T>(value_comparer);
_value_groups = new Dictionary<TKey, RefCountMap<T>>(key_comparer);
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.keys);
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.Keys);
}
public event Action<KeyValuePair<TKey, IEnumerable<T>>?> common_value_group_changed;
@@ -177,27 +177,27 @@ namespace bts.utils {
++_deferral_count;
return Disposable.Create(() => {
if (--_deferral_count == 0 && (_deferred_adds.Count > 0 || _deferred_removes.Count > 0)) {
foreach(var krc in _deferred_removes) {
var k = _key(krc.key);
foreach(var kv in _deferred_removes) {
var k = _key(kv.Key);
if (_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
rcm.decrement(krc.key, krc.count);
rcm.decrement(kv.Key, kv.Value);
if (rcm.Count == 0)
_value_groups.Remove(k);
}
}
foreach (var krc in _deferred_adds) {
var k = _key(krc.key);
foreach (var kv in _deferred_adds) {
var k = _key(kv.Key);
if (!_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
_value_groups.Add(k, rcm = new RefCountMap<T>(_value_comparer));
}
rcm.increment(krc.key, krc.count);
rcm.increment(kv.Key, kv.Value);
}
if (!EqualityComparer.nullable_equals(last_fired_common_key, common_value_group?.Key)) {
common_value_group_changed?.Invoke(common_value_group);
} else if (last_fired_common_key is TKey k) {
common_values_changed?.Invoke((
_deferred_removes.Select(krc => krc.key).Where(v => _key_comparer.Equals(_key(v), k)),
_deferred_adds.Select(krc => krc.key).Where(v => _key_comparer.Equals(_key(v), k))));
_deferred_removes.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k)),
_deferred_adds.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k))));
}
_deferred_adds = new RefCountMap<T>(_value_comparer);
_deferred_removes = new RefCountMap<T>(_value_comparer);
@@ -62,7 +62,7 @@ namespace mbts.utils {
_threshold = value;
}
}
public IReadOnlyCollection<T> filtered_items => _filtered_items.keys;
public IReadOnlyCollection<T> filtered_items => _filtered_items.Keys;
public IDisposable join(T item) =>
_subscribe_item == null ? throw new InvalidOperationException("No 'subscribe-item' delegate was specified in the constructor.") : join(item, _subscribe_item(item));
@@ -820,6 +820,11 @@ namespace mbts.ui.shell {
void _try_auto_select() {
if (_is_user_selected_item) {
// if we have a user-selected item and the _desired_item for which we might want to set as the selected item
// isn't force_override, then no change
if (!_desired_item.force_override) {
return;
}
if (desired_item_valid() && _desired_item.force_override && _try_match_item(_desired_item.value, out TValue match)) {
_set_selected_item(match, SelectionReason.SettingsMandatedSelection);
return;
@@ -3,11 +3,6 @@
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:cp="clr-namespace:mbts.ui.controls.color_picker"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:sys="System;assembly=mscorlib"
BorderBrush="Black"
Padding="3,0,0,0"
>
@@ -21,10 +16,10 @@
<ControlTemplate TargetType="ToggleButton">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse x:Name="ellipse" Width="19" Height="19" Stroke="{TemplateBinding BorderBrush}"/>
<Ellipse x:Name="ellipse" Width="20" Height="20" Stroke="{TemplateBinding BorderBrush}"/>
<Path
x:Name="arrow"
Data="M1,1.5L4.5,5 8,1.5"
@@ -50,6 +50,16 @@ namespace mbts.ui.controls {
LastMessage = 100,
ParentKey = 100,
AttachedTargetOrderKey = 100;
public static class MiniOrderBook {
public const double
Cancel = 37,
AttachDetach = 45,
Update = 45,
EditableSize = 40,
EditablePrice = 64,
Commit = 20;
}
}
public static class ShortColumnWidths {
@@ -20,7 +20,7 @@ namespace mbts.ui {
public void set_predicate_animations(SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> predicate_animations) {
_active_predicates.Clear();
_animation_ref_count.clear();
_animation_ref_count.Clear();
_row.row_animations.Clear();
foreach (var cs in _row.cell_stores)
cs.animations.Clear();
@@ -23,9 +23,10 @@ namespace mbts.spark {
/// For flag behavior implementation, see <see cref="GridSettingsVM{TRowData, TPredicateBuilder}.ColumnVM.apply_flags(GridColumnSettingsFeatureFlags)"/>
/// </summary>
enum GridColumnSettingsFeatureFlags {
None,
ImmutableFontColor,
ImmutableFontWeight
None = 0,
ImmutableFontColor = 1,
ImmutableFontWeight = 1 << 1,
ImmutableHeader = 1 << 2
}
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> {
@@ -38,27 +39,23 @@ namespace mbts.spark {
public class ColumnVM : DisposableViewModelBase, IColumnVM {
public readonly GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner;
readonly Property<string> _id, _display_name;
readonly Property<double> _width;
readonly Property<HorizontalAlignment> _horizontal_alignment;
readonly Property<bool> _is_visible, _can_override_font_color, _is_font_color_overridden, _can_override_font_weight, _is_font_weight_overridden;
readonly Property<FontWeight> _font_weight_override;
string
_id = string.Empty,
_display_name = string.Empty;
double _width = 70;
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
bool
_is_visible,
_can_override_display_name = true,
_can_override_font_color = true,
_is_font_color_overridden,
_can_override_font_weight = true,
_is_font_weight_overridden;
FontWeight _font_weight_override;
GridColumnSettings _settings;
public ColumnVM(GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner) {
this.owner = owner;
_id = backing(nameof(id), string.Empty);
_display_name = backing(nameof(display_name), string.Empty);
_width = backing(nameof(width), 70.0);
_horizontal_alignment = backing(nameof(horizontal_alignment), HorizontalAlignment.Left);
_is_visible = backing(nameof(is_visible), false);
// font color override
_can_override_font_color = backing(nameof(can_override_font_color), true);
_is_font_color_overridden = backing(nameof(is_font_color_overridden), false);
// font weight override
_can_override_font_weight = backing(nameof(can_override_font_weight), true);
_is_font_weight_overridden = backing(nameof(is_font_weight_overridden), false);
_font_weight_override = backing(nameof(font_weight_override), FontWeights.Normal);
// animations
predicate_animations = new ObservableEditableCollection<PredicateCellAnimationVM>(() =>
new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory), true);
@@ -66,24 +63,29 @@ namespace mbts.spark {
_dispose = Disposable.Create(() => predicate_animations.OfType<IDisposable>().DisposeAll());
}
public string id => _id.Value;
public string display_name { get => _display_name.Value; set => _display_name.set(value); }
public double width { get => _width.Value; set => _width.set(value); }
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment.Value; set => _horizontal_alignment.set(value); }
public bool is_visible { get => _is_visible.Value; set => _is_visible.set(value); }
public string id { get => _id; private set => Set(ref _id, value); }
public double width { get => _width; set => Set(ref _width, value); }
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
// display name
public bool can_override_display_name { get => _can_override_display_name; private set => Set(ref _can_override_display_name, value); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
// font color override
public bool can_override_font_color => _can_override_font_color.Value;
public bool is_font_color_overridden { get => _is_font_color_overridden.Value; set => _is_font_color_overridden.set(value); }
public bool can_override_font_color { get => _can_override_font_color; private set => Set(ref _can_override_font_color, value); }
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
// font weight override
public bool can_override_font_weight => _can_override_font_weight.Value;
public bool is_font_weight_overridden { get => _is_font_weight_overridden.Value; set => _is_font_weight_overridden.set(value); }
public FontWeight font_weight_override { get => _font_weight_override.Value; set => _font_weight_override.set(value); }
public bool can_override_font_weight { get => _can_override_font_weight; private set => Set(ref _can_override_font_weight, value); }
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
// animations
public ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
public (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create() {
var settings = _settings.with(display_name, is_visible, null,
var settings = _settings.with(
can_override_display_name ? display_name : null,
is_visible,
null,
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : default,
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : default,
width,
@@ -94,19 +96,21 @@ namespace mbts.spark {
public void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) value) {
_settings = value.settings;
_id.set(_settings.id);
_display_name.set(_settings.display_name);
_width.set(_settings.width);
_horizontal_alignment.set(_settings.horizontal_alignment);
_is_visible.set(_settings.is_visible);
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
if (is_font_weight_overridden) {
_font_weight_override.set(_settings.font_weight_override.Value);
}
if (_settings.font_color_override.HasValue) {
id = _settings.id;
display_name = _settings.display_name;
width = _settings.width;
horizontal_alignment = _settings.horizontal_alignment;
is_visible = _settings.is_visible;
// font color
is_font_color_overridden = _settings.font_color_override.HasValue;
if (is_font_color_overridden) {
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
}
// font weight
is_font_weight_overridden = _settings.font_weight_override.HasValue;
if (is_font_weight_overridden) {
_font_weight_override = _settings.font_weight_override.Value;
}
predicate_animations.SetItems(value.animations.empty_if_null().Select(g => {
var vm = new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory);
vm.load_from((g.Key, g));
@@ -115,15 +119,16 @@ namespace mbts.spark {
}
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
var split_flags = EnumHelper.split_flags(flags).ToList();
// display name
can_override_display_name = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
if (!can_override_display_name)
display_name = id;
// font color
_can_override_font_color.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontColor));
if (!can_override_font_color)
_is_font_color_overridden.set(false);
can_override_font_color = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
is_font_color_overridden &= can_override_font_color;
// font weight
_can_override_font_weight.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontWeight));
if (!can_override_font_weight)
_is_font_weight_overridden.set(false);
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
is_font_weight_overridden &= can_override_font_weight;
}
public override string ToString() => $"{display_name} ({id})";
@@ -60,21 +60,17 @@ namespace mbts.ui.settings {
[DefaultValue(_default_font_color)]
readonly uint _font_color;
[ProtoMember(5)]
[DefaultValue(default(string))]
readonly ColorOpacityProto _selection_highlight;
[ProtoMember(6)]
readonly List<GridColumnSettings> _columns;
List<GridColumnSettings> _columns;
[ProtoMember(7)]
readonly List<SortingCriteria> _sorting_criterion;
List<SortingCriteria> _sorting_criterion;
public GridSettings() {
_font_family = _default_font_family;
_font_size = _default_font_size;
_font_weight = _default_font_weight;
_font_color = _default_font_color;
_selection_highlight = default;
_columns = new List<GridColumnSettings>();
_sorting_criterion = new List<SortingCriteria>();
}
public GridSettings(
@@ -95,9 +91,9 @@ namespace mbts.ui.settings {
public uint font_color => _font_color;
public FontWeight font_weight => FontWeight.FromOpenTypeWeight(_font_weight);
public ColorOpacityProto selection_highlight => _selection_highlight;
public IReadOnlyList<GridColumnSettings> columns => _columns;
public IReadOnlyList<GridColumnSettings> columns => _columns ?? (_columns = new List<GridColumnSettings>(0));
[Obsolete] // TODO: remove sorting_criterion from GridSettings entirely. This should be part of IShellWindow's StartupData, since it is an instance-specific setting, and prob not part of the shared defaults
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion;
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = new List<SortingCriteria>(0));
public static GridSettings get_default() =>
new GridSettings(_default_font_family, _default_font_size, FontWeights.Normal, _default_font_color, new ColorOpacityProto(Colors.MediumSlateBlue, 0.4));
@@ -44,7 +44,6 @@ namespace mbts.ui {
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>> _predicate_animations_data;
public GridSettingsManager(
IObservable<string> owner_title,
IBus bus,
OrdersModel orders_model,
IEnumerable<TRow> rows,
@@ -90,8 +89,8 @@ namespace mbts.ui {
public SolidColorBrush font_color => _font_color.Value;
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings, IObservable<string> title) {
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(title, _bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings) {
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(_bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
vm.load(_create_settings_obj(settings, _predicate_animations_data));
return vm;
}
@@ -54,14 +54,15 @@ namespace mbts.spark {
readonly Property<IColumnVM> _current_column;
readonly Property<FontFamily> _font_family;
readonly Property<FontWeight> _font_weight;
readonly Property<string> _font_display_name, _owner_title;
readonly Property<string> _font_display_name;
readonly Property<double> _font_size;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
readonly OrdersModel _orders_model;
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
public GridSettingsVM(
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
IBus bus,
OrdersModel orders_model,
Func<TPredicateData> predicate_data_factory,
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
@@ -69,7 +70,6 @@ namespace mbts.spark {
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
//
_owner_title = backing(nameof(owner_title), owner_title);
this.bus = bus;
_orders_model = orders_model;
_predicate_data_factory = predicate_data_factory;
@@ -80,7 +80,7 @@ namespace mbts.spark {
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
_font_size = backing(nameof(font_size), FontSizes.Small);
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), nameof(font_family), nameof(font_size));
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), new[] { nameof(font_family), nameof(font_size) });
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
_current_column = backing<IColumnVM>(nameof(current_column));
@@ -92,7 +92,6 @@ namespace mbts.spark {
public event EventHandler RequestClose;
public event Action committed;
public string owner_title => _owner_title.Value;
public IBus bus { get; }
public ObservableCollection<IColumnVM> columns { get; } = new ObservableCollection<IColumnVM>();
public IColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
@@ -227,11 +227,23 @@
<u:TemplateSelectorKeyValue Key="{x:Static usp:PredicateTemplateKeys.SecurityCriteria}">
<DataTemplate>
<c:ContractPickerPopup
SelectedCriteria="{Binding value}"
AllowEmpty="True"
CloseOnOK="True"
CloseOnCancel="True"
MaxWidth="180"
SelectedCriteria="{Binding value, UpdateSourceTrigger=Explicit}"
SecurityMap="{Binding Path='[security_map]', UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
Header="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}}"
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
/>
Header="{Binding value}"
ShowDialogSkirt="True"
ToolTip="{Binding value, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
>
<c:ContractPickerPopup.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</c:ContractPickerPopup.HeaderTemplate>
</c:ContractPickerPopup>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
@@ -550,13 +562,35 @@
</c:DragDropListBox.ItemContainerStyle>
<c:DragDropListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid x:Name="column_item_content_root">
<u:StateManager.Groups>
<u:StateGroupsManager>
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding DataContext.can_override_display_name, Source={u:DirectReference column_item_content_root}, Converter={x:Static usp:GridSettingsView.ColumnVisibilityToggleVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="visibility_toggle" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="visibility_toggle" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="(N/A)"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding is_visible}" />
<CheckBox x:Name="visibility_toggle" ToolTipService.ShowOnDisabled="True" IsChecked="{Binding is_visible}"/>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<TextBlock Text="{Binding id, Converter={x:Static ut:DisplayNameHelper.CamelCaseSpacesConverter}}"/>
<TextBlock Margin="2,0,0,0" FontStyle="Italic" Text="{Binding display_name, Mode=OneWay, StringFormat='{} ({0})'}">
@@ -576,7 +610,29 @@
<GroupBox.Header>
<TextBlock Text="{Binding current_column.display_name, StringFormat='{}\'{0}\' Cell Appearance'}"/>
</GroupBox.Header>
<Grid>
<Grid x:Name="column_content_root">
<u:StateManager.Groups>
<u:StateGroupsManager>
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding DataContext.current_column.can_override_display_name, Source={u:DirectReference column_content_root}, Converter={x:Static usp:GridSettingsView.DisplayNameVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="display_name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="display_name_text_box" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="(N/A)"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
@@ -594,7 +650,7 @@
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock>Header</TextBlock>
<TextBox Grid.Column="2" Text="{Binding current_column.display_name}"/>
<TextBox x:Name="display_name_text_box" Grid.Column="2" ToolTipService.ShowOnDisabled="True" Text="{Binding current_column.display_name}"/>
<TextBlock Grid.Row="2">Width</TextBlock>
<c:TickUpDownDouble Grid.Row="2" Grid.Column="2" Value="{Binding current_column.width}"/>
<TextBlock Grid.Row="4">Horizontal Alignment</TextBlock>
@@ -11,6 +11,10 @@ namespace mbts.ui.spark {
/// Interaction logic for GridSettingsView.xaml
/// </summary>
partial class GridSettingsView : UserControl {
public static readonly IValueConverter
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
public GridSettingsView() =>
InitializeComponent();
@@ -38,6 +42,24 @@ namespace mbts.ui.spark {
ValueConverter.create<string, string, Visibility>((id, header) => id == header ? Visibility.Collapsed : Visibility.Visible);
public bool EnableSoundTriggers { get; set; }
public static class VisualStateNames {
public static class DisplayName {
public const string GroupName = nameof(DisplayName);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class ColumnVisibilityToggle {
public const string GroupName = nameof(ColumnVisibilityToggle);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
}
static class PredicateTemplateKeys {
@@ -12,6 +12,9 @@ namespace mbts.ui {
public static ImmutableColumnSettingStore create(GridColumnSettingsFeatureFlags flags) {
var stores_per_flag = new List<ImmutableColumnSettingStore>();
if ((flags & GridColumnSettingsFeatureFlags.ImmutableHeader) != 0) {
stores_per_flag.Add(new ImmutableHeaderOverrideStore());
}
if ((flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) != 0) {
stores_per_flag.Add(new ImmutableFontColorOverrideStore());
}
@@ -24,6 +27,19 @@ namespace mbts.ui {
public abstract GridColumnSettings apply(GridColumnSettings settings);
public abstract void store(GridColumnSettings settings);
class ImmutableHeaderOverrideStore : ImmutableColumnSettingStore {
string _header;
public ImmutableHeaderOverrideStore()
: base(GridColumnSettingsFeatureFlags.ImmutableHeader) { }
public override GridColumnSettings apply(GridColumnSettings settings) =>
settings.with(display_name: _header);
public override void store(GridColumnSettings settings) =>
_header = settings.display_name;
}
class ImmutableFontColorOverrideStore : ImmutableColumnSettingStore {
NullableColorProto _value;
@@ -69,4 +85,4 @@ namespace mbts.ui {
}
}
}
}
}
@@ -2,9 +2,6 @@
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
@@ -13,45 +10,45 @@ namespace mbts.spark {
public class SimpleColumnVM : ViewModelBase, ISimpleColumnVM {
public readonly SimpleGridSettingsVM owner;
readonly Property<string> _id, _display_name;
readonly Property<double> _width;
readonly Property<HorizontalAlignment> _horizontal_alignment;
readonly Property<bool> _is_visible, _can_override_font_color, _is_font_color_overridden, _can_override_font_weight, _is_font_weight_overridden;
readonly Property<FontWeight> _font_weight_override;
string
_id = string.Empty,
_display_name = string.Empty;
double _width = 70;
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
bool
_is_visible,
_can_override_display_name = true,
_can_override_font_color = true,
_is_font_color_overridden,
_can_override_font_weight = true,
_is_font_weight_overridden;
FontWeight _font_weight_override;
GridColumnSettings _settings;
public SimpleColumnVM(SimpleGridSettingsVM owner) {
public SimpleColumnVM(SimpleGridSettingsVM owner) =>
this.owner = owner;
_id = backing(nameof(id), string.Empty);
_display_name = backing(nameof(display_name), string.Empty);
_width = backing(nameof(width), 70.0);
_horizontal_alignment = backing(nameof(horizontal_alignment), HorizontalAlignment.Left);
_is_visible = backing(nameof(is_visible), false);
// font color override
_can_override_font_color = backing(nameof(can_override_font_color), true);
_is_font_color_overridden = backing(nameof(is_font_color_overridden), false);
// font weight override
_can_override_font_weight = backing(nameof(can_override_font_weight), true);
_is_font_weight_overridden = backing(nameof(is_font_weight_overridden), false);
_font_weight_override = backing(nameof(font_weight_override), FontWeights.Normal);
}
public string id => _id.Value;
public string display_name { get => _display_name.Value; set => _display_name.set(value); }
public double width { get => _width.Value; set => _width.set(value); }
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment.Value; set => _horizontal_alignment.set(value); }
public bool is_visible { get => _is_visible.Value; set => _is_visible.set(value); }
public string id { get => _id; private set => Set(ref _id, value); }
public double width { get => _width; set => Set(ref _width, value); }
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
// display name
public bool can_override_display_name { get => _can_override_display_name; private set => Set(ref _can_override_display_name, value); }
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
// font color override
public bool can_override_font_color => _can_override_font_color.Value;
public bool is_font_color_overridden { get => _is_font_color_overridden.Value; set => _is_font_color_overridden.set(value); }
public bool can_override_font_color { get => _can_override_font_color; private set => Set(ref _can_override_font_color, value); }
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
// font weight override
public bool can_override_font_weight => _can_override_font_weight.Value;
public bool is_font_weight_overridden { get => _is_font_weight_overridden.Value; set => _is_font_weight_overridden.set(value); }
public FontWeight font_weight_override { get => _font_weight_override.Value; set => _font_weight_override.set(value); }
public bool can_override_font_weight { get => _can_override_font_weight; private set => Set(ref _can_override_font_weight, value); }
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
public GridColumnSettings create() =>
_settings.with(display_name, is_visible, null,
_settings.with(
can_override_display_name ? display_name : null,
is_visible,
null,
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : (NullableFontWeightProto?)null,
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : (NullableColorProto?)null,
width,
@@ -59,31 +56,34 @@ namespace mbts.spark {
public void load_from(GridColumnSettings value) {
_settings = value;
_id.set(_settings.id);
_display_name.set(_settings.display_name);
_width.set(_settings.width);
_horizontal_alignment.set(_settings.horizontal_alignment);
_is_visible.set(_settings.is_visible);
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
if (is_font_weight_overridden) {
_font_weight_override.set(_settings.font_weight_override.Value);
}
id = value.id;
display_name = value.display_name;
width = value.width;
horizontal_alignment = value.horizontal_alignment;
is_visible = value.is_visible;
// font color
is_font_color_overridden = value.font_color_override.HasValue;
if (is_font_color_overridden) {
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
}
// font weight
is_font_weight_overridden = value.font_weight_override.HasValue;
if (is_font_weight_overridden) {
_font_weight_override = _settings.font_weight_override.Value;
}
}
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
var split_flags = EnumHelper.split_flags(flags).ToList();
// display name
can_override_display_name = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
if (!can_override_display_name)
display_name = id;
// font color
_can_override_font_color.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontColor));
if (!can_override_font_color)
_is_font_color_overridden.set(false);
can_override_font_color = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
is_font_color_overridden &= can_override_font_color;
// font weight
_can_override_font_weight.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontWeight));
if (!can_override_font_weight)
_is_font_weight_overridden.set(false);
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
is_font_weight_overridden &= can_override_font_weight;
}
public override string ToString() => $"{display_name} ({id})";
@@ -45,8 +45,8 @@ namespace mbts.ui {
public SolidColorBrush font_color => _font_color.Value;
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SimpleGridSettingsVM create_vm(GridSettings settings, IObservable<string> title) {
var vm = new SimpleGridSettingsVM(title, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
public SimpleGridSettingsVM create_vm(GridSettings settings) {
var vm = new SimpleGridSettingsVM(_column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
vm.load_from(settings);
return vm;
}
@@ -1,13 +1,18 @@
using bts.collections;
using bts.utils;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.ui.utility.text;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
using FontFamily = System.Windows.Media.FontFamily;
@@ -29,27 +34,44 @@ namespace mbts.spark {
readonly Property<ISimpleColumnVM> _current_column;
readonly Property<FontFamily> _font_family;
readonly Property<FontWeight> _font_weight;
readonly Property<string> _font_display_name, _owner_title;
readonly Property<string> _font_display_name;
readonly Property<double> _font_size;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
readonly DeferrableActionWrapper _update;
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
public SimpleGridSettingsVM(IObservable<string> owner_title, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
public SimpleGridSettingsVM(IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
_column_flags = column_flags ?? new Dictionary<string, GridColumnSettingsFeatureFlags>();
_owner_title = backing(nameof(owner_title), owner_title);
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
_font_size = backing(nameof(font_size), FontSizes.Small);
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), nameof(font_family), nameof(font_size));
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), new[] { nameof(font_family), nameof(font_size) });
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
_current_column = backing<ISimpleColumnVM>(nameof(current_column));
// update
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
foreach (var c in _observable_components)
c.PropertyChanged += on_prop_changed;
_dispose = new CompositeDisposable(
ObservableAutoWrapper.connect_item_hooks(columns, c => {
if (c is INotifyPropertyChanged npc) {
npc.PropertyChanged += on_prop_changed;
return Disposable.Create(() => npc.PropertyChanged -= on_prop_changed);
} else return Disposable.empty;
})) {
() => {
foreach (var c in _observable_components)
c.PropertyChanged -= on_prop_changed;
}
};
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
_update.execute();
}
public event EventHandler RequestClose;
public event Action committed;
public event Action committed, result_changed;
public string owner_title => _owner_title.Value;
public ObservableCollection<ISimpleColumnVM> columns { get; } = new ObservableCollection<ISimpleColumnVM>();
public ISimpleColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
public string font_display_name => _font_display_name.Value;
@@ -72,29 +94,41 @@ namespace mbts.spark {
public string key => ui.spark.GridSettingsTemplateKeys.SimpleGrid;
IEnumerable<INotifyPropertyChanged> _observable_components {
get {
yield return font_color;
yield return selection_highlight;
yield return this;
}
}
public GridSettings create() =>
new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), columns.Select(c => c.create()));
public void load_from(GridSettings value) {
_font_family.set(new FontFamily(value.font_family));
_font_weight.set(value.font_weight);
font_color.load_from(value.font_color.rgb_solid_brush());
selection_highlight.load_from(value.selection_highlight.to_brush());
_font_size.set(value.font_size);
var curr_col_id = current_column?.id;
columns.SetItems(value.columns.Select(col => {
var vm = new SimpleColumnVM(this);
vm.load_from(col);
// reapply our favorite column flags
// this comes in handy when performing Load From Factory - the editor will contain
// a consistent view of what can be overridden and what cannot
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
vm.apply_flags(flags);
return vm;
}));
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out ISimpleColumnVM curr_column))
curr_column = columns.FirstOrDefault();
_current_column.set(curr_column);
if (value == null)
return;
using (_update.defer_execution()) {
_font_family.set(new FontFamily(value.font_family));
_font_weight.set(value.font_weight);
font_color.load_from(value.font_color.rgb_solid_brush());
selection_highlight.load_from(value.selection_highlight.to_brush());
_font_size.set(value.font_size);
var curr_col_id = current_column?.id;
columns.SetItems(value.columns.Select(col => {
var vm = new SimpleColumnVM(this);
vm.load_from(col);
// reapply our favorite column flags
// this comes in handy when performing Load From Factory - the editor will contain
// a consistent view of what can be overridden and what cannot
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
vm.apply_flags(flags);
return vm;
}));
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out ISimpleColumnVM curr_column))
curr_column = columns.FirstOrDefault();
_current_column.set(curr_column);
}
}
void IWindowSettingsVM<GridSettings>.load(GridSettings value) => load_from(value);
@@ -37,7 +37,9 @@ namespace mbts.ui {
#endregion
public static bool Confirm(string message, string title) =>
Show(message, title, BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, MessageBoxImage.Question, true, true, true) == BTSMessageBoxButtons.Yes;
Confirm(message, title, MessageBoxImage.Question);
public static bool Confirm(string message, string title, MessageBoxImage icon) =>
Show(message, title, BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon, true, true, true) == BTSMessageBoxButtons.Yes;
public static void Error(string message, string title) =>
Show(message, title, BTSMessageBoxButtons.OK, MessageBoxImage.Error, true, true, true);
@@ -45,7 +45,7 @@ namespace mbts.ui.controls
public DragDropListBox() {
Loaded += (s, e) => {
var al = AdornerLayer.GetAdornerLayer(this);
al.Add(_adorner = new DragIndicatorAdorner<DraggableListBoxItem>(this));
al?.Add(_adorner = new DragIndicatorAdorner<DraggableListBoxItem>(this));
};
}
@@ -1,23 +1,14 @@
using System;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using mbts.ui;
using mbts.ui.shell;
namespace mbts.ui.controls
{
namespace mbts.ui.controls {
public partial class HierarchicalLaunchMenu : UserControl, IShellWindow
{
public HierarchicalLaunchMenu(object context) {
@@ -36,27 +27,7 @@ namespace mbts.ui.controls
public ShellWindowUI GetContent() {
return new ShellWindowUI(this);
}
//bool _tree_was_clicked = false;
//private async void _tree_clicked(object sender, RoutedEventArgs e)
//{
// e.Handled = true;
// if (_tree_was_clicked) return;
// _tree_was_clicked = true;
// DependencyObject source = e.OriginalSource as DependencyObject;
// while (source != null && !(source is TreeViewItem))
// source = VisualTreeHelper.GetParent(source);
// TreeViewItem tvi = source as TreeViewItem;
// if (tvi != null)
// tvi.IsExpanded = !tvi.IsExpanded;
// await Task.Delay(1000);
// _tree_was_clicked = false;
//}
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
}
public class HierarchicalViewModel : ViewModelBase
@@ -1,26 +1,12 @@
using System;
using System.Collections.Generic;
using mbts.ui.shell;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using mbts.ui;
using mbts.ui.shell;
namespace mbts.ui.controls
{
public partial class MenuLaunchMenu : UserControl, IShellWindow
{
namespace mbts.ui.controls {
public partial class MenuLaunchMenu : UserControl, IShellWindow {
public ObservableCollection<MenuLaunchMenuViewModel> MenuItems { get; set; }
public MenuLaunchMenu(object context)
{
@@ -39,11 +25,10 @@ namespace mbts.ui.controls
public ShellWindowUI GetContent() {
return new ShellWindowUI(this);
}
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
}
public class MenuLaunchMenuViewModel : ViewModelBase
{
public class MenuLaunchMenuViewModel : ViewModelBase {
public string Header { get; set; }
public ObservableCollection<MenuLaunchMenuViewModel> MenuItems { get; set; }
@@ -51,7 +36,5 @@ namespace mbts.ui.controls
public ICommand Command { get; set; }
public object Args { get; set; }
}
}
}
@@ -489,6 +489,8 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
@@ -503,9 +505,11 @@
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="If the order is at the top of book, the quantity remaining below which the order will be canceled."/>
</StackPanel>
<uag:QuantityMeasureControl x:Name="PART_TopThreshold_QuantityMeasureControl" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
<TextBlock Grid.Row="2">Ticks Off</TextBlock>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="2" Value="{Binding ticks_off}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
Grid.Row="2"
Grid.Row="4"
Grid.ColumnSpan="3"
ItemsSource="{Binding children_vm.child.algos_view}"
SelectedItem="{Binding children_vm.child.selected_algo}"
@@ -72,11 +72,13 @@ namespace mbts.ui.controls {
static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var target = (PopupControl)d;
if ((bool)e.NewValue) {
target.OnOpened(EventArgs.Empty);
target.RaiseEvent(new RoutedEventArgs(OpenedEvent, target));
(FocusManager.GetFocusScope(target) as UIElement)?.Focus();
Keyboard.Focus(target);
target?._popup?.Child?.Focus();
} else {
target.OnClosed(EventArgs.Empty);
target.ReleaseMouseCapture();
target.RaiseEvent(new RoutedEventArgs(ClosedEvent, target));
if (target.IsKeyboardFocused || target.IsKeyboardFocusWithin)
@@ -121,5 +123,8 @@ namespace mbts.ui.controls {
_popup.PlacementTarget = PlacementTarget ?? _button;
_popup_hook = PopupViewController.attach(_popup, _button);
}
protected virtual void OnOpened(EventArgs e) { }
protected virtual void OnClosed(EventArgs e) { }
}
}
@@ -1,4 +1,5 @@
using mbts.ui.extensions;
using bts.utils;
using mbts.ui.extensions;
using mbts.ui.utilities;
using System;
using System.Linq;
@@ -38,6 +39,32 @@ namespace mbts.ui.controls {
}
#endregion
#region Minimum
public static readonly DependencyProperty MinimumProperty =
WPFHelper.create_dp<uint, TickUpDownUInt>(
nameof(Minimum),
(o, v) => o.CoerceValue(ValueProperty),
uint.MinValue);
public uint Minimum {
get => (uint)GetValue(MinimumProperty);
set => SetValue(MinimumProperty, value);
}
#endregion
#region Maximum
public static readonly DependencyProperty MaximumProperty =
WPFHelper.create_dp<uint, TickUpDownUInt>(
nameof(Maximum),
(o, v) => o.CoerceValue(ValueProperty),
uint.MaxValue);
public uint Maximum {
get => (uint)GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
#endregion
#region Value
public static readonly DependencyProperty ValueProperty =
WPFHelper.create_dp<uint, TickUpDownUInt>(
@@ -53,6 +80,7 @@ namespace mbts.ui.controls {
}
}
},
(o, v) => MathUtils.within_range(o.Minimum, o.Maximum, v),
0u);
public uint Value {
@@ -1,4 +1,5 @@
using System;
using mbts.ui.shell;
using System;
using System.IO;
using System.Windows;
using System.Windows.Media;
@@ -38,30 +39,36 @@ namespace mbts.ui {
public Lazy<UIElement> Content { get; set; }
public Action<ShellWindowVM> on_shell_window_vm { get; set; }
void IShellWindow.on_shell_window_vm(ShellWindowVM shell_window_vm) =>
on_shell_window_vm?.Invoke(shell_window_vm);
}
class LazyAnonShellWindow : AnonShellWindow, ISharedWindow {
public string thread_id { get; set; }
public void thread_init() { }
}
public static IShellWindow create(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement, Stream> get_state = null, ImageSource icon = null, string thread_id=null) {
public static IShellWindow create(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement, Stream> get_state = null, ImageSource icon = null, string thread_id=null, Action<ShellWindowVM> on_shell_window_vm = null) {
if (string.IsNullOrEmpty(thread_id)) {
return create<AnonShellWindow>(owner, title, window_type, content, get_state, icon);
return create<AnonShellWindow>(owner, title, window_type, content, get_state, icon, on_shell_window_vm);
}
else {
var res = create<LazyAnonShellWindow>(owner, title, window_type, content, get_state, icon);
var res = create<LazyAnonShellWindow>(owner, title, window_type, content, get_state, icon, on_shell_window_vm);
res.thread_id = thread_id;
return res;
}
}
static T create<T>(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement,Stream> get_state=null, ImageSource icon=null) where T : AnonShellWindow, new() {
static T create<T>(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement,Stream> get_state=null, ImageSource icon=null, Action<ShellWindowVM> on_shell_window_vm = null) where T : AnonShellWindow, new() {
var result = new T() {
Title = title,
window_type = window_type,
Icon = icon ?? (owner != null ? owner.icon : Icons.Window),
Owner = owner,
get_state = get_state,
on_shell_window_vm = on_shell_window_vm,
Content = content,
Disposable = Disposable.Create(() => {
if (content.IsValueCreated) {
@@ -1,14 +1,16 @@
using System;
using System.Linq;
using bts;
using mbts.ui.native;
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using bts;
using mbts.ui.native;
namespace mbts.ui {
public partial class WindowStyle : ResourceDictionary {
// 'Top' is 0 because the window's TitleContent overlaps the top-resize-border.
public static readonly Thickness NegativeResizeBorderThickness = new Thickness(-5, 0, -5, -5);
public static readonly ExecutedRoutedEventHandler
CloseWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Close(), "closing"),
HideWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Visibility = Visibility.Hidden, "hiding"),
@@ -29,6 +29,8 @@ namespace mbts.ui.shell {
}
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
public void Dispose() {
}
@@ -28,16 +28,27 @@ namespace mbts.ui.shell {
model.Dispose();
}
[Export("username")]
[Export(Exports.UserName)]
public string UserName { get; private set; }
[Export("userid")]
[Export(Exports.UserID)]
public uint UserId { get; private set; }
[Export]
public UserTuple UserTuple => new UserTuple(UserId, UserName);
[Export]
public AuthTokenImmutable Token { get; private set; }
[Export("workspace")]
public string Workspace { get; private set; }
public static class Exports {
public const string
UserName = "username",
UserID = "userid",
UserTuple = "usertuple";
}
}
public struct User {
@@ -49,8 +60,7 @@ namespace mbts.ui.shell {
public readonly UInt32 id;
}
public class LoginModel : IDisposable
{
public class LoginModel : IDisposable {
// Singleton to help bootstrap the system
private static LoginModel _instance = new LoginModel();
public static LoginModel instance { get { return _instance; } }
@@ -70,6 +80,11 @@ namespace mbts.ui.shell {
public bool HasFatalError { get; set; }
public User[] Users { get; private set; }
public bool try_get_user(string name, out User user) =>
Users.TryGetFirst(u => u.name == name, out user);
public bool user_exists(string name) =>
Users.Any(u => u.name == name);
// The final username
public (string, uint, AuthTokenImmutable) get_user() {
@@ -6,6 +6,7 @@
xmlns:ui="clr-namespace:mbts.ui"
xmlns:uicore="clr-namespace:mbts.ui"
xmlns:converters="clr-namespace:mbts.ui.converters"
xmlns:autocomplete="clr-namespace:WPFTextBoxAutoComplete;assembly=WPFTextBoxAutoComplete"
mc:Ignorable="d" MouseDown="Window_MouseDown"
Width="500" Height="309" WindowStyle="None" ResizeMode="NoResize" WindowStartupLocation="CenterScreen"
ui:StateManager.VisualState="{Binding VisualState}">
@@ -82,7 +83,13 @@
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding UserNames}" Grid.ColumnSpan="2" Margin="10" SelectedItem="{Binding Name, Mode=TwoWay}" Width="160" />
<TextBox
Grid.ColumnSpan="2"
autocomplete:AutoCompleteBehavior.AutoCompleteItemsSource="{Binding KnownUsers}"
Margin="10"
Width="160"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
/>
<ComboBox ItemsSource="{Binding Workspaces}" Grid.Row="1" Grid.ColumnSpan="2" Margin="10" SelectedItem="{Binding SelectedWorkspace, Mode=TwoWay}" Width="160" MaxWidth="160" />
<PasswordBox Grid.Row="2" Margin="10" MinWidth="120" Grid.ColumnSpan="2" x:Name="_password" PasswordChanged="_passwordChanged" Style="{DynamicResource PasswordBoxStyle1}" Width="160">
@@ -18,7 +18,7 @@ namespace mbts.ui.shell
_dispatcher = disp;
_model = model;
Workspaces = new ObservableCollection<string>();
LoginCommand = new DelegateCommand(OnLogin, _ => UserNames.Count > 0 && SelectedWorkspace != null);
LoginCommand = new DelegateCommand(OnLogin, _ => !string.IsNullOrEmpty(_name) && SelectedWorkspace != null);
CancelCommand = new DelegateCommand(OnCancel);
EmailCommand = new DelegateCommand(OnSendMail);
if (_model.HasFatalError) {
@@ -42,9 +42,10 @@ namespace mbts.ui.shell
void _model_Ready() {
_dispatcher.BeginInvoke(() => {
if (!_model.HasFatalError) {
this.UserNames.AddRange(_model.Users.Select(u => u.name));
this.VisualState = "LoginState";
load_username();
if (!string.IsNullOrEmpty(_name))
KnownUsers.Add(_name);
LoginCommand.RaiseCanExecuteChanged();
}
else {
@@ -57,8 +58,8 @@ namespace mbts.ui.shell
private string _vstate = "Normal";
public string VisualState { get { return _vstate; } set { _vstate = value; RaisePropertyChanged("VisualState"); } }
private ObservableCollection<string> _user_names = new ObservableCollection<string>();
public ObservableCollection<string> UserNames { get { return _user_names; } }
/// <see cref="KnownUsers"/> should only contain the user-name of the last login (if none exists, it should be empty).
public ObservableCollection<string> KnownUsers { get; } = new ObservableCollection<string>();
private string _name;
public string Name {
get { return _name; }
@@ -82,12 +83,13 @@ namespace mbts.ui.shell
private async void update_workspaces(string name) {
Workspaces.Clear();
Workspaces.Add("New workspace...");
var user = _model.Users.First(u => u.name == name);
var to_add = await _model.workspaces_for(user);
this.SelectedWorkspace = Workspaces.First();
if (to_add != null && name == _name && Workspaces.Count == 1) {
Workspaces.AddRange(to_add);
load_last_workspace(name);
SelectedWorkspace = Workspaces.First();
if (_model.try_get_user(name, out var user)) {
var to_add = await _model.workspaces_for(user);
if (to_add != null && name == _name && Workspaces.Count == 1) {
Workspaces.AddRange(to_add);
load_last_workspace(name);
}
}
RaisePropertyChanged("SelectedWorkspace");
LoginCommand.RaiseCanExecuteChanged();
@@ -111,7 +113,7 @@ namespace mbts.ui.shell
var value = key.GetValue("login");
if (value != null) {
var n = value.ToString();
if (UserNames.Contains(n)) {
if (_model.user_exists(n)) {
Name = n;
}
}
@@ -10,10 +10,20 @@
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
SizeToContent="WidthAndHeight"
Style="{StaticResource BTSWindow}"
u:WindowStyle.AllowResize="True"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding}"
Style="{StaticResource BTSWindowTitleTemplate}"
UseLayoutRounding="True"
>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="-2,-2,0,1">
<Image HorizontalAlignment="Left" VerticalAlignment="Stretch" Height="17" Margin="0,-2,6,0" Source="{x:Static u:Icons.Admin}"/>
<TextBlock Margin="0,-1,0,0" Text="{Binding title, StringFormat='{}{0} Settings'}"/>
</StackPanel>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
<Window.Resources>
<!--Primitive Constants-->
<sys:Double x:Key="VerticalPush1">4</sys:Double>
@@ -346,13 +356,16 @@
</c:PopupControl>
<c:ContractPickerPopup
x:Name="security_picker"
AllowEmpty="True"
CloseOnCancel="True"
CloseOnOK="True"
IsEnabled="{Binding selected_profile.is_global}"
IsLazy="True"
SecurityMap="{DynamicResource {x:Static usp:ShellSettingsDialog.SecurityMapKey}}"
SelectedCriteria="{Binding selected_profile.security_criteria}"
SelectedCriteria="{Binding selected_profile.security_criteria, UpdateSourceTrigger=Explicit}"
ShowDialogSkirt="True"
Style="{StaticResource SparkToolBarItem}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
>
<c:ContractPickerPopup.Header>
<Image Source="{x:Static u:Icons.HandPointingOverDocument}"/>
@@ -376,7 +389,7 @@
<Separator VerticalAlignment="Stretch" Width="1" Margin="2,2,1,2" Background="Gray"/>
<c:ActionMenu Grid.Column="4" HorizontalAlignment="Right" Margin="0,0,4,0" VerticalAlignment="Center" ItemsSource="{Binding action_menu_items}"/>
</ToolBar>
<usp:LadderSettingsView Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Margin="0,10,8,0" DataContext="{Binding selected_profile.value}"/>
<usp:LadderSettingsView Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" HorizontalAlignment="Left" Width="758" MinHeight="234" Margin="0,10,8,0" DataContext="{Binding selected_profile.value}"/>
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
@@ -417,23 +430,26 @@
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
</Window.Resources>
<Grid>
<Grid Margin="{x:Static u:WindowStyle.NegativeResizeBorderThickness}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl
VerticalAlignment="Top"
Content="{Binding instance_vm}"
ContentTemplateSelector="{StaticResource GridSettingsTemplateSelector}"
/>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl
VerticalAlignment="Top"
Content="{Binding instance_vm}"
ContentTemplateSelector="{StaticResource GridSettingsTemplateSelector}"
/>
</ScrollViewer>
<c:DialogSkirt
Grid.Row="1"
BorderThickness="1"
BorderBrush="DarkGray"
LeftItems="{Binding left_skirt_items}"
LeftItemContainerStyleSelector="{StaticResource LeftButtonStyleSelector}"
RightItems="{Binding right_skirt_items}"
BorderBrush="DarkGray"
BorderThickness="0,1,0,0"
Margin="-1,0,-1,-1"
>
<c:DialogSkirt.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
@@ -23,11 +23,12 @@ namespace mbts.spark {
T get_result();
}
class ShellSettingsDialogVM<T, TVM> : ViewModelBase, IRequestViewClose, IDisposable where T : class, IEquatable<T> where TVM : IWindowSettingsVM<T> {
class ShellSettingsDialogVM<T, TVM> : ViewModelBase, IRequestViewClose, IDisposable, IWillLetYouKnowWhenIDispose where T : class, IEquatable<T> where TVM : IWindowSettingsVM<T> {
readonly Property<bool> _use_default;
readonly DialogSkirtItem _ok_item, _apply_item, _cancel_item, _apply_to_defaults_item;
readonly DelegateProperty<IEnumerable<DialogSkirtItem>> _left_skirt_items;
readonly List<IDisposable> _resources = new List<IDisposable>(2); // initialize with 2 capacity bc the only potential disposables are the instance/default vms
string _title;
public ShellSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory, Func<T, T> merge_with_current = null, bool show_save_as_defaults = true) {
if (merge_with_current == null)
@@ -94,7 +95,9 @@ namespace mbts.spark {
}
public event EventHandler RequestClose;
public event Action<bool> OnDisposed;
public string title { get => _title; set => Set(ref _title, value); }
public TVM instance_vm { get; }
public TVM default_vm { get; }
public IReadOnlyCollection<IMenuItemVM> action_menu_items { get; }
@@ -111,8 +114,10 @@ namespace mbts.spark {
set => _use_default.Value = value;
}
public void Dispose() =>
public void Dispose() {
Disposable.dispose(_resources);
OnDisposed?.Invoke(true);
}
}
class ProfiledShellSettingsDialogVM<T, TVM, TProfile> : DisposableViewModelBase, IRequestViewClose where T : class, IEquatable<T> where TVM : class, IValueFactory<T>, IObservable<T> where TProfile : ISettingsProfile<T> {
@@ -120,6 +125,7 @@ namespace mbts.spark {
readonly Property<ProfileVM> _selected_profile;
readonly Func<T, TVM> _vm_factory;
readonly ProfileVM _local_profile;
string _title;
bool _is_copy_menu_open;
DialogSkirtItem _apply_item, _ok_item, _cancel_item;
MenuItemVM _import_item, _export_item, _load_factory_item;
@@ -271,6 +277,7 @@ namespace mbts.spark {
public event Action<SettingsProfile<T>> applied;
public event EventHandler RequestClose;
public string title { get => _title; set => Set(ref _title, value); }
public ObservableCollection<ProfileVM> profiles { get; } = new ObservableCollection<ProfileVM>();
public ProfileVM selected_profile {
get => _selected_profile.Value;
@@ -1,14 +1,14 @@
using System;
using bts;
using bts.api;
using bts.spark;
using mbts.ui.native;
using mbts.ui.utility;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using mbts.ui.native;
using mbts.ui.utility;
using bts.api;
using bts.spark;
using bts;
namespace mbts.ui.shell {
[Export(typeof(ILauncher))]
@@ -23,7 +23,7 @@ namespace mbts.ui.shell {
[ImportingConstructor]
public LauncherVM(
ObservableCollection<IShellModule> modules,
[Import("username")]string username,
[Import(LoginContainer.Exports.UserName)]string username,
ErrorReporter errors, IBus bus) {
_user = username;
_modules = new ObservableAutoWrapper<ShellModuleVM, IShellModule>(modules, m => new ShellModuleVM(m, bus));
@@ -1,31 +1,20 @@
using System;
using mbts.ui;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Input;
using System.IO;
using System.Windows.Threading;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using mbts.ui.settings;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using bts;
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace mbts.ui.shell
{
public class ShellWindowVM : ViewModelBase, IDisposable
{
namespace mbts.ui.shell {
public class ShellWindowVM : ViewModelBase, IDisposable {
IShellWindow _model;
UIElement _ui;
IWindowManager _wm;
Dispatcher _dispatcher;
HwndContentHost _hwnd_host;
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = "") {
Log.assert(model != null);
_model = model;
@@ -44,6 +33,7 @@ namespace mbts.ui.shell
}
_edit_mode = backing(nameof(EditMode), false);
model.on_shell_window_vm(this);
}
void title_changed() {
@@ -56,6 +46,8 @@ namespace mbts.ui.shell
RaisePropertyChanged("Icon");
}
public event Action<string> TitleOverrideChanged;
public string host_id { get; set; }
public Dispatcher Dispatcher { get { return _dispatcher; } }
@@ -73,6 +65,7 @@ namespace mbts.ui.shell
}
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
_edit_mode.set(false);
TitleOverrideChanged?.Invoke(_title_override);
}
}
public bool HasCustomTitle => !string.IsNullOrWhiteSpace(TitleOverride);
@@ -218,7 +211,6 @@ namespace mbts.ui.shell
public void Dispose() {
_model.TitleChanged -= title_changed;
_model.IconChanged -= icon_changed;
if (IsShared) {
// Shared threads continue running
_dispatcher.Invoke(() => Disposable.dispose(ref _model));
@@ -237,5 +229,4 @@ namespace mbts.ui.shell
}
}
}
@@ -45,12 +45,7 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Margin="4" Text="{Binding SaveName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" />
<CheckBox Margin="4,0,4,0" Grid.Row="1" IsChecked="{Binding IsPublicWorkspace}" HorizontalAlignment="Left">Shared</CheckBox>
<Button Grid.Column="1" HorizontalAlignment="Right" Margin="4" Command="{Binding SaveWorkspace}" IsDefault="True" MinWidth="60">Save</Button>
</Grid>
</Grid>
@@ -1,4 +1,5 @@
using bts.api;
using bts.collections;
using mbts.ui;
using mbts.ui.shell;
using System;
@@ -13,10 +14,14 @@ using System.Windows.Input;
using static mbts.spark.SparkTags;
namespace mbts.spark {
internal class ContractPickerModel : ViewModelBase, IDisposable {
internal class ContractPickerModel : ViewModelBase, IEditableObject, IDisposable {
private const string dot = "•";
bool _is_edit_mode;
readonly IRaiseCanExecuteChangedCommand _add_criteria_command, _remove_criteria_command, _ok_button_command;
bool _allow_empty, _is_edit_mode;
SecurityImmutable? _pending_sec;
(SecurityCriteria criteria, SecurityImmutable? sec, SecurityMD? md)? _snapshot;
public ContractPickerModel(IBus bus, bool edit_mode, SecurityCriteria existing_criteria, SecurityCriteria world_view_filter = null)
: this(bus.subscribe(SecuritiesTag), edit_mode, existing_criteria, world_view_filter) { }
@@ -31,7 +36,7 @@ namespace mbts.spark {
(securities_view as ListCollectionView).CustomSort = new SecuritySorter();
_subs.Add(source.OnDispatcher().Subscribe(on_securities_changed));
add_criteria_command = new DelegateCommand(
_add_criteria_command = new DelegateCommand(
execute: _ => {
if (is_edit_mode && selected_securities.Count > 1) {
// make separate entries in case multiple securities are selected
@@ -65,18 +70,20 @@ namespace mbts.spark {
can_execute: _ => { return !String.IsNullOrEmpty(criteria_input_name); }
);
remove_criteria_command = new DelegateCommand(
_remove_criteria_command = new DelegateCommand(
execute: _ => {
security_criteria_clauses.removeRange(selected_security_criteria_clauses);
ok_button_command.TryRaiseCanExecuteChanged();
remove_criteria_command.TryRaiseCanExecuteChanged();
_remove_criteria_command.TryRaiseCanExecuteChanged();
},
can_execute: _ => { return security_criteria_clauses.Count > 0; }
);
ok_button_command = new DelegateCommand(
_ok_button_command = new DelegateCommand(
execute: _ => { ok_action?.Invoke(); },
can_execute: _ => {
if (_allow_empty)
return true;
if (!is_edit_mode) {
return (selected_securities.Count > 0);
} else {
@@ -236,9 +243,9 @@ namespace mbts.spark {
public List<uint> selected_securities = new List<uint>();
public ObservableCollection<SecurityImmutable> securities { get; } = new ObservableCollection<SecurityImmutable>();
public ICommand add_criteria_command { get; }
public ICommand remove_criteria_command { get; }
public ICommand ok_button_command { get; }
public ICommand add_criteria_command => _add_criteria_command;
public ICommand remove_criteria_command => _remove_criteria_command;
public ICommand ok_button_command => _ok_button_command;
public ICommand cancel_button_command { get; }
public ICommand add_symbol_command { get; }
public ICommand add_security_type_command { get; }
@@ -266,9 +273,18 @@ namespace mbts.spark {
private SecurityCriteria _world_view_filter;
public Action ok_action { get; set; }
public Action cancel_action { get; set; }
public event Action ok_action;
public event Action cancel_action;
public bool allow_empty {
get => _allow_empty;
set {
if (_allow_empty != value) {
_allow_empty = value;
_ok_button_command.RaiseCanExecuteChanged();
}
}
}
public bool is_edit_mode { get => _is_edit_mode; set => Set(ref _is_edit_mode, value); }
public event EventHandler<SecurityCriteriaClauseChangedEventsArgs> security_criteria_clause_changed;
@@ -319,6 +335,25 @@ namespace mbts.spark {
}
}
#region IEditableObject
public void BeginEdit() => _snapshot = (result_criteria, _pending_sec ?? selected_security, selected_security_md);
public void EndEdit() => _snapshot = null;
public void CancelEdit() {
if (_snapshot.HasValue) {
var (criteria, sec, md) = _snapshot.Value;
security_criteria_clauses.SetItems(criteria.clauses);
selected_security = sec;
/// A <see cref="_pending_sec"/> may have been used to create the snapshot when <see cref="BeginEdit"/> was called (this happens when securities are loaded lazily).
/// If this is the case, pending sec was assigned to the <see cref="_snapshot"/> security;
/// check if the security is now present in <see cref="all_securities"/>, if so, use that value instead of the null.
selected_security_md = sec.HasValue && !md.HasValue && all_securities.TryGetValue(sec.Value.Id, out var md_match) ? md_match : md;
_snapshot = null;
_add_criteria_command.RaiseCanExecuteChanged();
_remove_criteria_command.RaiseCanExecuteChanged();
}
}
#endregion
private void _update_exchanges() {
all_exchanges.sync_with(all_securities
.Select(s => s.Value.security.Exchange)
@@ -389,7 +424,6 @@ namespace mbts.spark {
public void Dispose() {
Disposable.dispose(_subs);
}
}
internal class SecurityCriteriaClauseChangedEventsArgs : EventArgs
@@ -14,9 +14,13 @@ namespace mbts.ui.controls {
/// Interaction logic for ContractPickerView.xaml
/// </summary>
partial class ContractPickerPopup : PopupControl {
public static readonly EventHandler
FlushSelectedCriteriaHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedCriteriaProperty)?.UpdateSource(),
FlushSelectedSecurityHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedSecurityProperty)?.UpdateSource();
IReadOnlyDictionary<uint, SecurityMD> _cached_security_map = DictionaryHelper.empty<uint, SecurityMD>();
ContractPickerPopupController _controller;
bool _is_lazy;
bool _allow_empty, _is_lazy;
public ContractPickerPopup() {
SetCurrentValue(SecurityMapProperty, new Dictionary<uint, SecurityMD>(0));
@@ -57,12 +61,14 @@ namespace mbts.ui.controls {
void on_data_context_disposed(bool obj) => Disposable.dispose(ref _controller);
}
public event EventHandler OKClick, CancelClick;
event Action<IReadOnlyDictionary<uint, SecurityMD>> _security_map_changed;
event Action<SecurityCriteria> _selected_criteria_changed;
event Action<bool> _is_lazy_changed;
event Action<bool> _allow_empty_changed, _is_lazy_changed;
public static IValueConverter SecurityCriteriaTextConverter =
ValueConverter.create<SecurityCriteria, string>(s => s == null ? "(None)" : string.Join(", ", s.clauses.empty_if_null()));
ValueConverter.create<SecurityCriteria, string>(s => s.is_defined() ? string.Join(", ", s.clauses.empty_if_null()) : "(None)");
#region ShowDialogSkirt
public static readonly DependencyProperty ShowDialogSkirtProperty =
@@ -134,13 +140,9 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty CloseOnOKProperty =
WPFHelper.create_dp<bool, ContractPickerPopup>(
nameof(CloseOnOK),
(o, v) => {
o.Controller.model.ok_action = v ? new Action(() => {
o.set_current_if_changed(SelectedSecurityMDProperty, o.Controller.model.selected_security_md);
o.set_current_if_changed(SelectedSecurityProperty, o.Controller.model.selected_security);
o.set_current_if_changed(SelectedCriteriaProperty, o.Controller.model.result_criteria);
o.SetCurrentValue(IsOpenProperty, false);
}) : null;
(o, oldval, newval) => {
if (oldval) o.Controller.model.ok_action -= o._close;
if (newval) o.Controller.model.ok_action += o._close;
});
public bool CloseOnOK {
@@ -153,7 +155,10 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty CloseOnCancelProperty =
WPFHelper.create_dp<bool, ContractPickerPopup>(
nameof(CloseOnCancel),
(o, v) => o.Controller.model.cancel_action = v ? new Action(() => o.SetCurrentValue(IsOpenProperty, false)) : null);
(o, oldval, newval) => {
if (oldval) o.Controller.model.cancel_action -= o._close;
if (newval) o.Controller.model.cancel_action += o._close;
});
public bool CloseOnCancel {
get => (bool)GetValue(CloseOnCancelProperty);
@@ -168,6 +173,16 @@ namespace mbts.ui.controls {
set => _controller.model.is_edit_mode = value;
}
public bool AllowEmpty {
get => _allow_empty;
set {
if (_allow_empty != value) {
_allow_empty = value;
_allow_empty_changed?.Invoke(value);
}
}
}
public bool IsLazy {
get => _is_lazy;
set {
@@ -178,12 +193,28 @@ namespace mbts.ui.controls {
}
}
protected override void OnClosed(EventArgs e) {
if (ShowDialogSkirt)
_controller.model.CancelEdit();
}
protected override void OnOpened(EventArgs e) {
if (ShowDialogSkirt)
_controller.model.BeginEdit();
}
void _close() => SetCurrentValue(IsOpenProperty, false);
public class ContractPickerPopupController : DisposableViewModelBase {
readonly Property<SecurityCriteria> _selected_criteria;
public ContractPickerPopupController(ContractPickerPopup owner, ContractPickerModel model) {
_selected_criteria = backing<SecurityCriteria>(nameof(selected_criteria));
model.allow_empty = owner._allow_empty;
model.security_criteria_clauses.CollectionChanged += on_clauses_collection_changed;
model.ok_action += on_ok;
model.cancel_action += on_cancel;
owner._allow_empty_changed += on_allow_empty_changed;
owner._selected_criteria_changed += on_selected_criteria_changed;
_dispose = this.model = model;
// local methods
@@ -203,12 +234,28 @@ namespace mbts.ui.controls {
_selected_criteria.set(criteria ?? new SecurityCriteria());
}
void on_allow_empty_changed(bool value) => model.allow_empty = value;
void on_clauses_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
owner._selected_criteria_changed -= on_selected_criteria_changed;
_selected_criteria.set(model.result_criteria);
WPFHelper.set_current_if_changed(owner, SelectedCriteriaProperty, selected_criteria);
owner._selected_criteria_changed += on_selected_criteria_changed;
}
void on_ok() {
model.EndEdit();
propagate_to_owner();
owner.OKClick?.Invoke(owner, EventArgs.Empty);
}
void on_cancel() {
model.CancelEdit();
propagate_to_owner();
owner.CancelClick?.Invoke(owner, EventArgs.Empty);
}
void propagate_to_owner() {
owner.set_current_if_changed(SelectedSecurityMDProperty, owner.Controller.model.selected_security_md);
owner.set_current_if_changed(SelectedSecurityProperty, owner.Controller.model.selected_security);
owner.set_current_if_changed(SelectedCriteriaProperty, owner.Controller.model.result_criteria);
}
}
public ContractPickerModel model { get; }
@@ -104,6 +104,10 @@ namespace mbts.spark {
}
#region Extensions
public static IObservable<SecurityMD?> to_security_md(this IObservable<SecurityImmutable?> security_source, IBus bus) =>
bus.subscribe(SparkTags.SecuritiesTag).CombineLatest(
security_source, (data, sec) => sec.HasValue && data.TryGetValue(sec.Value.Id, out var smd) ?smd : (SecurityMD?)null);
public static IObservable<SecurityImmutable> to_security(IObservable<uint> security_key, IBus bus) =>
security_key.CombineLatest(bus.subscribe(SparkTags.SecuritiesTag), (sk, map) => map.GetValueOrDefault(sk).security);
Loaded 100 of 243 files, more files were not shown because too many files have changed in this diff. Show more