Compare commits

..
291 Commits
Author SHA1 Message Date
Kevin Darby c8badf5689 Merged in fix_span_error_message (pull request #306)
guard against UI setting cash and leverage factors for accounts I don't know about when span is off

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-17 14:51:23 +00:00
Kevin Darby 58533bf16f guard against UI setting cash and leverage factors for accounts I don't know about when span is off 2019-07-17 10:48:16 -04:00
Pedro Pinto 6b3c3ded68 Fixed up release notes. 2019-07-17 10:24:51 -04:00
Pedro Pinto d65e00fade Merged in SP-132-make-algo-shutdown-orderly (pull request #305)
RTM. SP-132 make algo shutdown orderly
2019-07-17 14:23:09 +00:00
Pedro Pinto 68e5d07aef Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-07-17 09:36:15 -04:00
Pedro Pinto 68345a181e Updated release notes. 2019-07-17 09:35:43 -04:00
Kevin Darby 69c121a19c Merged in fix_span (pull request #301)
fix crash where account name > 12 chars, tested with long account name

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-17 13:32:33 +00:00
Pedro Pinto 780ce7416f Merge branch 'master' into SP-132-make-algo-shutdown-orderly 2019-07-17 09:07:54 -04:00
Pedro Pinto 9fe338a62a Updated release notes. 2019-07-17 09:07:24 -04:00
Jake Norbut f027af66c8 Merged in ob_price_filter_tick_comparison (pull request #304)
RTM - Ob price filter tick comparison

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-07-16 21:43:23 +00:00
Jake Norbut 6b2708b160 PR suggestions 2019-07-16 16:34:59 -05:00
Jake Norbut 534474621f price filter should use new add_price tick-based method 2019-07-16 15:51:27 -05:00
Jake Norbut 554614ab72 add means to perform price comparsion based on tick equality 2019-07-16 15:50:44 -05:00
Pedro Pinto 2ede69a263 Updated release notes. 2019-07-16 16:21:31 -04:00
Pedro Pinto 9def94aceb Changed IB to cancel rather than error if it sees a child cancel.
This allows IB to remain agnostic of wheter the child cancel was a
simply a consequence of the IB itself shutting down, in which case it
would be ignored. If the IB instead errorred out than that error
superseeds the cancel and results in the IB reporting an error.
2019-07-16 15:55:28 -04:00
Pedro Pinto 0af30b3cc0 Changed cover to handle fills while canceling. 2019-07-16 15:32:38 -04:00
Taha Afzal a91c2d9bc6 SP-146 (Limiter will not accept unresolved fills) 2019-07-16 11:20:15 -04:00
Pedro Pinto 481c1fd2de Fixed gcc build. 2019-07-16 10:29:59 -04:00
Pedro Pinto e80fc807e0 Moved all reporting state calls to top level event handler.
Any CPU the algo order receives must originate from a top level event,
its simpler to consolidate all report guards in those events, instead
of chasing every call that comes out of the algo.
2019-07-16 10:20:30 -04:00
Pedro Pinto c8e054d079 Added algo wrapper documentation. 2019-07-16 10:10:46 -04:00
Pedro Pinto 9480e76d4b Cleaned up algo wrapper 2019-07-16 10:10:11 -04:00
Pedro Pinto 538411353a Improved cover to hedge while shutting down. 2019-07-16 08:48:36 -04:00
Pedro Pinto da6998c391 Do not error out when receiving an udpate from an unknown child.
Because the child is presumably a detached child.
2019-07-15 16:43:39 -04:00
Pedro Pinto 60786b105c Handled terminate_error while shutting down. 2019-07-15 16:26:22 -04:00
Pedro Pinto f488248abf Fixed handling of child update while shutdown. 2019-07-15 16:03:36 -04:00
Pedro Pinto 830b590e5b Algos now go through an orderly shutdown phase.
During which they can still process child updates.
2019-07-15 10:59:13 -04:00
Kevin Darby c9c93b3cf0 Merged in fix_span1 (pull request #300)
fix crash when acct names are > 12 chars

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-12 18:58:13 +00:00
Kevin Darby 9e5d43f44a fix crash where account name > 12 chars, tested with long account name 2019-07-11 17:53:48 -04:00
Kevin Darby 5c1965f534 fix crash when acct names are > 12 chars 2019-07-11 14:07:31 -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
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
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 05e09cb977 Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-07-08 14:33:37 -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 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
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
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
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
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
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
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
221 changed files with 4915 additions and 1905 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/*
+61 -1
View File
@@ -4,6 +4,65 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.7.0
### User Visible Changes
- Allowed algos to process "in-flight" fills while shutting down
- Changed the cover algo to continue to hedge while shutting down
### API Changes
- Changed the API so that algos do not fully terminate until all
their children are terminated. Algos that are in the process of
shutting down in response to a complete or cancel call will still
receive child updates, which they can use to calculate and report
an accurate fill count (during this shutdown stage algos cannot
create, cancel or update children). A new IAlgoServices method,
is_shutting_down(), can be used to determine if an algo is
shutting down.
## 2.6.2
### User Visible Changes
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
- Fixed a rare race condition that could lead to erroneous limit enforcement
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
## 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 +435,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;
}));
@@ -144,7 +144,7 @@ namespace bts.shell.orderbook {
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.quantity());
pb.add_numeric(fs.price, o => o.order.price());
pb.add_price(fs.price, o => o.order.price(), o => o.security);
pb.add_numeric(fs.qty_filled, o => o.order.filled());
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.last_price());
@@ -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 {
@@ -37,6 +37,51 @@ namespace mbts.spark.filters {
_predicates.Add(impl);
bool impl(TRow o) => max.CompareTo(selector(o)) >= 0;
}
}
// this is a copy of add_numeric except we do a tick_equals comparsion
// if the ticks are not equals, then it falls back to a regular double comparison
//
// if the SecurityImmutable? doesn't have a value
// then we fallback to double comparison
public void add_price(DoubleMinMaxState value, Func<TRow, double> selector, Func<TRow, SecurityImmutable?> si_selector) {
if (value == null)
return;
if (value.minimum.active) {
var min = value.minimum.value.Value;
if (value.maximum.active) {
var max = value.maximum.value.Value;
_predicates.Add(impl);
bool impl(TRow o) {
var v = selector(o);
var si = si_selector(o);
if (si.HasValue) {
return si.Value.ContractSpec.compare_tick_fallback_to_double(min, v) <= 0 &&
si.Value.ContractSpec.compare_tick_fallback_to_double(max, v) >= 0;
}
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
}
} else {
_predicates.Add(impl);
bool impl(TRow o) {
var si = si_selector(o);
if (si.HasValue) {
return si.Value.ContractSpec.compare_tick_fallback_to_double(min, selector(o)) <= 0;
}
return min.CompareTo(selector(o)) <= 0;
}
}
} else if (value.maximum.active) {
var max = value.maximum.value.Value;
_predicates.Add(impl);
bool impl(TRow o) {
var si = si_selector(o);
if (si.HasValue) {
return si.Value.ContractSpec.compare_tick_fallback_to_double(max, selector(o)) >= 0;
}
return max.CompareTo(selector(o)) >= 0;
}
}
}
public void add_regex(string str, Func<TRow, string> selector) {
@@ -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; }
Loaded 100 of 221 files, more files were not shown because too many files have changed in this diff. Show more